@fusionkit/cli 0.1.5 → 0.1.7
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.
- package/README.md +32 -8
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1 -0
- package/dist/commands/doctor.js +7 -3
- package/dist/commands/ensemble-gateway.js +0 -2
- package/dist/commands/ensemble-records.d.ts +2 -1
- package/dist/commands/ensemble-records.js +3 -1
- package/dist/commands/ensemble.js +3 -4
- package/dist/commands/fusion.js +16 -13
- package/dist/commands/local.js +3 -3
- package/dist/cursor-acp.d.ts +3 -5
- package/dist/cursor-acp.js +12 -11
- package/dist/dashboard.d.ts +65 -0
- package/dist/dashboard.js +587 -0
- package/dist/fusion/env.d.ts +111 -0
- package/dist/fusion/env.js +98 -0
- package/dist/fusion/observability.d.ts +39 -0
- package/dist/fusion/observability.js +227 -0
- package/dist/fusion/preflight.d.ts +12 -0
- package/dist/fusion/preflight.js +42 -0
- package/dist/fusion/stack.d.ts +66 -0
- package/dist/fusion/stack.js +315 -0
- package/dist/fusion-config.d.ts +58 -7
- package/dist/fusion-config.js +152 -28
- package/dist/fusion-init.d.ts +1 -0
- package/dist/fusion-init.js +50 -15
- package/dist/fusion-quickstart.d.ts +11 -222
- package/dist/fusion-quickstart.js +58 -759
- package/dist/gateway.d.ts +0 -2
- package/dist/gateway.js +0 -2
- package/dist/local.d.ts +10 -17
- package/dist/local.js +50 -116
- package/dist/shared/options.d.ts +2 -1
- package/dist/shared/options.js +13 -19
- package/dist/shared/proc.d.ts +4 -70
- package/dist/shared/proc.js +3 -228
- package/dist/test/cli.test.js +11 -6
- package/dist/test/dashboard.test.d.ts +1 -0
- package/dist/test/dashboard.test.js +214 -0
- package/dist/test/fusion-config.test.js +64 -4
- package/dist/test/gateway-e2e.test.js +13 -10
- package/dist/test/local.test.js +4 -4
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +25 -0
- package/package.json +14 -9
- package/scope/.next/BUILD_ID +1 -1
- package/scope/.next/app-build-manifest.json +10 -10
- package/scope/.next/app-path-routes-manifest.json +2 -2
- package/scope/.next/build-manifest.json +2 -2
- package/scope/.next/prerender-manifest.json +13 -13
- package/scope/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/scope/.next/server/app/_not-found.html +1 -1
- package/scope/.next/server/app/_not-found.rsc +1 -1
- package/scope/.next/server/app/api/environments/route_client-reference-manifest.js +1 -1
- package/scope/.next/server/app/api/ingest/route_client-reference-manifest.js +1 -1
- package/scope/.next/server/app/api/models/route_client-reference-manifest.js +1 -1
- package/scope/.next/server/app/api/replay/route_client-reference-manifest.js +1 -1
- package/scope/.next/server/app/api/sessions/[traceId]/route_client-reference-manifest.js +1 -1
- package/scope/.next/server/app/api/sessions/route_client-reference-manifest.js +1 -1
- package/scope/.next/server/app/api/stream/route_client-reference-manifest.js +1 -1
- package/scope/.next/server/app/environments/page_client-reference-manifest.js +1 -1
- package/scope/.next/server/app/environments.html +1 -1
- package/scope/.next/server/app/environments.rsc +1 -1
- package/scope/.next/server/app/index.html +1 -1
- package/scope/.next/server/app/index.rsc +1 -1
- package/scope/.next/server/app/models/page_client-reference-manifest.js +1 -1
- package/scope/.next/server/app/models.html +1 -1
- package/scope/.next/server/app/models.rsc +1 -1
- package/scope/.next/server/app/page_client-reference-manifest.js +1 -1
- package/scope/.next/server/app/sessions/[traceId]/page_client-reference-manifest.js +1 -1
- package/scope/.next/server/app-paths-manifest.json +2 -2
- package/scope/.next/server/functions-config-manifest.json +2 -2
- package/scope/.next/server/pages/404.html +1 -1
- package/scope/.next/server/pages/500.html +1 -1
- package/scope/.next/server/server-reference-manifest.json +1 -1
- /package/scope/.next/static/{vxqImMqlOwssVTua5Facf → BrrtQvnEIgv-OVkaeanKI}/_buildManifest.js +0 -0
- /package/scope/.next/static/{vxqImMqlOwssVTua5Facf → BrrtQvnEIgv-OVkaeanKI}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -44,23 +44,25 @@ cloud panel (skip with `--yes`). Use `--local` for the on-device MLX panel, or
|
|
|
44
44
|
|
|
45
45
|
## Per-repo config
|
|
46
46
|
|
|
47
|
-
Tired of long flag lines? Scaffold a committed
|
|
47
|
+
Tired of long flag lines? Scaffold a committed `.fusionkit/` folder:
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
50
|
fusionkit init
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
It
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
It writes `.fusionkit/fusion.json` (the panel, judge, default tool, and run
|
|
54
|
+
defaults) plus editable system-prompt overrides in `.fusionkit/prompts/*.md`, so
|
|
55
|
+
the whole team can just run `fusionkit codex`. Only env-var *names* for keys are
|
|
56
|
+
stored, never secrets. Explicit CLI flags always override the folder. A legacy
|
|
57
|
+
`fusionkit.json` is auto-migrated on first run. Inspect the effective config and
|
|
58
|
+
a dry-run preview with `fusionkit status`.
|
|
57
59
|
|
|
58
60
|
## Commands
|
|
59
61
|
|
|
60
62
|
- `fusionkit codex | claude | cursor` — launch that agent backed by the panel.
|
|
61
63
|
- `fusionkit serve` — just run the gateway and print setup snippets for any tool.
|
|
62
64
|
- `fusionkit fusion [tool]` — the generic launcher (interactive picker on a TTY).
|
|
63
|
-
- `fusionkit init` — scaffold
|
|
65
|
+
- `fusionkit init` — scaffold the committed `.fusionkit/` folder for this repo.
|
|
64
66
|
- `fusionkit doctor` — check prerequisites with fix hints.
|
|
65
67
|
- `fusionkit status` — show the effective config and what a run will do.
|
|
66
68
|
|
|
@@ -73,5 +75,27 @@ tool name; everything after the tool is forwarded to it.
|
|
|
73
75
|
- `--observe` boots a local dashboard that streams live trace events. It is a
|
|
74
76
|
separate app and is not bundled in the npm package; fusionkit prints how to
|
|
75
77
|
enable it if it isn't available.
|
|
76
|
-
- `cursor` needs a
|
|
77
|
-
|
|
78
|
+
- `cursor` only needs a logged-in `cursor-agent` CLI; Cursorkit ships bundled
|
|
79
|
+
with this package, so no separate checkout is required.
|
|
80
|
+
|
|
81
|
+
## Adding a new tool
|
|
82
|
+
|
|
83
|
+
Each coding tool is its own workspace package implementing a single
|
|
84
|
+
`ToolIntegration` (the adapter), so supporting a new tool is additive:
|
|
85
|
+
|
|
86
|
+
1. Create `packages/tool-<name>/` (copy `packages/tool-codex` as a template). It
|
|
87
|
+
depends on `@fusionkit/tools` for the `ToolIntegration` / `ToolLaunchContext`
|
|
88
|
+
contract, and on `@fusionkit/ensemble` if it also ships a harness adapter.
|
|
89
|
+
2. Export a `const <name>Tool: ToolIntegration` with:
|
|
90
|
+
- `launch(ctx)` — boot the tool's binary against `ctx.gatewayUrl` (the host
|
|
91
|
+
injects `spawnTool`, portless, teardown, etc. via the context; tool packages
|
|
92
|
+
never import the CLI).
|
|
93
|
+
- `modes` — `"fusion"`, `"local"`, or both.
|
|
94
|
+
- `createHarness` + `harnessKinds` — optional, only if the tool also runs as
|
|
95
|
+
an ensemble harness in the gateway/e2e matrix.
|
|
96
|
+
3. Register it in [`packages/cli/src/tools.ts`](src/tools.ts) by adding it to the
|
|
97
|
+
`createToolRegistry([...])` list.
|
|
98
|
+
|
|
99
|
+
That single registry entry wires the tool into the `fusionkit <tool>` launcher,
|
|
100
|
+
`fusionkit local <tool>`, the interactive picker, preflight, and (when it has a
|
|
101
|
+
harness) the ensemble gateway — no other switch statements to update.
|
package/dist/cli.d.ts
CHANGED
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";
|
package/dist/commands/doctor.js
CHANGED
|
@@ -56,13 +56,15 @@ function runDoctor() {
|
|
|
56
56
|
console.log("");
|
|
57
57
|
console.log(bold("repo config"));
|
|
58
58
|
try {
|
|
59
|
-
const config = loadFusionConfig(repoRoot);
|
|
59
|
+
const config = loadFusionConfig(repoRoot, (message) => console.log(` ${gray(glyph.bullet())} ${dim(message)}`));
|
|
60
60
|
if (config === undefined) {
|
|
61
|
-
console.log(` ${gray(glyph.bullet())} no ${cyan("fusionkit
|
|
61
|
+
console.log(` ${gray(glyph.bullet())} no ${cyan(".fusionkit/")} yet — run ${bold("fusionkit fusion init")}`);
|
|
62
62
|
}
|
|
63
63
|
else {
|
|
64
|
+
const overrides = Object.keys(config.prompts ?? {});
|
|
64
65
|
console.log(` ${green(glyph.tick())} ${cyan(fusionConfigPath(repoRoot))}`);
|
|
65
66
|
console.log(` ${dim(`tool: ${config.tool ?? "(unset)"} panel: ${(config.panel ?? []).map((s) => s.id).join(", ") || "(unset)"}`)}`);
|
|
67
|
+
console.log(` ${dim(`prompt overrides: ${overrides.length > 0 ? overrides.join(", ") : "(none — built-in defaults)"}`)}`);
|
|
66
68
|
}
|
|
67
69
|
}
|
|
68
70
|
catch (error) {
|
|
@@ -94,7 +96,7 @@ function runStatus() {
|
|
|
94
96
|
}
|
|
95
97
|
let config;
|
|
96
98
|
try {
|
|
97
|
-
config = loadFusionConfig(repoRoot);
|
|
99
|
+
config = loadFusionConfig(repoRoot, (message) => console.log(dim(message)));
|
|
98
100
|
}
|
|
99
101
|
catch (error) {
|
|
100
102
|
console.log(`${red("config error:")} ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -110,6 +112,8 @@ function runStatus() {
|
|
|
110
112
|
console.log(`${dim("tool:")} ${bold(tool)}`);
|
|
111
113
|
console.log(`${dim("judge:")} ${judge}`);
|
|
112
114
|
console.log(`${dim("observe:")} ${config?.observe === true ? "on" : "off"}`);
|
|
115
|
+
const overrides = Object.keys(config?.prompts ?? {});
|
|
116
|
+
console.log(`${dim("prompts:")} ${overrides.length > 0 ? overrides.join(", ") : dim("(built-in defaults)")}`);
|
|
113
117
|
console.log(bold("\npanel"));
|
|
114
118
|
for (const spec of panel)
|
|
115
119
|
console.log(` ${glyph.bullet()} ${panelLabel(spec)}`);
|
|
@@ -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
|
|
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 {
|
|
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,
|
|
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);
|
package/dist/commands/fusion.js
CHANGED
|
@@ -16,11 +16,10 @@ 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
|
-
.option("--no-local", "override a fusionkit
|
|
20
|
+
.option("--no-local", "override a .fusionkit default of local=true")
|
|
22
21
|
.option("--observe", "boot the local scope dashboard and stream live trace events")
|
|
23
|
-
.option("--no-observe", "override a fusionkit
|
|
22
|
+
.option("--no-observe", "override a .fusionkit default of observe=true")
|
|
24
23
|
.option("--yes", "skip the interactive cloud-panel cost confirmation")
|
|
25
24
|
.option("--auth-token <token>", "require a bearer token on the gateway")
|
|
26
25
|
.option("--port <n>", "gateway port (default: ephemeral)")
|
|
@@ -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,10 +96,10 @@ 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;
|
|
101
|
+
if (options.prompts === undefined && config.prompts !== undefined)
|
|
102
|
+
options.prompts = config.prompts;
|
|
106
103
|
}
|
|
107
104
|
/** The repo root used for config lookup: --repo if given, else the cwd's git root. */
|
|
108
105
|
function configRepoRoot(options) {
|
|
@@ -129,15 +126,21 @@ function resolveContext(opts) {
|
|
|
129
126
|
return { options, ...(config?.tool !== undefined ? { configTool: config.tool } : {}) };
|
|
130
127
|
}
|
|
131
128
|
export function registerFusion(program) {
|
|
132
|
-
// Top-level `init` — scaffold a committed fusionkit
|
|
129
|
+
// Top-level `init` — scaffold a committed .fusionkit/ folder for this repo.
|
|
133
130
|
program
|
|
134
131
|
.command("init")
|
|
135
|
-
.description("scaffold a committed fusionkit
|
|
132
|
+
.description("scaffold a committed .fusionkit/ folder for this repo")
|
|
136
133
|
.option("--repo <dir>", "coding workspace the panel fuses over")
|
|
137
|
-
.option("--
|
|
134
|
+
.option("--fusionkit-dir <dir>", "local FusionKit checkout (dev override for default prompts)")
|
|
135
|
+
.option("--force", "overwrite an existing .fusionkit/ config and prompts")
|
|
138
136
|
.action(async (opts) => {
|
|
139
|
-
const
|
|
140
|
-
const
|
|
137
|
+
const options = resolveOptions(opts);
|
|
138
|
+
const repoRoot = configRepoRoot(options);
|
|
139
|
+
const code = await runFusionInit({
|
|
140
|
+
repoRoot,
|
|
141
|
+
force: opts.force === true,
|
|
142
|
+
...(options.fusionkitDir !== undefined ? { fusionkitDir: options.fusionkitDir } : {})
|
|
143
|
+
});
|
|
141
144
|
process.exit(code);
|
|
142
145
|
});
|
|
143
146
|
// Generic `fusion [tool]` — keeps the original surface and interactive pick.
|
|
@@ -148,7 +151,7 @@ export function registerFusion(program) {
|
|
|
148
151
|
.argument("[args...]", "arguments forwarded to the tool")
|
|
149
152
|
.option("--tool <tool>", `coding agent to launch (${FUSION_TOOLS.join(" | ")})`))
|
|
150
153
|
.addHelpText("after", "\nfusionkit's own flags must precede the tool name; everything after the tool is forwarded to it." +
|
|
151
|
-
"\nRun `fusionkit init` to scaffold a committed fusionkit
|
|
154
|
+
"\nRun `fusionkit init` to scaffold a committed .fusionkit/ folder for this repo." +
|
|
152
155
|
"\nRun `fusionkit fusion stop` to reap portless singleton services (router, dashboard, ...).")
|
|
153
156
|
.action(async (positionalTool, args, opts) => {
|
|
154
157
|
// `fusion stop` reaps persistent portless singletons left running by prior
|
package/dist/commands/local.js
CHANGED
|
@@ -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
|
|
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", "\
|
|
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:
|
|
16
|
+
fail(`usage: fusionkit local <${LOCAL_TOOLS.join(" | ")}> [args...]`);
|
|
17
17
|
}
|
|
18
18
|
const options = {
|
|
19
19
|
...(opts.publicUrl !== undefined ? { publicUrl: opts.publicUrl } : {}),
|
package/dist/cursor-acp.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
7
|
-
*
|
|
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;
|
package/dist/cursor-acp.js
CHANGED
|
@@ -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
|
-
*
|
|
7
|
-
*
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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, [
|
|
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
|
+
};
|