@fusionkit/cli 0.1.4 → 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 +26 -4
  2. package/dist/cli.d.ts +1 -0
  3. package/dist/cli.js +4 -17
  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 +14 -15
  9. package/dist/commands/local.js +3 -3
  10. package/dist/cursor-acp.d.ts +18 -0
  11. package/dist/cursor-acp.js +206 -0
  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 +12 -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 +32 -142
  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 +12 -12
  45. package/scope/.next/app-path-routes-manifest.json +3 -3
  46. package/scope/.next/build-manifest.json +2 -2
  47. package/scope/.next/prerender-manifest.json +16 -16
  48. package/scope/.next/server/app/_not-found.html +1 -1
  49. package/scope/.next/server/app/_not-found.rsc +1 -1
  50. package/scope/.next/server/app/environments.html +1 -1
  51. package/scope/.next/server/app/environments.rsc +1 -1
  52. package/scope/.next/server/app/index.html +1 -1
  53. package/scope/.next/server/app/index.rsc +1 -1
  54. package/scope/.next/server/app/models.html +1 -1
  55. package/scope/.next/server/app/models.rsc +1 -1
  56. package/scope/.next/server/app-paths-manifest.json +3 -3
  57. package/scope/.next/server/functions-config-manifest.json +2 -2
  58. package/scope/.next/server/pages/404.html +1 -1
  59. package/scope/.next/server/pages/500.html +1 -1
  60. package/scope/.next/server/server-reference-manifest.json +1 -1
  61. package/dist/commands/init.d.ts +0 -2
  62. package/dist/commands/init.js +0 -24
  63. package/dist/commands/lifecycle.d.ts +0 -2
  64. package/dist/commands/lifecycle.js +0 -124
  65. package/dist/commands/plane.d.ts +0 -2
  66. package/dist/commands/plane.js +0 -38
  67. package/dist/commands/run.d.ts +0 -2
  68. package/dist/commands/run.js +0 -149
  69. package/dist/commands/runner.d.ts +0 -2
  70. package/dist/commands/runner.js +0 -33
  71. package/dist/commands/secrets.d.ts +0 -2
  72. package/dist/commands/secrets.js +0 -21
  73. /package/scope/.next/static/{5tnFLuvnSbNZNtqRgoot8 → x7wPUCpgS31-5ZHJkcKsU}/_buildManifest.js +0 -0
  74. /package/scope/.next/static/{5tnFLuvnSbNZNtqRgoot8 → x7wPUCpgS31-5ZHJkcKsU}/_ssgManifest.js +0 -0
@@ -6,16 +6,19 @@ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync
6
6
  import { tmpdir } from "node:os";
7
7
  import { join } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
- import { after, before, test } from "node:test";
9
+ import { test } from "node:test";
10
10
  import { MODEL_FUSION_SCHEMA_BUNDLE_HASH } from "@fusionkit/protocol";
11
- import { makeRepo as makeStackRepo, mockRunRequest, startStack, uploadWorkspace } from "@fusionkit/testkit";
12
11
  const CLI = fileURLToPath(new URL("../index.js", import.meta.url));
13
12
  const SMOKE_ENV_KEYS = [
13
+ "FUSIONKIT_CLAUDE_SMOKE",
14
+ "FUSIONKIT_CODEX_SMOKE",
15
+ "FUSIONKIT_CURSOR_SMOKE",
16
+ "FUSIONKIT_ENSEMBLE_LIVE_SMOKE",
14
17
  "WARRANT_CLAUDE_SMOKE",
15
18
  "WARRANT_CODEX_SMOKE",
19
+ "WARRANT_CURSOR_SMOKE",
16
20
  "WARRANT_ENSEMBLE_LIVE_SMOKE"
17
21
  ];
