mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-10-02 04:43:58 +00:00
logicsrc CLI 0.2.0: every LogicSRC tool is a word after logicsrc (#157)
OpenContext, OpenCreds, OpenOntology and OpenPRD were already registered under the umbrella. Three were not: - `logicsrc openmcp …`: OpenMCP lives in its own repository, so it is a dependency (@logicsrc/openmcp ^0.3.1, which exports ./cli for this) and every argument goes untouched to the same main the standalone binary runs. Imported on first use, because the catalog is node:sqlite (Node 24) while the rest of the CLI runs on 18; below the floor that one word says so and offers the standalone installer, which brings its own Node. - `logicsrc openspec <anything else>`: import, export and change stay ours; any other word (init, list, validate, archive, show) runs OpenSpec.dev's own CLI (@fission-ai/openspec) as the group's default subcommand, flags intact. One command for a repo in compatibility mode, and the OpenSpec.dev half is upstream itself rather than a copy that would drift. - `logicsrc mcp`: the LogicSRC MCP server (@profullstack/logicsrc-mcp) over stdio, spawned as a child because it owns the process's stdio. build:cli now builds that workspace. README gets a table of every word and the standalone name it mirrors; the OpenMCP spec page and doc mention the umbrella form. Claude-Session: https://claude.ai/code/session_01Qh2dieNyPZ4Hx5g3XNJ1Eo Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
0f6326e910
commit
8951e824d5
11 changed files with 1466 additions and 12 deletions
87
packages/cli/src/umbrella.test.ts
Normal file
87
packages/cli/src/umbrella.test.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import { Command } from "commander";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { meetsOpenmcpNode, openmcpNodeMessage, registerOpenMcpCommands } from "./openmcp.js";
|
||||
import { openspecBin, registerOpenSpecDevPassthrough } from "./openspec-dev.js";
|
||||
import { logicsrcMcpBin, registerMcpCommands } from "./mcp.js";
|
||||
|
||||
/** A program shaped like the real one: positional options on, no process.exit. */
|
||||
function program(): Command {
|
||||
const p = new Command();
|
||||
p.name("logicsrc").enablePositionalOptions().exitOverride();
|
||||
return p;
|
||||
}
|
||||
|
||||
describe("logicsrc openmcp", () => {
|
||||
it("hands every argument, flags included, to openmcp's own main", async () => {
|
||||
const seen: string[][] = [];
|
||||
const p = program();
|
||||
registerOpenMcpCommands(p, async () => async (argv) => {
|
||||
seen.push(argv);
|
||||
return 0;
|
||||
});
|
||||
await p.parseAsync(["node", "logicsrc", "openmcp", "relays", "--online", "--catalog", "https://x.example", "--json"]);
|
||||
expect(seen).toEqual([["relays", "--online", "--catalog", "https://x.example", "--json"]]);
|
||||
expect(process.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
it("asks for help when given nothing, and passes the exit code back", async () => {
|
||||
const seen: string[][] = [];
|
||||
const p = program();
|
||||
registerOpenMcpCommands(p, async () => async (argv) => {
|
||||
seen.push(argv);
|
||||
return 3;
|
||||
});
|
||||
await p.parseAsync(["node", "logicsrc", "openmcp"]);
|
||||
expect(seen).toEqual([["help"]]);
|
||||
expect(process.exitCode).toBe(3);
|
||||
process.exitCode = 0;
|
||||
});
|
||||
|
||||
it("knows the catalog's Node floor and offers the standalone installer below it", () => {
|
||||
expect(meetsOpenmcpNode("v24.0.0")).toBe(true);
|
||||
expect(meetsOpenmcpNode("v22.18.0")).toBe(false);
|
||||
expect(openmcpNodeMessage("v22.18.0")).toContain("curl -fsSL https://openmcp.logicsrc.com/install.sh | sh");
|
||||
});
|
||||
});
|
||||
|
||||
describe("logicsrc openspec", () => {
|
||||
function group(run: (args: string[]) => Promise<number>): { p: Command; own: string[] } {
|
||||
const p = program();
|
||||
const own: string[] = [];
|
||||
const openspec = p.command("openspec");
|
||||
openspec.command("import").argument("[root]").action((root: string | undefined) => void own.push(`import ${root ?? ""}`.trim()));
|
||||
registerOpenSpecDevPassthrough(openspec, run);
|
||||
return { p, own };
|
||||
}
|
||||
|
||||
it("keeps its own words", async () => {
|
||||
const seen: string[][] = [];
|
||||
const { p, own } = group(async (args) => (seen.push(args), 0));
|
||||
await p.parseAsync(["node", "logicsrc", "openspec", "import", "specs"]);
|
||||
expect(own).toEqual(["import specs"]);
|
||||
expect(seen).toEqual([]);
|
||||
});
|
||||
|
||||
it("sends any other word to OpenSpec.dev's CLI with its flags intact", async () => {
|
||||
const seen: string[][] = [];
|
||||
const { p } = group(async (args) => (seen.push(args), 0));
|
||||
await p.parseAsync(["node", "logicsrc", "openspec", "validate", "--all", "--strict"]);
|
||||
await p.parseAsync(["node", "logicsrc", "openspec", "init"]);
|
||||
expect(seen).toEqual([["validate", "--all", "--strict"], ["init"]]);
|
||||
});
|
||||
|
||||
it("finds the bin beside dist/ from the package's only export", () => {
|
||||
expect(openspecBin(() => "/node_modules/@fission-ai/openspec/dist/index.js")).toBe("/node_modules/@fission-ai/openspec/bin/openspec.js");
|
||||
});
|
||||
});
|
||||
|
||||
describe("logicsrc mcp", () => {
|
||||
it("runs the server entry, which is also its bin", async () => {
|
||||
const seen: string[][] = [];
|
||||
const p = program();
|
||||
registerMcpCommands(p, async (args) => (seen.push(args), 0));
|
||||
await p.parseAsync(["node", "logicsrc", "mcp"]);
|
||||
expect(seen).toEqual([[]]);
|
||||
expect(logicsrcMcpBin(() => "/x/dist/index.js")).toBe("/x/dist/index.js");
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue