@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.
Files changed (42) hide show
  1. package/README.md +77 -0
  2. package/dist/cli.d.ts +0 -6
  3. package/dist/cli.js +16 -0
  4. package/dist/commands/doctor.d.ts +2 -0
  5. package/dist/commands/doctor.js +136 -0
  6. package/dist/commands/fusion.js +70 -12
  7. package/dist/fusion-config.d.ts +28 -0
  8. package/dist/fusion-config.js +133 -0
  9. package/dist/fusion-init.d.ts +4 -0
  10. package/dist/fusion-init.js +119 -0
  11. package/dist/fusion-quickstart.d.ts +48 -0
  12. package/dist/fusion-quickstart.js +340 -131
  13. package/dist/gateway.d.ts +2 -0
  14. package/dist/gateway.js +16 -4
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.js +1 -0
  17. package/dist/quiet-warnings.d.ts +1 -0
  18. package/dist/quiet-warnings.js +24 -0
  19. package/dist/shared/preflight.d.ts +1 -0
  20. package/dist/shared/preflight.js +1 -1
  21. package/dist/shared/proc.d.ts +36 -5
  22. package/dist/shared/proc.js +133 -25
  23. package/dist/test/fusion-config.test.d.ts +1 -0
  24. package/dist/test/fusion-config.test.js +80 -0
  25. package/dist/test/proc.test.js +23 -1
  26. package/dist/test/ui.test.d.ts +1 -0
  27. package/dist/test/ui.test.js +24 -0
  28. package/dist/ui/boot.d.ts +23 -0
  29. package/dist/ui/boot.js +56 -0
  30. package/dist/ui/index.d.ts +8 -0
  31. package/dist/ui/index.js +6 -0
  32. package/dist/ui/prompt.d.ts +30 -0
  33. package/dist/ui/prompt.js +178 -0
  34. package/dist/ui/runtime.d.ts +14 -0
  35. package/dist/ui/runtime.js +33 -0
  36. package/dist/ui/spinner.d.ts +31 -0
  37. package/dist/ui/spinner.js +102 -0
  38. package/dist/ui/steps.d.ts +38 -0
  39. package/dist/ui/steps.js +149 -0
  40. package/dist/ui/theme.d.ts +35 -0
  41. package/dist/ui/theme.js +52 -0
  42. 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>;