@fusionkit/cli 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +24 -2
  2. package/dist/cli.d.ts +1 -0
  3. package/dist/cli.js +1 -0
  4. package/dist/commands/ensemble-gateway.js +0 -2
  5. package/dist/commands/ensemble-records.d.ts +2 -1
  6. package/dist/commands/ensemble-records.js +3 -1
  7. package/dist/commands/ensemble.js +3 -4
  8. package/dist/commands/fusion.js +0 -5
  9. package/dist/commands/local.js +3 -3
  10. package/dist/cursor-acp.d.ts +3 -5
  11. package/dist/cursor-acp.js +12 -11
  12. package/dist/dashboard.d.ts +65 -0
  13. package/dist/dashboard.js +587 -0
  14. package/dist/fusion/env.d.ts +108 -0
  15. package/dist/fusion/env.js +98 -0
  16. package/dist/fusion/observability.d.ts +39 -0
  17. package/dist/fusion/observability.js +227 -0
  18. package/dist/fusion/preflight.d.ts +12 -0
  19. package/dist/fusion/preflight.js +42 -0
  20. package/dist/fusion/stack.d.ts +62 -0
  21. package/dist/fusion/stack.js +295 -0
  22. package/dist/fusion-config.d.ts +0 -1
  23. package/dist/fusion-config.js +0 -6
  24. package/dist/fusion-init.js +2 -11
  25. package/dist/fusion-quickstart.d.ts +11 -222
  26. package/dist/fusion-quickstart.js +57 -759
  27. package/dist/gateway.d.ts +0 -2
  28. package/dist/gateway.js +0 -2
  29. package/dist/local.d.ts +10 -17
  30. package/dist/local.js +50 -116
  31. package/dist/shared/options.d.ts +2 -1
  32. package/dist/shared/options.js +13 -19
  33. package/dist/shared/proc.d.ts +4 -70
  34. package/dist/shared/proc.js +3 -228
  35. package/dist/test/cli.test.js +8 -3
  36. package/dist/test/dashboard.test.d.ts +1 -0
  37. package/dist/test/dashboard.test.js +214 -0
  38. package/dist/test/gateway-e2e.test.js +13 -10
  39. package/dist/test/local.test.js +4 -4
  40. package/dist/tools.d.ts +2 -0
  41. package/dist/tools.js +25 -0
  42. package/package.json +14 -9
  43. package/scope/.next/BUILD_ID +1 -1
  44. package/scope/.next/app-build-manifest.json +16 -16
  45. package/scope/.next/app-path-routes-manifest.json +4 -4
  46. package/scope/.next/build-manifest.json +2 -2
  47. package/scope/.next/prerender-manifest.json +9 -9
  48. package/scope/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  49. package/scope/.next/server/app/_not-found.html +1 -1
  50. package/scope/.next/server/app/_not-found.rsc +1 -1
  51. package/scope/.next/server/app/api/environments/route_client-reference-manifest.js +1 -1
  52. package/scope/.next/server/app/api/ingest/route_client-reference-manifest.js +1 -1
  53. package/scope/.next/server/app/api/models/route_client-reference-manifest.js +1 -1
  54. package/scope/.next/server/app/api/replay/route_client-reference-manifest.js +1 -1
  55. package/scope/.next/server/app/api/sessions/[traceId]/route_client-reference-manifest.js +1 -1
  56. package/scope/.next/server/app/api/sessions/route_client-reference-manifest.js +1 -1
  57. package/scope/.next/server/app/api/stream/route_client-reference-manifest.js +1 -1
  58. package/scope/.next/server/app/environments/page_client-reference-manifest.js +1 -1
  59. package/scope/.next/server/app/environments.html +1 -1
  60. package/scope/.next/server/app/environments.rsc +1 -1
  61. package/scope/.next/server/app/index.html +1 -1
  62. package/scope/.next/server/app/index.rsc +1 -1
  63. package/scope/.next/server/app/models/page_client-reference-manifest.js +1 -1
  64. package/scope/.next/server/app/models.html +1 -1
  65. package/scope/.next/server/app/models.rsc +1 -1
  66. package/scope/.next/server/app/page_client-reference-manifest.js +1 -1
  67. package/scope/.next/server/app/sessions/[traceId]/page_client-reference-manifest.js +1 -1
  68. package/scope/.next/server/app-paths-manifest.json +4 -4
  69. package/scope/.next/server/functions-config-manifest.json +2 -2
  70. package/scope/.next/server/pages/404.html +1 -1
  71. package/scope/.next/server/pages/500.html +1 -1
  72. package/scope/.next/server/server-reference-manifest.json +1 -1
  73. /package/scope/.next/static/{vxqImMqlOwssVTua5Facf → x7wPUCpgS31-5ZHJkcKsU}/_buildManifest.js +0 -0
  74. /package/scope/.next/static/{vxqImMqlOwssVTua5Facf → x7wPUCpgS31-5ZHJkcKsU}/_ssgManifest.js +0 -0
