mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-13 22:37:29 +00:00
Scaffold LogicSRC and CommandBoard plugins
This commit is contained in:
parent
59927e140c
commit
5c9ea821f6
67 changed files with 5958 additions and 0 deletions
30
packages/cli/package.json
Normal file
30
packages/cli/package.json
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "@logicsrc/cli",
|
||||
"version": "0.1.0",
|
||||
"description": "CommandBoard.run CLI.",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"bin": {
|
||||
"commandboard": "./dist/index.js",
|
||||
"cb": "./dist/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"dev": "tsx src/index.ts",
|
||||
"test": "vitest run src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@logicsrc/plugin-core": "file:../plugin-core",
|
||||
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
|
||||
"@logicsrc/plugin-sh1pt": "file:../../plugins/sh1pt",
|
||||
"@logicsrc/plugin-ugig": "file:../../plugins/ugig",
|
||||
"@logicsrc/tui": "file:../tui",
|
||||
"@logicsrc/validators": "file:../validators",
|
||||
"commander": "^14.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsx": "^4.21.0",
|
||||
"vitest": "^4.0.8"
|
||||
}
|
||||
}
|
||||
25
packages/cli/src/fixtures.ts
Normal file
25
packages/cli/src/fixtures.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
export const boards = [
|
||||
{ path: "/general", title: "General", posts: 18, tasks: 2 },
|
||||
{ path: "/gigs", title: "Gigs", posts: 42, tasks: 12 },
|
||||
{ path: "/agents", title: "Agents", posts: 16, tasks: 5 },
|
||||
{ path: "/qa", title: "QA", posts: 9, tasks: 7 }
|
||||
];
|
||||
|
||||
export const tasks = [
|
||||
{
|
||||
id: "task_123",
|
||||
title: "Test checkout flow",
|
||||
board: "/qa",
|
||||
budget: "25 USDC",
|
||||
status: "submitted",
|
||||
assignee: "qa-agent-01.coinpay"
|
||||
},
|
||||
{
|
||||
id: "task_456",
|
||||
title: "Publish uGig integration smoke test",
|
||||
board: "/gigs",
|
||||
budget: "40 USDC",
|
||||
status: "funded",
|
||||
assignee: null
|
||||
}
|
||||
];
|
||||
22
packages/cli/src/format.ts
Normal file
22
packages/cli/src/format.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
export type OutputFormat = "json" | "table" | "markdown";
|
||||
|
||||
export function print(data: unknown, format: OutputFormat) {
|
||||
if (format === "json") {
|
||||
console.log(JSON.stringify(data, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
if (format === "markdown") {
|
||||
if (Array.isArray(data)) {
|
||||
for (const item of data) {
|
||||
console.log(`- ${Object.entries(item as Record<string, unknown>).map(([key, value]) => `**${key}:** ${String(value)}`).join(", ")}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(Object.entries(data as Record<string, unknown>).map(([key, value]) => `**${key}:** ${String(value)}`).join("\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
console.table(data);
|
||||
}
|
||||
12
packages/cli/src/index.test.ts
Normal file
12
packages/cli/src/index.test.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { defaultPluginRegistry } from "./registry.js";
|
||||
|
||||
describe("CLI registry", () => {
|
||||
it("loads default v1 plugins", () => {
|
||||
const ids = defaultPluginRegistry().snapshot().plugins.map((plugin: { id: string }) => plugin.id);
|
||||
|
||||
expect(ids).toContain("coinpay");
|
||||
expect(ids).toContain("ugig");
|
||||
expect(ids).toContain("sh1pt");
|
||||
});
|
||||
});
|
||||
211
packages/cli/src/index.ts
Normal file
211
packages/cli/src/index.ts
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
#!/usr/bin/env node
|
||||
import { readFileSync } from "node:fs";
|
||||
import { Command } from "commander";
|
||||
import { renderPluginStatus, renderTui } from "@logicsrc/tui";
|
||||
import { assertSchemaKind, parseDocument, validate } from "@logicsrc/validators";
|
||||
import { boards, tasks } from "./fixtures.js";
|
||||
import { print, type OutputFormat } from "./format.js";
|
||||
import { defaultPluginRegistry } from "./registry.js";
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name("commandboard")
|
||||
.alias("cb")
|
||||
.description("CommandBoard.run CLI for LogicSRC boards, tasks, agents, payments, plugins, and TUI.")
|
||||
.version("0.1.0");
|
||||
|
||||
program
|
||||
.command("login")
|
||||
.option("--did <did>", "CoinPay DID")
|
||||
.option("--oauth <provider>", "OAuth provider")
|
||||
.description("Start a login flow.")
|
||||
.action((options) => {
|
||||
const mode = options.did ? `CoinPay DID ${options.did}` : options.oauth ? `${options.oauth} OAuth` : "browser/device";
|
||||
console.log(`Login flow ready: ${mode}`);
|
||||
console.log("Token storage target: $HOME/.commandboard/auth.json");
|
||||
});
|
||||
|
||||
program.command("logout").description("Clear local auth token.").action(() => {
|
||||
console.log("Logged out. Local auth token would be removed from $HOME/.commandboard/auth.json.");
|
||||
});
|
||||
|
||||
program.command("whoami").description("Show current DID and account context.").action(() => {
|
||||
print({ did: process.env.COMMANDBOARD_DID || "anthony.coinpay", api_url: process.env.COMMANDBOARD_API_URL || "http://localhost:4010" }, "table");
|
||||
});
|
||||
|
||||
program
|
||||
.command("boards")
|
||||
.option("--format <format>", "table, json, or markdown", "table")
|
||||
.description("List boards.")
|
||||
.action((options) => print(boards, options.format as OutputFormat));
|
||||
|
||||
program
|
||||
.command("read")
|
||||
.argument("<board>", "Board path")
|
||||
.option("--limit <limit>", "Number of posts", "20")
|
||||
.option("--format <format>", "table, json, or markdown", "table")
|
||||
.description("Read a board feed.")
|
||||
.action((board, options) => {
|
||||
print(
|
||||
[
|
||||
{ type: "TASK", board, title: "QA checkout flow", meta: "25 USDC" },
|
||||
{ type: "POST", board, title: "New agent plugin idea", meta: "4 replies" },
|
||||
{ type: "RUN", board, title: "qa-agent completed task_123", meta: "completed" }
|
||||
].slice(0, Number(options.limit)),
|
||||
options.format as OutputFormat
|
||||
);
|
||||
});
|
||||
|
||||
program
|
||||
.command("post")
|
||||
.argument("<board>", "Board path")
|
||||
.argument("[message]", "Post body")
|
||||
.option("--file <file>", "Read post body from a file")
|
||||
.description("Create a post.")
|
||||
.action((board, message, options) => {
|
||||
const body = options.file ? readFileSync(options.file, "utf8") : message;
|
||||
console.log(`Created post on ${board}: ${body}`);
|
||||
});
|
||||
|
||||
const task = program.command("task").description("Task commands.");
|
||||
|
||||
task
|
||||
.command("list")
|
||||
.option("--open", "Only open tasks")
|
||||
.option("--board <board>", "Filter by board")
|
||||
.option("--format <format>", "table, json, or markdown", "table")
|
||||
.action((options) => {
|
||||
const filtered = tasks.filter((item) => (!options.open || item.status === "open" || item.status === "funded") && (!options.board || item.board === options.board));
|
||||
print(filtered, options.format as OutputFormat);
|
||||
});
|
||||
|
||||
task
|
||||
.command("get")
|
||||
.argument("<id>", "Task id")
|
||||
.option("--raw-schema", "Print LogicSRC schema")
|
||||
.option("--format <format>", "table, json, or markdown", "table")
|
||||
.action((id, options) => {
|
||||
const item = tasks.find((entry) => entry.id === id);
|
||||
if (!item) {
|
||||
throw new Error(`Task not found: ${id}`);
|
||||
}
|
||||
print(options.rawSchema ? toTaskSchema(item) : item, options.format as OutputFormat);
|
||||
});
|
||||
|
||||
task
|
||||
.command("create")
|
||||
.option("--board <board>", "Board path", "/gigs")
|
||||
.option("--title <title>", "Task title", "Untitled task")
|
||||
.option("--budget <budget>", "Budget, for example 25usdc")
|
||||
.option("--schema <file>", "LogicSRC task schema file")
|
||||
.action((options) => {
|
||||
if (options.schema) {
|
||||
validateFile("task", options.schema);
|
||||
}
|
||||
console.log(`Created task "${options.title}" on ${options.board}${options.budget ? ` with budget ${options.budget}` : ""}`);
|
||||
});
|
||||
|
||||
task.command("validate").argument("<file>", "Task YAML or JSON file").action((file) => validateFile("task", file));
|
||||
task.command("claim").argument("<id>", "Task id").action((id) => console.log(`Claimed ${id}`));
|
||||
task.command("submit").argument("<id>", "Task id").option("--file <file>", "Deliverable file").action((id, options) => console.log(`Submitted ${id}${options.file ? ` with ${options.file}` : ""}`));
|
||||
task.command("approve").argument("<id>", "Task id").action((id) => console.log(`Approved ${id}; escrow release requested through CoinPay.`));
|
||||
task.command("reject").argument("<id>", "Task id").option("--reason <reason>", "Rejection reason").action((id, options) => console.log(`Rejected ${id}${options.reason ? `: ${options.reason}` : ""}`));
|
||||
task.command("dispute").argument("<id>", "Task id").action((id) => console.log(`Opened dispute for ${id}`));
|
||||
|
||||
program.command("wallet").description("Show wallet balance.").action(() => {
|
||||
print({ did: process.env.COMMANDBOARD_DID || "anthony.coinpay", provider: "CoinPay", balance: "42 USDC" }, "table");
|
||||
});
|
||||
|
||||
program.command("events").description("Listen to LogicSRC event stream.").argument("[listen]", "listen").option("--board <board>").option("--type <type>").action((_listen, options) => {
|
||||
console.log(`Listening for events${options.board ? ` on ${options.board}` : ""}${options.type ? ` of type ${options.type}` : ""}...`);
|
||||
console.log(JSON.stringify({ type: "logicsrc.event", event: "task.created", resource_id: "task_789" }));
|
||||
});
|
||||
|
||||
program.command("plugins").option("--format <format>", "table, json, or markdown", "table").description("Show plugin status.").action((options) => {
|
||||
const snapshot = defaultPluginRegistry().snapshot();
|
||||
print(snapshot.plugins, options.format as OutputFormat);
|
||||
});
|
||||
|
||||
const sh1pt = program.command("sh1pt").description("sh1pt project, action, release, and delivery commands.");
|
||||
|
||||
sh1pt
|
||||
.command("projects")
|
||||
.option("--format <format>", "table, json, or markdown", "table")
|
||||
.description("List synced sh1pt projects.")
|
||||
.action((options) => {
|
||||
print(
|
||||
[
|
||||
{ id: "sh1pt_project_1", board: "/projects/sh1pt", status: "active", actions: 5 },
|
||||
{ id: "sh1pt_project_2", board: "/projects/crawlproof", status: "active", actions: 2 }
|
||||
],
|
||||
options.format as OutputFormat
|
||||
);
|
||||
});
|
||||
|
||||
sh1pt
|
||||
.command("actions")
|
||||
.option("--format <format>", "table, json, or markdown", "table")
|
||||
.description("List sh1pt actions available for task publishing.")
|
||||
.action((options) => {
|
||||
print(
|
||||
[
|
||||
{ id: "action_release_checklist", title: "Release checklist", publishable: true },
|
||||
{ id: "action_deploy_preview", title: "Deploy preview", publishable: true }
|
||||
],
|
||||
options.format as OutputFormat
|
||||
);
|
||||
});
|
||||
|
||||
sh1pt.command("publish").argument("<action>", "sh1pt action id").option("--board <board>", "Target board", "/projects/sh1pt").description("Publish a sh1pt action as a CommandBoard task.").action((action, options) => {
|
||||
console.log(`Published sh1pt action ${action} to ${options.board}`);
|
||||
});
|
||||
|
||||
program.command("tui").description("Launch the tmux-friendly TUI.").action(() => {
|
||||
console.log(renderTui());
|
||||
console.log("\nPlugin status:\n" + renderPluginStatus());
|
||||
});
|
||||
|
||||
program.command("update").alias("upgrade").description("Update the local CommandBoard.run CLI.").action(() => {
|
||||
console.log("Current version: 0.1.0");
|
||||
console.log("Latest version: 0.1.0");
|
||||
console.log("CommandBoard.run CLI is already up to date.");
|
||||
console.log("Config preserved at $HOME/.commandboard");
|
||||
});
|
||||
|
||||
program.command("remove").alias("uninstall").option("--purge", "Remove config and auth tokens").description("Remove local CommandBoard.run CLI.").action((options) => {
|
||||
console.log("Removed CommandBoard.run CLI.");
|
||||
console.log(options.purge ? "Removed config and auth tokens from $HOME/.commandboard." : "Preserved config at $HOME/.commandboard. Run with --purge to remove config and auth tokens.");
|
||||
});
|
||||
|
||||
function validateFile(kindArg: string, file: string) {
|
||||
const kind = assertSchemaKind(kindArg);
|
||||
const input = readFileSync(file, "utf8");
|
||||
const result = validate(kind, parseDocument(input, file));
|
||||
if (!result.ok) {
|
||||
for (const error of result.errors) {
|
||||
console.error(`- ${error.instancePath || "/"} ${error.message}`);
|
||||
}
|
||||
throw new Error(`Invalid LogicSRC ${kind} schema: ${file}`);
|
||||
}
|
||||
console.log(`Valid LogicSRC ${kind} schema: ${file}`);
|
||||
}
|
||||
|
||||
function toTaskSchema(item: (typeof tasks)[number]) {
|
||||
return {
|
||||
type: "logicsrc.task",
|
||||
version: "0.1",
|
||||
title: item.title,
|
||||
description: item.title,
|
||||
board: item.board,
|
||||
creator_did: "anthony.coinpay",
|
||||
status: item.status,
|
||||
budget: { amount: Number.parseFloat(item.budget), currency: item.budget.replace(/[0-9. ]/g, "") || "USDC" },
|
||||
assignee_did: item.assignee ?? undefined
|
||||
};
|
||||
}
|
||||
|
||||
program.parseAsync(process.argv).catch((error: unknown) => {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exitCode = 1;
|
||||
});
|
||||
8
packages/cli/src/registry.ts
Normal file
8
packages/cli/src/registry.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { createPluginRegistry } from "@logicsrc/plugin-core";
|
||||
import { coinPayPlugin } from "@logicsrc/plugin-coinpay";
|
||||
import { sh1ptPlugin } from "@logicsrc/plugin-sh1pt";
|
||||
import { uGigPlugin } from "@logicsrc/plugin-ugig";
|
||||
|
||||
export function defaultPluginRegistry() {
|
||||
return createPluginRegistry([coinPayPlugin, uGigPlugin, sh1ptPlugin]);
|
||||
}
|
||||
8
packages/cli/tsconfig.json
Normal file
8
packages/cli/tsconfig.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue