llmist 1.3.0 → 1.4.0
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 +10 -18
- package/dist/chunk-UEEESLOA.js +974 -0
- package/dist/chunk-UEEESLOA.js.map +1 -0
- package/dist/{chunk-RZTAKIDE.js → chunk-VGZCFUPX.js} +4627 -3211
- package/dist/chunk-VGZCFUPX.js.map +1 -0
- package/dist/cli.cjs +1321 -245
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1190 -215
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +52 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -6
- package/dist/index.d.ts +7 -6
- package/dist/index.js +20 -973
- package/dist/index.js.map +1 -1
- package/dist/{mock-stream-DNt-HBTn.d.cts → mock-stream-DD5yJM44.d.cts} +65 -0
- package/dist/{mock-stream-DNt-HBTn.d.ts → mock-stream-DD5yJM44.d.ts} +65 -0
- package/dist/testing/index.cjs +52 -10
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +2 -2
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +1 -2
- package/package.json +3 -1
- package/dist/chunk-RZTAKIDE.js.map +0 -1
- package/dist/chunk-TFIKR2RK.js +0 -1394
- package/dist/chunk-TFIKR2RK.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import "./chunk-UEEESLOA.js";
|
|
2
3
|
import {
|
|
3
4
|
AgentBuilder,
|
|
4
5
|
BaseGadget,
|
|
@@ -13,7 +14,7 @@ import {
|
|
|
13
14
|
createLogger,
|
|
14
15
|
init_builder,
|
|
15
16
|
init_client,
|
|
16
|
-
|
|
17
|
+
init_constants,
|
|
17
18
|
init_create_gadget,
|
|
18
19
|
init_exceptions,
|
|
19
20
|
init_gadget,
|
|
@@ -26,7 +27,7 @@ import {
|
|
|
26
27
|
resolveModel,
|
|
27
28
|
schemaToJSONSchema,
|
|
28
29
|
validateGadgetSchema
|
|
29
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-VGZCFUPX.js";
|
|
30
31
|
|
|
31
32
|
// src/cli/constants.ts
|
|
32
33
|
var CLI_NAME = "llmist";
|
|
@@ -79,7 +80,7 @@ import { Command, InvalidArgumentError as InvalidArgumentError2 } from "commande
|
|
|
79
80
|
// package.json
|
|
80
81
|
var package_default = {
|
|
81
82
|
name: "llmist",
|
|
82
|
-
version: "1.
|
|
83
|
+
version: "1.3.1",
|
|
83
84
|
description: "Universal TypeScript LLM client with streaming-first agent framework. Works with any model - no structured outputs or native tool calling required. Implements its own flexible grammar for function calling.",
|
|
84
85
|
type: "module",
|
|
85
86
|
main: "dist/index.cjs",
|
|
@@ -163,6 +164,7 @@ var package_default = {
|
|
|
163
164
|
"@google/genai": "^1.27.0",
|
|
164
165
|
chalk: "^5.6.2",
|
|
165
166
|
commander: "^12.1.0",
|
|
167
|
+
diff: "^8.0.2",
|
|
166
168
|
eta: "^4.4.1",
|
|
167
169
|
"js-toml": "^1.0.2",
|
|
168
170
|
"js-yaml": "^4.1.0",
|
|
@@ -179,6 +181,7 @@ var package_default = {
|
|
|
179
181
|
"@commitlint/config-conventional": "^20.0.0",
|
|
180
182
|
"@semantic-release/changelog": "^6.0.3",
|
|
181
183
|
"@semantic-release/git": "^10.0.1",
|
|
184
|
+
"@types/diff": "^8.0.0",
|
|
182
185
|
"@types/js-yaml": "^4.0.9",
|
|
183
186
|
"@types/marked-terminal": "^6.1.1",
|
|
184
187
|
"@types/node": "^20.12.7",
|
|
@@ -193,10 +196,272 @@ var package_default = {
|
|
|
193
196
|
|
|
194
197
|
// src/cli/agent-command.ts
|
|
195
198
|
init_builder();
|
|
199
|
+
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
200
|
+
import chalk5 from "chalk";
|
|
201
|
+
|
|
202
|
+
// src/core/errors.ts
|
|
203
|
+
function isAbortError(error) {
|
|
204
|
+
if (!(error instanceof Error)) return false;
|
|
205
|
+
if (error.name === "AbortError") return true;
|
|
206
|
+
if (error.name === "APIConnectionAbortedError") return true;
|
|
207
|
+
if (error.name === "APIUserAbortError") return true;
|
|
208
|
+
const message = error.message.toLowerCase();
|
|
209
|
+
if (message.includes("abort")) return true;
|
|
210
|
+
if (message.includes("cancelled")) return true;
|
|
211
|
+
if (message.includes("canceled")) return true;
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/cli/agent-command.ts
|
|
196
216
|
init_registry();
|
|
197
217
|
init_constants();
|
|
218
|
+
|
|
219
|
+
// src/cli/approval/manager.ts
|
|
198
220
|
import { createInterface } from "node:readline/promises";
|
|
199
|
-
import
|
|
221
|
+
import chalk2 from "chalk";
|
|
222
|
+
|
|
223
|
+
// src/cli/approval/context-providers.ts
|
|
224
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
225
|
+
import { resolve } from "node:path";
|
|
226
|
+
import { createPatch } from "diff";
|
|
227
|
+
|
|
228
|
+
// src/cli/approval/diff-renderer.ts
|
|
229
|
+
import chalk from "chalk";
|
|
230
|
+
function renderColoredDiff(diff) {
|
|
231
|
+
return diff.split("\n").map((line) => {
|
|
232
|
+
if (line.startsWith("---") || line.startsWith("+++")) {
|
|
233
|
+
return chalk.bold(line);
|
|
234
|
+
}
|
|
235
|
+
if (line.startsWith("+")) {
|
|
236
|
+
return chalk.green(line);
|
|
237
|
+
}
|
|
238
|
+
if (line.startsWith("-")) {
|
|
239
|
+
return chalk.red(line);
|
|
240
|
+
}
|
|
241
|
+
if (line.startsWith("@@")) {
|
|
242
|
+
return chalk.cyan(line);
|
|
243
|
+
}
|
|
244
|
+
return chalk.dim(line);
|
|
245
|
+
}).join("\n");
|
|
246
|
+
}
|
|
247
|
+
function formatNewFileDiff(filePath, content) {
|
|
248
|
+
const lines = content.split("\n");
|
|
249
|
+
const header = `+++ ${filePath} (new file)`;
|
|
250
|
+
const addedLines = lines.map((line) => `+ ${line}`).join("\n");
|
|
251
|
+
return `${header}
|
|
252
|
+
${addedLines}`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/cli/approval/context-providers.ts
|
|
256
|
+
var WriteFileContextProvider = class {
|
|
257
|
+
gadgetName = "WriteFile";
|
|
258
|
+
async getContext(params) {
|
|
259
|
+
const filePath = String(params.filePath ?? params.path ?? "");
|
|
260
|
+
const newContent = String(params.content ?? "");
|
|
261
|
+
const resolvedPath = resolve(process.cwd(), filePath);
|
|
262
|
+
if (!existsSync(resolvedPath)) {
|
|
263
|
+
return {
|
|
264
|
+
summary: `Create new file: ${filePath}`,
|
|
265
|
+
details: formatNewFileDiff(filePath, newContent)
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
const oldContent = readFileSync(resolvedPath, "utf-8");
|
|
269
|
+
const diff = createPatch(filePath, oldContent, newContent, "original", "modified");
|
|
270
|
+
return {
|
|
271
|
+
summary: `Modify: ${filePath}`,
|
|
272
|
+
details: diff
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
var EditFileContextProvider = class {
|
|
277
|
+
gadgetName = "EditFile";
|
|
278
|
+
async getContext(params) {
|
|
279
|
+
const filePath = String(params.filePath ?? params.path ?? "");
|
|
280
|
+
const resolvedPath = resolve(process.cwd(), filePath);
|
|
281
|
+
if ("content" in params) {
|
|
282
|
+
const newContent = String(params.content);
|
|
283
|
+
if (!existsSync(resolvedPath)) {
|
|
284
|
+
return {
|
|
285
|
+
summary: `Create new file: ${filePath}`,
|
|
286
|
+
details: formatNewFileDiff(filePath, newContent)
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
const oldContent = readFileSync(resolvedPath, "utf-8");
|
|
290
|
+
const diff = createPatch(filePath, oldContent, newContent, "original", "modified");
|
|
291
|
+
return {
|
|
292
|
+
summary: `Modify: ${filePath}`,
|
|
293
|
+
details: diff
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
if ("commands" in params) {
|
|
297
|
+
const commands = String(params.commands);
|
|
298
|
+
return {
|
|
299
|
+
summary: `Edit: ${filePath}`,
|
|
300
|
+
details: `Commands:
|
|
301
|
+
${commands}`
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
summary: `Edit: ${filePath}`
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
var RunCommandContextProvider = class {
|
|
310
|
+
gadgetName = "RunCommand";
|
|
311
|
+
async getContext(params) {
|
|
312
|
+
const command = String(params.command ?? "");
|
|
313
|
+
const cwd = params.cwd ? ` (in ${params.cwd})` : "";
|
|
314
|
+
return {
|
|
315
|
+
summary: `Execute: ${command}${cwd}`
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
var DefaultContextProvider = class {
|
|
320
|
+
constructor(gadgetName) {
|
|
321
|
+
this.gadgetName = gadgetName;
|
|
322
|
+
}
|
|
323
|
+
async getContext(params) {
|
|
324
|
+
const paramEntries = Object.entries(params);
|
|
325
|
+
if (paramEntries.length === 0) {
|
|
326
|
+
return {
|
|
327
|
+
summary: `${this.gadgetName}()`
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
const formatValue = (value) => {
|
|
331
|
+
const MAX_LEN = 50;
|
|
332
|
+
const str = JSON.stringify(value);
|
|
333
|
+
return str.length > MAX_LEN ? `${str.slice(0, MAX_LEN - 3)}...` : str;
|
|
334
|
+
};
|
|
335
|
+
const paramStr = paramEntries.map(([k, v]) => `${k}=${formatValue(v)}`).join(", ");
|
|
336
|
+
return {
|
|
337
|
+
summary: `${this.gadgetName}(${paramStr})`
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
var builtinContextProviders = [
|
|
342
|
+
new WriteFileContextProvider(),
|
|
343
|
+
new EditFileContextProvider(),
|
|
344
|
+
new RunCommandContextProvider()
|
|
345
|
+
];
|
|
346
|
+
|
|
347
|
+
// src/cli/approval/manager.ts
|
|
348
|
+
var ApprovalManager = class {
|
|
349
|
+
/**
|
|
350
|
+
* Creates a new ApprovalManager.
|
|
351
|
+
*
|
|
352
|
+
* @param config - Approval configuration with per-gadget modes
|
|
353
|
+
* @param env - CLI environment for I/O operations
|
|
354
|
+
* @param progress - Optional progress indicator to pause during prompts
|
|
355
|
+
*/
|
|
356
|
+
constructor(config, env, progress) {
|
|
357
|
+
this.config = config;
|
|
358
|
+
this.env = env;
|
|
359
|
+
this.progress = progress;
|
|
360
|
+
for (const provider of builtinContextProviders) {
|
|
361
|
+
this.registerProvider(provider);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
providers = /* @__PURE__ */ new Map();
|
|
365
|
+
/**
|
|
366
|
+
* Registers a custom context provider for a gadget.
|
|
367
|
+
*
|
|
368
|
+
* @param provider - The context provider to register
|
|
369
|
+
*/
|
|
370
|
+
registerProvider(provider) {
|
|
371
|
+
this.providers.set(provider.gadgetName.toLowerCase(), provider);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Gets the approval mode for a gadget.
|
|
375
|
+
*
|
|
376
|
+
* Resolution order:
|
|
377
|
+
* 1. Explicit configuration for the gadget name
|
|
378
|
+
* 2. Wildcard "*" configuration
|
|
379
|
+
* 3. Default mode from config
|
|
380
|
+
*
|
|
381
|
+
* @param gadgetName - Name of the gadget
|
|
382
|
+
* @returns The approval mode to use
|
|
383
|
+
*/
|
|
384
|
+
getApprovalMode(gadgetName) {
|
|
385
|
+
const normalizedName = gadgetName.toLowerCase();
|
|
386
|
+
for (const [configName, mode] of Object.entries(this.config.gadgetApprovals)) {
|
|
387
|
+
if (configName.toLowerCase() === normalizedName) {
|
|
388
|
+
return mode;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if ("*" in this.config.gadgetApprovals) {
|
|
392
|
+
return this.config.gadgetApprovals["*"];
|
|
393
|
+
}
|
|
394
|
+
return this.config.defaultMode;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Requests approval for a gadget execution.
|
|
398
|
+
*
|
|
399
|
+
* Behavior depends on the gadget's approval mode:
|
|
400
|
+
* - "allowed": Returns approved immediately
|
|
401
|
+
* - "denied": Returns denied with configuration message
|
|
402
|
+
* - "approval-required": Prompts user interactively
|
|
403
|
+
*
|
|
404
|
+
* @param gadgetName - Name of the gadget
|
|
405
|
+
* @param params - The gadget's execution parameters
|
|
406
|
+
* @returns Approval result indicating whether to proceed
|
|
407
|
+
*/
|
|
408
|
+
async requestApproval(gadgetName, params) {
|
|
409
|
+
const mode = this.getApprovalMode(gadgetName);
|
|
410
|
+
if (mode === "allowed") {
|
|
411
|
+
return { approved: true };
|
|
412
|
+
}
|
|
413
|
+
if (mode === "denied") {
|
|
414
|
+
return {
|
|
415
|
+
approved: false,
|
|
416
|
+
reason: `${gadgetName} is denied by configuration`
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
return this.promptForApproval(gadgetName, params);
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Prompts the user for approval interactively.
|
|
423
|
+
*/
|
|
424
|
+
async promptForApproval(gadgetName, params) {
|
|
425
|
+
const provider = this.providers.get(gadgetName.toLowerCase()) ?? new DefaultContextProvider(gadgetName);
|
|
426
|
+
const context = await provider.getContext(params);
|
|
427
|
+
this.progress?.pause();
|
|
428
|
+
this.env.stderr.write(`
|
|
429
|
+
${chalk2.yellow("\u{1F512} Approval required:")} ${context.summary}
|
|
430
|
+
`);
|
|
431
|
+
if (context.details) {
|
|
432
|
+
this.env.stderr.write(`
|
|
433
|
+
${renderColoredDiff(context.details)}
|
|
434
|
+
`);
|
|
435
|
+
}
|
|
436
|
+
const response = await this.prompt(" \u23CE approve, or type to reject: ");
|
|
437
|
+
const isApproved = response === "" || response.toLowerCase() === "y";
|
|
438
|
+
if (isApproved) {
|
|
439
|
+
this.env.stderr.write(` ${chalk2.green("\u2713 Approved")}
|
|
440
|
+
|
|
441
|
+
`);
|
|
442
|
+
return { approved: true };
|
|
443
|
+
}
|
|
444
|
+
this.env.stderr.write(` ${chalk2.red("\u2717 Denied")}
|
|
445
|
+
|
|
446
|
+
`);
|
|
447
|
+
return { approved: false, reason: response || "Rejected by user" };
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Prompts for user input.
|
|
451
|
+
*/
|
|
452
|
+
async prompt(message) {
|
|
453
|
+
const rl = createInterface({
|
|
454
|
+
input: this.env.stdin,
|
|
455
|
+
output: this.env.stderr
|
|
456
|
+
});
|
|
457
|
+
try {
|
|
458
|
+
const answer = await rl.question(message);
|
|
459
|
+
return answer.trim();
|
|
460
|
+
} finally {
|
|
461
|
+
rl.close();
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
};
|
|
200
465
|
|
|
201
466
|
// src/cli/builtin-gadgets.ts
|
|
202
467
|
init_create_gadget();
|
|
@@ -278,10 +543,422 @@ var builtinGadgets = [askUser, tellUser, finish];
|
|
|
278
543
|
|
|
279
544
|
// src/cli/gadgets.ts
|
|
280
545
|
init_gadget();
|
|
546
|
+
import fs5 from "node:fs";
|
|
547
|
+
import path4 from "node:path";
|
|
548
|
+
import { pathToFileURL } from "node:url";
|
|
549
|
+
|
|
550
|
+
// src/cli/builtins/filesystem/list-directory.ts
|
|
551
|
+
import fs2 from "node:fs";
|
|
552
|
+
import path2 from "node:path";
|
|
553
|
+
import { z as z2 } from "zod";
|
|
554
|
+
|
|
555
|
+
// src/cli/builtins/filesystem/utils.ts
|
|
281
556
|
import fs from "node:fs";
|
|
282
557
|
import path from "node:path";
|
|
283
|
-
|
|
558
|
+
var PathSandboxException = class extends Error {
|
|
559
|
+
constructor(inputPath, reason) {
|
|
560
|
+
super(`Path access denied: ${inputPath}. ${reason}`);
|
|
561
|
+
this.name = "PathSandboxException";
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
function validatePathIsWithinCwd(inputPath) {
|
|
565
|
+
const cwd = process.cwd();
|
|
566
|
+
const resolvedPath = path.resolve(cwd, inputPath);
|
|
567
|
+
let finalPath;
|
|
568
|
+
try {
|
|
569
|
+
finalPath = fs.realpathSync(resolvedPath);
|
|
570
|
+
} catch (error) {
|
|
571
|
+
const nodeError = error;
|
|
572
|
+
if (nodeError.code === "ENOENT") {
|
|
573
|
+
finalPath = resolvedPath;
|
|
574
|
+
} else {
|
|
575
|
+
throw error;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
const cwdWithSep = cwd + path.sep;
|
|
579
|
+
if (!finalPath.startsWith(cwdWithSep) && finalPath !== cwd) {
|
|
580
|
+
throw new PathSandboxException(inputPath, "Path is outside the current working directory");
|
|
581
|
+
}
|
|
582
|
+
return finalPath;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// src/cli/builtins/filesystem/list-directory.ts
|
|
586
|
+
function listFiles(dirPath, basePath = dirPath, maxDepth = 1, currentDepth = 1) {
|
|
587
|
+
const entries = [];
|
|
588
|
+
try {
|
|
589
|
+
const items = fs2.readdirSync(dirPath);
|
|
590
|
+
for (const item of items) {
|
|
591
|
+
const fullPath = path2.join(dirPath, item);
|
|
592
|
+
const relativePath = path2.relative(basePath, fullPath);
|
|
593
|
+
try {
|
|
594
|
+
const stats = fs2.lstatSync(fullPath);
|
|
595
|
+
let type;
|
|
596
|
+
let size;
|
|
597
|
+
if (stats.isSymbolicLink()) {
|
|
598
|
+
type = "symlink";
|
|
599
|
+
size = 0;
|
|
600
|
+
} else if (stats.isDirectory()) {
|
|
601
|
+
type = "directory";
|
|
602
|
+
size = 0;
|
|
603
|
+
} else {
|
|
604
|
+
type = "file";
|
|
605
|
+
size = stats.size;
|
|
606
|
+
}
|
|
607
|
+
entries.push({
|
|
608
|
+
name: item,
|
|
609
|
+
relativePath,
|
|
610
|
+
type,
|
|
611
|
+
size,
|
|
612
|
+
modified: Math.floor(stats.mtime.getTime() / 1e3)
|
|
613
|
+
});
|
|
614
|
+
if (type === "directory" && currentDepth < maxDepth) {
|
|
615
|
+
try {
|
|
616
|
+
validatePathIsWithinCwd(fullPath);
|
|
617
|
+
const subEntries = listFiles(fullPath, basePath, maxDepth, currentDepth + 1);
|
|
618
|
+
entries.push(...subEntries);
|
|
619
|
+
} catch {
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
} catch {
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
} catch {
|
|
626
|
+
return [];
|
|
627
|
+
}
|
|
628
|
+
return entries;
|
|
629
|
+
}
|
|
630
|
+
function formatAge(epochSeconds) {
|
|
631
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
632
|
+
const seconds = now - epochSeconds;
|
|
633
|
+
if (seconds < 60) return `${seconds}s`;
|
|
634
|
+
const minutes = Math.floor(seconds / 60);
|
|
635
|
+
if (minutes < 60) return `${minutes}m`;
|
|
636
|
+
const hours = Math.floor(minutes / 60);
|
|
637
|
+
if (hours < 24) return `${hours}h`;
|
|
638
|
+
const days = Math.floor(hours / 24);
|
|
639
|
+
if (days < 7) return `${days}d`;
|
|
640
|
+
const weeks = Math.floor(days / 7);
|
|
641
|
+
if (weeks < 4) return `${weeks}w`;
|
|
642
|
+
const months = Math.floor(days / 30);
|
|
643
|
+
if (months < 12) return `${months}mo`;
|
|
644
|
+
const years = Math.floor(days / 365);
|
|
645
|
+
return `${years}y`;
|
|
646
|
+
}
|
|
647
|
+
function formatEntriesAsString(entries) {
|
|
648
|
+
if (entries.length === 0) {
|
|
649
|
+
return "#empty";
|
|
650
|
+
}
|
|
651
|
+
const sortedEntries = [...entries].sort((a, b) => {
|
|
652
|
+
const typeOrder = { directory: 0, file: 1, symlink: 2 };
|
|
653
|
+
const typeCompare = typeOrder[a.type] - typeOrder[b.type];
|
|
654
|
+
if (typeCompare !== 0) return typeCompare;
|
|
655
|
+
return a.relativePath.localeCompare(b.relativePath);
|
|
656
|
+
});
|
|
657
|
+
const typeCode = {
|
|
658
|
+
directory: "D",
|
|
659
|
+
file: "F",
|
|
660
|
+
symlink: "L"
|
|
661
|
+
};
|
|
662
|
+
const encodeName = (name) => name.replace(/\|/g, "%7C").replace(/\n/g, "%0A");
|
|
663
|
+
const header = "#T|N|S|A";
|
|
664
|
+
const rows = sortedEntries.map(
|
|
665
|
+
(e) => `${typeCode[e.type]}|${encodeName(e.relativePath)}|${e.size}|${formatAge(e.modified)}`
|
|
666
|
+
);
|
|
667
|
+
return [header, ...rows].join("\n");
|
|
668
|
+
}
|
|
669
|
+
var listDirectory = createGadget({
|
|
670
|
+
name: "ListDirectory",
|
|
671
|
+
description: "List files and directories in a directory with full details (names, types, sizes, modification dates). Use maxDepth to explore subdirectories recursively. The directory path must be within the current working directory or its subdirectories.",
|
|
672
|
+
schema: z2.object({
|
|
673
|
+
directoryPath: z2.string().default(".").describe("Path to the directory to list"),
|
|
674
|
+
maxDepth: z2.number().int().min(1).max(10).default(1).describe(
|
|
675
|
+
"Maximum depth to recurse (1 = immediate children only, 2 = include grandchildren, etc.)"
|
|
676
|
+
)
|
|
677
|
+
}),
|
|
678
|
+
examples: [
|
|
679
|
+
{
|
|
680
|
+
params: { directoryPath: ".", maxDepth: 1 },
|
|
681
|
+
output: "path=. maxDepth=1\n\n#T|N|S|A\nD|src|0|2h\nD|tests|0|1d\nF|package.json|2841|3h",
|
|
682
|
+
comment: "List current directory"
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
params: { directoryPath: "src", maxDepth: 2 },
|
|
686
|
+
output: "path=src maxDepth=2\n\n#T|N|S|A\nD|components|0|1d\nD|utils|0|2d\nF|index.ts|512|1h\nF|components/Button.tsx|1024|3h",
|
|
687
|
+
comment: "List src directory recursively"
|
|
688
|
+
}
|
|
689
|
+
],
|
|
690
|
+
execute: ({ directoryPath, maxDepth }) => {
|
|
691
|
+
const validatedPath = validatePathIsWithinCwd(directoryPath);
|
|
692
|
+
const stats = fs2.statSync(validatedPath);
|
|
693
|
+
if (!stats.isDirectory()) {
|
|
694
|
+
throw new Error(`Path is not a directory: ${directoryPath}`);
|
|
695
|
+
}
|
|
696
|
+
const entries = listFiles(validatedPath, validatedPath, maxDepth);
|
|
697
|
+
const formattedList = formatEntriesAsString(entries);
|
|
698
|
+
return `path=${directoryPath} maxDepth=${maxDepth}
|
|
699
|
+
|
|
700
|
+
${formattedList}`;
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
// src/cli/builtins/filesystem/read-file.ts
|
|
705
|
+
import fs3 from "node:fs";
|
|
706
|
+
import { z as z3 } from "zod";
|
|
707
|
+
var readFile = createGadget({
|
|
708
|
+
name: "ReadFile",
|
|
709
|
+
description: "Read the entire content of a file and return it as text. The file path must be within the current working directory or its subdirectories.",
|
|
710
|
+
schema: z3.object({
|
|
711
|
+
filePath: z3.string().describe("Path to the file to read (relative or absolute)")
|
|
712
|
+
}),
|
|
713
|
+
examples: [
|
|
714
|
+
{
|
|
715
|
+
params: { filePath: "package.json" },
|
|
716
|
+
output: 'path=package.json\n\n{\n "name": "my-project",\n "version": "1.0.0"\n ...\n}',
|
|
717
|
+
comment: "Read a JSON config file"
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
params: { filePath: "src/index.ts" },
|
|
721
|
+
output: "path=src/index.ts\n\nexport function main() { ... }",
|
|
722
|
+
comment: "Read a source file"
|
|
723
|
+
}
|
|
724
|
+
],
|
|
725
|
+
execute: ({ filePath }) => {
|
|
726
|
+
const validatedPath = validatePathIsWithinCwd(filePath);
|
|
727
|
+
const content = fs3.readFileSync(validatedPath, "utf-8");
|
|
728
|
+
return `path=${filePath}
|
|
729
|
+
|
|
730
|
+
${content}`;
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
// src/cli/builtins/filesystem/write-file.ts
|
|
735
|
+
import fs4 from "node:fs";
|
|
736
|
+
import path3 from "node:path";
|
|
737
|
+
import { z as z4 } from "zod";
|
|
738
|
+
var writeFile = createGadget({
|
|
739
|
+
name: "WriteFile",
|
|
740
|
+
description: "Write content to a file. Creates parent directories if needed. Overwrites existing files. The file path must be within the current working directory or its subdirectories.",
|
|
741
|
+
schema: z4.object({
|
|
742
|
+
filePath: z4.string().describe("Path to the file to write (relative or absolute)"),
|
|
743
|
+
content: z4.string().describe("Content to write to the file")
|
|
744
|
+
}),
|
|
745
|
+
examples: [
|
|
746
|
+
{
|
|
747
|
+
params: { filePath: "output.txt", content: "Hello, World!" },
|
|
748
|
+
output: "path=output.txt\n\nWrote 13 bytes",
|
|
749
|
+
comment: "Write a simple text file"
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
params: {
|
|
753
|
+
filePath: "src/server.ts",
|
|
754
|
+
content: `import { serve } from "bun";
|
|
755
|
+
|
|
756
|
+
const port = 3000;
|
|
757
|
+
|
|
758
|
+
serve({
|
|
759
|
+
port,
|
|
760
|
+
fetch: (req) => new Response(\`Hello from \${req.url}\`),
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
console.log(\`Server running on http://localhost:\${port}\`);`
|
|
764
|
+
},
|
|
765
|
+
output: "path=src/server.ts\n\nWrote 198 bytes (created directory: src)",
|
|
766
|
+
comment: "Write code with template literals - NO escaping needed inside heredoc (use <<<EOF...EOF)"
|
|
767
|
+
}
|
|
768
|
+
],
|
|
769
|
+
execute: ({ filePath, content }) => {
|
|
770
|
+
const validatedPath = validatePathIsWithinCwd(filePath);
|
|
771
|
+
const parentDir = path3.dirname(validatedPath);
|
|
772
|
+
let createdDir = false;
|
|
773
|
+
if (!fs4.existsSync(parentDir)) {
|
|
774
|
+
validatePathIsWithinCwd(parentDir);
|
|
775
|
+
fs4.mkdirSync(parentDir, { recursive: true });
|
|
776
|
+
createdDir = true;
|
|
777
|
+
}
|
|
778
|
+
fs4.writeFileSync(validatedPath, content, "utf-8");
|
|
779
|
+
const bytesWritten = Buffer.byteLength(content, "utf-8");
|
|
780
|
+
const dirNote = createdDir ? ` (created directory: ${path3.dirname(filePath)})` : "";
|
|
781
|
+
return `path=${filePath}
|
|
782
|
+
|
|
783
|
+
Wrote ${bytesWritten} bytes${dirNote}`;
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
// src/cli/builtins/filesystem/edit-file.ts
|
|
788
|
+
import { z as z5 } from "zod";
|
|
789
|
+
function filterDangerousCommands(commands) {
|
|
790
|
+
return commands.split("\n").filter((line) => !line.trimStart().startsWith("!")).join("\n");
|
|
791
|
+
}
|
|
792
|
+
var editFile = createGadget({
|
|
793
|
+
name: "EditFile",
|
|
794
|
+
description: "Edit a file using ed commands. Ed is a line-oriented text editor - pipe commands to it for precise file modifications. Commands are executed in sequence. Remember to end with 'w' (write) and 'q' (quit). Shell escape commands (!) are filtered for security.",
|
|
795
|
+
schema: z5.object({
|
|
796
|
+
filePath: z5.string().describe("Path to the file to edit (relative or absolute)"),
|
|
797
|
+
commands: z5.string().describe("Ed commands to execute, one per line")
|
|
798
|
+
}),
|
|
799
|
+
examples: [
|
|
800
|
+
{
|
|
801
|
+
params: {
|
|
802
|
+
filePath: "config.txt",
|
|
803
|
+
commands: `1,$p
|
|
804
|
+
q`
|
|
805
|
+
},
|
|
806
|
+
output: "path=config.txt\n\n32\nkey=value\noption=true",
|
|
807
|
+
comment: "Print entire file contents (ed shows byte count, then content)"
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
params: {
|
|
811
|
+
filePath: "data.txt",
|
|
812
|
+
commands: `1,$s/foo/bar/g
|
|
813
|
+
w
|
|
814
|
+
q`
|
|
815
|
+
},
|
|
816
|
+
output: "path=data.txt\n\n42\n42",
|
|
817
|
+
comment: "Replace all 'foo' with 'bar' (ed shows bytes read, then bytes written)"
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
params: {
|
|
821
|
+
filePath: "list.txt",
|
|
822
|
+
commands: `3d
|
|
823
|
+
w
|
|
824
|
+
q`
|
|
825
|
+
},
|
|
826
|
+
output: "path=list.txt\n\n45\n28",
|
|
827
|
+
comment: "Delete line 3, save and quit"
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
params: {
|
|
831
|
+
filePath: "readme.txt",
|
|
832
|
+
commands: `$a
|
|
833
|
+
New last line
|
|
834
|
+
.
|
|
835
|
+
w
|
|
836
|
+
q`
|
|
837
|
+
},
|
|
838
|
+
output: "path=readme.txt\n\n40\n56",
|
|
839
|
+
comment: "Append text after last line ($ = last line, . = end input mode)"
|
|
840
|
+
}
|
|
841
|
+
],
|
|
842
|
+
timeoutMs: 3e4,
|
|
843
|
+
execute: async ({ filePath, commands }) => {
|
|
844
|
+
const validatedPath = validatePathIsWithinCwd(filePath);
|
|
845
|
+
const safeCommands = filterDangerousCommands(commands);
|
|
846
|
+
try {
|
|
847
|
+
const proc = Bun.spawn(["ed", validatedPath], {
|
|
848
|
+
stdin: "pipe",
|
|
849
|
+
stdout: "pipe",
|
|
850
|
+
stderr: "pipe"
|
|
851
|
+
});
|
|
852
|
+
proc.stdin.write(`${safeCommands}
|
|
853
|
+
`);
|
|
854
|
+
proc.stdin.end();
|
|
855
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
856
|
+
setTimeout(() => {
|
|
857
|
+
proc.kill();
|
|
858
|
+
reject(new Error("ed command timed out after 30000ms"));
|
|
859
|
+
}, 3e4);
|
|
860
|
+
});
|
|
861
|
+
const exitCode = await Promise.race([proc.exited, timeoutPromise]);
|
|
862
|
+
const stdout = await new Response(proc.stdout).text();
|
|
863
|
+
const stderr = await new Response(proc.stderr).text();
|
|
864
|
+
const output = [stdout, stderr].filter(Boolean).join("\n").trim();
|
|
865
|
+
if (exitCode !== 0) {
|
|
866
|
+
return `path=${filePath}
|
|
867
|
+
|
|
868
|
+
${output || "ed exited with non-zero status"}`;
|
|
869
|
+
}
|
|
870
|
+
return `path=${filePath}
|
|
871
|
+
|
|
872
|
+
${output || "(no output)"}`;
|
|
873
|
+
} catch (error) {
|
|
874
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
875
|
+
return `path=${filePath}
|
|
876
|
+
|
|
877
|
+
error: ${message}`;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
// src/cli/builtins/run-command.ts
|
|
883
|
+
import { z as z6 } from "zod";
|
|
884
|
+
var runCommand = createGadget({
|
|
885
|
+
name: "RunCommand",
|
|
886
|
+
description: "Execute a shell command and return its output. Returns both stdout and stderr combined with the exit status.",
|
|
887
|
+
schema: z6.object({
|
|
888
|
+
command: z6.string().describe("The shell command to execute"),
|
|
889
|
+
cwd: z6.string().optional().describe("Working directory for the command (default: current directory)"),
|
|
890
|
+
timeout: z6.number().default(3e4).describe("Timeout in milliseconds (default: 30000)")
|
|
891
|
+
}),
|
|
892
|
+
examples: [
|
|
893
|
+
{
|
|
894
|
+
params: { command: "ls -la", timeout: 3e4 },
|
|
895
|
+
output: "status=0\n\ntotal 24\ndrwxr-xr-x 5 user staff 160 Nov 27 10:00 .\ndrwxr-xr-x 3 user staff 96 Nov 27 09:00 ..\n-rw-r--r-- 1 user staff 1024 Nov 27 10:00 package.json",
|
|
896
|
+
comment: "List directory contents with details"
|
|
897
|
+
},
|
|
898
|
+
{
|
|
899
|
+
params: { command: "echo 'Hello World'", timeout: 3e4 },
|
|
900
|
+
output: "status=0\n\nHello World",
|
|
901
|
+
comment: "Simple echo command"
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
params: { command: "cat nonexistent.txt", timeout: 3e4 },
|
|
905
|
+
output: "status=1\n\ncat: nonexistent.txt: No such file or directory",
|
|
906
|
+
comment: "Command that fails returns non-zero status"
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
params: { command: "pwd", cwd: "/tmp", timeout: 3e4 },
|
|
910
|
+
output: "status=0\n\n/tmp",
|
|
911
|
+
comment: "Execute command in a specific directory"
|
|
912
|
+
}
|
|
913
|
+
],
|
|
914
|
+
execute: async ({ command, cwd, timeout }) => {
|
|
915
|
+
const workingDir = cwd ?? process.cwd();
|
|
916
|
+
try {
|
|
917
|
+
const proc = Bun.spawn(["sh", "-c", command], {
|
|
918
|
+
cwd: workingDir,
|
|
919
|
+
stdout: "pipe",
|
|
920
|
+
stderr: "pipe"
|
|
921
|
+
});
|
|
922
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
923
|
+
setTimeout(() => {
|
|
924
|
+
proc.kill();
|
|
925
|
+
reject(new Error(`Command timed out after ${timeout}ms`));
|
|
926
|
+
}, timeout);
|
|
927
|
+
});
|
|
928
|
+
const exitCode = await Promise.race([proc.exited, timeoutPromise]);
|
|
929
|
+
const stdout = await new Response(proc.stdout).text();
|
|
930
|
+
const stderr = await new Response(proc.stderr).text();
|
|
931
|
+
const output = [stdout, stderr].filter(Boolean).join("\n").trim();
|
|
932
|
+
return `status=${exitCode}
|
|
933
|
+
|
|
934
|
+
${output || "(no output)"}`;
|
|
935
|
+
} catch (error) {
|
|
936
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
937
|
+
return `status=1
|
|
938
|
+
|
|
939
|
+
error: ${message}`;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
// src/cli/builtins/index.ts
|
|
945
|
+
var builtinGadgetRegistry = {
|
|
946
|
+
ListDirectory: listDirectory,
|
|
947
|
+
ReadFile: readFile,
|
|
948
|
+
WriteFile: writeFile,
|
|
949
|
+
EditFile: editFile,
|
|
950
|
+
RunCommand: runCommand
|
|
951
|
+
};
|
|
952
|
+
function getBuiltinGadget(name) {
|
|
953
|
+
return builtinGadgetRegistry[name];
|
|
954
|
+
}
|
|
955
|
+
function isBuiltinGadgetName(name) {
|
|
956
|
+
return name in builtinGadgetRegistry;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// src/cli/gadgets.ts
|
|
284
960
|
var PATH_PREFIXES = [".", "/", "~"];
|
|
961
|
+
var BUILTIN_PREFIX = "builtin:";
|
|
285
962
|
function isGadgetLike(value) {
|
|
286
963
|
if (typeof value !== "object" || value === null) {
|
|
287
964
|
return false;
|
|
@@ -304,18 +981,34 @@ function expandHomePath(input) {
|
|
|
304
981
|
if (!home) {
|
|
305
982
|
return input;
|
|
306
983
|
}
|
|
307
|
-
return
|
|
984
|
+
return path4.join(home, input.slice(1));
|
|
308
985
|
}
|
|
309
986
|
function isFileLikeSpecifier(specifier) {
|
|
310
|
-
return PATH_PREFIXES.some((prefix) => specifier.startsWith(prefix)) || specifier.includes(
|
|
987
|
+
return PATH_PREFIXES.some((prefix) => specifier.startsWith(prefix)) || specifier.includes(path4.sep);
|
|
988
|
+
}
|
|
989
|
+
function tryResolveBuiltin(specifier) {
|
|
990
|
+
if (specifier.startsWith(BUILTIN_PREFIX)) {
|
|
991
|
+
const name = specifier.slice(BUILTIN_PREFIX.length);
|
|
992
|
+
const gadget = getBuiltinGadget(name);
|
|
993
|
+
if (!gadget) {
|
|
994
|
+
throw new Error(
|
|
995
|
+
`Unknown builtin gadget: ${name}. Available builtins: ListDirectory, ReadFile, WriteFile, EditFile, RunCommand`
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
return gadget;
|
|
999
|
+
}
|
|
1000
|
+
if (!isFileLikeSpecifier(specifier) && isBuiltinGadgetName(specifier)) {
|
|
1001
|
+
return getBuiltinGadget(specifier);
|
|
1002
|
+
}
|
|
1003
|
+
return null;
|
|
311
1004
|
}
|
|
312
1005
|
function resolveGadgetSpecifier(specifier, cwd) {
|
|
313
1006
|
if (!isFileLikeSpecifier(specifier)) {
|
|
314
1007
|
return specifier;
|
|
315
1008
|
}
|
|
316
1009
|
const expanded = expandHomePath(specifier);
|
|
317
|
-
const resolvedPath =
|
|
318
|
-
if (!
|
|
1010
|
+
const resolvedPath = path4.resolve(cwd, expanded);
|
|
1011
|
+
if (!fs5.existsSync(resolvedPath)) {
|
|
319
1012
|
throw new Error(`Gadget module not found at ${resolvedPath}`);
|
|
320
1013
|
}
|
|
321
1014
|
return pathToFileURL(resolvedPath).href;
|
|
@@ -357,6 +1050,11 @@ function extractGadgetsFromModule(moduleExports) {
|
|
|
357
1050
|
async function loadGadgets(specifiers, cwd, importer = (specifier) => import(specifier)) {
|
|
358
1051
|
const gadgets = [];
|
|
359
1052
|
for (const specifier of specifiers) {
|
|
1053
|
+
const builtin = tryResolveBuiltin(specifier);
|
|
1054
|
+
if (builtin) {
|
|
1055
|
+
gadgets.push(builtin);
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
360
1058
|
const resolved = resolveGadgetSpecifier(specifier, cwd);
|
|
361
1059
|
let exports;
|
|
362
1060
|
try {
|
|
@@ -381,7 +1079,7 @@ async function loadGadgets(specifiers, cwd, importer = (specifier) => import(spe
|
|
|
381
1079
|
}
|
|
382
1080
|
|
|
383
1081
|
// src/cli/llm-logging.ts
|
|
384
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
1082
|
+
import { mkdir, writeFile as writeFile2 } from "node:fs/promises";
|
|
385
1083
|
import { homedir } from "node:os";
|
|
386
1084
|
import { join } from "node:path";
|
|
387
1085
|
var DEFAULT_LLM_LOG_DIR = join(homedir(), ".llmist", "logs");
|
|
@@ -405,43 +1103,43 @@ function formatLlmRequest(messages) {
|
|
|
405
1103
|
}
|
|
406
1104
|
async function writeLogFile(dir, filename, content) {
|
|
407
1105
|
await mkdir(dir, { recursive: true });
|
|
408
|
-
await
|
|
1106
|
+
await writeFile2(join(dir, filename), content, "utf-8");
|
|
409
1107
|
}
|
|
410
1108
|
|
|
411
1109
|
// src/cli/utils.ts
|
|
412
1110
|
init_constants();
|
|
413
|
-
import
|
|
1111
|
+
import chalk4 from "chalk";
|
|
414
1112
|
import { InvalidArgumentError } from "commander";
|
|
415
1113
|
|
|
416
1114
|
// src/cli/ui/formatters.ts
|
|
417
|
-
import
|
|
1115
|
+
import chalk3 from "chalk";
|
|
418
1116
|
import { marked } from "marked";
|
|
419
1117
|
import { markedTerminal } from "marked-terminal";
|
|
420
1118
|
var markedConfigured = false;
|
|
421
1119
|
function ensureMarkedConfigured() {
|
|
422
1120
|
if (!markedConfigured) {
|
|
423
|
-
|
|
1121
|
+
chalk3.level = process.env.NO_COLOR ? 0 : 3;
|
|
424
1122
|
marked.use(
|
|
425
1123
|
markedTerminal({
|
|
426
1124
|
// Text styling
|
|
427
|
-
strong:
|
|
428
|
-
em:
|
|
429
|
-
del:
|
|
1125
|
+
strong: chalk3.bold,
|
|
1126
|
+
em: chalk3.italic,
|
|
1127
|
+
del: chalk3.dim.gray.strikethrough,
|
|
430
1128
|
// Code styling
|
|
431
|
-
code:
|
|
432
|
-
codespan:
|
|
1129
|
+
code: chalk3.yellow,
|
|
1130
|
+
codespan: chalk3.yellow,
|
|
433
1131
|
// Headings
|
|
434
|
-
heading:
|
|
435
|
-
firstHeading:
|
|
1132
|
+
heading: chalk3.green.bold,
|
|
1133
|
+
firstHeading: chalk3.magenta.underline.bold,
|
|
436
1134
|
// Links
|
|
437
|
-
link:
|
|
438
|
-
href:
|
|
1135
|
+
link: chalk3.blue,
|
|
1136
|
+
href: chalk3.blue.underline,
|
|
439
1137
|
// Block elements
|
|
440
|
-
blockquote:
|
|
1138
|
+
blockquote: chalk3.gray.italic,
|
|
441
1139
|
// List formatting - reduce indentation and add bullet styling
|
|
442
1140
|
tab: 2,
|
|
443
1141
|
// Reduce from default 4 to 2 spaces
|
|
444
|
-
listitem:
|
|
1142
|
+
listitem: chalk3.reset
|
|
445
1143
|
// Keep items readable (no dim)
|
|
446
1144
|
})
|
|
447
1145
|
);
|
|
@@ -451,11 +1149,11 @@ function ensureMarkedConfigured() {
|
|
|
451
1149
|
function renderMarkdown(text) {
|
|
452
1150
|
ensureMarkedConfigured();
|
|
453
1151
|
let rendered = marked.parse(text);
|
|
454
|
-
rendered = rendered.replace(/\*\*(.+?)\*\*/g, (_, content) =>
|
|
1152
|
+
rendered = rendered.replace(/\*\*(.+?)\*\*/g, (_, content) => chalk3.bold(content)).replace(/(?<!\*)\*(\S[^*]*)\*(?!\*)/g, (_, content) => chalk3.italic(content));
|
|
455
1153
|
return rendered.trimEnd();
|
|
456
1154
|
}
|
|
457
1155
|
function createRainbowSeparator() {
|
|
458
|
-
const colors = [
|
|
1156
|
+
const colors = [chalk3.red, chalk3.yellow, chalk3.green, chalk3.cyan, chalk3.blue, chalk3.magenta];
|
|
459
1157
|
const char = "\u2500";
|
|
460
1158
|
const width = process.stdout.columns || 80;
|
|
461
1159
|
let result = "";
|
|
@@ -491,58 +1189,58 @@ function formatCost(cost) {
|
|
|
491
1189
|
function renderSummary(metadata) {
|
|
492
1190
|
const parts = [];
|
|
493
1191
|
if (metadata.iterations !== void 0) {
|
|
494
|
-
const iterPart =
|
|
1192
|
+
const iterPart = chalk3.cyan(`#${metadata.iterations}`);
|
|
495
1193
|
if (metadata.model) {
|
|
496
|
-
parts.push(`${iterPart} ${
|
|
1194
|
+
parts.push(`${iterPart} ${chalk3.magenta(metadata.model)}`);
|
|
497
1195
|
} else {
|
|
498
1196
|
parts.push(iterPart);
|
|
499
1197
|
}
|
|
500
1198
|
} else if (metadata.model) {
|
|
501
|
-
parts.push(
|
|
1199
|
+
parts.push(chalk3.magenta(metadata.model));
|
|
502
1200
|
}
|
|
503
1201
|
if (metadata.usage) {
|
|
504
1202
|
const { inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens } = metadata.usage;
|
|
505
|
-
parts.push(
|
|
1203
|
+
parts.push(chalk3.dim("\u2191") + chalk3.yellow(` ${formatTokens(inputTokens)}`));
|
|
506
1204
|
if (cachedInputTokens && cachedInputTokens > 0) {
|
|
507
|
-
parts.push(
|
|
1205
|
+
parts.push(chalk3.dim("\u27F3") + chalk3.blue(` ${formatTokens(cachedInputTokens)}`));
|
|
508
1206
|
}
|
|
509
1207
|
if (cacheCreationInputTokens && cacheCreationInputTokens > 0) {
|
|
510
|
-
parts.push(
|
|
1208
|
+
parts.push(chalk3.dim("\u270E") + chalk3.magenta(` ${formatTokens(cacheCreationInputTokens)}`));
|
|
511
1209
|
}
|
|
512
|
-
parts.push(
|
|
1210
|
+
parts.push(chalk3.dim("\u2193") + chalk3.green(` ${formatTokens(outputTokens)}`));
|
|
513
1211
|
}
|
|
514
1212
|
if (metadata.elapsedSeconds !== void 0 && metadata.elapsedSeconds > 0) {
|
|
515
|
-
parts.push(
|
|
1213
|
+
parts.push(chalk3.dim(`${metadata.elapsedSeconds}s`));
|
|
516
1214
|
}
|
|
517
1215
|
if (metadata.cost !== void 0 && metadata.cost > 0) {
|
|
518
|
-
parts.push(
|
|
1216
|
+
parts.push(chalk3.cyan(`$${formatCost(metadata.cost)}`));
|
|
519
1217
|
}
|
|
520
1218
|
if (metadata.finishReason) {
|
|
521
|
-
parts.push(
|
|
1219
|
+
parts.push(chalk3.dim(metadata.finishReason));
|
|
522
1220
|
}
|
|
523
1221
|
if (parts.length === 0) {
|
|
524
1222
|
return null;
|
|
525
1223
|
}
|
|
526
|
-
return parts.join(
|
|
1224
|
+
return parts.join(chalk3.dim(" | "));
|
|
527
1225
|
}
|
|
528
1226
|
function renderOverallSummary(metadata) {
|
|
529
1227
|
const parts = [];
|
|
530
1228
|
if (metadata.totalTokens !== void 0 && metadata.totalTokens > 0) {
|
|
531
|
-
parts.push(
|
|
1229
|
+
parts.push(chalk3.dim("total:") + chalk3.magenta(` ${formatTokens(metadata.totalTokens)}`));
|
|
532
1230
|
}
|
|
533
1231
|
if (metadata.iterations !== void 0 && metadata.iterations > 0) {
|
|
534
|
-
parts.push(
|
|
1232
|
+
parts.push(chalk3.cyan(`#${metadata.iterations}`));
|
|
535
1233
|
}
|
|
536
1234
|
if (metadata.elapsedSeconds !== void 0 && metadata.elapsedSeconds > 0) {
|
|
537
|
-
parts.push(
|
|
1235
|
+
parts.push(chalk3.dim(`${metadata.elapsedSeconds}s`));
|
|
538
1236
|
}
|
|
539
1237
|
if (metadata.cost !== void 0 && metadata.cost > 0) {
|
|
540
|
-
parts.push(
|
|
1238
|
+
parts.push(chalk3.cyan(`$${formatCost(metadata.cost)}`));
|
|
541
1239
|
}
|
|
542
1240
|
if (parts.length === 0) {
|
|
543
1241
|
return null;
|
|
544
1242
|
}
|
|
545
|
-
return parts.join(
|
|
1243
|
+
return parts.join(chalk3.dim(" | "));
|
|
546
1244
|
}
|
|
547
1245
|
function formatParametersInline(params) {
|
|
548
1246
|
if (!params || Object.keys(params).length === 0) {
|
|
@@ -558,8 +1256,8 @@ function formatParametersInline(params) {
|
|
|
558
1256
|
const json = JSON.stringify(value);
|
|
559
1257
|
formatted = json.length > 30 ? `${json.slice(0, 30)}\u2026` : json;
|
|
560
1258
|
}
|
|
561
|
-
return `${
|
|
562
|
-
}).join(
|
|
1259
|
+
return `${chalk3.dim(key)}${chalk3.dim("=")}${chalk3.cyan(formatted)}`;
|
|
1260
|
+
}).join(chalk3.dim(", "));
|
|
563
1261
|
}
|
|
564
1262
|
function formatBytes(bytes) {
|
|
565
1263
|
if (bytes < 1024) {
|
|
@@ -571,25 +1269,25 @@ function formatBytes(bytes) {
|
|
|
571
1269
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
572
1270
|
}
|
|
573
1271
|
function formatGadgetSummary(result) {
|
|
574
|
-
const gadgetLabel =
|
|
575
|
-
const timeLabel =
|
|
1272
|
+
const gadgetLabel = chalk3.magenta.bold(result.gadgetName);
|
|
1273
|
+
const timeLabel = chalk3.dim(`${Math.round(result.executionTimeMs)}ms`);
|
|
576
1274
|
const paramsStr = formatParametersInline(result.parameters);
|
|
577
|
-
const paramsLabel = paramsStr ? `${
|
|
1275
|
+
const paramsLabel = paramsStr ? `${chalk3.dim("(")}${paramsStr}${chalk3.dim(")")}` : "";
|
|
578
1276
|
if (result.error) {
|
|
579
1277
|
const errorMsg = result.error.length > 50 ? `${result.error.slice(0, 50)}\u2026` : result.error;
|
|
580
|
-
return `${
|
|
1278
|
+
return `${chalk3.red("\u2717")} ${gadgetLabel}${paramsLabel} ${chalk3.red("error:")} ${errorMsg} ${timeLabel}`;
|
|
581
1279
|
}
|
|
582
1280
|
let outputLabel;
|
|
583
1281
|
if (result.tokenCount !== void 0 && result.tokenCount > 0) {
|
|
584
|
-
outputLabel =
|
|
1282
|
+
outputLabel = chalk3.green(`${formatTokens(result.tokenCount)} tokens`);
|
|
585
1283
|
} else if (result.result) {
|
|
586
1284
|
const outputBytes = Buffer.byteLength(result.result, "utf-8");
|
|
587
|
-
outputLabel = outputBytes > 0 ?
|
|
1285
|
+
outputLabel = outputBytes > 0 ? chalk3.green(formatBytes(outputBytes)) : chalk3.dim("no output");
|
|
588
1286
|
} else {
|
|
589
|
-
outputLabel =
|
|
1287
|
+
outputLabel = chalk3.dim("no output");
|
|
590
1288
|
}
|
|
591
|
-
const icon = result.breaksLoop ?
|
|
592
|
-
const summaryLine = `${icon} ${gadgetLabel}${paramsLabel} ${
|
|
1289
|
+
const icon = result.breaksLoop ? chalk3.yellow("\u23F9") : chalk3.green("\u2713");
|
|
1290
|
+
const summaryLine = `${icon} ${gadgetLabel}${paramsLabel} ${chalk3.dim("\u2192")} ${outputLabel} ${timeLabel}`;
|
|
593
1291
|
if (result.gadgetName === "TellUser" && result.parameters?.message) {
|
|
594
1292
|
const message = String(result.parameters.message);
|
|
595
1293
|
const rendered = renderMarkdownWithSeparators(message);
|
|
@@ -653,6 +1351,66 @@ var StreamPrinter = class {
|
|
|
653
1351
|
function isInteractive(stream) {
|
|
654
1352
|
return Boolean(stream.isTTY);
|
|
655
1353
|
}
|
|
1354
|
+
var ESC_KEY = 27;
|
|
1355
|
+
var ESC_TIMEOUT_MS = 50;
|
|
1356
|
+
function createEscKeyListener(stdin, onEsc) {
|
|
1357
|
+
if (!stdin.isTTY || typeof stdin.setRawMode !== "function") {
|
|
1358
|
+
return null;
|
|
1359
|
+
}
|
|
1360
|
+
let escTimeout = null;
|
|
1361
|
+
const handleData = (data) => {
|
|
1362
|
+
if (data[0] === ESC_KEY) {
|
|
1363
|
+
if (data.length === 1) {
|
|
1364
|
+
escTimeout = setTimeout(() => {
|
|
1365
|
+
onEsc();
|
|
1366
|
+
}, ESC_TIMEOUT_MS);
|
|
1367
|
+
} else {
|
|
1368
|
+
if (escTimeout) {
|
|
1369
|
+
clearTimeout(escTimeout);
|
|
1370
|
+
escTimeout = null;
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
} else {
|
|
1374
|
+
if (escTimeout) {
|
|
1375
|
+
clearTimeout(escTimeout);
|
|
1376
|
+
escTimeout = null;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
};
|
|
1380
|
+
stdin.setRawMode(true);
|
|
1381
|
+
stdin.resume();
|
|
1382
|
+
stdin.on("data", handleData);
|
|
1383
|
+
return () => {
|
|
1384
|
+
if (escTimeout) {
|
|
1385
|
+
clearTimeout(escTimeout);
|
|
1386
|
+
}
|
|
1387
|
+
stdin.removeListener("data", handleData);
|
|
1388
|
+
stdin.setRawMode(false);
|
|
1389
|
+
stdin.pause();
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
var SIGINT_DOUBLE_PRESS_MS = 1e3;
|
|
1393
|
+
function createSigintListener(onCancel, onQuit, isOperationActive, stderr = process.stderr) {
|
|
1394
|
+
let lastSigintTime = 0;
|
|
1395
|
+
const handler = () => {
|
|
1396
|
+
const now = Date.now();
|
|
1397
|
+
if (isOperationActive()) {
|
|
1398
|
+
onCancel();
|
|
1399
|
+
lastSigintTime = now;
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
if (now - lastSigintTime < SIGINT_DOUBLE_PRESS_MS) {
|
|
1403
|
+
onQuit();
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
lastSigintTime = now;
|
|
1407
|
+
stderr.write(chalk4.dim("\n[Press Ctrl+C again to quit]\n"));
|
|
1408
|
+
};
|
|
1409
|
+
process.on("SIGINT", handler);
|
|
1410
|
+
return () => {
|
|
1411
|
+
process.removeListener("SIGINT", handler);
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
656
1414
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
657
1415
|
var SPINNER_DELAY_MS = 500;
|
|
658
1416
|
var StreamProgress = class {
|
|
@@ -824,9 +1582,9 @@ var StreamProgress = class {
|
|
|
824
1582
|
const elapsed = ((Date.now() - this.callStartTime) / 1e3).toFixed(1);
|
|
825
1583
|
const outTokens = this.callOutputTokensEstimated ? Math.round(this.callOutputChars / FALLBACK_CHARS_PER_TOKEN) : this.callOutputTokens;
|
|
826
1584
|
const parts = [];
|
|
827
|
-
const iterPart =
|
|
1585
|
+
const iterPart = chalk4.cyan(`#${this.currentIteration}`);
|
|
828
1586
|
if (this.model) {
|
|
829
|
-
parts.push(`${iterPart} ${
|
|
1587
|
+
parts.push(`${iterPart} ${chalk4.magenta(this.model)}`);
|
|
830
1588
|
} else {
|
|
831
1589
|
parts.push(iterPart);
|
|
832
1590
|
}
|
|
@@ -834,27 +1592,27 @@ var StreamProgress = class {
|
|
|
834
1592
|
if (usagePercent !== null) {
|
|
835
1593
|
const formatted = `${Math.round(usagePercent)}%`;
|
|
836
1594
|
if (usagePercent >= 80) {
|
|
837
|
-
parts.push(
|
|
1595
|
+
parts.push(chalk4.red(formatted));
|
|
838
1596
|
} else if (usagePercent >= 50) {
|
|
839
|
-
parts.push(
|
|
1597
|
+
parts.push(chalk4.yellow(formatted));
|
|
840
1598
|
} else {
|
|
841
|
-
parts.push(
|
|
1599
|
+
parts.push(chalk4.green(formatted));
|
|
842
1600
|
}
|
|
843
1601
|
}
|
|
844
1602
|
if (this.callInputTokens > 0) {
|
|
845
1603
|
const prefix = this.callInputTokensEstimated ? "~" : "";
|
|
846
|
-
parts.push(
|
|
1604
|
+
parts.push(chalk4.dim("\u2191") + chalk4.yellow(` ${prefix}${formatTokens(this.callInputTokens)}`));
|
|
847
1605
|
}
|
|
848
1606
|
if (this.isStreaming || outTokens > 0) {
|
|
849
1607
|
const prefix = this.callOutputTokensEstimated ? "~" : "";
|
|
850
|
-
parts.push(
|
|
1608
|
+
parts.push(chalk4.dim("\u2193") + chalk4.green(` ${prefix}${formatTokens(outTokens)}`));
|
|
851
1609
|
}
|
|
852
|
-
parts.push(
|
|
1610
|
+
parts.push(chalk4.dim(`${elapsed}s`));
|
|
853
1611
|
const callCost = this.calculateCurrentCallCost(outTokens);
|
|
854
1612
|
if (callCost > 0) {
|
|
855
|
-
parts.push(
|
|
1613
|
+
parts.push(chalk4.cyan(`$${formatCost(callCost)}`));
|
|
856
1614
|
}
|
|
857
|
-
this.target.write(`\r${parts.join(
|
|
1615
|
+
this.target.write(`\r${parts.join(chalk4.dim(" | "))} ${chalk4.cyan(spinner)}`);
|
|
858
1616
|
}
|
|
859
1617
|
/**
|
|
860
1618
|
* Calculates live cost estimate for the current streaming call.
|
|
@@ -895,19 +1653,19 @@ var StreamProgress = class {
|
|
|
895
1653
|
const elapsed = ((Date.now() - this.totalStartTime) / 1e3).toFixed(1);
|
|
896
1654
|
const parts = [];
|
|
897
1655
|
if (this.model) {
|
|
898
|
-
parts.push(
|
|
1656
|
+
parts.push(chalk4.cyan(this.model));
|
|
899
1657
|
}
|
|
900
1658
|
if (this.totalTokens > 0) {
|
|
901
|
-
parts.push(
|
|
1659
|
+
parts.push(chalk4.dim("total:") + chalk4.magenta(` ${this.totalTokens}`));
|
|
902
1660
|
}
|
|
903
1661
|
if (this.iterations > 0) {
|
|
904
|
-
parts.push(
|
|
1662
|
+
parts.push(chalk4.dim("iter:") + chalk4.blue(` ${this.iterations}`));
|
|
905
1663
|
}
|
|
906
1664
|
if (this.totalCost > 0) {
|
|
907
|
-
parts.push(
|
|
1665
|
+
parts.push(chalk4.dim("cost:") + chalk4.cyan(` $${formatCost(this.totalCost)}`));
|
|
908
1666
|
}
|
|
909
|
-
parts.push(
|
|
910
|
-
this.target.write(`\r${parts.join(
|
|
1667
|
+
parts.push(chalk4.dim(`${elapsed}s`));
|
|
1668
|
+
this.target.write(`\r${parts.join(chalk4.dim(" | "))} ${chalk4.cyan(spinner)}`);
|
|
911
1669
|
}
|
|
912
1670
|
/**
|
|
913
1671
|
* Pauses the progress indicator and clears the line.
|
|
@@ -941,6 +1699,25 @@ var StreamProgress = class {
|
|
|
941
1699
|
getTotalCost() {
|
|
942
1700
|
return this.totalCost;
|
|
943
1701
|
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Returns a formatted stats string for cancellation messages.
|
|
1704
|
+
* Format: "↑ 1.2k | ↓ 300 | 5.0s"
|
|
1705
|
+
*/
|
|
1706
|
+
formatStats() {
|
|
1707
|
+
const parts = [];
|
|
1708
|
+
const elapsed = ((Date.now() - this.callStartTime) / 1e3).toFixed(1);
|
|
1709
|
+
const outTokens = this.callOutputTokensEstimated ? Math.round(this.callOutputChars / FALLBACK_CHARS_PER_TOKEN) : this.callOutputTokens;
|
|
1710
|
+
if (this.callInputTokens > 0) {
|
|
1711
|
+
const prefix = this.callInputTokensEstimated ? "~" : "";
|
|
1712
|
+
parts.push(`\u2191 ${prefix}${formatTokens(this.callInputTokens)}`);
|
|
1713
|
+
}
|
|
1714
|
+
if (outTokens > 0) {
|
|
1715
|
+
const prefix = this.callOutputTokensEstimated ? "~" : "";
|
|
1716
|
+
parts.push(`\u2193 ${prefix}${formatTokens(outTokens)}`);
|
|
1717
|
+
}
|
|
1718
|
+
parts.push(`${elapsed}s`);
|
|
1719
|
+
return parts.join(" | ");
|
|
1720
|
+
}
|
|
944
1721
|
/**
|
|
945
1722
|
* Returns a formatted prompt string with stats (like bash PS1).
|
|
946
1723
|
* Shows current call stats during streaming, cumulative stats otherwise.
|
|
@@ -955,28 +1732,28 @@ var StreamProgress = class {
|
|
|
955
1732
|
if (this.callInputTokens > 0) {
|
|
956
1733
|
const prefix = this.callInputTokensEstimated ? "~" : "";
|
|
957
1734
|
parts.push(
|
|
958
|
-
|
|
1735
|
+
chalk4.dim("\u2191") + chalk4.yellow(` ${prefix}${formatTokens(this.callInputTokens)}`)
|
|
959
1736
|
);
|
|
960
1737
|
}
|
|
961
1738
|
if (outTokens > 0) {
|
|
962
1739
|
const prefix = outEstimated ? "~" : "";
|
|
963
|
-
parts.push(
|
|
1740
|
+
parts.push(chalk4.dim("\u2193") + chalk4.green(` ${prefix}${formatTokens(outTokens)}`));
|
|
964
1741
|
}
|
|
965
|
-
parts.push(
|
|
1742
|
+
parts.push(chalk4.dim(`${elapsed}s`));
|
|
966
1743
|
} else {
|
|
967
1744
|
const elapsed = Math.round((Date.now() - this.totalStartTime) / 1e3);
|
|
968
1745
|
if (this.totalTokens > 0) {
|
|
969
|
-
parts.push(
|
|
1746
|
+
parts.push(chalk4.magenta(formatTokens(this.totalTokens)));
|
|
970
1747
|
}
|
|
971
1748
|
if (this.iterations > 0) {
|
|
972
|
-
parts.push(
|
|
1749
|
+
parts.push(chalk4.blue(`i${this.iterations}`));
|
|
973
1750
|
}
|
|
974
1751
|
if (this.totalCost > 0) {
|
|
975
|
-
parts.push(
|
|
1752
|
+
parts.push(chalk4.cyan(`$${formatCost(this.totalCost)}`));
|
|
976
1753
|
}
|
|
977
|
-
parts.push(
|
|
1754
|
+
parts.push(chalk4.dim(`${elapsed}s`));
|
|
978
1755
|
}
|
|
979
|
-
return `${parts.join(
|
|
1756
|
+
return `${parts.join(chalk4.dim(" | "))} ${chalk4.green(">")} `;
|
|
980
1757
|
}
|
|
981
1758
|
};
|
|
982
1759
|
async function readStream(stream) {
|
|
@@ -1011,7 +1788,7 @@ async function executeAction(action, env) {
|
|
|
1011
1788
|
await action();
|
|
1012
1789
|
} catch (error) {
|
|
1013
1790
|
const message = error instanceof Error ? error.message : String(error);
|
|
1014
|
-
env.stderr.write(`${
|
|
1791
|
+
env.stderr.write(`${chalk4.red.bold("Error:")} ${message}
|
|
1015
1792
|
`);
|
|
1016
1793
|
env.setExitCode(1);
|
|
1017
1794
|
}
|
|
@@ -1036,7 +1813,7 @@ function addAgentOptions(cmd, defaults) {
|
|
|
1036
1813
|
...previous,
|
|
1037
1814
|
value
|
|
1038
1815
|
];
|
|
1039
|
-
const defaultGadgets = defaults?.gadget ?? [];
|
|
1816
|
+
const defaultGadgets = defaults?.gadgets ?? defaults?.gadget ?? [];
|
|
1040
1817
|
return cmd.option(OPTION_FLAGS.model, OPTION_DESCRIPTIONS.model, defaults?.model ?? DEFAULT_MODEL).option(OPTION_FLAGS.systemPrompt, OPTION_DESCRIPTIONS.systemPrompt, defaults?.system).option(
|
|
1041
1818
|
OPTION_FLAGS.temperature,
|
|
1042
1819
|
OPTION_DESCRIPTIONS.temperature,
|
|
@@ -1072,7 +1849,8 @@ function configToAgentOptions(config) {
|
|
|
1072
1849
|
if (config.system !== void 0) result.system = config.system;
|
|
1073
1850
|
if (config.temperature !== void 0) result.temperature = config.temperature;
|
|
1074
1851
|
if (config["max-iterations"] !== void 0) result.maxIterations = config["max-iterations"];
|
|
1075
|
-
|
|
1852
|
+
const gadgets = config.gadgets ?? config.gadget;
|
|
1853
|
+
if (gadgets !== void 0) result.gadget = gadgets;
|
|
1076
1854
|
if (config.builtins !== void 0) result.builtins = config.builtins;
|
|
1077
1855
|
if (config["builtin-interaction"] !== void 0)
|
|
1078
1856
|
result.builtinInteraction = config["builtin-interaction"];
|
|
@@ -1082,6 +1860,8 @@ function configToAgentOptions(config) {
|
|
|
1082
1860
|
result.gadgetEndPrefix = config["gadget-end-prefix"];
|
|
1083
1861
|
if (config["gadget-arg-prefix"] !== void 0)
|
|
1084
1862
|
result.gadgetArgPrefix = config["gadget-arg-prefix"];
|
|
1863
|
+
if (config["gadget-approval"] !== void 0)
|
|
1864
|
+
result.gadgetApproval = config["gadget-approval"];
|
|
1085
1865
|
if (config.quiet !== void 0) result.quiet = config.quiet;
|
|
1086
1866
|
if (config["log-llm-requests"] !== void 0) result.logLlmRequests = config["log-llm-requests"];
|
|
1087
1867
|
if (config["log-llm-responses"] !== void 0) result.logLlmResponses = config["log-llm-responses"];
|
|
@@ -1089,23 +1869,18 @@ function configToAgentOptions(config) {
|
|
|
1089
1869
|
}
|
|
1090
1870
|
|
|
1091
1871
|
// src/cli/agent-command.ts
|
|
1092
|
-
|
|
1093
|
-
const rl = createInterface({ input: env.stdin, output: env.stderr });
|
|
1094
|
-
try {
|
|
1095
|
-
const answer = await rl.question(prompt);
|
|
1096
|
-
return answer.trim();
|
|
1097
|
-
} finally {
|
|
1098
|
-
rl.close();
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
function createHumanInputHandler(env, progress) {
|
|
1872
|
+
function createHumanInputHandler(env, progress, keyboard) {
|
|
1102
1873
|
const stdout = env.stdout;
|
|
1103
1874
|
if (!isInteractive(env.stdin) || typeof stdout.isTTY !== "boolean" || !stdout.isTTY) {
|
|
1104
1875
|
return void 0;
|
|
1105
1876
|
}
|
|
1106
1877
|
return async (question) => {
|
|
1107
1878
|
progress.pause();
|
|
1108
|
-
|
|
1879
|
+
if (keyboard.cleanupEsc) {
|
|
1880
|
+
keyboard.cleanupEsc();
|
|
1881
|
+
keyboard.cleanupEsc = null;
|
|
1882
|
+
}
|
|
1883
|
+
const rl = createInterface2({ input: env.stdin, output: env.stdout });
|
|
1109
1884
|
try {
|
|
1110
1885
|
const questionLine = question.trim() ? `
|
|
1111
1886
|
${renderMarkdownWithSeparators(question.trim())}` : "";
|
|
@@ -1123,6 +1898,7 @@ ${statsPrompt}` : statsPrompt;
|
|
|
1123
1898
|
}
|
|
1124
1899
|
} finally {
|
|
1125
1900
|
rl.close();
|
|
1901
|
+
keyboard.restore();
|
|
1126
1902
|
}
|
|
1127
1903
|
};
|
|
1128
1904
|
}
|
|
@@ -1149,6 +1925,77 @@ async function executeAgent(promptArg, options, env) {
|
|
|
1149
1925
|
const printer = new StreamPrinter(env.stdout);
|
|
1150
1926
|
const stderrTTY = env.stderr.isTTY === true;
|
|
1151
1927
|
const progress = new StreamProgress(env.stderr, stderrTTY, client.modelRegistry);
|
|
1928
|
+
const abortController = new AbortController();
|
|
1929
|
+
let wasCancelled = false;
|
|
1930
|
+
let isStreaming = false;
|
|
1931
|
+
const stdinStream = env.stdin;
|
|
1932
|
+
const handleCancel = () => {
|
|
1933
|
+
if (!abortController.signal.aborted) {
|
|
1934
|
+
wasCancelled = true;
|
|
1935
|
+
abortController.abort();
|
|
1936
|
+
progress.pause();
|
|
1937
|
+
env.stderr.write(chalk5.yellow(`
|
|
1938
|
+
[Cancelled] ${progress.formatStats()}
|
|
1939
|
+
`));
|
|
1940
|
+
}
|
|
1941
|
+
};
|
|
1942
|
+
const keyboard = {
|
|
1943
|
+
cleanupEsc: null,
|
|
1944
|
+
cleanupSigint: null,
|
|
1945
|
+
restore: () => {
|
|
1946
|
+
if (stdinIsInteractive && stdinStream.isTTY && !wasCancelled) {
|
|
1947
|
+
keyboard.cleanupEsc = createEscKeyListener(stdinStream, handleCancel);
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
};
|
|
1951
|
+
const handleQuit = () => {
|
|
1952
|
+
keyboard.cleanupEsc?.();
|
|
1953
|
+
keyboard.cleanupSigint?.();
|
|
1954
|
+
progress.complete();
|
|
1955
|
+
printer.ensureNewline();
|
|
1956
|
+
const summary = renderOverallSummary({
|
|
1957
|
+
totalTokens: usage?.totalTokens,
|
|
1958
|
+
iterations,
|
|
1959
|
+
elapsedSeconds: progress.getTotalElapsedSeconds(),
|
|
1960
|
+
cost: progress.getTotalCost()
|
|
1961
|
+
});
|
|
1962
|
+
if (summary) {
|
|
1963
|
+
env.stderr.write(`${chalk5.dim("\u2500".repeat(40))}
|
|
1964
|
+
`);
|
|
1965
|
+
env.stderr.write(`${summary}
|
|
1966
|
+
`);
|
|
1967
|
+
}
|
|
1968
|
+
env.stderr.write(chalk5.dim("[Quit]\n"));
|
|
1969
|
+
process.exit(130);
|
|
1970
|
+
};
|
|
1971
|
+
if (stdinIsInteractive && stdinStream.isTTY) {
|
|
1972
|
+
keyboard.cleanupEsc = createEscKeyListener(stdinStream, handleCancel);
|
|
1973
|
+
}
|
|
1974
|
+
keyboard.cleanupSigint = createSigintListener(
|
|
1975
|
+
handleCancel,
|
|
1976
|
+
handleQuit,
|
|
1977
|
+
() => isStreaming && !abortController.signal.aborted,
|
|
1978
|
+
env.stderr
|
|
1979
|
+
);
|
|
1980
|
+
const DEFAULT_APPROVAL_REQUIRED = ["RunCommand", "WriteFile", "EditFile"];
|
|
1981
|
+
const userApprovals = options.gadgetApproval ?? {};
|
|
1982
|
+
const gadgetApprovals = {
|
|
1983
|
+
...userApprovals
|
|
1984
|
+
};
|
|
1985
|
+
for (const gadget of DEFAULT_APPROVAL_REQUIRED) {
|
|
1986
|
+
const normalizedGadget = gadget.toLowerCase();
|
|
1987
|
+
const isConfigured = Object.keys(userApprovals).some(
|
|
1988
|
+
(key) => key.toLowerCase() === normalizedGadget
|
|
1989
|
+
);
|
|
1990
|
+
if (!isConfigured) {
|
|
1991
|
+
gadgetApprovals[gadget] = "approval-required";
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
const approvalConfig = {
|
|
1995
|
+
gadgetApprovals,
|
|
1996
|
+
defaultMode: "allowed"
|
|
1997
|
+
};
|
|
1998
|
+
const approvalManager = new ApprovalManager(approvalConfig, env, progress);
|
|
1152
1999
|
let usage;
|
|
1153
2000
|
let iterations = 0;
|
|
1154
2001
|
const llmRequestsDir = resolveLogDir(options.logLlmRequests, "requests");
|
|
@@ -1176,6 +2023,7 @@ async function executeAgent(promptArg, options, env) {
|
|
|
1176
2023
|
// onLLMCallStart: Start progress indicator for each LLM call
|
|
1177
2024
|
// This showcases how to react to agent lifecycle events
|
|
1178
2025
|
onLLMCallStart: async (context) => {
|
|
2026
|
+
isStreaming = true;
|
|
1179
2027
|
llmCallCounter++;
|
|
1180
2028
|
const inputTokens = await countMessagesTokens(
|
|
1181
2029
|
context.options.model,
|
|
@@ -1209,6 +2057,7 @@ async function executeAgent(promptArg, options, env) {
|
|
|
1209
2057
|
// onLLMCallComplete: Finalize metrics after each LLM call
|
|
1210
2058
|
// This is where you'd typically log metrics or update dashboards
|
|
1211
2059
|
onLLMCallComplete: async (context) => {
|
|
2060
|
+
isStreaming = false;
|
|
1212
2061
|
usage = context.usage;
|
|
1213
2062
|
iterations = Math.max(iterations, context.iteration + 1);
|
|
1214
2063
|
if (context.usage) {
|
|
@@ -1256,47 +2105,53 @@ async function executeAgent(promptArg, options, env) {
|
|
|
1256
2105
|
}
|
|
1257
2106
|
}
|
|
1258
2107
|
},
|
|
1259
|
-
// SHOWCASE: Controller-based approval gating for
|
|
2108
|
+
// SHOWCASE: Controller-based approval gating for gadgets
|
|
1260
2109
|
//
|
|
1261
2110
|
// This demonstrates how to add safety layers WITHOUT modifying gadgets.
|
|
1262
|
-
// The
|
|
1263
|
-
//
|
|
2111
|
+
// The ApprovalManager handles approval flows externally via beforeGadgetExecution.
|
|
2112
|
+
// Approval modes are configurable via cli.toml:
|
|
2113
|
+
// - "allowed": auto-proceed
|
|
2114
|
+
// - "denied": auto-reject, return message to LLM
|
|
2115
|
+
// - "approval-required": prompt user interactively
|
|
1264
2116
|
//
|
|
1265
|
-
//
|
|
1266
|
-
// any gadget (DeleteFile, SendEmail, etc.) without changing the gadgets.
|
|
2117
|
+
// Default: RunCommand, WriteFile, EditFile require approval unless overridden.
|
|
1267
2118
|
controllers: {
|
|
1268
2119
|
beforeGadgetExecution: async (ctx) => {
|
|
1269
|
-
|
|
2120
|
+
const mode = approvalManager.getApprovalMode(ctx.gadgetName);
|
|
2121
|
+
if (mode === "allowed") {
|
|
1270
2122
|
return { action: "proceed" };
|
|
1271
2123
|
}
|
|
1272
2124
|
const stdinTTY = isInteractive(env.stdin);
|
|
1273
2125
|
const stderrTTY2 = env.stderr.isTTY === true;
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
const command = ctx.parameters.command;
|
|
1281
|
-
progress.pause();
|
|
1282
|
-
env.stderr.write(`
|
|
1283
|
-
\u{1F512} Execute: ${chalk3.cyan(command)}
|
|
1284
|
-
`);
|
|
1285
|
-
const response = await promptApproval(env, " \u23CE approve, or type to reject: ");
|
|
1286
|
-
const isApproved = response === "" || response.toLowerCase() === "y";
|
|
1287
|
-
if (!isApproved) {
|
|
1288
|
-
env.stderr.write(` ${chalk3.red("\u2717 Denied")}
|
|
2126
|
+
const canPrompt = stdinTTY && stderrTTY2;
|
|
2127
|
+
if (!canPrompt) {
|
|
2128
|
+
if (mode === "approval-required") {
|
|
2129
|
+
return {
|
|
2130
|
+
action: "skip",
|
|
2131
|
+
syntheticResult: `status=denied
|
|
1289
2132
|
|
|
1290
|
-
|
|
2133
|
+
${ctx.gadgetName} requires interactive approval. Run in a terminal to approve.`
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
if (mode === "denied") {
|
|
2137
|
+
return {
|
|
2138
|
+
action: "skip",
|
|
2139
|
+
syntheticResult: `status=denied
|
|
2140
|
+
|
|
2141
|
+
${ctx.gadgetName} is denied by configuration.`
|
|
2142
|
+
};
|
|
2143
|
+
}
|
|
2144
|
+
return { action: "proceed" };
|
|
2145
|
+
}
|
|
2146
|
+
const result = await approvalManager.requestApproval(ctx.gadgetName, ctx.parameters);
|
|
2147
|
+
if (!result.approved) {
|
|
1291
2148
|
return {
|
|
1292
2149
|
action: "skip",
|
|
1293
2150
|
syntheticResult: `status=denied
|
|
1294
2151
|
|
|
1295
|
-
|
|
2152
|
+
Denied: ${result.reason ?? "by user"}`
|
|
1296
2153
|
};
|
|
1297
2154
|
}
|
|
1298
|
-
env.stderr.write(` ${chalk3.green("\u2713 Approved")}
|
|
1299
|
-
`);
|
|
1300
2155
|
return { action: "proceed" };
|
|
1301
2156
|
}
|
|
1302
2157
|
}
|
|
@@ -1310,10 +2165,11 @@ Command rejected by user with message: "${response}"`
|
|
|
1310
2165
|
if (options.temperature !== void 0) {
|
|
1311
2166
|
builder.withTemperature(options.temperature);
|
|
1312
2167
|
}
|
|
1313
|
-
const humanInputHandler = createHumanInputHandler(env, progress);
|
|
2168
|
+
const humanInputHandler = createHumanInputHandler(env, progress, keyboard);
|
|
1314
2169
|
if (humanInputHandler) {
|
|
1315
2170
|
builder.onHumanInput(humanInputHandler);
|
|
1316
2171
|
}
|
|
2172
|
+
builder.withSignal(abortController.signal);
|
|
1317
2173
|
const gadgets = registry.getAll();
|
|
1318
2174
|
if (gadgets.length > 0) {
|
|
1319
2175
|
builder.withGadgets(...gadgets);
|
|
@@ -1351,31 +2207,41 @@ Command rejected by user with message: "${response}"`
|
|
|
1351
2207
|
textBuffer = "";
|
|
1352
2208
|
}
|
|
1353
2209
|
};
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
if (
|
|
1363
|
-
|
|
1364
|
-
|
|
2210
|
+
try {
|
|
2211
|
+
for await (const event of agent.run()) {
|
|
2212
|
+
if (event.type === "text") {
|
|
2213
|
+
progress.pause();
|
|
2214
|
+
textBuffer += event.content;
|
|
2215
|
+
} else if (event.type === "gadget_result") {
|
|
2216
|
+
flushTextBuffer();
|
|
2217
|
+
progress.pause();
|
|
2218
|
+
if (options.quiet) {
|
|
2219
|
+
if (event.result.gadgetName === "TellUser" && event.result.parameters?.message) {
|
|
2220
|
+
const message = String(event.result.parameters.message);
|
|
2221
|
+
env.stdout.write(`${message}
|
|
1365
2222
|
`);
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
2223
|
+
}
|
|
2224
|
+
} else {
|
|
2225
|
+
const tokenCount = await countGadgetOutputTokens(event.result.result);
|
|
2226
|
+
env.stderr.write(`${formatGadgetSummary({ ...event.result, tokenCount })}
|
|
1370
2227
|
`);
|
|
2228
|
+
}
|
|
1371
2229
|
}
|
|
1372
2230
|
}
|
|
2231
|
+
} catch (error) {
|
|
2232
|
+
if (!isAbortError(error)) {
|
|
2233
|
+
throw error;
|
|
2234
|
+
}
|
|
2235
|
+
} finally {
|
|
2236
|
+
isStreaming = false;
|
|
2237
|
+
keyboard.cleanupEsc?.();
|
|
2238
|
+
keyboard.cleanupSigint?.();
|
|
1373
2239
|
}
|
|
1374
2240
|
flushTextBuffer();
|
|
1375
2241
|
progress.complete();
|
|
1376
2242
|
printer.ensureNewline();
|
|
1377
2243
|
if (!options.quiet && iterations > 1) {
|
|
1378
|
-
env.stderr.write(`${
|
|
2244
|
+
env.stderr.write(`${chalk5.dim("\u2500".repeat(40))}
|
|
1379
2245
|
`);
|
|
1380
2246
|
const summary = renderOverallSummary({
|
|
1381
2247
|
totalTokens: usage?.totalTokens,
|
|
@@ -1393,7 +2259,13 @@ function registerAgentCommand(program, env, config) {
|
|
|
1393
2259
|
const cmd = program.command(COMMANDS.agent).description("Run the llmist agent loop with optional gadgets.").argument("[prompt]", "Prompt for the agent loop. Falls back to stdin when available.");
|
|
1394
2260
|
addAgentOptions(cmd, config);
|
|
1395
2261
|
cmd.action(
|
|
1396
|
-
(prompt, options) => executeAction(() =>
|
|
2262
|
+
(prompt, options) => executeAction(() => {
|
|
2263
|
+
const mergedOptions = {
|
|
2264
|
+
...options,
|
|
2265
|
+
gadgetApproval: config?.["gadget-approval"]
|
|
2266
|
+
};
|
|
2267
|
+
return executeAgent(prompt, mergedOptions, env);
|
|
2268
|
+
}, env)
|
|
1397
2269
|
);
|
|
1398
2270
|
}
|
|
1399
2271
|
|
|
@@ -1477,7 +2349,7 @@ function registerCompleteCommand(program, env, config) {
|
|
|
1477
2349
|
}
|
|
1478
2350
|
|
|
1479
2351
|
// src/cli/config.ts
|
|
1480
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2352
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
1481
2353
|
import { homedir as homedir2 } from "node:os";
|
|
1482
2354
|
import { join as join2 } from "node:path";
|
|
1483
2355
|
import { load as parseToml } from "js-toml";
|
|
@@ -1564,6 +2436,7 @@ function hasTemplateSyntax(str) {
|
|
|
1564
2436
|
}
|
|
1565
2437
|
|
|
1566
2438
|
// src/cli/config.ts
|
|
2439
|
+
var VALID_APPROVAL_MODES = ["allowed", "denied", "approval-required"];
|
|
1567
2440
|
var GLOBAL_CONFIG_KEYS = /* @__PURE__ */ new Set(["log-level", "log-file", "log-reset"]);
|
|
1568
2441
|
var VALID_LOG_LEVELS = ["silly", "trace", "debug", "info", "warn", "error", "fatal"];
|
|
1569
2442
|
var COMPLETE_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -1586,12 +2459,20 @@ var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
1586
2459
|
"system",
|
|
1587
2460
|
"temperature",
|
|
1588
2461
|
"max-iterations",
|
|
2462
|
+
"gadgets",
|
|
2463
|
+
// Full replacement (preferred)
|
|
2464
|
+
"gadget-add",
|
|
2465
|
+
// Add to inherited gadgets
|
|
2466
|
+
"gadget-remove",
|
|
2467
|
+
// Remove from inherited gadgets
|
|
1589
2468
|
"gadget",
|
|
2469
|
+
// DEPRECATED: alias for gadgets
|
|
1590
2470
|
"builtins",
|
|
1591
2471
|
"builtin-interaction",
|
|
1592
2472
|
"gadget-start-prefix",
|
|
1593
2473
|
"gadget-end-prefix",
|
|
1594
2474
|
"gadget-arg-prefix",
|
|
2475
|
+
"gadget-approval",
|
|
1595
2476
|
"quiet",
|
|
1596
2477
|
"inherits",
|
|
1597
2478
|
"log-level",
|
|
@@ -1612,9 +2493,9 @@ function getConfigPath() {
|
|
|
1612
2493
|
return join2(homedir2(), ".llmist", "cli.toml");
|
|
1613
2494
|
}
|
|
1614
2495
|
var ConfigError = class extends Error {
|
|
1615
|
-
constructor(message,
|
|
1616
|
-
super(
|
|
1617
|
-
this.path =
|
|
2496
|
+
constructor(message, path5) {
|
|
2497
|
+
super(path5 ? `${path5}: ${message}` : message);
|
|
2498
|
+
this.path = path5;
|
|
1618
2499
|
this.name = "ConfigError";
|
|
1619
2500
|
}
|
|
1620
2501
|
};
|
|
@@ -1670,6 +2551,28 @@ function validateInherits(value, section) {
|
|
|
1670
2551
|
}
|
|
1671
2552
|
throw new ConfigError(`[${section}].inherits must be a string or array of strings`);
|
|
1672
2553
|
}
|
|
2554
|
+
function validateGadgetApproval(value, section) {
|
|
2555
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2556
|
+
throw new ConfigError(
|
|
2557
|
+
`[${section}].gadget-approval must be a table (e.g., { WriteFile = "approval-required" })`
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
const result = {};
|
|
2561
|
+
for (const [gadgetName, mode] of Object.entries(value)) {
|
|
2562
|
+
if (typeof mode !== "string") {
|
|
2563
|
+
throw new ConfigError(
|
|
2564
|
+
`[${section}].gadget-approval.${gadgetName} must be a string`
|
|
2565
|
+
);
|
|
2566
|
+
}
|
|
2567
|
+
if (!VALID_APPROVAL_MODES.includes(mode)) {
|
|
2568
|
+
throw new ConfigError(
|
|
2569
|
+
`[${section}].gadget-approval.${gadgetName} must be one of: ${VALID_APPROVAL_MODES.join(", ")}`
|
|
2570
|
+
);
|
|
2571
|
+
}
|
|
2572
|
+
result[gadgetName] = mode;
|
|
2573
|
+
}
|
|
2574
|
+
return result;
|
|
2575
|
+
}
|
|
1673
2576
|
function validateLoggingConfig(raw, section) {
|
|
1674
2577
|
const result = {};
|
|
1675
2578
|
if ("log-level" in raw) {
|
|
@@ -1779,6 +2682,15 @@ function validateAgentConfig(raw, section) {
|
|
|
1779
2682
|
min: 1
|
|
1780
2683
|
});
|
|
1781
2684
|
}
|
|
2685
|
+
if ("gadgets" in rawObj) {
|
|
2686
|
+
result.gadgets = validateStringArray(rawObj.gadgets, "gadgets", section);
|
|
2687
|
+
}
|
|
2688
|
+
if ("gadget-add" in rawObj) {
|
|
2689
|
+
result["gadget-add"] = validateStringArray(rawObj["gadget-add"], "gadget-add", section);
|
|
2690
|
+
}
|
|
2691
|
+
if ("gadget-remove" in rawObj) {
|
|
2692
|
+
result["gadget-remove"] = validateStringArray(rawObj["gadget-remove"], "gadget-remove", section);
|
|
2693
|
+
}
|
|
1782
2694
|
if ("gadget" in rawObj) {
|
|
1783
2695
|
result.gadget = validateStringArray(rawObj.gadget, "gadget", section);
|
|
1784
2696
|
}
|
|
@@ -1813,6 +2725,9 @@ function validateAgentConfig(raw, section) {
|
|
|
1813
2725
|
section
|
|
1814
2726
|
);
|
|
1815
2727
|
}
|
|
2728
|
+
if ("gadget-approval" in rawObj) {
|
|
2729
|
+
result["gadget-approval"] = validateGadgetApproval(rawObj["gadget-approval"], section);
|
|
2730
|
+
}
|
|
1816
2731
|
if ("quiet" in rawObj) {
|
|
1817
2732
|
result.quiet = validateBoolean(rawObj.quiet, "quiet", section);
|
|
1818
2733
|
}
|
|
@@ -1869,6 +2784,15 @@ function validateCustomConfig(raw, section) {
|
|
|
1869
2784
|
min: 1
|
|
1870
2785
|
});
|
|
1871
2786
|
}
|
|
2787
|
+
if ("gadgets" in rawObj) {
|
|
2788
|
+
result.gadgets = validateStringArray(rawObj.gadgets, "gadgets", section);
|
|
2789
|
+
}
|
|
2790
|
+
if ("gadget-add" in rawObj) {
|
|
2791
|
+
result["gadget-add"] = validateStringArray(rawObj["gadget-add"], "gadget-add", section);
|
|
2792
|
+
}
|
|
2793
|
+
if ("gadget-remove" in rawObj) {
|
|
2794
|
+
result["gadget-remove"] = validateStringArray(rawObj["gadget-remove"], "gadget-remove", section);
|
|
2795
|
+
}
|
|
1872
2796
|
if ("gadget" in rawObj) {
|
|
1873
2797
|
result.gadget = validateStringArray(rawObj.gadget, "gadget", section);
|
|
1874
2798
|
}
|
|
@@ -1903,6 +2827,9 @@ function validateCustomConfig(raw, section) {
|
|
|
1903
2827
|
section
|
|
1904
2828
|
);
|
|
1905
2829
|
}
|
|
2830
|
+
if ("gadget-approval" in rawObj) {
|
|
2831
|
+
result["gadget-approval"] = validateGadgetApproval(rawObj["gadget-approval"], section);
|
|
2832
|
+
}
|
|
1906
2833
|
if ("max-tokens" in rawObj) {
|
|
1907
2834
|
result["max-tokens"] = validateNumber(rawObj["max-tokens"], "max-tokens", section, {
|
|
1908
2835
|
integer: true,
|
|
@@ -1958,12 +2885,12 @@ function validateConfig(raw, configPath) {
|
|
|
1958
2885
|
}
|
|
1959
2886
|
function loadConfig() {
|
|
1960
2887
|
const configPath = getConfigPath();
|
|
1961
|
-
if (!
|
|
2888
|
+
if (!existsSync2(configPath)) {
|
|
1962
2889
|
return {};
|
|
1963
2890
|
}
|
|
1964
2891
|
let content;
|
|
1965
2892
|
try {
|
|
1966
|
-
content =
|
|
2893
|
+
content = readFileSync2(configPath, "utf-8");
|
|
1967
2894
|
} catch (error) {
|
|
1968
2895
|
throw new ConfigError(
|
|
1969
2896
|
`Failed to read config file: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
@@ -2058,6 +2985,39 @@ function resolveTemplatesInConfig(config, configPath) {
|
|
|
2058
2985
|
}
|
|
2059
2986
|
return result;
|
|
2060
2987
|
}
|
|
2988
|
+
function resolveGadgets(section, inheritedGadgets, sectionName, configPath) {
|
|
2989
|
+
const hasGadgets = "gadgets" in section;
|
|
2990
|
+
const hasGadgetLegacy = "gadget" in section;
|
|
2991
|
+
const hasGadgetAdd = "gadget-add" in section;
|
|
2992
|
+
const hasGadgetRemove = "gadget-remove" in section;
|
|
2993
|
+
if (hasGadgetLegacy && !hasGadgets) {
|
|
2994
|
+
console.warn(
|
|
2995
|
+
`[config] Warning: [${sectionName}].gadget is deprecated, use 'gadgets' (plural) instead`
|
|
2996
|
+
);
|
|
2997
|
+
}
|
|
2998
|
+
if ((hasGadgets || hasGadgetLegacy) && (hasGadgetAdd || hasGadgetRemove)) {
|
|
2999
|
+
throw new ConfigError(
|
|
3000
|
+
`[${sectionName}] Cannot use 'gadgets' with 'gadget-add'/'gadget-remove'. Use either full replacement (gadgets) OR modification (gadget-add/gadget-remove).`,
|
|
3001
|
+
configPath
|
|
3002
|
+
);
|
|
3003
|
+
}
|
|
3004
|
+
if (hasGadgets) {
|
|
3005
|
+
return section.gadgets;
|
|
3006
|
+
}
|
|
3007
|
+
if (hasGadgetLegacy) {
|
|
3008
|
+
return section.gadget;
|
|
3009
|
+
}
|
|
3010
|
+
let result = [...inheritedGadgets];
|
|
3011
|
+
if (hasGadgetRemove) {
|
|
3012
|
+
const toRemove = new Set(section["gadget-remove"]);
|
|
3013
|
+
result = result.filter((g) => !toRemove.has(g));
|
|
3014
|
+
}
|
|
3015
|
+
if (hasGadgetAdd) {
|
|
3016
|
+
const toAdd = section["gadget-add"];
|
|
3017
|
+
result.push(...toAdd);
|
|
3018
|
+
}
|
|
3019
|
+
return result;
|
|
3020
|
+
}
|
|
2061
3021
|
function resolveInheritance(config, configPath) {
|
|
2062
3022
|
const resolved = {};
|
|
2063
3023
|
const resolving = /* @__PURE__ */ new Set();
|
|
@@ -2081,8 +3041,23 @@ function resolveInheritance(config, configPath) {
|
|
|
2081
3041
|
const parentResolved = resolveSection(parent);
|
|
2082
3042
|
merged = { ...merged, ...parentResolved };
|
|
2083
3043
|
}
|
|
2084
|
-
const
|
|
3044
|
+
const inheritedGadgets = merged.gadgets ?? [];
|
|
3045
|
+
const {
|
|
3046
|
+
inherits: _inherits,
|
|
3047
|
+
gadgets: _gadgets,
|
|
3048
|
+
gadget: _gadget,
|
|
3049
|
+
"gadget-add": _gadgetAdd,
|
|
3050
|
+
"gadget-remove": _gadgetRemove,
|
|
3051
|
+
...ownValues
|
|
3052
|
+
} = sectionObj;
|
|
2085
3053
|
merged = { ...merged, ...ownValues };
|
|
3054
|
+
const resolvedGadgets = resolveGadgets(sectionObj, inheritedGadgets, name, configPath);
|
|
3055
|
+
if (resolvedGadgets.length > 0) {
|
|
3056
|
+
merged.gadgets = resolvedGadgets;
|
|
3057
|
+
}
|
|
3058
|
+
delete merged["gadget"];
|
|
3059
|
+
delete merged["gadget-add"];
|
|
3060
|
+
delete merged["gadget-remove"];
|
|
2086
3061
|
resolving.delete(name);
|
|
2087
3062
|
resolved[name] = merged;
|
|
2088
3063
|
return merged;
|
|
@@ -2096,12 +3071,12 @@ function resolveInheritance(config, configPath) {
|
|
|
2096
3071
|
// src/cli/gadget-command.ts
|
|
2097
3072
|
init_schema_to_json();
|
|
2098
3073
|
init_schema_validator();
|
|
2099
|
-
import
|
|
3074
|
+
import chalk7 from "chalk";
|
|
2100
3075
|
|
|
2101
3076
|
// src/cli/gadget-prompts.ts
|
|
2102
3077
|
init_schema_to_json();
|
|
2103
|
-
import { createInterface as
|
|
2104
|
-
import
|
|
3078
|
+
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
3079
|
+
import chalk6 from "chalk";
|
|
2105
3080
|
async function promptForParameters(schema, ctx) {
|
|
2106
3081
|
if (!schema) {
|
|
2107
3082
|
return {};
|
|
@@ -2110,7 +3085,7 @@ async function promptForParameters(schema, ctx) {
|
|
|
2110
3085
|
if (!jsonSchema.properties || Object.keys(jsonSchema.properties).length === 0) {
|
|
2111
3086
|
return {};
|
|
2112
3087
|
}
|
|
2113
|
-
const rl =
|
|
3088
|
+
const rl = createInterface3({ input: ctx.stdin, output: ctx.stdout });
|
|
2114
3089
|
const params = {};
|
|
2115
3090
|
try {
|
|
2116
3091
|
for (const [key, prop] of Object.entries(jsonSchema.properties)) {
|
|
@@ -2133,16 +3108,16 @@ ${issues}`);
|
|
|
2133
3108
|
async function promptForField(rl, key, prop, required) {
|
|
2134
3109
|
const isRequired = required.includes(key);
|
|
2135
3110
|
const typeHint = formatTypeHint(prop);
|
|
2136
|
-
const defaultHint = prop.default !== void 0 ?
|
|
2137
|
-
const requiredMarker = isRequired ?
|
|
3111
|
+
const defaultHint = prop.default !== void 0 ? chalk6.dim(` [default: ${JSON.stringify(prop.default)}]`) : "";
|
|
3112
|
+
const requiredMarker = isRequired ? chalk6.red("*") : "";
|
|
2138
3113
|
let prompt = `
|
|
2139
|
-
${
|
|
3114
|
+
${chalk6.cyan.bold(key)}${requiredMarker}`;
|
|
2140
3115
|
if (prop.description) {
|
|
2141
|
-
prompt +=
|
|
3116
|
+
prompt += chalk6.dim(` - ${prop.description}`);
|
|
2142
3117
|
}
|
|
2143
3118
|
prompt += `
|
|
2144
3119
|
${typeHint}${defaultHint}
|
|
2145
|
-
${
|
|
3120
|
+
${chalk6.green(">")} `;
|
|
2146
3121
|
const answer = await rl.question(prompt);
|
|
2147
3122
|
const trimmed = answer.trim();
|
|
2148
3123
|
if (!trimmed) {
|
|
@@ -2158,20 +3133,20 @@ ${chalk4.cyan.bold(key)}${requiredMarker}`;
|
|
|
2158
3133
|
}
|
|
2159
3134
|
function formatTypeHint(prop) {
|
|
2160
3135
|
if (prop.enum) {
|
|
2161
|
-
return
|
|
3136
|
+
return chalk6.yellow(`(${prop.enum.join(" | ")})`);
|
|
2162
3137
|
}
|
|
2163
3138
|
if (prop.type === "array") {
|
|
2164
3139
|
const items = prop.items;
|
|
2165
3140
|
if (items?.enum) {
|
|
2166
|
-
return
|
|
3141
|
+
return chalk6.yellow(`(${items.enum.join(" | ")})[] comma-separated`);
|
|
2167
3142
|
}
|
|
2168
3143
|
const itemType = items?.type ?? "any";
|
|
2169
|
-
return
|
|
3144
|
+
return chalk6.yellow(`(${itemType}[]) comma-separated`);
|
|
2170
3145
|
}
|
|
2171
3146
|
if (prop.type === "object" && prop.properties) {
|
|
2172
|
-
return
|
|
3147
|
+
return chalk6.yellow("(object) enter as JSON");
|
|
2173
3148
|
}
|
|
2174
|
-
return
|
|
3149
|
+
return chalk6.yellow(`(${prop.type ?? "any"})`);
|
|
2175
3150
|
}
|
|
2176
3151
|
function parseValue(input, prop, key) {
|
|
2177
3152
|
const type = prop.type;
|
|
@@ -2282,7 +3257,7 @@ Available gadgets:
|
|
|
2282
3257
|
async function executeGadgetRun(file, options, env) {
|
|
2283
3258
|
const cwd = process.cwd();
|
|
2284
3259
|
const { gadget, name } = await selectGadget(file, options.name, cwd);
|
|
2285
|
-
env.stderr.write(
|
|
3260
|
+
env.stderr.write(chalk7.cyan.bold(`
|
|
2286
3261
|
\u{1F527} Running gadget: ${name}
|
|
2287
3262
|
`));
|
|
2288
3263
|
let params;
|
|
@@ -2293,7 +3268,7 @@ async function executeGadgetRun(file, options, env) {
|
|
|
2293
3268
|
// Prompts go to stderr to keep stdout clean
|
|
2294
3269
|
});
|
|
2295
3270
|
} else {
|
|
2296
|
-
env.stderr.write(
|
|
3271
|
+
env.stderr.write(chalk7.dim("Reading parameters from stdin...\n"));
|
|
2297
3272
|
const stdinParams = await readStdinJson(env.stdin);
|
|
2298
3273
|
if (gadget.parameterSchema) {
|
|
2299
3274
|
const result2 = gadget.parameterSchema.safeParse(stdinParams);
|
|
@@ -2307,7 +3282,7 @@ ${issues}`);
|
|
|
2307
3282
|
params = stdinParams;
|
|
2308
3283
|
}
|
|
2309
3284
|
}
|
|
2310
|
-
env.stderr.write(
|
|
3285
|
+
env.stderr.write(chalk7.dim("\nExecuting...\n"));
|
|
2311
3286
|
const startTime = Date.now();
|
|
2312
3287
|
let result;
|
|
2313
3288
|
try {
|
|
@@ -2329,7 +3304,7 @@ ${issues}`);
|
|
|
2329
3304
|
throw new Error(`Execution failed: ${message}`);
|
|
2330
3305
|
}
|
|
2331
3306
|
const elapsed = Date.now() - startTime;
|
|
2332
|
-
env.stderr.write(
|
|
3307
|
+
env.stderr.write(chalk7.green(`
|
|
2333
3308
|
\u2713 Completed in ${elapsed}ms
|
|
2334
3309
|
|
|
2335
3310
|
`));
|
|
@@ -2365,37 +3340,37 @@ async function executeGadgetInfo(file, options, env) {
|
|
|
2365
3340
|
return;
|
|
2366
3341
|
}
|
|
2367
3342
|
env.stdout.write("\n");
|
|
2368
|
-
env.stdout.write(
|
|
3343
|
+
env.stdout.write(chalk7.cyan.bold(`${name}
|
|
2369
3344
|
`));
|
|
2370
|
-
env.stdout.write(
|
|
2371
|
-
env.stdout.write(
|
|
3345
|
+
env.stdout.write(chalk7.cyan("\u2550".repeat(name.length)) + "\n\n");
|
|
3346
|
+
env.stdout.write(chalk7.bold("Description:\n"));
|
|
2372
3347
|
env.stdout.write(` ${gadget.description}
|
|
2373
3348
|
|
|
2374
3349
|
`);
|
|
2375
3350
|
if (gadget.parameterSchema) {
|
|
2376
|
-
env.stdout.write(
|
|
3351
|
+
env.stdout.write(chalk7.bold("Parameters:\n"));
|
|
2377
3352
|
const jsonSchema = schemaToJSONSchema(gadget.parameterSchema, { target: "draft-7" });
|
|
2378
3353
|
env.stdout.write(formatSchemaAsText(jsonSchema, " ") + "\n\n");
|
|
2379
3354
|
} else {
|
|
2380
|
-
env.stdout.write(
|
|
3355
|
+
env.stdout.write(chalk7.dim("No parameters required.\n\n"));
|
|
2381
3356
|
}
|
|
2382
3357
|
if (gadget.timeoutMs) {
|
|
2383
|
-
env.stdout.write(
|
|
3358
|
+
env.stdout.write(chalk7.bold("Timeout:\n"));
|
|
2384
3359
|
env.stdout.write(` ${gadget.timeoutMs}ms
|
|
2385
3360
|
|
|
2386
3361
|
`);
|
|
2387
3362
|
}
|
|
2388
3363
|
if (gadget.examples && gadget.examples.length > 0) {
|
|
2389
|
-
env.stdout.write(
|
|
3364
|
+
env.stdout.write(chalk7.bold("Examples:\n"));
|
|
2390
3365
|
for (const example of gadget.examples) {
|
|
2391
3366
|
if (example.comment) {
|
|
2392
|
-
env.stdout.write(
|
|
3367
|
+
env.stdout.write(chalk7.dim(` # ${example.comment}
|
|
2393
3368
|
`));
|
|
2394
3369
|
}
|
|
2395
|
-
env.stdout.write(` Input: ${
|
|
3370
|
+
env.stdout.write(` Input: ${chalk7.cyan(JSON.stringify(example.params))}
|
|
2396
3371
|
`);
|
|
2397
3372
|
if (example.output !== void 0) {
|
|
2398
|
-
env.stdout.write(` Output: ${
|
|
3373
|
+
env.stdout.write(` Output: ${chalk7.green(example.output)}
|
|
2399
3374
|
`);
|
|
2400
3375
|
}
|
|
2401
3376
|
env.stdout.write("\n");
|
|
@@ -2428,27 +3403,27 @@ function formatSchemaAsText(schema, indent = "") {
|
|
|
2428
3403
|
const isRequired = required.includes(key);
|
|
2429
3404
|
const enumValues = prop.enum;
|
|
2430
3405
|
const defaultValue = prop.default;
|
|
2431
|
-
let line = `${indent}${
|
|
3406
|
+
let line = `${indent}${chalk7.cyan(key)}`;
|
|
2432
3407
|
if (isRequired) {
|
|
2433
|
-
line +=
|
|
3408
|
+
line += chalk7.red("*");
|
|
2434
3409
|
}
|
|
2435
3410
|
if (type === "array") {
|
|
2436
3411
|
const items = prop.items;
|
|
2437
3412
|
const itemType = items?.type || "any";
|
|
2438
|
-
line +=
|
|
3413
|
+
line += chalk7.dim(` (${itemType}[])`);
|
|
2439
3414
|
} else if (type === "object" && prop.properties) {
|
|
2440
|
-
line +=
|
|
3415
|
+
line += chalk7.dim(" (object)");
|
|
2441
3416
|
} else {
|
|
2442
|
-
line +=
|
|
3417
|
+
line += chalk7.dim(` (${type})`);
|
|
2443
3418
|
}
|
|
2444
3419
|
if (defaultValue !== void 0) {
|
|
2445
|
-
line +=
|
|
3420
|
+
line += chalk7.dim(` [default: ${JSON.stringify(defaultValue)}]`);
|
|
2446
3421
|
}
|
|
2447
3422
|
if (description) {
|
|
2448
3423
|
line += `: ${description}`;
|
|
2449
3424
|
}
|
|
2450
3425
|
if (enumValues) {
|
|
2451
|
-
line +=
|
|
3426
|
+
line += chalk7.yellow(` - one of: ${enumValues.join(", ")}`);
|
|
2452
3427
|
}
|
|
2453
3428
|
lines.push(line);
|
|
2454
3429
|
if (type === "object" && prop.properties) {
|
|
@@ -2488,20 +3463,20 @@ async function executeGadgetValidate(file, env) {
|
|
|
2488
3463
|
throw new Error(`Validation issues:
|
|
2489
3464
|
${issues.map((i) => ` - ${i}`).join("\n")}`);
|
|
2490
3465
|
}
|
|
2491
|
-
env.stdout.write(
|
|
2492
|
-
env.stdout.write(
|
|
3466
|
+
env.stdout.write(chalk7.green.bold("\n\u2713 Valid\n\n"));
|
|
3467
|
+
env.stdout.write(chalk7.bold("Gadgets found:\n"));
|
|
2493
3468
|
for (const gadget of gadgets) {
|
|
2494
3469
|
const name = gadget.name ?? gadget.constructor.name;
|
|
2495
|
-
const schemaInfo = gadget.parameterSchema ?
|
|
2496
|
-
env.stdout.write(` ${
|
|
3470
|
+
const schemaInfo = gadget.parameterSchema ? chalk7.cyan("(with schema)") : chalk7.dim("(no schema)");
|
|
3471
|
+
env.stdout.write(` ${chalk7.bold(name)} ${schemaInfo}
|
|
2497
3472
|
`);
|
|
2498
|
-
env.stdout.write(
|
|
3473
|
+
env.stdout.write(chalk7.dim(` ${gadget.description}
|
|
2499
3474
|
`));
|
|
2500
3475
|
}
|
|
2501
3476
|
env.stdout.write("\n");
|
|
2502
3477
|
} catch (error) {
|
|
2503
3478
|
const message = error instanceof Error ? error.message : String(error);
|
|
2504
|
-
env.stdout.write(
|
|
3479
|
+
env.stdout.write(chalk7.red.bold(`
|
|
2505
3480
|
\u2717 Invalid
|
|
2506
3481
|
|
|
2507
3482
|
`));
|
|
@@ -2525,7 +3500,7 @@ function registerGadgetCommand(program, env) {
|
|
|
2525
3500
|
}
|
|
2526
3501
|
|
|
2527
3502
|
// src/cli/models-command.ts
|
|
2528
|
-
import
|
|
3503
|
+
import chalk8 from "chalk";
|
|
2529
3504
|
init_model_shortcuts();
|
|
2530
3505
|
async function handleModelsCommand(options, env) {
|
|
2531
3506
|
const client = env.createClient();
|
|
@@ -2545,13 +3520,13 @@ function renderTable(models, verbose, stream) {
|
|
|
2545
3520
|
}
|
|
2546
3521
|
grouped.get(provider).push(model);
|
|
2547
3522
|
}
|
|
2548
|
-
stream.write(
|
|
2549
|
-
stream.write(
|
|
3523
|
+
stream.write(chalk8.bold.cyan("\nAvailable Models\n"));
|
|
3524
|
+
stream.write(chalk8.cyan("=".repeat(80)) + "\n\n");
|
|
2550
3525
|
const providers = Array.from(grouped.keys()).sort();
|
|
2551
3526
|
for (const provider of providers) {
|
|
2552
3527
|
const providerModels = grouped.get(provider);
|
|
2553
3528
|
const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
2554
|
-
stream.write(
|
|
3529
|
+
stream.write(chalk8.bold.yellow(`${providerName} Models
|
|
2555
3530
|
`));
|
|
2556
3531
|
if (verbose) {
|
|
2557
3532
|
renderVerboseTable(providerModels, stream);
|
|
@@ -2560,11 +3535,11 @@ function renderTable(models, verbose, stream) {
|
|
|
2560
3535
|
}
|
|
2561
3536
|
stream.write("\n");
|
|
2562
3537
|
}
|
|
2563
|
-
stream.write(
|
|
2564
|
-
stream.write(
|
|
3538
|
+
stream.write(chalk8.bold.magenta("Model Shortcuts\n"));
|
|
3539
|
+
stream.write(chalk8.dim("\u2500".repeat(80)) + "\n");
|
|
2565
3540
|
const shortcuts = Object.entries(MODEL_ALIASES).sort((a, b) => a[0].localeCompare(b[0]));
|
|
2566
3541
|
for (const [shortcut, fullName] of shortcuts) {
|
|
2567
|
-
stream.write(
|
|
3542
|
+
stream.write(chalk8.cyan(` ${shortcut.padEnd(15)}`) + chalk8.dim(" \u2192 ") + chalk8.white(fullName) + "\n");
|
|
2568
3543
|
}
|
|
2569
3544
|
stream.write("\n");
|
|
2570
3545
|
}
|
|
@@ -2574,45 +3549,45 @@ function renderCompactTable(models, stream) {
|
|
|
2574
3549
|
const contextWidth = 13;
|
|
2575
3550
|
const inputWidth = 10;
|
|
2576
3551
|
const outputWidth = 10;
|
|
2577
|
-
stream.write(
|
|
3552
|
+
stream.write(chalk8.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
|
|
2578
3553
|
stream.write(
|
|
2579
|
-
|
|
3554
|
+
chalk8.bold(
|
|
2580
3555
|
"Model ID".padEnd(idWidth) + " " + "Display Name".padEnd(nameWidth) + " " + "Context".padEnd(contextWidth) + " " + "Input".padEnd(inputWidth) + " " + "Output".padEnd(outputWidth)
|
|
2581
3556
|
) + "\n"
|
|
2582
3557
|
);
|
|
2583
|
-
stream.write(
|
|
3558
|
+
stream.write(chalk8.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
|
|
2584
3559
|
for (const model of models) {
|
|
2585
3560
|
const contextFormatted = formatTokens2(model.contextWindow);
|
|
2586
3561
|
const inputPrice = `$${model.pricing.input.toFixed(2)}`;
|
|
2587
3562
|
const outputPrice = `$${model.pricing.output.toFixed(2)}`;
|
|
2588
3563
|
stream.write(
|
|
2589
|
-
|
|
3564
|
+
chalk8.green(model.modelId.padEnd(idWidth)) + " " + chalk8.white(model.displayName.padEnd(nameWidth)) + " " + chalk8.yellow(contextFormatted.padEnd(contextWidth)) + " " + chalk8.cyan(inputPrice.padEnd(inputWidth)) + " " + chalk8.cyan(outputPrice.padEnd(outputWidth)) + "\n"
|
|
2590
3565
|
);
|
|
2591
3566
|
}
|
|
2592
|
-
stream.write(
|
|
2593
|
-
stream.write(
|
|
3567
|
+
stream.write(chalk8.dim("\u2500".repeat(idWidth + nameWidth + contextWidth + inputWidth + outputWidth + 8)) + "\n");
|
|
3568
|
+
stream.write(chalk8.dim(` * Prices are per 1M tokens
|
|
2594
3569
|
`));
|
|
2595
3570
|
}
|
|
2596
3571
|
function renderVerboseTable(models, stream) {
|
|
2597
3572
|
for (const model of models) {
|
|
2598
|
-
stream.write(
|
|
3573
|
+
stream.write(chalk8.bold.green(`
|
|
2599
3574
|
${model.modelId}
|
|
2600
3575
|
`));
|
|
2601
|
-
stream.write(
|
|
2602
|
-
stream.write(` ${
|
|
3576
|
+
stream.write(chalk8.dim(" " + "\u2500".repeat(60)) + "\n");
|
|
3577
|
+
stream.write(` ${chalk8.dim("Name:")} ${chalk8.white(model.displayName)}
|
|
2603
3578
|
`);
|
|
2604
|
-
stream.write(` ${
|
|
3579
|
+
stream.write(` ${chalk8.dim("Context:")} ${chalk8.yellow(formatTokens2(model.contextWindow))}
|
|
2605
3580
|
`);
|
|
2606
|
-
stream.write(` ${
|
|
3581
|
+
stream.write(` ${chalk8.dim("Max Output:")} ${chalk8.yellow(formatTokens2(model.maxOutputTokens))}
|
|
2607
3582
|
`);
|
|
2608
|
-
stream.write(` ${
|
|
3583
|
+
stream.write(` ${chalk8.dim("Pricing:")} ${chalk8.cyan(`$${model.pricing.input.toFixed(2)} input`)} ${chalk8.dim("/")} ${chalk8.cyan(`$${model.pricing.output.toFixed(2)} output`)} ${chalk8.dim("(per 1M tokens)")}
|
|
2609
3584
|
`);
|
|
2610
3585
|
if (model.pricing.cachedInput !== void 0) {
|
|
2611
|
-
stream.write(` ${
|
|
3586
|
+
stream.write(` ${chalk8.dim("Cached Input:")} ${chalk8.cyan(`$${model.pricing.cachedInput.toFixed(2)} per 1M tokens`)}
|
|
2612
3587
|
`);
|
|
2613
3588
|
}
|
|
2614
3589
|
if (model.knowledgeCutoff) {
|
|
2615
|
-
stream.write(` ${
|
|
3590
|
+
stream.write(` ${chalk8.dim("Knowledge:")} ${model.knowledgeCutoff}
|
|
2616
3591
|
`);
|
|
2617
3592
|
}
|
|
2618
3593
|
const features = [];
|
|
@@ -2623,20 +3598,20 @@ function renderVerboseTable(models, stream) {
|
|
|
2623
3598
|
if (model.features.structuredOutputs) features.push("structured-outputs");
|
|
2624
3599
|
if (model.features.fineTuning) features.push("fine-tuning");
|
|
2625
3600
|
if (features.length > 0) {
|
|
2626
|
-
stream.write(` ${
|
|
3601
|
+
stream.write(` ${chalk8.dim("Features:")} ${chalk8.blue(features.join(", "))}
|
|
2627
3602
|
`);
|
|
2628
3603
|
}
|
|
2629
3604
|
if (model.metadata) {
|
|
2630
3605
|
if (model.metadata.family) {
|
|
2631
|
-
stream.write(` ${
|
|
3606
|
+
stream.write(` ${chalk8.dim("Family:")} ${model.metadata.family}
|
|
2632
3607
|
`);
|
|
2633
3608
|
}
|
|
2634
3609
|
if (model.metadata.releaseDate) {
|
|
2635
|
-
stream.write(` ${
|
|
3610
|
+
stream.write(` ${chalk8.dim("Released:")} ${model.metadata.releaseDate}
|
|
2636
3611
|
`);
|
|
2637
3612
|
}
|
|
2638
3613
|
if (model.metadata.notes) {
|
|
2639
|
-
stream.write(` ${
|
|
3614
|
+
stream.write(` ${chalk8.dim("Notes:")} ${chalk8.italic(model.metadata.notes)}
|
|
2640
3615
|
`);
|
|
2641
3616
|
}
|
|
2642
3617
|
}
|
|
@@ -2688,7 +3663,7 @@ function registerModelsCommand(program, env) {
|
|
|
2688
3663
|
init_client();
|
|
2689
3664
|
init_logger();
|
|
2690
3665
|
import readline from "node:readline";
|
|
2691
|
-
import
|
|
3666
|
+
import chalk9 from "chalk";
|
|
2692
3667
|
var LOG_LEVEL_MAP = {
|
|
2693
3668
|
silly: 0,
|
|
2694
3669
|
trace: 1,
|
|
@@ -2729,22 +3704,22 @@ function createLoggerFactory(config) {
|
|
|
2729
3704
|
}
|
|
2730
3705
|
function createPromptFunction(stdin, stdout) {
|
|
2731
3706
|
return (question) => {
|
|
2732
|
-
return new Promise((
|
|
3707
|
+
return new Promise((resolve2) => {
|
|
2733
3708
|
const rl = readline.createInterface({
|
|
2734
3709
|
input: stdin,
|
|
2735
3710
|
output: stdout
|
|
2736
3711
|
});
|
|
2737
3712
|
stdout.write("\n");
|
|
2738
|
-
stdout.write(`${
|
|
3713
|
+
stdout.write(`${chalk9.cyan("\u2500".repeat(60))}
|
|
2739
3714
|
`);
|
|
2740
|
-
stdout.write(
|
|
3715
|
+
stdout.write(chalk9.cyan.bold("\u{1F916} Agent asks:\n"));
|
|
2741
3716
|
stdout.write(`${question}
|
|
2742
3717
|
`);
|
|
2743
|
-
stdout.write(`${
|
|
3718
|
+
stdout.write(`${chalk9.cyan("\u2500".repeat(60))}
|
|
2744
3719
|
`);
|
|
2745
|
-
rl.question(
|
|
3720
|
+
rl.question(chalk9.green.bold("You: "), (answer) => {
|
|
2746
3721
|
rl.close();
|
|
2747
|
-
|
|
3722
|
+
resolve2(answer);
|
|
2748
3723
|
});
|
|
2749
3724
|
});
|
|
2750
3725
|
};
|