flavor-code 1.3.12 → 1.3.15

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/cli.js CHANGED
@@ -1,1032 +1,73 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module"; const require = createRequire(import.meta.url);
3
- import {
4
- FlavorRpcServer,
5
- TraceRecorder
6
- } from "./chunk-EZNIDHFZ.js";
7
- import {
8
- staticTaskLines
9
- } from "./chunk-2BF7CVWZ.js";
10
- import {
11
- DEFAULT_MAX_OUTPUT_BYTES,
12
- MAX_ALIAS_LENGTH,
13
- MEMORY_TYPES,
14
- MemoryStore,
15
- PluginManifestSchema,
16
- SkillRegistry,
17
- classifyMemoryHeat,
18
- createExecutionEnvironment,
19
- createProductionRuntime,
20
- executeShell,
21
- initializeFlavor,
22
- message,
23
- packageVersion,
24
- redactErrorText,
25
- runPalBroker
26
- } from "./chunk-QC5OYQTB.js";
27
- import {
28
- McpServerConfigSchema,
29
- McpServerNameSchema,
30
- SkillNameSchema,
31
- loadConfig,
32
- readRecoverableFile,
33
- setProjectSkillDisabled,
34
- updateProtectedFile
35
- } from "./chunk-JFKRX2KG.js";
36
- import "./chunk-RQTYHUMK.js";
37
3
  import "./chunk-S32JRDZK.js";
38
4
 
39
- // src/cli.tsx
40
- import { randomUUID as randomUUID2 } from "crypto";
5
+ // src/launcher.ts
6
+ import { spawn } from "child_process";
41
7
  import { realpathSync } from "fs";
42
- import { homedir as homedir3 } from "os";
43
- import { posix, resolve as resolve3 } from "path";
8
+ import { release } from "os";
44
9
  import { fileURLToPath } from "url";