18
- let home;
19
22
  async function readBody(req) {
20
23
  const chunks = [];
21
24
  for await (const chunk of req)
@@ -100,7 +103,7 @@ function warrant(args, options = {}) {
100
103
  else
101
104
  env[key] = value;
102
105
  }
103
- const result = spawnSync(process.execPath, [CLI, "--dir", options.dir ?? home, ...args], {
106
+ const result = spawnSync(process.execPath, [CLI, ...args], {
104
107
  encoding: "utf8",
105
108
  env,
106
109
  input: options.input
@@ -122,7 +125,7 @@ async function warrantAsync(args, options = {}) {
122
125
  env[key] = value;
123
126
  }
124
127
  return await new Promise((resolve) => {
125
- const child = spawn(process.execPath, [CLI, "--dir", options.dir ?? home, ...args], {
128
+ const child = spawn(process.execPath, [CLI, ...args], {
126
129
  env,
127
130
  stdio: ["pipe", "pipe", "pipe"]
128
131
  });
@@ -145,18 +148,11 @@ async function warrantAsync(args, options = {}) {
145
148
  }
146
149
  });
147
150
  }
148
- before(() => {
149
- home = mkdtempSync(join(tmpdir(), "warrant-cli-test-"));
150
- rmSync(home, { recursive: true, force: true });
151
- });
152
- after(() => {
153
- rmSync(home, { recursive: true, force: true });
154
- });
155
151
  test("help prints usage and lists the top-level commands", () => {
156
152
  const result = warrant(["help"]);
157
153
  assert.equal(result.status, 0);
158
154
  assert.match(result.stdout, /real model fusion behind your coding agent/);
159
- for (const command of ["run", "continue", "ensemble", "local", "fusion", "ui", "codex", "claude", "cursor", "serve"]) {
155
+ for (const command of ["init", "ensemble", "local", "fusion", "codex", "claude", "cursor", "serve"]) {
160
156
  assert.match(result.stdout, new RegExp(`\\b${command}\\b`));
161
157
  }
162
158
  });
@@ -186,12 +182,12 @@ test("gateway acp-registry rejects an unknown action", () => {
186
182
  test("local without a tool prints usage and fails", () => {
187
183
  const result = warrant(["local"]);
188
184
  assert.equal(result.status, 1);
189
- assert.match(result.stderr, /usage: warrant local </);
185
+ assert.match(result.stderr, /usage: fusionkit local </);
190
186
  });
191
187
  test("local rejects an unknown tool", () => {
192
188
  const result = warrant(["local", "frobnicate"]);
193
189
  assert.equal(result.status, 1);
194
- assert.match(result.stderr, /usage: warrant local </);
190
+ assert.match(result.stderr, /usage: fusionkit local </);
195
191
  });
196
192
  test("local help documents the flags-before-tool contract", () => {
197
193
  const result = warrant(["local", "--help"]);
@@ -203,78 +199,29 @@ test("fusion help documents the flags-before-tool contract", () => {
203
199
  assert.equal(result.status, 0);
204
200
  assert.match(result.stdout, /must precede the tool name/);
205
201
  });
206
- test("init creates keys, config, and policy; refuses to re-init", () => {
207
- const result = warrant(["init"]);
208
- assert.equal(result.status, 0, result.stderr);
209
- assert.match(result.stdout, /initialized warrant home/);
210
- assert.match(result.stdout, /admin token \(for the control panel\)/);
211
- assert.ok(existsSync(join(home, "config.json")));
212
- assert.ok(existsSync(join(home, "policy.json")));
213
- // The org private key is sealed at rest; a master key file is generated.
214
- assert.ok(existsSync(join(home, "keys", "plane.key.enc")));
215
- assert.ok(existsSync(join(home, "keys", "plane.pub.pem")));
216
- assert.ok(existsSync(join(home, "master.key")));
217
- const config = JSON.parse(readFileSync(join(home, "config.json"), "utf8"));
218
- assert.equal(config.version, "warrant.config.v2");
219
- assert.equal(config.host, "127.0.0.1");
220
- // No key material lives in config.json anymore.
221
- assert.equal(config.secretsKeyHex, undefined);
222
- const again = warrant(["init"]);
223
- assert.equal(again.status, 1);
224
- assert.match(again.stderr, /already initialized/);
225
- });
226
- test("secrets are stored encrypted and listed by name only", () => {
227
- const set = warrant(["secrets", "set", "NPM_TOKEN", "super-secret-value"]);
228
- assert.equal(set.status, 0, set.stderr);
229
- assert.match(set.stdout, /encrypted at rest/);
230
- const list = warrant(["secrets", "list"]);
231
- assert.equal(list.status, 0);
232
- assert.equal(list.stdout.trim(), "NPM_TOKEN");
233
- const stored = readFileSync(join(home, "secrets.enc"), "utf8");
234
- assert.ok(!stored.includes("super-secret-value"), "value must be encrypted");
235
- });
236
- test("ui prints the control panel address and login token", () => {
237
- const result = warrant(["ui"]);
238
- assert.equal(result.status, 0);
239
- assert.match(result.stdout, /control panel: http:\/\/127\.0\.0\.1:7172\/ui\//);
240
- assert.match(result.stdout, /login token: {3}\S+/);
202
+ test("init scaffolds a fusionkit.json and refuses to clobber without --force", () => {
203
+ const fixture = makeRepo();
204
+ try {
205
+ const result = warrant(["init", "--repo", fixture.repo]);
206
+ assert.equal(result.status, 0, result.stderr);
207
+ const configPath = join(fixture.repo, "fusionkit.json");
208
+ assert.ok(existsSync(configPath));
209
+ const config = JSON.parse(readFileSync(configPath, "utf8"));
210
+ assert.equal(config.version, "fusionkit.fusion.v1");
211
+ const again = warrant(["init", "--repo", fixture.repo]);
212
+ assert.equal(again.status, 1);
213
+ assert.match(again.stderr, /already exists/);
214
+ const forced = warrant(["init", "--repo", fixture.repo, "--force"]);
215
+ assert.equal(forced.status, 0, forced.stderr);
216
+ }
217
+ finally {
218
+ fixture.cleanup();
219
+ }
241
220
  });
242
- test("unknown commands and missing arguments fail with guidance", () => {
221
+ test("unknown commands fail with guidance", () => {
243
222
  const unknown = warrant(["frobnicate"]);
244
223
  assert.equal(unknown.status, 1);
245
224
  assert.match(unknown.stderr, /unknown command/);
246
- const missingAgent = warrant(["run", "do things"]);
247
- assert.equal(missingAgent.status, 1);
248
- assert.match(missingAgent.stderr, /--agent is required/);
249
- const missingTask = warrant(["continue", "--agent", "mock"]);
250
- assert.equal(missingTask.status, 1);
251
- assert.match(missingTask.stderr, /task prompt is required/);
252
- const badAgent = warrant(["continue", "--agent", "nonsense", "task"]);
253
- assert.equal(badAgent.status, 1);
254
- assert.match(badAgent.stderr, /unknown agent kind/);
255
- });
256
- test("verify fails closed on a tampered bundle file", () => {
257
- const path = join(home, "garbage.bundle.json");
258
- const fake = {
259
- version: "warrant.bundle.v1",
260
- contract: { signatures: [], workspace: { baseRef: "x" } },
261
- receipt: {
262
- contractHash: "0".repeat(64),
263
- signatures: [],
264
- status: "completed",
265
- workspaceIn: { baseRef: "y", manifestHash: "z" },
266
- workspaceOut: { diffHash: "", artifactHashes: [] },
267
- secretsReleased: [],
268
- eventsHead: "",
269
- eventCount: 0
270
- },
271
- events: [],
272
- keys: { planePublicKeyPem: "", runnerPublicKeyPem: "" }
273
- };
274
- writeFileSync(path, JSON.stringify(fake));
275
- const result = warrant(["verify", path]);
276
- assert.equal(result.status, 1);
277
- assert.match(result.stderr, /VERIFICATION FAILED/);
278
225
  });
279
226
  function makeRepo() {
280
227
  const root = mkdtempSync(join(tmpdir(), "warrant-ensemble-cli-"));
@@ -630,7 +577,7 @@ test("ensemble dashboard writes markdown and run-result records", () => {
630
577
  assert.match(result.stdout, /records: 6/);
631
578
  assert.ok(existsSync(join(fixture.output, "dashboard.md")));
632
579
  assert.ok(existsSync(join(fixture.output, "harness-run-results", "mock-success.json")));
633
- assert.ok(existsSync(join(fixture.output, "harness-run-results", "cursor-missing.json")));
580
+ assert.ok(existsSync(join(fixture.output, "harness-run-results", "cursor-skipped.json")));
634
581
  const dashboard = readFileSync(join(fixture.output, "dashboard.md"), "utf8");
635
582
  assert.match(dashboard, /Capability Matrix/);
636
583
  assert.match(dashboard, /command-failure/);
@@ -678,7 +625,7 @@ test("ensemble dashboard rejects unknown live-smoke targets", () => {
678
625
  "--out",
679
626
  fixture.output,
680
627
  "--live-smoke",
681
- "cursor"
628
+ "bogus"
682
629
  ]);
683
630
  assert.equal(result.status, 1);
684
631
  assert.match(result.stderr, /--live-smoke must be/);
@@ -808,60 +755,3 @@ test("ensemble gateway test runs the unified front-door acceptance suite", async
808
755
  fixture.cleanup();
809
756
  }
810
757
  });
811
- test("lifecycle commands read a real run from a live plane", async () => {
812
- const stack = await startStack({
813
- policy: (policy) => {
814
- policy.agents.allow = ["mock"];
815
- }
816
- });
817
- const repo = makeStackRepo({ files: { "README.md": "# cli lifecycle\n" } });
818
- const liveHome = mkdtempSync(join(tmpdir(), "warrant-cli-live-"));
819
- rmSync(liveHome, { recursive: true, force: true });
820
- try {
821
- // The plane runs in this test process, so every CLI call must use the async
822
- // spawner: a synchronous spawn would block the event loop and deadlock the
823
- // in-process plane.
824
- const init = await warrantAsync(["init"], { dir: liveHome });
825
- assert.equal(init.status, 0, init.stderr);
826
- // Point the freshly initialized home at the in-process test stack.
827
- const configPath = join(liveHome, "config.json");
828
- const config = JSON.parse(readFileSync(configPath, "utf8"));
829
- config.planeUrl = stack.planeUrl;
830
- config.adminToken = stack.adminToken;
831
- writeFileSync(configPath, JSON.stringify(config, null, 2));
832
- // Create one completed run through the SDK so the CLI has something to read.
833
- const captured = await uploadWorkspace(stack.client, repo);
834
- const created = await stack.client.requestRun(mockRunRequest({ prompt: "lifecycle probe", pool: stack.pool, workspace: captured.manifest }));
835
- if (created.status === "awaiting_approval") {
836
- await stack.client.approve(created.runId, { kind: "human", id: "cli-tester" });
837
- }
838
- assert.ok(await stack.runOnce());
839
- const runs = await warrantAsync(["runs"], { dir: liveHome });
840
- assert.equal(runs.status, 0, runs.stderr);
841
- assert.match(runs.stdout, new RegExp(created.runId));
842
- const receipt = await warrantAsync(["receipt", created.runId], { dir: liveHome });
843
- assert.equal(receipt.status, 0, receipt.stderr);
844
- const bundlePath = join(liveHome, "out.bundle.json");
845
- const bundle = await warrantAsync(["bundle", created.runId, "--out", bundlePath], {
846
- dir: liveHome
847
- });
848
- assert.equal(bundle.status, 0, bundle.stderr);
849
- assert.match(bundle.stdout, /bundle written to/);
850
- assert.ok(existsSync(bundlePath));
851
- // The CLI round-trips its own bundle through offline verification.
852
- const verify = await warrantAsync(["verify", bundlePath], { dir: liveHome });
853
- assert.equal(verify.status, 0, verify.stderr);
854
- assert.match(verify.stdout, /VERIFIED/);
855
- const exported = await warrantAsync(["export"], { dir: liveHome });
856
- assert.equal(exported.status, 0, exported.stderr);
857
- assert.match(exported.stdout, new RegExp(created.runId));
858
- const pull = await warrantAsync(["pull", created.runId, "--repo", repo], { dir: liveHome });
859
- assert.equal(pull.status, 0, pull.stderr);
860
- assert.match(pull.stdout, /applied|nothing to pull|branch/);
861
- }
862
- finally {
863
- await stack.stop();
864
- rmSync(repo, { recursive: true, force: true });
865
- rmSync(liveHome, { recursive: true, force: true });
866
- }
867
- });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,214 @@
1
+ import assert from "node:assert/strict";
2
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { test } from "node:test";
6
+ import { assertHarnessRunResultV1 } from "@fusionkit/protocol";
7
+ import { gitText } from "@fusionkit/workspace";
8
+ import { createMockHarness } from "@fusionkit/ensemble";
9
+ import { createHarnessCapabilityMatrix, runHarnessSmokeDashboard } from "../dashboard.js";
10
+ function makeRepo() {
11
+ const root = mkdtempSync(join(tmpdir(), "ensemble-dashboard-"));
12
+ const repo = join(root, "repo");
13
+ mkdirSync(repo);
14
+ gitText(repo, ["init", "--quiet", "--initial-branch=main"]);
15
+ gitText(repo, ["config", "user.email", "dashboard@warrant.local"]);
16
+ gitText(repo, ["config", "user.name", "dashboard"]);
17
+ writeFileSync(join(repo, "README.md"), "# dashboard\n");
18
+ gitText(repo, ["add", "-A"]);
19
+ gitText(repo, ["commit", "--quiet", "-m", "init"]);
20
+ return {
21
+ repo,
22
+ outputRoot: join(root, "dashboard-out"),
23
+ cleanup: () => rmSync(root, { recursive: true, force: true })
24
+ };
25
+ }
26
+ test("capability matrix covers Cursor, Claude Code, Codex, command, and mock", () => {
27
+ const matrix = createHarnessCapabilityMatrix({ env: {} });
28
+ const harnessIds = matrix.rows.map((row) => row.harnessId);
29
+ assert.deepEqual(harnessIds, ["codex", "claude-code", "cursor", "command", "mock"]);
30
+ assert.ok(matrix.capabilities.includes("model_override"));
31
+ assert.ok(matrix.capabilities.includes("transcript_capture"));
32
+ assert.ok(matrix.capabilities.includes("diff_capture"));
33
+ assert.ok(matrix.capabilities.includes("tool_loop_capture"));
34
+ assert.ok(matrix.capabilities.includes("patch_apply_visibility"));
35
+ assert.ok(matrix.capabilities.includes("route_model_observation"));
36
+ assert.ok(matrix.capabilities.includes("verification_hint"));
37
+ assert.ok(matrix.capabilities.includes("replay_support"));
38
+ assert.ok(matrix.capabilities.includes("workspace_read"));
39
+ assert.ok(matrix.capabilities.includes("verification"));
40
+ assert.equal(matrix.rows.find((row) => row.harnessId === "cursor")?.availability, "credential_gated");
41
+ assert.equal(matrix.rows.find((row) => row.harnessId === "claude-code")?.harnessKind, "claude_code");
42
+ assert.equal(matrix.rows.find((row) => row.harnessId === "codex")?.harnessKind, "codex");
43
+ });
44
+ test("smoke dashboard writes schema-valid success, failure, skipped, and missing records", async () => {
45
+ const fixture = makeRepo();
46
+ try {
47
+ const dashboard = await runHarnessSmokeDashboard({
48
+ repo: fixture.repo,
49
+ outputRoot: fixture.outputRoot,
50
+ timeoutMs: 1_000,
51
+ createdAt: "2026-06-16T00:00:00.000Z"
52
+ });
53
+ assert.equal(dashboard.records.length, 6);
54
+ assert.equal(existsSync(dashboard.dashboardPath), true);
55
+ for (const record of dashboard.records) {
56
+ assertHarnessRunResultV1(record.result);
57
+ assert.equal(existsSync(record.resultPath), true);
58
+ const written = JSON.parse(readFileSync(record.resultPath, "utf8"));
59
+ assertHarnessRunResultV1(written);
60
+ }
61
+ const statuses = dashboard.records.map((record) => record.result.status).sort();
62
+ assert.deepEqual(statuses, [
63
+ "failed",
64
+ "skipped",
65
+ "skipped",
66
+ "skipped",
67
+ "succeeded",
68
+ "succeeded"
69
+ ]);
70
+ assert.equal(dashboard.records.find((record) => record.taskId === "claude-code-skipped")?.result
71
+ .harness_kind, "claude_code");
72
+ assert.equal(dashboard.records.find((record) => record.taskId === "codex-skipped")?.result.harness_kind, "codex");
73
+ assert.equal(dashboard.records.find((record) => record.taskId === "cursor-skipped")?.result.harness_kind, "cursor");
74
+ assert.equal(dashboard.records.find((record) => record.taskId === "cursor-skipped")?.result.status, "skipped");
75
+ const markdown = readFileSync(dashboard.dashboardPath, "utf8");
76
+ assert.match(markdown, /# HandoffKit Harness Smoke Dashboard/);
77
+ assert.match(markdown, /## Capability Matrix/);
78
+ assert.match(markdown, /## Adapter Readiness/);
79
+ assert.match(markdown, /contract\/mock ready/);
80
+ assert.match(markdown, /credentials missing\/skipped/);
81
+ assert.match(markdown, /live smoke not requested/);
82
+ assert.match(markdown, /command-failure/);
83
+ assert.match(markdown, /cursor-skipped/);
84
+ assert.match(markdown, /harness-run-results\/mock-success\.json/);
85
+ assert.equal(dashboard.readiness.length, 5);
86
+ }
87
+ finally {
88
+ fixture.cleanup();
89
+ }
90
+ });
91
+ test("smoke dashboard only adds live records when explicit smoke env is enabled", async () => {
92
+ const fixture = makeRepo();
93
+ try {
94
+ const dashboard = await runHarnessSmokeDashboard({
95
+ repo: fixture.repo,
96
+ outputRoot: fixture.outputRoot,
97
+ timeoutMs: 1_000,
98
+ createdAt: "2026-06-16T00:00:00.000Z",
99
+ env: {},
100
+ liveSmoke: ["claude-code", "codex"]
101
+ });
102
+ assert.equal(dashboard.records.length, 6);
103
+ assert.equal(dashboard.records.some((record) => record.purpose === "live"), false);
104
+ }
105
+ finally {
106
+ fixture.cleanup();
107
+ }
108
+ });
109
+ test("explicit live smoke without credentials records a failed preflight", async () => {
110
+ const fixture = makeRepo();
111
+ try {
112
+ const dashboard = await runHarnessSmokeDashboard({
113
+ repo: fixture.repo,
114
+ outputRoot: fixture.outputRoot,
115
+ timeoutMs: 1_000,
116
+ createdAt: "2026-06-16T00:00:00.000Z",
117
+ env: { WARRANT_CLAUDE_SMOKE: "1" },
118
+ liveSmoke: ["claude-code"]
119
+ });
120
+ const live = dashboard.records.find((record) => record.taskId === "claude-code-live");
121
+ assert.equal(live?.purpose, "live");
122
+ assert.equal(live?.result.status, "failed");
123
+ assert.match(live?.result.output_summary ?? "", /Explicit live smoke failed before launch/);
124
+ assert.equal(dashboard.readiness.find((row) => row.harnessId === "claude-code")?.liveSmoke, "live smoke failed");
125
+ }
126
+ finally {
127
+ fixture.cleanup();
128
+ }
129
+ });
130
+ test("live smoke readiness reports sanitized local evidence refs", async () => {
131
+ const fixture = makeRepo();
132
+ const privateTranscript = "raw private transcript should not render";
133
+ try {
134
+ const claudeHarness = {
135
+ ...createMockHarness({
136
+ id: "claude-code-live-mock",
137
+ candidates: {
138
+ claude: {
139
+ transcript: privateTranscript,
140
+ artifacts: [
141
+ {
142
+ artifact_id: "claude_safe_log",
143
+ kind: "log",
144
+ hash: `sha256:${"a".repeat(64)}`,
145
+ uri: "file:///tmp/private-claude.log",
146
+ redaction_status: "synthetic"
147
+ },
148
+ {
149
+ artifact_id: "claude_raw_transcript",
150
+ kind: "transcript",
151
+ hash: `sha256:${"b".repeat(64)}`,
152
+ uri: "file:///tmp/raw-claude.txt",
153
+ redaction_status: "raw"
154
+ }
155
+ ]
156
+ }
157
+ }
158
+ }),
159
+ harnessKind: "claude_code"
160
+ };
161
+ const codexHarness = {
162
+ ...createMockHarness({
163
+ id: "codex-live-mock",
164
+ candidates: {
165
+ codex: {
166
+ transcript: "codex private transcript should not render",
167
+ artifacts: [
168
+ {
169
+ artifact_id: "codex_safe_log",
170
+ kind: "log",
171
+ hash: `sha256:${"c".repeat(64)}`,
172
+ uri: "file:///tmp/private-codex.log",
173
+ redaction_status: "synthetic"
174
+ }
175
+ ]
176
+ }
177
+ }
178
+ }),
179
+ harnessKind: "codex"
180
+ };
181
+ const dashboard = await runHarnessSmokeDashboard({
182
+ repo: fixture.repo,
183
+ outputRoot: fixture.outputRoot,
184
+ timeoutMs: 1_000,
185
+ createdAt: "2026-06-16T00:00:00.000Z",
186
+ env: {
187
+ WARRANT_ENSEMBLE_LIVE_SMOKE: "1",
188
+ VERCEL_TOKEN: "vercel-test",
189
+ ANTHROPIC_API_KEY: "anthropic-test",
190
+ CODEX_API_KEY: "codex-test"
191
+ },
192
+ liveSmoke: ["claude-code", "codex"],
193
+ liveSmokeHarnesses: {
194
+ "claude-code": claudeHarness,
195
+ codex: codexHarness
196
+ }
197
+ });
198
+ assert.equal(dashboard.records.length, 8);
199
+ assert.equal(dashboard.records.find((record) => record.taskId === "claude-code-live")?.result.status, "succeeded");
200
+ assert.equal(dashboard.records.find((record) => record.taskId === "codex-live")?.result.status, "succeeded");
201
+ assert.equal(dashboard.readiness.find((row) => row.harnessId === "claude-code")?.liveSmoke, "live smoke passed");
202
+ assert.equal(dashboard.readiness.find((row) => row.harnessId === "codex")?.liveSmoke, "live smoke passed");
203
+ const markdown = readFileSync(dashboard.dashboardPath, "utf8");
204
+ assert.match(markdown, /log:claude_safe_log:sha256/);
205
+ assert.match(markdown, /log:codex_safe_log:sha256/);
206
+ assert.match(markdown, /raw artifact ref\(s\) withheld/);
207
+ assert.equal(markdown.includes(privateTranscript), false);
208
+ assert.equal(markdown.includes("file:///tmp/private-claude.log"), false);
209
+ assert.equal(markdown.includes("file:///tmp/private-codex.log"), false);
210
+ }
211
+ finally {
212
+ fixture.cleanup();
213
+ }
214
+ });
@@ -9,6 +9,7 @@ import { PassThrough } from "node:stream";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { after, before, test } from "node:test";
11
11
  import { FUSION_REPORT_HEADER, FUSION_RUN_ID_HEADER, runAcpAgent, runFrontDoorAcceptance } from "@fusionkit/model-gateway";
12
+ import { resolveCursorkitCli } from "@fusionkit/ensemble";
12
13
  import { buildAcpRunner, startConfiguredGateway } from "../gateway.js";
13
14
  /**
14
15
  * Comprehensive front-door e2e. Exercises the real chain end to end:
@@ -386,7 +387,9 @@ test("unified acceptance suite passes every reachable front door against the rea
386
387
  assert.equal(statusOf("cursor-acp"), "blocked");
387
388
  assert.ok(backend.judgeCallCount() >= 4, "judge synthesis must hit the model backend per front door");
388
389
  });
389
- const LIVE_CLAUDE = process.env.WARRANT_GATEWAY_LIVE_CLAUDE === "1" ? false : "set WARRANT_GATEWAY_LIVE_CLAUDE=1 with a working claude CLI";
390
+ const LIVE_CLAUDE = (process.env.FUSIONKIT_GATEWAY_LIVE_CLAUDE ?? process.env.WARRANT_GATEWAY_LIVE_CLAUDE) === "1"
391
+ ? false
392
+ : "set FUSIONKIT_GATEWAY_LIVE_CLAUDE=1 with a working claude CLI";
390
393
  test("live: real Claude Code CLI drives the gateway fusion run and receives the synthesized answer", { skip: LIVE_CLAUDE }, async () => {
391
394
  // A dedicated single-model gateway keeps the live run light: each Claude
392
395
  // model call triggers one real unified harness run (worktree + command +
@@ -429,7 +432,9 @@ test("live: real Claude Code CLI drives the gateway fusion run and receives the
429
432
  await liveGateway.close();
430
433
  }
431
434
  });
432
- const LIVE_CODEX = process.env.WARRANT_GATEWAY_LIVE_CODEX === "1" ? false : "set WARRANT_GATEWAY_LIVE_CODEX=1 with a working codex CLI";
435
+ const LIVE_CODEX = (process.env.FUSIONKIT_GATEWAY_LIVE_CODEX ?? process.env.WARRANT_GATEWAY_LIVE_CODEX) === "1"
436
+ ? false
437
+ : "set FUSIONKIT_GATEWAY_LIVE_CODEX=1 with a working codex CLI";
433
438
  test("live: real Codex CLI drives the gateway fusion run and receives the synthesized answer", { skip: LIVE_CODEX }, async () => {
434
439
  // Codex streams `/v1/responses`; the gateway must emit the Responses SSE
435
440
  // sequence ending in response.completed for Codex to accept the answer.
@@ -482,15 +487,14 @@ test("live: real Codex CLI drives the gateway fusion run and receives the synthe
482
487
  rmSync(codexHome, { recursive: true, force: true });
483
488
  }
484
489
  });
485
- // Drives the real cursor-agent CLI in ACP mode through the real Cursorkit
490
+ // Drives the real cursor-agent CLI in ACP mode through the bundled Cursorkit
486
491
  // bridge, whose local model backend is pointed at this gateway. Requires a
487
- // logged-in cursor-agent and a built Cursorkit checkout (WARRANT_CURSORKIT_DIR).
488
- const CURSORKIT_DIR = process.env.WARRANT_CURSORKIT_DIR;
489
- const LIVE_CURSOR = process.env.WARRANT_GATEWAY_LIVE_CURSOR === "1" && CURSORKIT_DIR !== undefined
492
+ // logged-in cursor-agent (Cursorkit is bundled as an npm dependency).
493
+ const LIVE_CURSOR = (process.env.FUSIONKIT_GATEWAY_LIVE_CURSOR ?? process.env.WARRANT_GATEWAY_LIVE_CURSOR) === "1"
490
494
  ? false
491
- : "set WARRANT_GATEWAY_LIVE_CURSOR=1 and WARRANT_CURSORKIT_DIR=<built cursorkit checkout> with a logged-in cursor-agent";
495
+ : "set FUSIONKIT_GATEWAY_LIVE_CURSOR=1 with a logged-in cursor-agent";
492
496
  test("live: real cursor-agent (ACP) drives the Cursorkit bridge into the gateway fusion run", { skip: LIVE_CURSOR }, async () => {
493
- const cursorkitDir = CURSORKIT_DIR;
497
+ const { serveCli } = resolveCursorkitCli();
494
498
  const liveGateway = await startConfiguredGateway({
495
499
  config: { ...config, models: [{ id: "cursor-panel", model: "fusion-cursor" }] },
496
500
  host: "127.0.0.1",
@@ -519,8 +523,7 @@ test("live: real cursor-agent (ACP) drives the Cursorkit bridge into the gateway
519
523
  MODEL_PROVIDER_MODEL: "fusion-panel",
520
524
  MODEL_CONTEXT_TOKEN_LIMIT: "128000"
521
525
  });
522
- const bridge = spawn(process.execPath, ["dist/src/cli.js", "serve"], {
523
- cwd: cursorkitDir,
526
+ const bridge = spawn(process.execPath, [serveCli, "serve"], {
524
527
  env: bridgeEnv,
525
528
  stdio: ["ignore", "pipe", "pipe"]
526
529
  });
@@ -13,23 +13,23 @@ test("claudeEnv points Claude Code at the gateway's Anthropic surface", () => {
13
13
  });
14
14
  test("claudeEnv falls back to a placeholder auth token", () => {
15
15
  const env = claudeEnv("http://127.0.0.1:9000");
16
- assert.equal(env.ANTHROPIC_AUTH_TOKEN, "warrant-local");
16
+ assert.equal(env.ANTHROPIC_AUTH_TOKEN, "fusionkit-local");
17
17
  });
18
18
  test("codexConfigToml declares a Responses provider at the gateway", () => {
19
19
  const toml = codexConfigToml("http://127.0.0.1:9000", "local-model");
20
20
  assert.ok(toml.includes('model = "local-model"'));
21
- assert.ok(toml.includes("[model_providers.warrant-local]"));
21
+ assert.ok(toml.includes("[model_providers.fusionkit-local]"));
22
22
  assert.ok(toml.includes('base_url = "http://127.0.0.1:9000/v1"'));
23
23
  assert.ok(toml.includes('wire_api = "responses"'));
24
24
  assert.ok(toml.includes("requires_openai_auth = false"));
25
25
  });
26
26
  test("opencodeConfig registers an OpenAI-compatible provider", () => {
27
27
  const config = opencodeConfig("http://127.0.0.1:9000", "local-model");
28
- const provider = config.provider["warrant-local"];
28
+ const provider = config.provider["fusionkit-local"];
29
29
  assert.equal(provider?.npm, "@ai-sdk/openai-compatible");
30
30
  assert.equal(provider?.options.baseURL, "http://127.0.0.1:9000/v1");
31
31
  assert.ok("local-model" in (provider?.models ?? {}));
32
- assert.equal(opencodeModelArg("local-model"), "warrant-local/local-model");
32
+ assert.equal(opencodeModelArg("local-model"), "fusionkit-local/local-model");
33
33
  });
34
34
  test("cursorInstructions surfaces the public URL and plan-mode caveat", () => {
35
35
  const text = cursorInstructions("https://abc.example", "local-model");
@@ -0,0 +1,2 @@
1
+ import type { ToolRegistry } from "@fusionkit/tools";
2
+ export declare const toolRegistry: ToolRegistry;
package/dist/tools.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The fusionkit tool registry: the single place that knows every tool package.
3
+ * Importing this module also wires the ensemble harness gateway to resolve
4
+ * tool-backed adapters (codex / claude-code / cursor) from the registry, so
5
+ * `@fusionkit/ensemble` itself stays free of any per-tool dependency.
6
+ *
7
+ * Adding a new tool is one new `@fusionkit/tool-*` package plus one entry here.
8
+ */
9
+ import { setToolHarnessProvider } from "@fusionkit/ensemble";
10
+ import { createToolRegistry } from "@fusionkit/tools";
11
+ import { claudeTool } from "@fusionkit/tool-claude";
12
+ import { codexTool } from "@fusionkit/tool-codex";
13
+ import { cursorTool } from "@fusionkit/tool-cursor";
14
+ import { opencodeTool } from "@fusionkit/tool-opencode";
15
+ export const toolRegistry = createToolRegistry([
16
+ codexTool,
17
+ claudeTool,
18
+ cursorTool,
19
+ opencodeTool
20
+ ]);
21
+ setToolHarnessProvider({
22
+ adapter: (kind, options) => toolRegistry.harnessForKind(kind, options),
23
+ sideEffects: (kind) => toolRegistry.sideEffectsForKind(kind),
24
+ responseShape: (kind) => toolRegistry.responseShapeForKind(kind)
25
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fusionkit/cli",
3
3
  "private": false,
4
- "version": "0.1.4",
4
+ "version": "0.1.6",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/velum-labs/handoffkit.git",
@@ -34,14 +34,19 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "commander": "14.0.3",
37
- "@fusionkit/ensemble": "0.1.4",
38
- "@fusionkit/handoff": "0.1.4",
39
- "@fusionkit/model-gateway": "0.1.4",
40
- "@fusionkit/plane": "0.1.4",
41
- "@fusionkit/protocol": "0.1.4",
42
- "@fusionkit/runner": "0.1.4",
43
- "@fusionkit/workspace": "0.1.4",
44
- "@fusionkit/sdk": "0.1.4"
37
+ "@fusionkit/ensemble": "0.1.6",
38
+ "@fusionkit/handoff": "0.1.6",
39
+ "@fusionkit/model-gateway": "0.1.6",
40
+ "@fusionkit/protocol": "0.1.6",
41
+ "@fusionkit/plane": "0.1.6",
42
+ "@fusionkit/runner": "0.1.6",
43
+ "@fusionkit/sdk": "0.1.6",
44
+ "@fusionkit/tool-cursor": "0.1.6",
45
+ "@fusionkit/tool-codex": "0.1.6",
46
+ "@fusionkit/tool-claude": "0.1.6",
47
+ "@fusionkit/tool-opencode": "0.1.6",
48
+ "@fusionkit/tools": "0.1.6",
49
+ "@fusionkit/workspace": "0.1.6"
45
50
  },
46
51
  "optionalDependencies": {
47
52
  "portless": "0.14.0"
@@ -1 +1 @@
1
- 5tnFLuvnSbNZNtqRgoot8
1
+ x7wPUCpgS31-5ZHJkcKsU