package/README.md CHANGED
@@ -73,5 +73,27 @@ tool name; everything after the tool is forwarded to it.
73
73
  - `--observe` boots a local dashboard that streams live trace events. It is a
74
74
  separate app and is not bundled in the npm package; fusionkit prints how to
75
75
  enable it if it isn't available.
76
- - `cursor` needs a built Cursorkit checkout (`--cursor-kit-dir` or
77
- `FUSIONKIT_CURSORKIT_DIR`); fusionkit prints setup guidance if it's missing.
76
+ - `cursor` only needs a logged-in `cursor-agent` CLI; Cursorkit ships bundled
77
+ with this package, so no separate checkout is required.
78
+
79
+ ## Adding a new tool
80
+
81
+ Each coding tool is its own workspace package implementing a single
82
+ `ToolIntegration` (the adapter), so supporting a new tool is additive:
83
+
84
+ 1. Create `packages/tool-<name>/` (copy `packages/tool-codex` as a template). It
85
+ depends on `@fusionkit/tools` for the `ToolIntegration` / `ToolLaunchContext`
86
+ contract, and on `@fusionkit/ensemble` if it also ships a harness adapter.
87
+ 2. Export a `const <name>Tool: ToolIntegration` with:
88
+ - `launch(ctx)` — boot the tool's binary against `ctx.gatewayUrl` (the host
89
+ injects `spawnTool`, portless, teardown, etc. via the context; tool packages
90
+ never import the CLI).
91
+ - `modes` — `"fusion"`, `"local"`, or both.
92
+ - `createHarness` + `harnessKinds` — optional, only if the tool also runs as
93
+ an ensemble harness in the gateway/e2e matrix.
94
+ 3. Register it in [`packages/cli/src/tools.ts`](src/tools.ts) by adding it to the
95
+ `createToolRegistry([...])` list.
96
+
97
+ That single registry entry wires the tool into the `fusionkit <tool>` launcher,
98
+ `fusionkit local <tool>`, the interactive picker, preflight, and (when it has a
99
+ harness) the ensemble gateway — no other switch statements to update.
package/dist/cli.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  import { Command } from "commander";
2
+ import "./tools.js";
2
3
  export declare function buildProgram(): Command;
package/dist/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { Command } from "commander";
4
+ import "./tools.js";
4
5
  import { FUSIONKIT_PYPI_VERSION } from "./fusion-quickstart.js";
5
6
  import { registerDoctor } from "./commands/doctor.js";
6
7
  import { registerEnsemble } from "./commands/ensemble.js";
@@ -12,7 +12,6 @@ function addCommonGatewayOptions(cmd) {
12
12
  .option("--out <dir>", "output directory")
13
13
  .option("--model <spec>", "panel model mapping ID=MODEL (repeatable)", collect)
14
14
  .option("--judge-model <model>", "model used for judge synthesis")
15
- .option("--cursor-kit-dir <dir>", "Cursorkit repo for cursor scenarios")
16
15
  .option("--timeout-ms <n>", "candidate timeout")
17
16
  .option("--fusion-api-key <key>", "API key for the fusion backend");
18
17
  }
