@fusionkit/cli 0.1.3 → 0.1.4
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/dist/commands/fusion.js +17 -2
- package/dist/commands/plane.js +10 -2
- package/dist/fusion-config.d.ts +1 -0
- package/dist/fusion-config.js +5 -0
- package/dist/fusion-quickstart.d.ts +33 -34
- package/dist/fusion-quickstart.js +324 -278
- package/dist/shared/portless.d.ts +97 -0
- package/dist/shared/portless.js +253 -0
- package/dist/test/portless.test.d.ts +1 -0
- package/dist/test/portless.test.js +65 -0
- package/package.json +12 -9
- package/scope/.next/BUILD_ID +1 -1
- package/scope/.next/app-build-manifest.json +8 -8
- package/scope/.next/app-path-routes-manifest.json +3 -3
- package/scope/.next/build-manifest.json +2 -2
- package/scope/.next/prerender-manifest.json +9 -9
- package/scope/.next/required-server-files.json +4 -0
- 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/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.html +1 -1
- package/scope/.next/server/app/models.rsc +1 -1
- package/scope/.next/server/app-paths-manifest.json +3 -3
- package/scope/.next/server/functions-config-manifest.json +3 -3
- 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/package.json +3 -1
- package/scope/server.js +1 -1
- /package/scope/.next/static/{8b1kwXDxrKvteRVkOC_Z2 → 5tnFLuvnSbNZNtqRgoot8}/_buildManifest.js +0 -0
- /package/scope/.next/static/{8b1kwXDxrKvteRVkOC_Z2 → 5tnFLuvnSbNZNtqRgoot8}/_ssgManifest.js +0 -0
package/dist/commands/fusion.js
CHANGED
|
@@ -4,6 +4,7 @@ import { loadFusionConfig } from "../fusion-config.js";
|
|
|
4
4
|
import { runFusionInit } from "../fusion-init.js";
|
|
5
5
|
import { fail } from "../shared/errors.js";
|
|
6
6
|
import { collect, parseFusionTool, parseIdValue, parsePanelModelSpec, parsePort } from "../shared/options.js";
|
|
7
|
+
import { reapFusionServices } from "../shared/portless.js";
|
|
7
8
|
/** Attach the panel/gateway flags shared by `fusion` and the per-tool launchers. */
|
|
8
9
|
function applyFusionOptions(command) {
|
|
9
10
|
return command
|
|
@@ -23,6 +24,8 @@ function applyFusionOptions(command) {
|
|
|
23
24
|
.option("--yes", "skip the interactive cloud-panel cost confirmation")
|
|
24
25
|
.option("--auth-token <token>", "require a bearer token on the gateway")
|
|
25
26
|
.option("--port <n>", "gateway port (default: ephemeral)")
|
|
27
|
+
.option("--portless", "route services through portless stable URLs (default; needs the proxy)")
|
|
28
|
+
.option("--no-portless", "disable portless; use raw loopback ports (same as PORTLESS=0)")
|
|
26
29
|
.allowUnknownOption()
|
|
27
30
|
.passThroughOptions();
|
|
28
31
|
}
|
|
@@ -52,6 +55,8 @@ function resolveOptions(opts) {
|
|
|
52
55
|
options.observe = opts.observe;
|
|
53
56
|
if (opts.yes === true)
|
|
54
57
|
options.yes = true;
|
|
58
|
+
if (opts.portless !== undefined)
|
|
59
|
+
options.portless = opts.portless;
|
|
55
60
|
if (opts.authToken !== undefined)
|
|
56
61
|
options.authToken = opts.authToken;
|
|
57
62
|
if (opts.port !== undefined)
|
|
@@ -92,6 +97,8 @@ function mergeConfig(options, config) {
|
|
|
92
97
|
options.local = config.local;
|
|
93
98
|
if (options.observe === undefined && config.observe !== undefined)
|
|
94
99
|
options.observe = config.observe;
|
|
100
|
+
if (options.portless === undefined && config.portless !== undefined)
|
|
101
|
+
options.portless = config.portless;
|
|
95
102
|
if (options.cursorKitDir === undefined && config.cursorKitDir != null)
|
|
96
103
|
options.cursorKitDir = config.cursorKitDir;
|
|
97
104
|
if (options.port === undefined && config.port != null)
|
|
@@ -126,12 +133,13 @@ export function registerFusion(program) {
|
|
|
126
133
|
applyFusionOptions(program
|
|
127
134
|
.command("fusion")
|
|
128
135
|
.description("one command: real model fusion backs a coding agent")
|
|
129
|
-
.argument("[tool]", `${FUSION_TOOLS.join(" | ")} | init (omit on a TTY to pick interactively)`)
|
|
136
|
+
.argument("[tool]", `${FUSION_TOOLS.join(" | ")} | init | stop (omit on a TTY to pick interactively)`)
|
|
130
137
|
.argument("[args...]", "arguments forwarded to the tool")
|
|
131
138
|
.option("--tool <tool>", `coding agent to launch (${FUSION_TOOLS.join(" | ")})`)
|
|
132
139
|
.option("--force", "overwrite an existing fusionkit.json (with `fusion init`)"))
|
|
133
140
|
.addHelpText("after", "\nfusionkit's own flags must precede the tool name; everything after the tool is forwarded to it." +
|
|
134
|
-
"\nRun `fusionkit fusion init` to scaffold a committed fusionkit.json for this repo."
|
|
141
|
+
"\nRun `fusionkit fusion init` to scaffold a committed fusionkit.json for this repo." +
|
|
142
|
+
"\nRun `fusionkit fusion stop` to reap portless singleton services (router, dashboard, ...).")
|
|
135
143
|
.action(async (positionalTool, args, opts) => {
|
|
136
144
|
// `fusion init` scaffolds the per-repo config instead of launching a tool.
|
|
137
145
|
if (positionalTool === "init") {
|
|
@@ -139,6 +147,13 @@ export function registerFusion(program) {
|
|
|
139
147
|
const code = await runFusionInit({ repoRoot, force: opts.force === true });
|
|
140
148
|
process.exit(code);
|
|
141
149
|
}
|
|
150
|
+
// `fusion stop` reaps persistent portless singletons left running by prior
|
|
151
|
+
// runs (the router, dashboard, ...).
|
|
152
|
+
if (positionalTool === "stop") {
|
|
153
|
+
const stopped = await reapFusionServices((line) => console.error(line));
|
|
154
|
+
console.error(`fusion: stopped ${stopped} portless service(s)`);
|
|
155
|
+
process.exit(0);
|
|
156
|
+
}
|
|
142
157
|
const { options, configTool } = resolveContext(opts);
|
|
143
158
|
let tool = opts.tool ? parseFusionTool(opts.tool) : undefined;
|
|
144
159
|
let toolArgs = [...args];
|
package/dist/commands/plane.js
CHANGED
|
@@ -2,6 +2,7 @@ import { join } from "node:path";
|
|
|
2
2
|
import { Plane, startPlaneServer } from "@fusionkit/plane";
|
|
3
3
|
import { loadHome, secretStoreFor } from "../config.js";
|
|
4
4
|
import { resolveDir } from "../shared/plane.js";
|
|
5
|
+
import { createPortlessSession } from "../shared/portless.js";
|
|
5
6
|
export function registerPlane(program) {
|
|
6
7
|
const plane = program.command("plane").description("control plane + control panel");
|
|
7
8
|
plane
|
|
@@ -24,7 +25,14 @@ export function registerPlane(program) {
|
|
|
24
25
|
const port = opts.port ? Number(opts.port) : home.config.port;
|
|
25
26
|
const host = opts.host ?? home.config.host;
|
|
26
27
|
const started = await startPlaneServer(planeInstance, { port, host });
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
// Register a stable portless name for the control panel (enabled unless
|
|
29
|
+
// PORTLESS=0 or no proxy is detected; falls back to the loopback URL).
|
|
30
|
+
const portless = await createPortlessSession({
|
|
31
|
+
enabled: process.env.PORTLESS !== "0",
|
|
32
|
+
log: (line) => console.error(line)
|
|
33
|
+
});
|
|
34
|
+
const baseUrl = portless.register("plane", started.port);
|
|
35
|
+
console.log(`warrant plane listening on ${baseUrl}`);
|
|
36
|
+
console.log(`control panel: ${baseUrl}/ui/`);
|
|
29
37
|
});
|
|
30
38
|
}
|
package/dist/fusion-config.d.ts
CHANGED
package/dist/fusion-config.js
CHANGED
|
@@ -91,6 +91,11 @@ export function parseFusionConfig(raw, source) {
|
|
|
91
91
|
throw new FusionConfigError(`${source}: observe must be a boolean`);
|
|
92
92
|
config.observe = raw.observe;
|
|
93
93
|
}
|
|
94
|
+
if (raw.portless !== undefined) {
|
|
95
|
+
if (typeof raw.portless !== "boolean")
|
|
96
|
+
throw new FusionConfigError(`${source}: portless must be a boolean`);
|
|
97
|
+
config.portless = raw.portless;
|
|
98
|
+
}
|
|
94
99
|
if (raw.cursorKitDir !== undefined && raw.cursorKitDir !== null) {
|
|
95
100
|
if (typeof raw.cursorKitDir !== "string") {
|
|
96
101
|
throw new FusionConfigError(`${source}: cursorKitDir must be a string or null`);
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { ChildProcess } from "node:child_process";
|
|
15
15
|
import type { EnsembleModel } from "@fusionkit/ensemble";
|
|
16
|
+
import type { PortlessSession } from "./shared/portless.js";
|
|
16
17
|
export type FusionTool = "codex" | "claude" | "cursor" | "serve";
|
|
17
18
|
export declare const FUSION_TOOLS: readonly FusionTool[];
|
|
18
19
|
export type PanelProvider = "mlx" | "openai" | "anthropic" | "google" | "openai-compatible";
|
|
@@ -34,7 +35,7 @@ export type PanelModelSpec = {
|
|
|
34
35
|
* synthesizer (`fusionkit serve`) and the single-model OpenAI shim
|
|
35
36
|
* (`fusionkit serve-endpoint`). Pinned so `uvx` resolves a reproducible build.
|
|
36
37
|
*/
|
|
37
|
-
export declare const FUSIONKIT_PYPI_VERSION = "0.
|
|
38
|
+
export declare const FUSIONKIT_PYPI_VERSION = "0.2.0";
|
|
38
39
|
/**
|
|
39
40
|
* Default cloud panel — works cross-platform with only `OPENAI_API_KEY` and
|
|
40
41
|
* `ANTHROPIC_API_KEY` set. The judge defaults to the first entry.
|
|
@@ -94,23 +95,29 @@ export type Observability = {
|
|
|
94
95
|
traceDir: string;
|
|
95
96
|
close: () => Promise<void>;
|
|
96
97
|
};
|
|
97
|
-
/**
|
|
98
|
-
* Start the scope dashboard on the fixed port, backed by a fresh per-run SQLite
|
|
99
|
-
* file and trace dir, and return the URLs the caller injects (as
|
|
100
|
-
* FUSION_TRACE_URL / FUSION_TRACE_DIR) into every spawned process. Prefers the
|
|
101
|
-
* prebuilt bundle shipped inside the npm package; falls back to building the
|
|
102
|
-
* app from source in a monorepo dev checkout.
|
|
103
|
-
*/
|
|
104
98
|
export declare function startObservability(input: {
|
|
105
99
|
log: (line: string) => void;
|
|
106
100
|
logFile?: string;
|
|
107
101
|
report?: StackReporter;
|
|
102
|
+
portless: PortlessSession;
|
|
108
103
|
}): Promise<Observability>;
|
|
109
|
-
|
|
104
|
+
/**
|
|
105
|
+
* The single `fusionkit serve` router: one process that fronts every panel
|
|
106
|
+
* model (passthrough, routed by the endpoint id in the request `model` field)
|
|
107
|
+
* and also performs trajectory synthesis. `endpoints` maps each panel id to the
|
|
108
|
+
* router URL so the harness reaches its model through the one base URL.
|
|
109
|
+
*/
|
|
110
|
+
export type Router = {
|
|
111
|
+
url: string;
|
|
112
|
+
port: number;
|
|
113
|
+
/** The router process pid (owns its portless route across runs). */
|
|
114
|
+
pid?: number;
|
|
110
115
|
endpoints: Record<string, string>;
|
|
111
|
-
judgeUrl: string;
|
|
112
|
-
judgeModel: string;
|
|
113
116
|
models: EnsembleModel[];
|
|
117
|
+
/** The endpoint id used as the judge/synthesizer. */
|
|
118
|
+
judgeModel: string;
|
|
119
|
+
/** Sorted endpoint ids — the router's discover-or-spawn identity token. */
|
|
120
|
+
identity: string;
|
|
114
121
|
close: () => Promise<void>;
|
|
115
122
|
};
|
|
116
123
|
/**
|
|
@@ -152,19 +159,20 @@ export type StackEvent = {
|
|
|
152
159
|
};
|
|
153
160
|
export type StackReporter = (event: StackEvent) => void;
|
|
154
161
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
162
|
+
* Spawn the single `fusionkit serve` router fronting every panel model + the
|
|
163
|
+
* synthesizer. MLX specs first get an in-process OpenAI-compatible gateway
|
|
164
|
+
* (loopback) that the router proxies to; cloud specs call their provider
|
|
165
|
+
* directly. Returns the router URL, an id->routerUrl endpoint map, and a close
|
|
166
|
+
* that tears down the router process and any MLX gateways it fronts.
|
|
159
167
|
*/
|
|
160
|
-
export declare function
|
|
168
|
+
export declare function startRouter(options: {
|
|
161
169
|
specs: PanelModelSpec[];
|
|
162
|
-
|
|
170
|
+
judgeModel?: string;
|
|
163
171
|
fusionkitDir?: string;
|
|
164
172
|
logsDir?: string;
|
|
165
173
|
report?: StackReporter;
|
|
166
174
|
log: (line: string) => void;
|
|
167
|
-
}): Promise<
|
|
175
|
+
}): Promise<Router>;
|
|
168
176
|
export type FusionStack = {
|
|
169
177
|
fusionUrl: string;
|
|
170
178
|
endpoints: Record<string, string>;
|
|
@@ -185,24 +193,10 @@ export type StartFusionStackOptions = {
|
|
|
185
193
|
timeoutMs?: number;
|
|
186
194
|
logsDir?: string;
|
|
187
195
|
report?: StackReporter;
|
|
196
|
+
/** Active portless session; defaults to a disabled (loopback) session. */
|
|
197
|
+
portless?: PortlessSession;
|
|
188
198
|
log: (line: string) => void;
|
|
189
199
|
};
|
|
190
|
-
/**
|
|
191
|
-
* Spawn a `fusionkit serve` as the trajectory-synthesis backend, configured
|
|
192
|
-
* with the judge model. FusionKit owns synthesis, so the agent harness fuses
|
|
193
|
-
* its trajectories through this server's `/v1/fusion/trajectories:fuse`.
|
|
194
|
-
*/
|
|
195
|
-
export declare function startSynthesisServer(input: {
|
|
196
|
-
fusionkitDir?: string;
|
|
197
|
-
judgeModel: string;
|
|
198
|
-
judgeBaseUrl: string;
|
|
199
|
-
env: Record<string, string | undefined>;
|
|
200
|
-
logFile?: string;
|
|
201
|
-
log: (line: string) => void;
|
|
202
|
-
}): Promise<{
|
|
203
|
-
url: string;
|
|
204
|
-
child: ChildProcess;
|
|
205
|
-
}>;
|
|
206
200
|
export declare function startFusionStack(options: StartFusionStackOptions): Promise<FusionStack>;
|
|
207
201
|
/**
|
|
208
202
|
* Start the Cursorkit bridge with its local-model backend pointed at the fusion
|
|
@@ -212,6 +206,7 @@ export declare function startCursorBridge(input: {
|
|
|
212
206
|
cursorKitDir: string;
|
|
213
207
|
fusionUrl: string;
|
|
214
208
|
logFile?: string;
|
|
209
|
+
caCertPath?: string;
|
|
215
210
|
log: (line: string) => void;
|
|
216
211
|
}): Promise<{
|
|
217
212
|
child: ChildProcess;
|
|
@@ -234,8 +229,12 @@ export type RunFusionOptions = {
|
|
|
234
229
|
observe?: boolean;
|
|
235
230
|
/** Skip the interactive cost/scope confirmation for the cloud panel. */
|
|
236
231
|
yes?: boolean;
|
|
232
|
+
/** Route services through portless (stable named URLs + singletons). Default on. */
|
|
233
|
+
portless?: boolean;
|
|
237
234
|
log?: (line: string) => void;
|
|
238
235
|
};
|
|
236
|
+
/** Whether portless is enabled: explicit flag/config wins, else on unless PORTLESS=0. */
|
|
237
|
+
export declare function portlessEnabled(options: RunFusionOptions): boolean;
|
|
239
238
|
export declare function runFusion(tool: FusionTool, toolArgs: string[], options?: RunFusionOptions): Promise<number>;
|
|
240
239
|
/** Interactive tool picker for when no `--tool` was provided on a TTY. */
|
|
241
240
|
export declare function pickTool(): Promise<FusionTool>;
|