45
- import { Command, Option } from "commander";
46
-
47
- // src/utils/crash-guard.ts
48
- import { mkdirSync, writeFileSync } from "fs";
49
- import { join } from "path";
50
- var installed = false;
51
- function installCrashGuard(options = {}) {
52
- if (installed) return;
53
- installed = true;
54
- const crashLogDir = () => {
55
- try {
56
- return join((options.workspace ?? process.cwd)(), ".flavor");
57
- } catch {
58
- return process.cwd();
59
- }
10
+ function needsWindowsMaglevWorkaround(runtime) {
11
+ if (runtime.platform !== "win32") return false;
12
+ const build = Number(runtime.osRelease.split(".")[2]);
13
+ const major = Number(runtime.nodeVersion.replace(/^v/u, "").split(".")[0]);
14
+ if (build !== 26200 || major < 24) return false;
15
+ return !runtime.execArgv.some((argument) => argument === "--no-maglev" || argument === "--jitless");
16
+ }
17
+ function cliMainArguments(runtime, mainPath, argv) {
18
+ return [
19
+ ...needsWindowsMaglevWorkaround(runtime) ? ["--no-maglev"] : [],
20
+ mainPath,
21
+ ...argv
22
+ ];
23
+ }
24
+ async function launchCli() {
25
+ const mainUrl = new URL("./cli-main.js", import.meta.url);
26
+ const runtime = {
27
+ platform: process.platform,
28
+ osRelease: release(),
29
+ nodeVersion: process.version,
30
+ execArgv: process.execArgv
60
31
  };
61
- const writeCrashLog = (kind, detail) => {
62
- try {
63
- const dir = crashLogDir();
64
- mkdirSync(dir, { recursive: true });
65
- const path = join(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
66
- writeFileSync(path, [
67
- `kind: ${kind}`,
68
- `version: ${process.version}`,
69
- `platform: ${process.platform}`,
70
- `time: ${(/* @__PURE__ */ new Date()).toISOString()}`,
71
- "",
72
- detail
73
- ].join("\n"), { encoding: "utf8", mode: 384 });
74
- return path;
75
- } catch {
76
- return void 0;
77
- }
78
- };
79
- const restoreTerminal = () => {
80
- try {
81
- if (process.stdout.isTTY === true) process.stdout.write("\x1B[?25h\x1B[?1049l");
82
- } catch {
83
- }
84
- };
85
- const describe = (reason) => {
86
- if (reason instanceof Error) return `${reason.name}: ${reason.message}
87
- ${reason.stack ?? ""}`;
88
- return String(reason);
89
- };
90
- const crash = (kind, reason) => {
91
- const detail = describe(reason);
92
- const logPath = writeCrashLog(kind, detail);
93
- restoreTerminal();
94
- try {
95
- const safe = redactErrorText(detail.split("\n").slice(0, 3).join("\n"));
96
- process.stderr.write(`flavor crashed (${kind}): ${safe}
97
- `);
98
- if (logPath !== void 0) process.stderr.write(`crash log: ${logPath}
99
- `);
100
- } catch {
101
- }
102
- process.exit(1);
32
+ if (!needsWindowsMaglevWorkaround(runtime)) {
33
+ const cli = await import(mainUrl.href);
34
+ await cli.runCli(process.argv);
35
+ return;
36
+ }
37
+ const child = spawn(
38
+ process.execPath,
39
+ cliMainArguments(runtime, fileURLToPath(mainUrl), process.argv.slice(2)),
40
+ { stdio: "inherit" }
41
+ );
42
+ const ignoreSignal = () => {
103
43
  };
104
- process.on("unhandledRejection", (reason) => crash("unhandledRejection", reason));
105
- process.on("uncaughtException", (error) => crash("uncaughtException", error));
106
- }
107
-
108
- // src/skills/manager.ts
109
- import { lstat, mkdir, readFile, readdir, realpath, rm, writeFile } from "fs/promises";
110
- import { homedir } from "os";
111
- import { isAbsolute, join as join2, relative, resolve, sep } from "path";
112
- import { stringify } from "yaml";
113
- var SkillManager = class {
114
- #workspace;
115
- #home;
116
- constructor(options) {
117
- this.#workspace = resolve(options.workspace);
118
- this.#home = resolve(options.home ?? homedir());
119
- }
120
- async list() {
121
- const { registry, disabled } = await this.#registry();
122
- return Promise.all((await registry.discover()).map((metadata) => this.#summary(metadata, disabled)));
123
- }
124
- async get(name) {
125
- const validName = parseName(name);
126
- const { registry, disabled } = await this.#registry();
127
- const metadata = (await registry.discover()).find((skill) => skill.name === validName);
128
- if (metadata === void 0) throw new Error(`Skill not found: ${validName}`);
129
- return { ...await this.#summary(metadata, disabled), body: await registry.loadBody(metadata) };
130
- }
131
- async create(draft) {
132
- const value = parseDraft(draft);
133
- const projectRoot = this.#projectRoot();
134
- const directory = join2(projectRoot, value.name);
135
- await mkdir(projectRoot, { recursive: true, mode: 448 });
136
- try {
137
- await mkdir(directory, { mode: 448 });
138
- } catch (error) {
139
- if (isCode(error, "EEXIST")) throw new Error(`A project skill named "${value.name}" already exists`);
140
- throw error;
141
- }
142
- try {
143
- await writeFile(join2(directory, "SKILL.md"), renderSkill(value), { encoding: "utf8", flag: "wx", mode: 384 });
144
- await this.#persistEnabled(value.name, true);
145
- return this.get(value.name);
146
- } catch (error) {
147
- await rm(directory, { recursive: true, force: true }).catch(() => void 0);
148
- throw error;
149
- }
150
- }
151
- async update(name, draft) {
152
- const validName = parseName(name);
153
- const value = parseDraft(draft);
154
- if (value.name !== validName) throw new Error("Rename is not supported; create a new skill instead");
155
- const directory = this.#projectSkillDirectory(validName);
156
- const info = await lstat(directory).catch((error) => {
157
- if (isCode(error, "ENOENT")) throw new Error(`Project skill not found: ${validName}`);
158
- throw error;
159
- });
160
- if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`Project skill path is not a real directory: ${validName}`);
161
- const skillFile = join2(directory, "SKILL.md");
162
- const fileInfo = await lstat(skillFile);
163
- if (!fileInfo.isFile() || fileInfo.isSymbolicLink()) throw new Error(`Project skill file is not a real file: ${validName}`);
164
- await writeFile(skillFile, renderSkill(value), { encoding: "utf8", mode: 384 });
165
- return this.get(validName);
166
- }
167
- async delete(name) {
168
- const validName = parseName(name);
169
- const directory = this.#projectSkillDirectory(validName);
170
- const info = await lstat(directory).catch((error) => {
171
- if (isCode(error, "ENOENT")) throw new Error(`Project skill not found: ${validName}`);
172
- throw error;
173
- });
174
- if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`Project skill path is not a real directory: ${validName}`);
175
- await rm(directory, { recursive: true });
176
- await this.#persistEnabled(validName, true);
177
- }
178
- async setEnabled(name, enabled) {
179
- const validName = parseName(name);
180
- const { registry } = await this.#registry();
181
- if (!(await registry.discover()).some((skill) => skill.name === validName)) {
182
- throw new Error(`Skill not found: ${validName}`);
183
- }
184
- await this.#persistEnabled(validName, enabled);
185
- }
186
- async #persistEnabled(validName, enabled) {
187
- const loaded = await loadConfig({ cwd: this.#workspace, home: this.#home });
188
- await setProjectSkillDisabled(this.#workspace, validName, !enabled, loaded.config.skills.disabled);
189
- }
190
- async #registry() {
191
- const loaded = await loadConfig({ cwd: this.#workspace, home: this.#home });
192
- const [globalPluginRoots, projectPluginRoots] = await Promise.all([
193
- discoverPluginSkillRoots(join2(this.#home, ".flavor-code", "plugins")),
194
- discoverPluginSkillRoots(join2(this.#workspace, ".flavor", "plugins"))
195
- ]);
196
- return {
197
- registry: new SkillRegistry({
198
- globalRoots: [join2(this.#home, ".flavor-code", "skills"), ...globalPluginRoots],
199
- projectRoots: [this.#projectRoot(), ...projectPluginRoots]
200
- }),
201
- disabled: new Set(loaded.config.skills.disabled)
202
- };
203
- }
204
- async #summary(metadata, disabled) {
205
- const expected = this.#projectSkillDirectory(metadata.name);
206
- const editable = metadata.source === "project" && await realpath(expected).then((physical) => physical === metadata.root).catch(() => false);
207
- return {
208
- ...metadata,
209
- enabled: !disabled.has(metadata.name),
210
- editable
211
- };
212
- }
213
- #projectRoot() {
214
- return join2(this.#workspace, ".flavor", "skills");
215
- }
216
- #projectSkillDirectory(name) {
217
- return join2(this.#projectRoot(), name);
218
- }
219
- };
220
- function parseName(value) {
221
- return SkillNameSchema.parse(value.trim());
222
- }
223
- function parseDraft(draft) {
224
- const name = parseName(draft.name);
225
- const description = draft.description.trim();
226
- const body = draft.body.replace(/\r\n/g, "\n").trim();
227
- if (description.length === 0 || description.length > 4e3) throw new Error("Skill description must be between 1 and 4,000 characters");
228
- if (body.length === 0 || Buffer.byteLength(body, "utf8") > 256 * 1024) throw new Error("Skill instructions must be between 1 byte and 256 KiB");
229
- return { name, description, body, disableModelInvocation: draft.disableModelInvocation ?? false };
230
- }
231
- function renderSkill(draft) {
232
- const frontmatter = stringify({
233
- name: draft.name,
234
- description: draft.description,
235
- "disable-model-invocation": draft.disableModelInvocation
236
- }).trimEnd();
237
- return `---
238
- ${frontmatter}
239
- ---
240
-
241
- ${draft.body}
242
- `;
243
- }
244
- function isCode(error, code) {
245
- return typeof error === "object" && error !== null && "code" in error && error.code === code;
246
- }
247
- async function discoverPluginSkillRoots(directory) {
248
- let entries;
44
+ process.on("SIGINT", ignoreSignal);
45
+ process.on("SIGBREAK", ignoreSignal);
249
46
  try {
250
- entries = await readdir(directory, { withFileTypes: true });
251
- } catch (error) {
252
- if (isCode(error, "ENOENT")) return [];
253
- throw error;
254
- }
255
- const roots = [];
256
- for (const entry of entries) {
257
- if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
258
- const pluginRoot = await realpath(join2(directory, entry.name));
259
- try {
260
- const manifest = PluginManifestSchema.parse(JSON.parse(await readFile(join2(pluginRoot, "flavor-plugin.json"), "utf8")));
261
- for (const contribution of manifest.contributes.skillRoots) {
262
- const candidate = resolve(pluginRoot, contribution.path);
263
- const info = await lstat(candidate);
264
- if (!info.isDirectory() || info.isSymbolicLink()) continue;
265
- const physical = await realpath(candidate);
266
- const relation = relative(pluginRoot, physical);
267
- if (relation === ".." || relation.startsWith(`..${sep}`) || isAbsolute(relation)) continue;
268
- roots.push(physical);
269
- }
270
- } catch {
271
- }
272
- }
273
- return roots;
274
- }
275
-
276
- // src/memory/cli.ts
277
- import { homedir as homedir2 } from "os";
278
-
279
- // src/memory/manager.ts
280
- var ProjectMemoryManager = class {
281
- #store;
282
- #enabled;
283
- constructor(store, enabled = true) {
284
- this.#store = store;
285
- this.#enabled = enabled;
286
- }
287
- async snapshot(now = /* @__PURE__ */ new Date()) {
288
- const references = this.#enabled ? await this.#store.references() : [];
289
- return {
290
- enabled: this.#enabled,
291
- path: this.#store.path,
292
- entries: references.map((reference) => {
293
- const lastRecalledAt = Object.values(reference.recalls).sort().at(-1);
294
- return {
295
- id: reference.id,
296
- type: reference.type,
297
- content: reference.summary,
298
- recallTotal: reference.recallTotal,
299
- heat: classifyMemoryHeat(reference, now),
300
- ...lastRecalledAt === void 0 ? {} : { lastRecalledAt }
301
- };
302
- })
303
- };
304
- }
305
- async remember(candidate) {
306
- this.#assertEnabled();
307
- const result = await this.#store.remember(candidate);
308
- if (!result.added) throw new Error("Memory entry already exists or the configured capacity has been reached");
309
- return result.entry;
310
- }
311
- async update(id, candidate) {
312
- this.#assertEnabled();
313
- return this.#store.update(id, candidate);
314
- }
315
- async delete(id) {
316
- this.#assertEnabled();
317
- return this.#store.delete(id);
318
- }
319
- async deleteCold(now = /* @__PURE__ */ new Date()) {
320
- this.#assertEnabled();
321
- return this.#store.forgetCold(now);
322
- }
323
- #assertEnabled() {
324
- if (!this.#enabled) throw new Error("Long-term memory is disabled for this project");
325
- }
326
- };
327
- async function createProjectMemoryManager(options) {
328
- const { memory } = (await loadConfig({ cwd: options.workspace, home: options.home })).config;
329
- const store = new MemoryStore({
330
- workspace: options.workspace,
331
- maxEntries: memory.maxEntries,
332
- maxEntryChars: memory.maxEntryChars
333
- });
334
- return new ProjectMemoryManager(store, memory.enabled);
335
- }
336
-
337
- // src/memory/cli.ts
338
- function registerMemoryCommands(program, dependencies = {}) {
339
- const open = dependencies.open ?? createProjectMemoryManager;
340
- const cwd = dependencies.cwd ?? (() => process.cwd());
341
- const home = dependencies.home ?? homedir2;
342
- const write = dependencies.write ?? ((text) => process.stdout.write(text));
343
- const manager = () => open({ workspace: cwd(), home: home() });
344
- const memory = program.command("memory").description("Inspect and maintain project long-term memory");
345
- memory.command("list", { isDefault: true }).description("List project memories").option("--json", "print a machine-readable snapshot").action(async (options) => {
346
- const snapshot = await (await manager()).snapshot();
347
- if (options.json) {
348
- write(`${JSON.stringify(snapshot, null, 2)}
349
- `);
350
- return;
351
- }
352
- write(`Memory file: ${snapshot.path}
353
- `);
354
- if (!snapshot.enabled) {
355
- write("Long-term memory is disabled for this project.\n");
356
- return;
357
- }
358
- if (snapshot.entries.length === 0) {
359
- write("No long-term memories.\n");
360
- return;
361
- }
362
- for (const entry of snapshot.entries) write(`${entry.id} ${entry.type} ${entry.content}
363
- `);
364
- });
365
- memory.command("add").description("Add a memory").argument("<type>", "user, feedback, project, or reference").argument("<text...>", "memory text").action(async (type, words) => {
366
- const entry = await (await manager()).remember({ type: parseMemoryType(type), content: words.join(" ") });
367
- write(`Added ${entry.id} ${entry.type} ${entry.content}
368
- `);
369
- });
370
- memory.command("update").description("Replace one memory by id").argument("<id>", "12-character memory id").argument("<type>", "user, feedback, project, or reference").argument("<text...>", "replacement text").action(async (id, type, words) => {
371
- const entry = await (await manager()).update(id, { type: parseMemoryType(type), content: words.join(" ") });
372
- write(`Updated ${id} -> ${entry.id} ${entry.type} ${entry.content}
373
- `);
374
- });
375
- memory.command("delete").description("Delete one memory by id").argument("<id>", "12-character memory id").action(async (id) => {
376
- const deleted = await (await manager()).delete(id);
377
- if (!deleted) throw new Error(`Memory entry not found: ${id}`);
378
- write(`Deleted ${id}
379
- `);
380
- });
381
- memory.command("path").description("Print the project memory file path").action(async () => write(`${(await (await manager()).snapshot()).path}
382
- `));
383
- }
384
- function parseMemoryType(value) {
385
- if (!MEMORY_TYPES.includes(value)) {
386
- throw new Error(`Unsupported memory type: ${value}. Use ${MEMORY_TYPES.join(", ")}.`);
387
- }
388
- return value;
389
- }
390
-
391
- // src/mcp/config-manager.ts
392
- import { join as join3 } from "path";
393
- var ProjectMcpConfigManager = class {
394
- path;
395
- constructor(workspace) {
396
- this.path = join3(workspace, ".flavor", "flavor.json");
397
- }
398
- async list() {
399
- const document = await readRecoverableFile(this.path, (raw) => parseDocument(this.path, raw));
400
- return parseServers(this.path, document?.value["mcpServers"]);
401
- }
402
- async create(name, config) {
403
- const validName = McpServerNameSchema.parse(name);
404
- const validConfig = McpServerConfigSchema.parse(config);
405
- await this.#mutate((servers) => {
406
- if (servers[validName] !== void 0) throw new Error(`MCP service "${validName}" already exists`);
407
- servers[validName] = validConfig;
47
+ const code = await new Promise((resolvePromise, reject) => {
48
+ child.once("error", reject);
49
+ child.once("exit", (exitCode) => resolvePromise(exitCode ?? 1));
408
50
  });
409
- return managedServer(validName, validConfig);
410
- }
411
- async update(originalName, name, config) {
412
- const validOriginal = McpServerNameSchema.parse(originalName);
413
- const validName = McpServerNameSchema.parse(name);
414
- const parsedConfig = McpServerConfigSchema.parse(config);
415
- let persistedConfig = parsedConfig;
416
- await this.#mutate((servers) => {
417
- const current = servers[validOriginal];
418
- if (current === void 0) throw new Error(`MCP service "${validOriginal}" not found`);
419
- if (validName !== validOriginal && servers[validName] !== void 0) {
420
- throw new Error(`MCP service "${validName}" already exists`);
421
- }
422
- const sameTransport = "command" in current && "command" in config || "url" in current && "url" in config;
423
- persistedConfig = sameTransport ? McpServerConfigSchema.parse({ ...current, ...config }) : parsedConfig;
424
- delete servers[validOriginal];
425
- servers[validName] = persistedConfig;
426
- });
427
- return managedServer(validName, persistedConfig);
428
- }
429
- async setEnabled(name, enabled) {
430
- const validName = McpServerNameSchema.parse(name);
431
- let next;
432
- await this.#mutate((servers) => {
433
- const current = servers[validName];
434
- if (current === void 0) throw new Error(`MCP service "${validName}" not found`);
435
- next = McpServerConfigSchema.parse({ ...current, disabled: !enabled });
436
- servers[validName] = next;
437
- });
438
- return managedServer(validName, next);
439
- }
440
- async delete(name) {
441
- const validName = McpServerNameSchema.parse(name);
442
- await this.#mutate((servers) => {
443
- if (servers[validName] === void 0) throw new Error(`MCP service "${validName}" not found`);
444
- delete servers[validName];
445
- });
446
- }
447
- async #mutate(update) {
448
- await updateProtectedFile({
449
- path: this.path,
450
- decode: (raw) => parseDocument(this.path, raw),
451
- encode: (value) => `${JSON.stringify(value, null, 2)}
452
- `,
453
- update: (current) => {
454
- const document = { ...current ?? {} };
455
- const servers = Object.fromEntries(parseServers(this.path, document["mcpServers"]).map((server) => [server.name, server.config]));
456
- update(servers);
457
- document["mcpServers"] = servers;
458
- return document;
459
- }
460
- });
461
- }
462
- };
463
- function parseDocument(path, raw) {
464
- const value = JSON.parse(raw);
465
- if (!isRecord(value)) throw new Error(`Configuration file ${path} must contain a JSON object`);
466
- return value;
467
- }
468
- function parseServers(path, value) {
469
- if (value === void 0) return [];
470
- if (!isRecord(value)) throw new Error(`Configuration field mcpServers in ${path} must be an object`);
471
- return Object.entries(value).map(([name, config]) => {
472
- try {
473
- return managedServer(McpServerNameSchema.parse(name), McpServerConfigSchema.parse(config));
474
- } catch (error) {
475
- const reason = error instanceof Error ? error.message : String(error);
476
- throw new Error(`Invalid MCP service "${name}" in ${path}: ${reason}`);
477
- }
478
- }).sort((left, right) => left.name.localeCompare(right.name));
479
- }
480
- function managedServer(name, config) {
481
- return {
482
- name,
483
- transport: "command" in config ? "stdio" : "http",
484
- enabled: !config.disabled,
485
- config
486
- };
487
- }
488
- function isRecord(value) {
489
- return typeof value === "object" && value !== null && !Array.isArray(value);
490
- }
491
-
492
- // src/mcp/cli.ts
493
- function registerMcpCommands(program, dependencies) {
494
- const deps = {
495
- open: dependencies?.open ?? (() => new ProjectMcpConfigManager(process.cwd())),
496
- write: dependencies?.write ?? ((text) => process.stdout.write(text))
497
- };
498
- const mcp = program.command("mcp").description("Manage project MCP service configurations");
499
- mcp.command("list", { isDefault: true }).option("--json", "print JSON").action(async (options) => {
500
- const services = await deps.open().list();
501
- if (options.json) deps.write(`${JSON.stringify(services, null, 2)}
502
- `);
503
- else if (services.length === 0) deps.write("No project MCP services configured.\n");
504
- else for (const service of services) {
505
- deps.write(`${service.enabled ? "on " : "off"} ${service.name} ${service.transport}
506
- `);
507
- }
508
- });
509
- addConfigCommand(mcp.command("add <name>").description("Add a project MCP service")).action(async (name, options) => {
510
- await deps.open().create(name, configFromOptions(options));
511
- deps.write(`Added MCP service ${name}.
512
- `);
513
- });
514
- addConfigCommand(mcp.command("update <name>").description("Replace a project MCP service configuration")).option("--rename <name>", "rename the service").action(async (name, options) => {
515
- await deps.open().update(name, options.rename ?? name, configFromOptions(options));
516
- deps.write(`Updated MCP service ${options.rename ?? name}.
517
- `);
518
- });
519
- for (const enabled of [true, false]) {
520
- const action = enabled ? "enable" : "disable";
521
- mcp.command(`${action} <name>`).description(`${enabled ? "Enable" : "Disable"} a project MCP service`).action(async (name) => {
522
- await deps.open().setEnabled(name, enabled);
523
- deps.write(`${enabled ? "Enabled" : "Disabled"} MCP service ${name}.
524
- `);
525
- });
526
- }
527
- mcp.command("delete <name>").description("Delete a project MCP service").action(async (name) => {
528
- await deps.open().delete(name);
529
- deps.write(`Deleted MCP service ${name}.
530
- `);
531
- });
532
- mcp.command("path").description("Print the project MCP configuration path").action(() => {
533
- deps.write(`${deps.open().path}
534
- `);
535
- });
536
- }
537
- function addConfigCommand(command) {
538
- return command.option("--command <program>", "stdio executable").option("--url <url>", "Streamable HTTP endpoint").option("--arg <value>", "stdio argument; repeatable", collect, []).option("--env <KEY=VALUE>", "stdio environment entry; repeatable", collect, []).option("--header <KEY=VALUE>", "HTTP header; repeatable", collect, []).option("--cwd <path>", "stdio working directory").option("--timeout <ms>", "request timeout in milliseconds", parseTimeout);
539
- }
540
- function configFromOptions(options) {
541
- if (options.command === void 0 === (options.url === void 0)) {
542
- throw new Error("Specify exactly one transport with --command or --url");
543
- }
544
- if (options.command !== void 0) {
545
- if ((options.header?.length ?? 0) > 0) throw new Error("--header is available only with --url");
546
- return {
547
- command: options.command,
548
- ...options.arg?.length ? { args: options.arg } : {},
549
- ...options.env?.length ? { env: keyValueRecord(options.env, "environment") } : {},
550
- ...options.cwd === void 0 ? {} : { cwd: options.cwd },
551
- ...options.timeout === void 0 ? {} : { timeoutMs: options.timeout }
552
- };
553
- }
554
- if ((options.arg?.length ?? 0) > 0 || (options.env?.length ?? 0) > 0 || options.cwd !== void 0) {
555
- throw new Error("--arg, --env, and --cwd are available only with --command");
556
- }
557
- return {
558
- url: options.url,
559
- ...options.header?.length ? { headers: keyValueRecord(options.header, "header") } : {},
560
- ...options.timeout === void 0 ? {} : { timeoutMs: options.timeout }
561
- };
562
- }
563
- function collect(value, previous) {
564
- return [...previous, value];
565
- }
566
- function parseTimeout(value) {
567
- const parsed = Number(value);
568
- if (!Number.isSafeInteger(parsed) || parsed < 100) throw new Error("--timeout must be an integer of at least 100ms");
569
- return parsed;
570
- }
571
- function keyValueRecord(entries, label) {
572
- return Object.fromEntries(entries.map((entry) => {
573
- const separator = entry.indexOf("=");
574
- if (separator <= 0) throw new Error(`Invalid ${label} entry "${entry}"; use KEY=VALUE`);
575
- return [entry.slice(0, separator), entry.slice(separator + 1)];
576
- }));
577
- }
578
-
579
- // src/rpc/write-stream.ts
580
- import { randomUUID } from "crypto";
581
- var RpcWriteStreamBridge = class {
582
- #output;
583
- #pending;
584
- constructor(output) {
585
- this.#output = output;
586
- }
587
- get pendingId() {
588
- return this.#pending?.id;
589
- }
590
- async preview(proposal, signal) {
591
- if (this.#pending !== void 0) throw new Error("Another streamed write is still awaiting commit");
592
- if (signal.aborted) throw signal.reason;
593
- const id = randomUUID();
594
- this.#output({
595
- type: "write-start",
596
- id,
597
- path: proposal.path,
598
- before: proposal.before,
599
- totalBytes: Buffer.byteLength(proposal.after)
600
- });
601
- for (const delta of streamChunks(proposal.after)) this.#output({ type: "write-delta", id, delta });
602
- this.#output({ type: "write-ready", id });
603
- await new Promise((resolve4, reject) => {
604
- const onAbort = () => {
605
- this.#output({ type: "write-cancelled", id });
606
- this.#clear(id);
607
- reject(signal.reason);
608
- };
609
- signal.addEventListener("abort", onAbort, { once: true });
610
- this.#pending = {
611
- id,
612
- resolve: resolve4,
613
- reject,
614
- removeAbort: () => signal.removeEventListener("abort", onAbort)
615
- };
616
- });
617
- }
618
- commit(id) {
619
- const pending = this.#pending;
620
- if (pending === void 0 || pending.id !== id) throw new Error("Streamed write is no longer awaiting commit");
621
- pending.removeAbort();
622
- this.#pending = void 0;
623
- pending.resolve();
624
- }
625
- dispose() {
626
- const pending = this.#pending;
627
- if (pending === void 0) return;
628
- pending.removeAbort();
629
- this.#pending = void 0;
630
- pending.reject(new Error("Streamed write bridge disposed"));
631
- }
632
- #clear(id) {
633
- if (this.#pending?.id !== id) return;
634
- this.#pending.removeAbort();
635
- this.#pending = void 0;
636
- }
637
- };
638
- function streamChunks(content, maxChars = 2048) {
639
- if (!content) return [];
640
- const chunks = [];
641
- for (const line of content.match(/[^\n]*\n|[^\n]+$/g) ?? []) {
642
- for (let offset = 0; offset < line.length; offset += maxChars) {
643
- chunks.push(line.slice(offset, offset + maxChars));
644
- }
645
- }
646
- return chunks;
647
- }
648
-
649
- // src/eval/cli.ts
650
- import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
651
- import { dirname, resolve as resolve2 } from "path";
652
-
653
- // src/execution/local.ts
654
- var LocalExecutionEnvironment = class {
655
- kind = "local";
656
- exec(request, signal) {
657
- const cancellation = signal ?? new AbortController().signal;
658
- return executeShell(request.cwd, {
659
- command: request.command,
660
- args: [...request.args],
661
- cwd: request.cwd,
662
- ...request.timeoutMs === void 0 ? {} : { timeoutMs: request.timeoutMs }
663
- }, cancellation, DEFAULT_MAX_OUTPUT_BYTES);
664
- }
665
- async dispose() {
666
- }
667
- };
668
-
669
- // src/eval/schema.ts
670
- import { z } from "zod";
671
- var EvaluationSpecSchema = z.object({
672
- name: z.string().trim().min(1).max(256),
673
- workspace: z.string().trim().min(1).max(32768),
674
- prompt: z.string().trim().min(1).max(1e6),
675
- verification: z.array(z.object({
676
- command: z.string().trim().min(1).max(32768),
677
- args: z.array(z.string()).max(1e3),
678
- timeoutMs: z.number().int().positive().max(864e5).optional()
679
- }).strict()).min(1).max(100),
680
- maxTokens: z.number().int().positive().max(Number.MAX_SAFE_INTEGER).optional()
681
- }).strict();
682
-
683
- // src/eval/runner.ts
684
- var DEFAULT_VERIFICATION_TIMEOUT_MS = 10 * 6e4;
685
- async function runEvaluation(spec, dependencies) {
686
- const now = dependencies.now ?? Date.now;
687
- const started = now();
688
- let input = 0;
689
- let output = 0;
690
- const runtime = await dependencies.createRuntime({
691
- workspace: spec.workspace,
692
- output: (event) => {
693
- if (event.type === "usage") {
694
- input = event.totalInputTokens;
695
- output = event.totalOutputTokens;
696
- } else if (event.type === "done" && input === 0 && output === 0) {
697
- input = event.usage.inputTokens;
698
- output = event.usage.outputTokens;
699
- }
700
- }
701
- });
702
- try {
703
- await runtime.session.start();
704
- await runtime.session.submit(spec.prompt);
705
- } finally {
706
- await runtime.session.close();
707
- await runtime.dispose();
708
- }
709
- const verification = [];
710
- for (const check of spec.verification) {
711
- const result = await dependencies.executionEnvironment.exec({
712
- command: check.command,
713
- args: check.args,
714
- cwd: spec.workspace,
715
- timeoutMs: check.timeoutMs ?? DEFAULT_VERIFICATION_TIMEOUT_MS
716
- });
717
- verification.push({
718
- command: [check.command, ...check.args].join(" "),
719
- exitCode: result.exitCode,
720
- passed: result.exitCode === 0,
721
- stdout: result.stdout,
722
- stderr: result.stderr
723
- });
724
- }
725
- const total = input + output;
726
- const withinBudget = spec.maxTokens === void 0 || total <= spec.maxTokens;
727
- return {
728
- name: spec.name,
729
- workspace: spec.workspace,
730
- passed: withinBudget && verification.every((item) => item.passed),
731
- durationMs: Math.max(0, now() - started),
732
- tokens: { input, output, total, withinBudget },
733
- verification
734
- };
735
- }
736
-
737
- // src/eval/cli.ts
738
- async function runEvaluationFile(path, outputPath) {
739
- const specPath = resolve2(path);
740
- const parsed = EvaluationSpecSchema.parse(JSON.parse(await readFile2(specPath, "utf8")));
741
- const workspace = resolve2(dirname(specPath), parsed.workspace);
742
- const spec = {
743
- name: parsed.name,
744
- prompt: parsed.prompt,
745
- workspace,
746
- verification: parsed.verification.map((step) => ({
747
- command: step.command,
748
- args: step.args,
749
- ...step.timeoutMs === void 0 ? {} : { timeoutMs: step.timeoutMs }
750
- })),
751
- ...parsed.maxTokens === void 0 ? {} : { maxTokens: parsed.maxTokens }
752
- };
753
- const configured = await import("./load-LTTKVPJZ.js").then(({ loadConfig: loadConfig2 }) => loadConfig2({ cwd: workspace, home: process.env.USERPROFILE ?? process.env.HOME ?? workspace }));
754
- const environment = createExecutionEnvironment(workspace, configured.config.execution) ?? new LocalExecutionEnvironment();
755
- let report;
756
- try {
757
- report = await runEvaluation(spec, {
758
- createRuntime: (options) => createProductionRuntime({
759
- ...options,
760
- home: process.env.USERPROFILE ?? process.env.HOME ?? workspace,
761
- approvalPolicy: "deny"
762
- }),
763
- executionEnvironment: environment
764
- });
765
- } finally {
766
- await environment.dispose();
767
- }
768
- const body = `${JSON.stringify(report, null, 2)}
769
- `;
770
- if (outputPath === void 0) process.stdout.write(body);
771
- else await writeFile2(resolve2(outputPath), body, "utf8");
772
- return report.passed ? 0 : 1;
773
- }
774
-
775
- // src/cli.tsx
776
- function createProgram(dependencies = {}) {
777
- const program = new Command().name("flavor").description("Interactive coding agent").version(packageVersion()).option("-p, --print <prompt>", "run one prompt without the interactive UI").option("--resume [session-id]", "resume a saved session (latest when id is omitted)").option("--mode <mode>", "runtime mode: interactive or rpc").option("--workspace <path>", "workspace path (RPC mode)").option("--rpc-approvals", "allow an RPC client to resolve interactive tool approvals").option("--rpc-streamed-writes", "stream proposed file writes to an RPC client before committing them").option("--trace <path>", "write a redacted JSONL execution trace").option("--pal-name <alias>", "name this interactive Flavor instance for /pals and /chat").addOption(new Option("--pals-broker <address>").hideHelp());
778
- program.command("init [directory]").description("Initialize Flavor project files in a directory (defaults to cwd)").action(async (directory) => {
779
- const cwd = directory ? resolve3(directory) : process.cwd();
780
- try {
781
- const result = await initializeFlavor(cwd);
782
- process.stdout.write(`${result.created ? "Created" : "Updated"} ${result.path}
783
- `);
784
- } catch (error) {
785
- process.stderr.write(`init: ${safeError(error)}
786
- `);
787
- process.exitCode = 1;
788
- }
789
- });
790
- const skills = program.command("skills").description("List and enable or disable project skills");
791
- skills.command("list", { isDefault: true }).description("List skills visible in the current project").action(async () => {
792
- try {
793
- const entries = await new SkillManager({ workspace: process.cwd(), home: homedir3() }).list();
794
- if (entries.length === 0) process.stdout.write("No skills found.\n");
795
- else for (const skill of entries) {
796
- process.stdout.write(`${skill.enabled ? "on " : "off"} ${skill.name} [${skill.source}] ${skill.description}
797
- `);
798
- }
799
- } catch (error) {
800
- process.stderr.write(`skills: ${safeError(error)}
801
- `);
802
- process.exitCode = 1;
803
- }
804
- });
805
- for (const enabled of [true, false]) {
806
- const action = enabled ? "enable" : "disable";
807
- skills.command(`${action} <name>`).description(`${enabled ? "Enable" : "Disable"} a skill for this project`).action(async (name) => {
808
- try {
809
- await new SkillManager({ workspace: process.cwd(), home: homedir3() }).setEnabled(name, enabled);
810
- process.stdout.write(`${enabled ? "Enabled" : "Disabled"} ${name}.
811
- `);
812
- } catch (error) {
813
- process.stderr.write(`skills: ${safeError(error)}
814
- `);
815
- process.exitCode = 1;
816
- }
817
- });
818
- }
819
- registerMemoryCommands(program);
820
- registerMcpCommands(program);
821
- program.command("eval <spec>").option("--output <path>", "write the JSON report to a file").description("run a repeatable coding-agent evaluation").action(async (spec, command) => {
822
- process.exitCode = await runEvaluationFile(spec, command.output);
823
- });
824
- program.action(async (options) => {
825
- if (options.palsBroker !== void 0) {
826
- if (!isLocalPalBrokerAddress(options.palsBroker, process.platform)) {
827
- throw new Error("Invalid local pals broker address");
828
- }
829
- const broker = await (dependencies.runBroker ?? runPalBroker)({ address: options.palsBroker });
830
- await broker.closed;
831
- return;
832
- }
833
- const resumeSession = options.resume === true ? true : typeof options.resume === "string" ? options.resume : void 0;
834
- if (options.mode === "rpc") {
835
- process.exitCode = await runRpcMode({
836
- workspace: resolve3(options.workspace ?? process.cwd()),
837
- ...resumeSession === void 0 ? {} : { resumeSession },
838
- ...options.trace === void 0 ? {} : { trace: resolve3(options.trace) },
839
- interactiveApprovals: options.rpcApprovals === true,
840
- streamedWrites: options.rpcStreamedWrites === true
841
- });
842
- return;
843
- }
844
- if (options.mode !== void 0 && options.mode !== "interactive") {
845
- process.stderr.write(`Unsupported mode: ${options.mode}
846
- `);
847
- process.exitCode = 2;
848
- return;
849
- }
850
- if (options.print !== void 0) {
851
- process.exitCode = await runPrint(options.print, {}, resumeSession);
852
- return;
853
- }
854
- if (!(dependencies.isTTY?.() ?? process.stdin.isTTY)) {
855
- process.stderr.write("Interactive mode needs a TTY. Use --print <prompt> for scripts.\n");
856
- process.exitCode = 2;
857
- return;
858
- }
859
- const palAlias = options.palName === void 0 ? void 0 : parsePalAlias(options.palName);
860
- setInteractiveProcessTitle();
861
- const props = {
862
- workspace: process.cwd(),
863
- home: homedir3(),
864
- ...resumeSession === void 0 ? {} : { resumeSession },
865
- instanceId: (dependencies.randomUUID ?? randomUUID2)(),
866
- ...palAlias === void 0 ? {} : { palAlias }
867
- };
868
- if (dependencies.runInteractive !== void 0) {
869
- await dependencies.runInteractive(props);
870
- return;
871
- }
872
- await runInteractiveCli(props);
873
- });
874
- return program;
875
- }
876
- async function runInteractiveCli(props) {
877
- const [{ render, AlternateScreen }, { createElement }, { App }] = await Promise.all([
878
- import("./claude-ink-ORUA7GWG.js"),
879
- import("react"),
880
- import("./app-Z7D7XPVE.js")
881
- ]);
882
- const instance = await render(createElement(
883
- AlternateScreen,
884
- { mouseTracking: true },
885
- createElement(App, props)
886
- ), { exitOnCtrlC: false });
887
- await instance.waitUntilExit();
888
- }
889
- function parsePalAlias(value) {
890
- const alias = value.trim();
891
- if (alias.length < 1 || alias.length > MAX_ALIAS_LENGTH) {
892
- throw new Error(`Pal name must be between 1 and ${MAX_ALIAS_LENGTH} characters`);
893
- }
894
- return alias;
895
- }
896
- function isLocalPalBrokerAddress(address, platform) {
897
- if (platform === "win32") {
898
- const prefix = "\\\\.\\pipe\\flavor-code-pals-u-";
899
- if (!address.startsWith(prefix) || !address.endsWith("-v1")) return false;
900
- return /^[a-f0-9]{16}$/u.test(address.slice(prefix.length, -3));
901
- }
902
- if (!address.startsWith("/") || address.startsWith("//") || address.includes("\0")) return false;
903
- return posix.normalize(address) === address && posix.basename(address) === "pals-v1.sock" && posix.dirname(address) !== "/";
904
- }
905
- async function runRpcMode(options) {
906
- let recorder;
907
- try {
908
- const server = new FlavorRpcServer({
909
- input: process.stdin,
910
- output: process.stdout,
911
- workspace: options.workspace,
912
- createRuntime: async ({ workspace, output }) => {
913
- let activeRuntime;
914
- const streamedWrites = options.streamedWrites ? new RpcWriteStreamBridge(output) : void 0;
915
- const onApprovalChange = () => {
916
- if (!options.interactiveApprovals) return;
917
- const approval = activeRuntime?.approvals.pending;
918
- output(approval === void 0 ? { type: "approval-cleared" } : { type: "approval-request", request: approval });
919
- };
920
- const runtime = await createProductionRuntime({
921
- workspace,
922
- home: homedir3(),
923
- approvalPolicy: "deny",
924
- rpcToolApprovals: options.interactiveApprovals === true,
925
- ...streamedWrites === void 0 ? {} : { beforeFileCommit: streamedWrites.preview.bind(streamedWrites) },
926
- ...options.interactiveApprovals ? { onApprovalChange } : {},
927
- ...options.resumeSession === void 0 ? {} : { resumeSession: options.resumeSession },
928
- output
929
- });
930
- activeRuntime = runtime;
931
- Object.defineProperty(runtime, "rpcApprovals", { value: options.interactiveApprovals === true, enumerable: true });
932
- if (streamedWrites !== void 0) Object.defineProperty(runtime, "rpcWrites", { value: streamedWrites, enumerable: true });
933
- if (options.trace !== void 0) recorder = new TraceRecorder({
934
- path: options.trace,
935
- sessionId: runtime.sessionId
936
- });
937
- return runtime;
938
- },
939
- onRecord: (kind, payload) => recorder?.record(kind, payload)
940
- });
941
- await server.start();
942
- await recorder?.close();
943
- return 0;
944
- } catch (error) {
945
- await recorder?.close().catch(() => void 0);
946
- process.stderr.write(`rpc: ${safeError(error)}
947
- `);
948
- return 1;
949
- }
950
- }
951
- function setInteractiveProcessTitle(target = process) {
952
- target.title = "Flavor Code";
953
- }
954
- async function runPrint(prompt, dependencies = {}, resumeSession) {
955
- let code = 0;
956
- let runtime;
957
- const stdout = dependencies.stdout ?? ((text) => process.stdout.write(text));
958
- const stderr = dependencies.stderr ?? ((text) => process.stderr.write(text));
959
- try {
960
- runtime = await (dependencies.createRuntime ?? createProductionRuntime)({
961
- workspace: process.cwd(),
962
- home: homedir3(),
963
- approvalPolicy: "deny",
964
- ...resumeSession === void 0 ? {} : { resumeSession },
965
- output(event) {
966
- if (event.type === "text") stdout(event.text);
967
- else if (event.type === "notice") stdout(`${event.message}
968
- `);
969
- else if (event.type === "tasks") {
970
- for (const line of staticTaskLines(event.snapshot)) stdout(`${line}
971
- `);
972
- } else if (event.type === "error") {
973
- stderr(`${event.error.code}: ${event.error.message}
974
- `);
975
- code = 1;
976
- }
977
- }
978
- });
979
- } catch (error) {
980
- stderr(`startup: ${safeError(error)}
981
- `);
982
- return 2;
983
- }
984
- try {
985
- await runtime.session.start();
986
- await runtime.session.submit(prompt);
987
- } catch (error) {
988
- stderr(`runtime: ${safeError(error)}
989
- `);
990
- code = 1;
51
+ process.exitCode = code;
991
52
  } finally {
992
- try {
993
- await runtime.session.close();
994
- } catch (error) {
995
- stderr(`runtime: ${safeError(error)}
996
- `);
997
- code = 1;
998
- }
999
- try {
1000
- await runtime.dispose();
1001
- } catch (error) {
1002
- stderr(`runtime: ${safeError(error)}
1003
- `);
1004
- code = 1;
1005
- }
53
+ process.off("SIGINT", ignoreSignal);
54
+ process.off("SIGBREAK", ignoreSignal);
1006
55
  }
1007
- if (code === 0) stdout("\n");
1008
- return code;
1009
- }
1010
- function safeError(error) {
1011
- return redactErrorText(message(error));
1012
56
  }
1013
57
  if (process.argv[1]) {
1014
58
  const scriptPath = fileURLToPath(import.meta.url);
1015
59
  if (realpathSync(scriptPath) === realpathSync(process.argv[1])) {
1016
- installCrashGuard();
1017
60
  try {
1018
- await createProgram().parseAsync(process.argv);
61
+ await launchCli();
1019
62
  } catch (error) {
1020
- process.stderr.write(`flavor: ${safeError(error)}
63
+ process.stderr.write(`flavor launcher: ${error instanceof Error ? error.message : String(error)}
1021
64
  `);
1022
65
  process.exitCode = 1;
1023
66
  }
1024
67
  }
1025
68
  }
1026
69
  export {
1027
- createProgram,
1028
- isLocalPalBrokerAddress,
1029
- runPrint,
1030
- runRpcMode,
1031
- setInteractiveProcessTitle
70
+ cliMainArguments,
71
+ launchCli,
72
+ needsWindowsMaglevWorkaround
1032
73
  };