@@ -30,7 +29,6 @@ function gatewayConfig(opts) {
30
29
  timeoutMs,
31
30
  ...(opts.command !== undefined ? { command: opts.command } : {}),
32
31
  ...(opts.judgeModel !== undefined ? { judgeModel: opts.judgeModel } : {}),
33
- ...(opts.cursorKitDir !== undefined ? { cursorKitDir: resolve(opts.cursorKitDir) } : {}),
34
32
  ...(opts.fusionApiKey !== undefined ? { fusionApiKey: opts.fusionApiKey } : {})
35
33
  };
36
34
  }
@@ -1,4 +1,5 @@
1
- import type { EnsembleRunResult, HarnessAdapter, HarnessSmokeDashboard } from "@fusionkit/ensemble";
1
+ import type { EnsembleRunResult, HarnessAdapter } from "@fusionkit/ensemble";
2
+ import type { HarnessSmokeDashboard } from "../dashboard.js";
2
3
  import type { BenchmarkTaskRecordV1, ModelFusionHarnessKind, ModelFusionRecordV1, ModelFusionSideEffects } from "@fusionkit/protocol";
3
4
  export type HandoffPayload = {
4
5
  category?: string;
@@ -1,6 +1,8 @@
1
1
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { claudeCodeHarness, claudeCodeHarnessCredentialSkipReason, codexHarness, codexHarnessCredentialSkipReason, createCommandHarness, createMockHarness } from "@fusionkit/ensemble";
3
+ import { createCommandHarness, createMockHarness } from "@fusionkit/ensemble";
4
+ import { claudeCodeHarness, claudeCodeHarnessCredentialSkipReason } from "@fusionkit/tool-claude";
5
+ import { codexHarness, codexHarnessCredentialSkipReason } from "@fusionkit/tool-codex";
4
6
  import { assertBenchmarkTaskRecordV1, assertHarnessCandidateRecordV1, assertJudgeSynthesisRecordV1, assertModelCallRecordV1, assertModelFusionRecord, assertToolExecutionRecordV1, MODEL_FUSION_SCHEMA_BUNDLE_HASH } from "@fusionkit/protocol";
5
7
  import { gitText } from "@fusionkit/workspace";
6
8
  import { fail } from "../shared/errors.js";
@@ -1,9 +1,10 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
  import { Command } from "commander";
4
- import { createCommandHarness, createMockHarness, createMockJudgeSynthesizer, runEnsemble, runHarnessSmokeDashboard, runUnifiedHarnessE2E } from "@fusionkit/ensemble";
4
+ import { createCommandHarness, createMockHarness, createMockJudgeSynthesizer, runEnsemble, runUnifiedHarnessE2E } from "@fusionkit/ensemble";
5
5
  import { assertHarnessRunRequestV1, assertHarnessRunResultV1 } from "@fusionkit/protocol";
6
6
  import { gitText } from "@fusionkit/workspace";
7
+ import { runHarnessSmokeDashboard } from "../dashboard.js";
7
8
  import { fail } from "../shared/errors.js";
8
9
  import { collect, ensembleModels, liveSmokeTargets, parseTimeoutMs, unifiedHarnessKinds } from "../shared/options.js";
9
10
  import { buildGatewayCommand } from "./ensemble-gateway.js";
@@ -172,8 +173,7 @@ async function runEnsembleE2E(task, opts) {
172
173
  models,
173
174
  ...(opts.command !== undefined ? { command: opts.command } : {}),
174
175
  timeoutMs,
175
- ...(opts.judgeModel !== undefined ? { judgeModel: opts.judgeModel } : {}),
176
- ...(opts.cursorKitDir !== undefined ? { cursorKitDir: resolve(opts.cursorKitDir) } : {})
176
+ ...(opts.judgeModel !== undefined ? { judgeModel: opts.judgeModel } : {})
177
177
  });
178
178
  const counts = new Map();
