@fusionkit/cli 0.1.0 → 0.1.1
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 +77 -0
- package/dist/cli.d.ts +0 -6
- package/dist/cli.js +16 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +136 -0
- package/dist/commands/fusion.js +70 -12
- package/dist/fusion-config.d.ts +28 -0
- package/dist/fusion-config.js +133 -0
- package/dist/fusion-init.d.ts +4 -0
- package/dist/fusion-init.js +119 -0
- package/dist/fusion-quickstart.d.ts +48 -0
- package/dist/fusion-quickstart.js +340 -131
- package/dist/gateway.d.ts +2 -0
- package/dist/gateway.js +16 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -0
- package/dist/quiet-warnings.d.ts +1 -0
- package/dist/quiet-warnings.js +24 -0
- package/dist/shared/preflight.d.ts +1 -0
- package/dist/shared/preflight.js +1 -1
- package/dist/shared/proc.d.ts +36 -5
- package/dist/shared/proc.js +133 -25
- package/dist/test/fusion-config.test.d.ts +1 -0
- package/dist/test/fusion-config.test.js +80 -0
- package/dist/test/proc.test.js +23 -1
- package/dist/test/ui.test.d.ts +1 -0
- package/dist/test/ui.test.js +24 -0
- package/dist/ui/boot.d.ts +23 -0
- package/dist/ui/boot.js +56 -0
- package/dist/ui/index.d.ts +8 -0
- package/dist/ui/index.js +6 -0
- package/dist/ui/prompt.d.ts +30 -0
- package/dist/ui/prompt.js +178 -0
- package/dist/ui/runtime.d.ts +14 -0
- package/dist/ui/runtime.js +33 -0
- package/dist/ui/spinner.d.ts +31 -0
- package/dist/ui/spinner.js +102 -0
- package/dist/ui/steps.d.ts +38 -0
- package/dist/ui/steps.js +149 -0
- package/dist/ui/theme.d.ts +35 -0
- package/dist/ui/theme.js +52 -0
- package/package.json +9 -9
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fusionkit fusion init` — an interactive wizard that scaffolds a committed
|
|
3
|
+
* per-repo `fusionkit.json`. On a non-interactive stdin the prompts fall back to
|
|
4
|
+
* their defaults, so `fusion init` still produces a sensible config in CI.
|
|
5
|
+
*/
|
|
6
|
+
import { DEFAULT_CLOUD_PANEL, DEFAULT_TRIO, defaultKeyEnv } from "./fusion-quickstart.js";
|
|
7
|
+
import { FUSION_CONFIG_VERSION, FusionConfigError, fusionConfigPath, writeFusionConfig } from "./fusion-config.js";
|
|
8
|
+
import { parsePanelModelSpec } from "./shared/options.js";
|
|
9
|
+
import { confirm, done, note, select, text } from "./ui/prompt.js";
|
|
10
|
+
import { uiStream } from "./ui/runtime.js";
|
|
11
|
+
import { bold, brandHeader, cyan, dim, red } from "./ui/theme.js";
|
|
12
|
+
const out = uiStream();
|
|
13
|
+
/** Ensure each cloud spec records the env var holding its key (self-documenting). */
|
|
14
|
+
function withKeyEnv(spec) {
|
|
15
|
+
const provider = spec.provider ?? "mlx";
|
|
16
|
+
if (spec.keyEnv !== undefined || provider === "mlx")
|
|
17
|
+
return { ...spec };
|
|
18
|
+
const keyEnv = defaultKeyEnv(provider);
|
|
19
|
+
return keyEnv !== undefined ? { ...spec, keyEnv } : { ...spec };
|
|
20
|
+
}
|
|
21
|
+
async function buildCustomPanel() {
|
|
22
|
+
out.write(dim("Add panel models as ID=PROVIDER:MODEL (e.g. gpt=openai:gpt-5.5). Blank line to finish.\n"));
|
|
23
|
+
const specs = [];
|
|
24
|
+
for (let index = 0; index < 16; index++) {
|
|
25
|
+
const entry = await text({ message: `model ${index + 1}` });
|
|
26
|
+
if (entry.length === 0)
|
|
27
|
+
break;
|
|
28
|
+
try {
|
|
29
|
+
specs.push(withKeyEnv(parsePanelModelSpec(entry, {})));
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
out.write(`${red("!")} ${error instanceof Error ? error.message : String(error)}\n`);
|
|
33
|
+
index--;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (specs.length === 0) {
|
|
37
|
+
out.write(dim("No models entered; using the default cloud panel.\n"));
|
|
38
|
+
return DEFAULT_CLOUD_PANEL.map((spec) => withKeyEnv(spec));
|
|
39
|
+
}
|
|
40
|
+
return specs;
|
|
41
|
+
}
|
|
42
|
+
export async function runFusionInit(input) {
|
|
43
|
+
if (input.repoRoot === undefined) {
|
|
44
|
+
out.write(`${red("error:")} not inside a git repository.\n` +
|
|
45
|
+
" cd into your project (or run from a repo) so fusionkit.json lands at the repo root.\n");
|
|
46
|
+
return 1;
|
|
47
|
+
}
|
|
48
|
+
out.write(`\n${brandHeader("let's set up model fusion for this repo")}\n\n`);
|
|
49
|
+
const tool = await select({
|
|
50
|
+
message: "Default coding agent",
|
|
51
|
+
options: [
|
|
52
|
+
{ value: "codex", label: "codex", hint: "OpenAI Codex CLI" },
|
|
53
|
+
{ value: "claude", label: "claude", hint: "Claude Code" },
|
|
54
|
+
{ value: "cursor", label: "cursor", hint: "cursor-agent (needs a Cursorkit checkout)" },
|
|
55
|
+
{ value: "serve", label: "serve", hint: "just run the gateway and print setup" }
|
|
56
|
+
],
|
|
57
|
+
defaultIndex: 0
|
|
58
|
+
});
|
|
59
|
+
const preset = await select({
|
|
60
|
+
message: "Panel",
|
|
61
|
+
options: [
|
|
62
|
+
{ value: "cloud", label: "cloud (default)", hint: DEFAULT_CLOUD_PANEL.map((s) => s.id).join(" + ") },
|
|
63
|
+
{ value: "local", label: "local MLX trio", hint: "Apple Silicon, no API keys" },
|
|
64
|
+
{ value: "custom", label: "custom", hint: "pick your own models" }
|
|
65
|
+
],
|
|
66
|
+
defaultIndex: 0
|
|
67
|
+
});
|
|
68
|
+
let panel;
|
|
69
|
+
switch (preset) {
|
|
70
|
+
case "cloud":
|
|
71
|
+
panel = DEFAULT_CLOUD_PANEL.map((spec) => withKeyEnv(spec));
|
|
72
|
+
break;
|
|
73
|
+
case "local":
|
|
74
|
+
panel = DEFAULT_TRIO.map((spec) => ({ ...spec }));
|
|
75
|
+
break;
|
|
76
|
+
case "custom":
|
|
77
|
+
panel = await buildCustomPanel();
|
|
78
|
+
break;
|
|
79
|
+
default: {
|
|
80
|
+
const exhaustive = preset;
|
|
81
|
+
throw new Error(`unknown panel preset: ${String(exhaustive)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const judgeDefault = panel[0]?.model ?? "";
|
|
85
|
+
const judgeModel = await text({ message: "Judge model (for synthesis)", defaultValue: judgeDefault });
|
|
86
|
+
let cursorKitDir;
|
|
87
|
+
if (tool === "cursor") {
|
|
88
|
+
const answer = await text({
|
|
89
|
+
message: "Cursorkit checkout dir (optional, can set FUSIONKIT_CURSORKIT_DIR later)"
|
|
90
|
+
});
|
|
91
|
+
if (answer.length > 0)
|
|
92
|
+
cursorKitDir = answer;
|
|
93
|
+
}
|
|
94
|
+
const observe = await confirm({ message: "Enable the observability dashboard by default?", defaultValue: false });
|
|
95
|
+
const config = {
|
|
96
|
+
version: FUSION_CONFIG_VERSION,
|
|
97
|
+
tool,
|
|
98
|
+
panel,
|
|
99
|
+
...(judgeModel.length > 0 ? { judgeModel } : {}),
|
|
100
|
+
local: preset === "local",
|
|
101
|
+
observe,
|
|
102
|
+
...(cursorKitDir !== undefined ? { cursorKitDir } : {})
|
|
103
|
+
};
|
|
104
|
+
let path;
|
|
105
|
+
try {
|
|
106
|
+
path = writeFusionConfig(input.repoRoot, config, { force: input.force === true });
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
if (error instanceof FusionConfigError) {
|
|
110
|
+
out.write(`${red("error:")} ${error.message}\n`);
|
|
111
|
+
return 1;
|
|
112
|
+
}
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
out.write("\n");
|
|
116
|
+
done(`wrote ${cyan(fusionConfigPath(input.repoRoot))}`);
|
|
117
|
+
note(`commit it, then just run: ${bold(`fusionkit ${tool === "serve" ? "serve" : tool}`)}`);
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
@@ -92,6 +92,8 @@ export type Observability = {
|
|
|
92
92
|
*/
|
|
93
93
|
export declare function startObservability(input: {
|
|
94
94
|
log: (line: string) => void;
|
|
95
|
+
logFile?: string;
|
|
96
|
+
report?: StackReporter;
|
|
95
97
|
}): Promise<Observability>;
|
|
96
98
|
export type ModelServers = {
|
|
97
99
|
endpoints: Record<string, string>;
|
|
@@ -100,6 +102,44 @@ export type ModelServers = {
|
|
|
100
102
|
models: EnsembleModel[];
|
|
101
103
|
close: () => Promise<void>;
|
|
102
104
|
};
|
|
105
|
+
/**
|
|
106
|
+
* Structured boot progress. When a reporter is supplied the stack emits these
|
|
107
|
+
* events instead of the plain `fusion: ...` log lines, so a live TUI (or any
|
|
108
|
+
* other consumer) can render per-stage status. Without one, callers keep getting
|
|
109
|
+
* the existing line logs.
|
|
110
|
+
*/
|
|
111
|
+
export type StackEvent = {
|
|
112
|
+
kind: "server.start";
|
|
113
|
+
id: string;
|
|
114
|
+
label: string;
|
|
115
|
+
} | {
|
|
116
|
+
kind: "server.ready";
|
|
117
|
+
id: string;
|
|
118
|
+
detail: string;
|
|
119
|
+
} | {
|
|
120
|
+
kind: "server.fail";
|
|
121
|
+
id: string;
|
|
122
|
+
detail: string;
|
|
123
|
+
} | {
|
|
124
|
+
kind: "synth.start";
|
|
125
|
+
} | {
|
|
126
|
+
kind: "synth.ready";
|
|
127
|
+
detail: string;
|
|
128
|
+
} | {
|
|
129
|
+
kind: "gateway.start";
|
|
130
|
+
} | {
|
|
131
|
+
kind: "gateway.ready";
|
|
132
|
+
detail: string;
|
|
133
|
+
} | {
|
|
134
|
+
kind: "dashboard.start";
|
|
135
|
+
} | {
|
|
136
|
+
kind: "dashboard.ready";
|
|
137
|
+
detail: string;
|
|
138
|
+
} | {
|
|
139
|
+
kind: "dashboard.fail";
|
|
140
|
+
detail: string;
|
|
141
|
+
};
|
|
142
|
+
export type StackReporter = (event: StackEvent) => void;
|
|
103
143
|
/**
|
|
104
144
|
* Bring up one real model server per panel model and return an id -> base URL
|
|
105
145
|
* map. `mlx` specs run locally; cloud specs are fronted by FusionKit. When
|
|
@@ -110,6 +150,8 @@ export declare function startModelServers(options: {
|
|
|
110
150
|
specs: PanelModelSpec[];
|
|
111
151
|
endpoints?: Record<string, string>;
|
|
112
152
|
fusionkitDir?: string;
|
|
153
|
+
logsDir?: string;
|
|
154
|
+
report?: StackReporter;
|
|
113
155
|
log: (line: string) => void;
|
|
114
156
|
}): Promise<ModelServers>;
|
|
115
157
|
export type FusionStack = {
|
|
@@ -130,6 +172,8 @@ export type StartFusionStackOptions = {
|
|
|
130
172
|
port?: number;
|
|
131
173
|
authToken?: string;
|
|
132
174
|
timeoutMs?: number;
|
|
175
|
+
logsDir?: string;
|
|
176
|
+
report?: StackReporter;
|
|
133
177
|
log: (line: string) => void;
|
|
134
178
|
};
|
|
135
179
|
/**
|
|
@@ -142,6 +186,7 @@ export declare function startSynthesisServer(input: {
|
|
|
142
186
|
judgeModel: string;
|
|
143
187
|
judgeBaseUrl: string;
|
|
144
188
|
env: Record<string, string | undefined>;
|
|
189
|
+
logFile?: string;
|
|
145
190
|
log: (line: string) => void;
|
|
146
191
|
}): Promise<{
|
|
147
192
|
url: string;
|
|
@@ -155,6 +200,7 @@ export declare function startFusionStack(options: StartFusionStackOptions): Prom
|
|
|
155
200
|
export declare function startCursorBridge(input: {
|
|
156
201
|
cursorKitDir: string;
|
|
157
202
|
fusionUrl: string;
|
|
203
|
+
logFile?: string;
|
|
158
204
|
log: (line: string) => void;
|
|
159
205
|
}): Promise<{
|
|
160
206
|
child: ChildProcess;
|
|
@@ -175,6 +221,8 @@ export type RunFusionOptions = {
|
|
|
175
221
|
local?: boolean;
|
|
176
222
|
/** Boot the local scope dashboard and stream trace events into it. */
|
|
177
223
|
observe?: boolean;
|
|
224
|
+
/** Skip the interactive cost/scope confirmation for the cloud panel. */
|
|
225
|
+
yes?: boolean;
|
|
178
226
|
log?: (line: string) => void;
|
|
179
227
|
};
|
|
180
228
|
export declare function runFusion(tool: FusionTool, toolArgs: string[], options?: RunFusionOptions): Promise<number>;
|