179
179
  for (const row of result.results) {
@@ -245,7 +245,6 @@ export function registerEnsemble(program) {
245
245
  .option("--id <id>", "descriptor id")
246
246
  .option("--model <spec>", "panel model mapping ID=MODEL (repeatable)", collect)
247
247
  .option("--judge-model <model>", "model used for judge synthesis")
248
- .option("--cursor-kit-dir <dir>", "Cursorkit repo for cursor ACP/desktop scenarios")
249
248
  .option("--timeout-ms <n>", "candidate timeout")
250
249
  .option("--task-file <file>", "read task prompt from file")
251
250
  .action(runEnsembleE2E);
@@ -16,7 +16,6 @@ function applyFusionOptions(command) {
16
16
  .option("--synthesis-url <url>", "pre-running fusionkit serve for synthesis")
17
17
  .option("--fusionkit-dir <dir>", "local FusionKit checkout (dev override for the uvx synthesizer)")
18
18
  .option("--repo <dir>", "coding workspace the panel fuses over")
19
- .option("--cursor-kit-dir <dir>", "built Cursorkit checkout for the cursor tool")
20
19
  .option("--local", "use the local MLX panel trio instead of the default cloud panel")
21
20
  .option("--no-local", "override a fusionkit.json default of local=true")
22
21
  .option("--observe", "boot the local scope dashboard and stream live trace events")
@@ -45,8 +44,6 @@ function resolveOptions(opts) {
45
44
  options.fusionkitDir = resolve(opts.fusionkitDir);
46
45
  if (opts.repo !== undefined)
47
46
  options.repo = resolve(opts.repo);
48
- if (opts.cursorKitDir !== undefined)
49
- options.cursorKitDir = resolve(opts.cursorKitDir);
50
47
  // local/observe are tri-state: only set when the user passed --local/--no-local
51
48
  // (or --observe/--no-observe), so an unset flag can fall through to the config.
52
49
  if (opts.local !== undefined)
@@ -99,8 +96,6 @@ function mergeConfig(options, config) {
99
96
  options.observe = config.observe;
100
97
  if (options.portless === undefined && config.portless !== undefined)
101
98
  options.portless = config.portless;
102
- if (options.cursorKitDir === undefined && config.cursorKitDir != null)
103
- options.cursorKitDir = config.cursorKitDir;
104
99
  if (options.port === undefined && config.port != null)
105
100
  options.port = config.port;
106
101
  }
@@ -6,14 +6,14 @@ export function registerLocal(program) {
6
6
  .description("back a vendor agent with a local model")
7
7
  .argument("[tool]", `${LOCAL_TOOLS.join(" | ")}`)
8
8
  .argument("[args...]", "arguments forwarded to the tool")
9
- .option("--public-url <url>", "public tunnel URL for Cursor (or WARRANT_PUBLIC_URL)")
9
+ .option("--public-url <url>", "public tunnel URL for Cursor (or FUSIONKIT_PUBLIC_URL)")
10
10
  .option("--auth-token <token>", "require a bearer token on the gateway")
11
11
  .allowUnknownOption()
12
12
  .passThroughOptions()
13
- .addHelpText("after", "\nwarrant's own flags must precede the tool name; everything after the tool is forwarded to it.")
13
+ .addHelpText("after", "\nfusionkit's own flags must precede the tool name; everything after the tool is forwarded to it.")
14
14
  .action(async (tool, args, opts) => {
15
15
  if (tool === undefined || !LOCAL_TOOLS.includes(tool)) {
16
- fail(`usage: warrant local <${LOCAL_TOOLS.join(" | ")}> [args...]`);
16
+ fail(`usage: fusionkit local <${LOCAL_TOOLS.join(" | ")}> [args...]`);
17
17
  }
18
18
  const options = {
19
19
  ...(opts.publicUrl !== undefined ? { publicUrl: opts.publicUrl } : {}),
@@ -1,15 +1,13 @@
1
1
  /**
2
- * Real Cursor ACP front-door producer. Spawns the Cursorkit bridge (its
2
+ * Real Cursor ACP front-door producer. Spawns the bundled Cursorkit bridge (its
3
3
  * local-model backend pointed at the running Fusion Harness Gateway) and drives
4
4
  * the real cursor-agent CLI in ACP mode, asserting the fusion-synthesized
5
5
  * sentinel reaches Cursor via session/update. Returns undefined when the
6
- * Cursorkit checkout or the cursor-agent CLI are unavailable, so the acceptance
7
- * suite records the explicit `blocked` / `cursorkit_backend_not_running`
8
- * outcome instead of a silent pass.
6
+ * cursor-agent CLI is unavailable, so the acceptance suite records the explicit
7
+ * `blocked` / `cursorkit_backend_not_running` outcome instead of a silent pass.
9
8
  */
10
9
  import type { FrontDoorOutcomeProducer } from "@fusionkit/model-gateway";
11
10
  export type CursorAcpProducerInput = {
12
- cursorKitDir: string | undefined;
13
11
  gatewayUrl: string;
14
12
  sentinel: string;
15
13
  repo: string;
@@ -1,16 +1,17 @@
1
1
  /**
2
- * Real Cursor ACP front-door producer. Spawns the Cursorkit bridge (its
2
+ * Real Cursor ACP front-door producer. Spawns the bundled Cursorkit bridge (its
3
3
  * local-model backend pointed at the running Fusion Harness Gateway) and drives
4
4
  * the real cursor-agent CLI in ACP mode, asserting the fusion-synthesized
5
5
  * sentinel reaches Cursor via session/update. Returns undefined when the
6
- * Cursorkit checkout or the cursor-agent CLI are unavailable, so the acceptance
7
- * suite records the explicit `blocked` / `cursorkit_backend_not_running`
8
- * outcome instead of a silent pass.
6
+ * cursor-agent CLI is unavailable, so the acceptance suite records the explicit
7
+ * `blocked` / `cursorkit_backend_not_running` outcome instead of a silent pass.
9
8
  */
10
9
  import { spawn } from "node:child_process";
11
10
  import { existsSync } from "node:fs";
12
11
  import { delimiter, join } from "node:path";
13
12
  import { createInterface } from "node:readline";
13
+ import { resolveCursorkitCli } from "@fusionkit/ensemble";
14
+ import { readEnv } from "@fusionkit/tools";
14
15
  function commandOnPath(command) {
15
16
  if (command.includes("/"))
16
17
  return existsSync(command);
@@ -26,10 +27,11 @@ function normalizeModelBaseUrl(gatewayUrl) {
26
27
  }
27
28
  export function buildCursorAcpProducer(input) {
28
29
  const command = input.command ?? "cursor-agent";
29
- if (input.cursorKitDir === undefined || input.cursorKitDir.length === 0) {
30
- return undefined;
31
- }
32
- if (!existsSync(join(input.cursorKitDir, "dist/src/cli.js"))) {
30
+ // The live Cursor ACP probe drives the real cursor-agent CLI through the
31
+ // bundled Cursorkit bridge, so it stays opt-in: without the live flag the
32
+ // acceptance suite reports this door as `blocked` rather than spawning live
33
+ // tooling (keeping deterministic runs free of credential/CLI dependencies).
34
+ if (readEnv(process.env, "FUSIONKIT_GATEWAY_LIVE_CURSOR") !== "1") {
33
35
  return undefined;
34
36
  }
35
37
  if (!commandOnPath(command)) {
@@ -38,7 +40,6 @@ export function buildCursorAcpProducer(input) {
38
40
  return () => runCursorAcpOutcome({ ...input, command });
39
41
  }
40
42
  async function runCursorAcpOutcome(input) {
41
- const cursorKitDir = input.cursorKitDir;
42
43
  const modelName = input.modelName ?? "local-fusion";
43
44
  const bridgePort = 9700 + Math.floor(Math.random() * 250);
44
45
  const bridgeEnv = {};
@@ -60,9 +61,9 @@ async function runCursorAcpOutcome(input) {
60
61
  MODEL_PROVIDER_MODEL: "fusion-panel",
61
62
  MODEL_CONTEXT_TOKEN_LIMIT: "128000"
62
63
  });
64
+ const { serveCli } = resolveCursorkitCli();
63
65
  let bridgeOut = "";
64
- const bridge = spawn(process.execPath, ["dist/src/cli.js", "serve"], {
65
- cwd: cursorKitDir,
66
+ const bridge = spawn(process.execPath, [serveCli, "serve"], {
66
67
  env: bridgeEnv,
67
68
  stdio: ["ignore", "pipe", "pipe"]
68
69
  });
@@ -0,0 +1,65 @@
1
+ import type { HarnessRunResultV1, ModelFusionHarnessKind } from "@fusionkit/protocol";
2
+ import type { HarnessAdapter, HarnessCapabilities } from "@fusionkit/ensemble";
3
+ import type { ToolDashboardMetadata } from "@fusionkit/tools";
4
+ /** Dashboard target id (a tool id like "claude-code", or "command"/"mock"). */
5
+ export type HarnessCapabilityTarget = string;
6
+ export type HarnessAvailability = "available" | "credential_gated" | "missing";
7
+ /** A tool id that exposes a live smoke (e.g. "claude-code", "codex", "cursor"). */
8
+ export type HarnessLiveSmokeTarget = string;
9
+ export type HarnessSmokePurpose = "contract" | "credential-skip" | "live" | "missing";
10
+ export type HarnessAdapterReadiness = {
11
+ harnessId: HarnessCapabilityTarget;
12
+ displayName: string;
13
+ contractReadiness: string;
14
+ credentialState: string;
15
+ liveSmoke: string;
16
+ evidence: string[];
17
+ artifactRefs: string[];
18
+ };
19
+ export type HarnessCapabilityMatrixRow = {
20
+ harnessId: HarnessCapabilityTarget;
21
+ harnessKind: ModelFusionHarnessKind;
22
+ displayName: string;
23
+ availability: HarnessAvailability;
24
+ capabilities: HarnessCapabilities;
25
+ notes: string[];
26
+ };
27
+ export type HarnessCapabilityMatrix = {
28
+ capabilities: string[];
29
+ rows: HarnessCapabilityMatrixRow[];
30
+ };
31
+ export type HarnessSmokeOutcome = "success" | "failure" | "missing" | "skipped";
32
+ export type HarnessSmokeRecord = {
33
+ taskId: string;
34
+ harnessId: HarnessCapabilityTarget;
35
+ purpose: HarnessSmokePurpose;
36
+ outcome: HarnessSmokeOutcome;
37
+ result: HarnessRunResultV1;
38
+ resultPath: string;
39
+ };
40
+ export type HarnessSmokeDashboard = {
41
+ outputRoot: string;
42
+ dashboardPath: string;
43
+ matrix: HarnessCapabilityMatrix;
44
+ records: HarnessSmokeRecord[];
45
+ readiness: HarnessAdapterReadiness[];
46
+ };
47
+ export type HarnessSmokeDashboardOptions = {
48
+ repo?: string;
49
+ outputRoot?: string;
50
+ timeoutMs?: number;
51
+ createdAt?: string;
52
+ env?: Record<string, string | undefined>;
53
+ commandSuccess?: string;
54
+ commandFailure?: string;
55
+ liveSmoke?: readonly HarnessLiveSmokeTarget[];
56
+ liveSmokeHarnesses?: Partial<Record<HarnessLiveSmokeTarget, HarnessAdapter>>;
57
+ /** Per-tool dashboard metadata; defaults to the registered tool registry. */
58
+ tools?: readonly ToolDashboardMetadata[];
59
+ };
60
+ export declare function createHarnessCapabilityMatrix(options?: HarnessSmokeDashboardOptions): HarnessCapabilityMatrix;
61
+ export declare function runHarnessSmokeDashboard(options?: HarnessSmokeDashboardOptions): Promise<HarnessSmokeDashboard>;
62
+ export declare const harnessDashboard: {
63
+ readonly capabilities: typeof createHarnessCapabilityMatrix;
64
+ readonly run: typeof runHarnessSmokeDashboard;
65
+ };