@cryer/star-cli 0.3.12 → 0.3.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +17 -5
  2. package/README.zh-CN.md +15 -5
  3. package/dist/{chunk-LSYGHLRU.js → chunk-2ISBRSBA.js} +479 -296
  4. package/dist/chunk-2ISBRSBA.js.map +1 -0
  5. package/dist/{chunk-F5RWZMQY.js → chunk-4HANB3DJ.js} +458 -221
  6. package/dist/chunk-4HANB3DJ.js.map +1 -0
  7. package/dist/chunk-4IRY44TG.js +0 -0
  8. package/dist/{chunk-Q5FJN4LN.js → chunk-54KH4JSO.js} +213 -125
  9. package/dist/chunk-54KH4JSO.js.map +1 -0
  10. package/dist/{chunk-MYHD3SZB.js → chunk-CECMCDZG.js} +43 -30
  11. package/dist/chunk-CECMCDZG.js.map +1 -0
  12. package/dist/{chunk-MVAMPVTA.js → chunk-KILMI3S5.js} +13 -5
  13. package/dist/{chunk-MVAMPVTA.js.map → chunk-KILMI3S5.js.map} +1 -1
  14. package/dist/{chunk-2GAUAYCU.js → chunk-NRLGGA7L.js} +507 -175
  15. package/dist/chunk-NRLGGA7L.js.map +1 -0
  16. package/dist/loop-M64D5YII.js +17 -0
  17. package/dist/main.js +177 -131
  18. package/dist/main.js.map +1 -1
  19. package/dist/{provider-EWMUPBLH.js → provider-3Z4B7RMF.js} +2 -2
  20. package/dist/{repl-SSCURTAH.js → repl-N2H7JDSH.js} +617 -327
  21. package/dist/repl-N2H7JDSH.js.map +1 -0
  22. package/dist/{tools-BH55BVIS.js → tools-5RKM7ZI3.js} +3 -3
  23. package/package.json +16 -25
  24. package/dist/chunk-2GAUAYCU.js.map +0 -1
  25. package/dist/chunk-F5RWZMQY.js.map +0 -1
  26. package/dist/chunk-LSYGHLRU.js.map +0 -1
  27. package/dist/chunk-MYHD3SZB.js.map +0 -1
  28. package/dist/chunk-Q5FJN4LN.js.map +0 -1
  29. package/dist/loop-BYXBNFFY.js +0 -17
  30. package/dist/repl-SSCURTAH.js.map +0 -1
  31. /package/dist/{loop-BYXBNFFY.js.map → loop-M64D5YII.js.map} +0 -0
  32. /package/dist/{provider-EWMUPBLH.js.map → provider-3Z4B7RMF.js.map} +0 -0
  33. /package/dist/{tools-BH55BVIS.js.map → tools-5RKM7ZI3.js.map} +0 -0
package/dist/main.js CHANGED
@@ -15,8 +15,9 @@ import {
15
15
  readImageInput,
16
16
  resolveMentions,
17
17
  resolveSessionId,
18
- summarizeArgs
19
- } from "./chunk-2GAUAYCU.js";
18
+ summarizeArgs,
19
+ toTerminalSafe
20
+ } from "./chunk-NRLGGA7L.js";
20
21
  import {
21
22
  ConfigSchema,
22
23
  HookConfigSchema,
@@ -25,7 +26,7 @@ import {
25
26
  ProviderConfigSchema,
26
27
  contextWindowTokens,
27
28
  reconcileStarMessages
28
- } from "./chunk-MYHD3SZB.js";
29
+ } from "./chunk-CECMCDZG.js";
29
30
  import {
30
31
  resolveModelConfig,
31
32
  resolveStartupModel
@@ -41,48 +42,57 @@ import {
41
42
  registerSensitivePatterns,
42
43
  setTodoSession,
43
44
  setWebFetchAllowPrivateHosts
44
- } from "./chunk-LSYGHLRU.js";
45
+ } from "./chunk-2ISBRSBA.js";
45
46
 
46
47
  // src/main.tsx
47
48
  import { Command } from "commander";
48
49
 
49
50
  // src/cli/print-human.ts
50
- function printEventLine(event) {
51
+ function printEventLine(event, tty = {}) {
52
+ let line;
51
53
  switch (event.type) {
52
54
  case "text-delta":
53
- return { stream: "stdout", text: event.text };
55
+ line = { stream: "stdout", text: event.text };
56
+ break;
54
57
  case "tool-call":
55
- return { stream: "stderr", text: `
58
+ line = { stream: "stderr", text: `
56
59
  [tool] ${event.name} ${summarizeArgs(event.args)}
57
60
  ` };
61
+ break;
58
62
  case "tool-result": {
59
63
  const preview = event.content.length > 500 ? `${event.content.slice(0, 500)}... (truncated)` : event.content;
60
- return {
64
+ line = {
61
65
  stream: "stderr",
62
66
  text: `[result] ${event.isError ? "ERROR: " : ""}${preview}
63
67
  `
64
68
  };
69
+ break;
65
70
  }
66
71
  case "retry": {
67
72
  const wait = event.delayMs !== void 0 && event.delayMs >= 1e3 ? ` in ${Math.round(event.delayMs / 1e3)}s` : "";
68
- return {
73
+ line = {
69
74
  stream: "stderr",
70
75
  text: `
71
76
  [retry ${event.attempt}/${event.maxAttempts}${wait}] ${event.reason}
72
77
  `
73
78
  };
79
+ break;
74
80
  }
75
81
  case "notice":
76
- return { stream: "stderr", text: `
82
+ line = { stream: "stderr", text: `
77
83
  [notice] ${event.message}
78
84
  ` };
85
+ break;
79
86
  case "error":
80
- return { stream: "stderr", text: `
87
+ line = { stream: "stderr", text: `
81
88
  [error] ${formatStreamError(event.error)}
82
89
  ` };
90
+ break;
83
91
  default:
84
92
  return null;
85
93
  }
94
+ const isTTY = line.stream === "stdout" ? tty.stdoutIsTTY ?? Boolean(process.stdout.isTTY) : tty.stderrIsTTY ?? Boolean(process.stderr.isTTY);
95
+ return isTTY ? { ...line, text: toTerminalSafe(line.text) } : line;
86
96
  }
87
97
 
88
98
  // src/cli/print-json.ts
@@ -91,7 +101,7 @@ function eventToJsonLine(event) {
91
101
  case "finish":
92
102
  return null;
93
103
  case "error":
94
- return JSON.stringify({ type: "error", error: { message: event.error.message } });
104
+ return JSON.stringify(event);
95
105
  default:
96
106
  return JSON.stringify(event);
97
107
  }
@@ -101,33 +111,154 @@ var UsageTracker = class {
101
111
  promptTokens = 0;
102
112
  completionTokens = 0;
103
113
  totalTokens = 0;
114
+ cachedPromptTokens;
115
+ cacheReadInputTokens;
104
116
  add(usage) {
105
117
  if (!usage) return;
106
118
  this.requests += 1;
107
119
  this.promptTokens += usage.promptTokens;
108
120
  this.completionTokens += usage.completionTokens;
109
121
  this.totalTokens += usage.totalTokens;
122
+ if (usage.cachedPromptTokens !== void 0) {
123
+ this.cachedPromptTokens = (this.cachedPromptTokens ?? 0) + usage.cachedPromptTokens;
124
+ }
125
+ if (usage.cacheReadInputTokens !== void 0) {
126
+ this.cacheReadInputTokens = (this.cacheReadInputTokens ?? 0) + usage.cacheReadInputTokens;
127
+ }
110
128
  }
111
129
  get totals() {
112
130
  return {
113
131
  requests: this.requests,
114
132
  promptTokens: this.promptTokens,
115
133
  completionTokens: this.completionTokens,
116
- totalTokens: this.totalTokens
134
+ totalTokens: this.totalTokens,
135
+ ...this.cachedPromptTokens !== void 0 ? { cachedPromptTokens: this.cachedPromptTokens } : {},
136
+ ...this.cacheReadInputTokens !== void 0 ? { cacheReadInputTokens: this.cacheReadInputTokens } : {}
117
137
  };
118
138
  }
119
139
  toJsonLine() {
120
140
  if (this.requests === 0) return null;
121
- return JSON.stringify({
122
- type: "usage",
123
- requests: this.requests,
124
- promptTokens: this.promptTokens,
125
- completionTokens: this.completionTokens,
126
- totalTokens: this.totalTokens
127
- });
141
+ return JSON.stringify({ type: "usage", ...this.totals });
128
142
  }
129
143
  };
130
144
 
145
+ // src/cli/print-mode.ts
146
+ async function printMode(loop, prompt, cwd, json, imagePaths, sessionStore) {
147
+ const flagImages = [];
148
+ for (const imagePath of imagePaths) {
149
+ if (!imageMimeType(imagePath)) {
150
+ process.stderr.write(
151
+ `[error] unsupported image type: ${imagePath} (supported: png, jpg, jpeg, gif, webp)
152
+ `
153
+ );
154
+ return 1;
155
+ }
156
+ const image = await readImageInput(imagePath, cwd);
157
+ if (!image) {
158
+ process.stderr.write(
159
+ `[error] cannot read image: ${imagePath} (missing, unreadable, or larger than ${MAX_IMAGE_BYTES / (1024 * 1024)}MB)
160
+ `
161
+ );
162
+ return 1;
163
+ }
164
+ flagImages.push(image);
165
+ }
166
+ const controller = new AbortController();
167
+ let interrupted = false;
168
+ const onSigint = () => {
169
+ interrupted = true;
170
+ controller.abort();
171
+ };
172
+ process.on("SIGINT", onSigint);
173
+ loop.onHookWarning = (message) => {
174
+ process.stderr.write(`[hook] ${message}
175
+ `);
176
+ };
177
+ const usage = new UsageTracker();
178
+ loop.onUsage = (delta) => usage.add(delta);
179
+ let exitCode = 0;
180
+ let pending = [];
181
+ const emit = (event) => {
182
+ if (json) {
183
+ const line2 = eventToJsonLine(event);
184
+ if (line2) process.stdout.write(`${line2}
185
+ `);
186
+ return;
187
+ }
188
+ const line = printEventLine(event);
189
+ if (line) (line.stream === "stdout" ? process.stdout : process.stderr).write(line.text);
190
+ };
191
+ const flushPending = () => {
192
+ for (const event of pending) emit(event);
193
+ pending = [];
194
+ };
195
+ try {
196
+ const resolved = await resolveMentions(prompt, cwd);
197
+ if (resolved.attached.length > 0) {
198
+ process.stderr.write(`[attached] ${resolved.attached.join(", ")}
199
+ `);
200
+ }
201
+ for (const skip of resolved.skipped) {
202
+ process.stderr.write(`[skipped] @${skip.path}: ${skip.reason}
203
+ `);
204
+ }
205
+ const images = [...flagImages, ...resolved.images];
206
+ const input = images.length > 0 ? { text: resolved.input, images } : resolved.input;
207
+ for await (const event of loop.stream(input, controller.signal, { persistAs: prompt })) {
208
+ if (event.type === "retry") {
209
+ pending = [];
210
+ emit(event);
211
+ } else if (event.type === "finish" || event.type === "error") {
212
+ flushPending();
213
+ if (event.type === "error") {
214
+ emit(event);
215
+ exitCode = 1;
216
+ }
217
+ } else if (event.type === "notice") {
218
+ emit(event);
219
+ } else {
220
+ pending.push(event);
221
+ }
222
+ if (exitCode !== 0) break;
223
+ }
224
+ } catch (error) {
225
+ if (!(error instanceof DOMException && error.name === "AbortError")) {
226
+ const message = error instanceof Error ? formatStreamError(error) : String(error);
227
+ process.stderr.write(`
228
+ [error] ${message}
229
+ `);
230
+ exitCode = 1;
231
+ }
232
+ } finally {
233
+ flushPending();
234
+ if (interrupted) exitCode = 130;
235
+ const usageLine = usage.toJsonLine();
236
+ if (usageLine) {
237
+ if (json) {
238
+ process.stdout.write(`${usageLine}
239
+ `);
240
+ } else {
241
+ const t = usage.totals;
242
+ process.stderr.write(
243
+ `[usage] ${t.requests} requests, ${formatTokens(t.promptTokens)} prompt + ${formatTokens(t.completionTokens)} completion = ${formatTokens(t.totalTokens)} tokens
244
+ `
245
+ );
246
+ }
247
+ }
248
+ if (!json) process.stdout.write("\n");
249
+ process.off("SIGINT", onSigint);
250
+ const stopped = defaultTaskManager.cleanup().length + defaultAgentTasks.cleanup().length;
251
+ if (stopped > 0) {
252
+ process.stderr.write(
253
+ `[info] ${stopped} background task(s) were still running and have been stopped.
254
+ `
255
+ );
256
+ }
257
+ await sessionStore?.close();
258
+ }
259
+ return exitCode;
260
+ }
261
+
131
262
  // src/cli/startup.ts
132
263
  function imageRequiresPrintError(print, images) {
133
264
  if (print !== void 0) return null;
@@ -136,9 +267,9 @@ function imageRequiresPrintError(print, images) {
136
267
  function interactiveRequiresTtyError(streams) {
137
268
  return streams.stdinTTY && streams.stdoutTTY ? null : "interactive mode requires a TTY; use -p/--print";
138
269
  }
139
- function installStdoutEpipeGuard(stdout = process.stdout) {
270
+ function installStdoutEpipeGuard(stdout = process.stdout, onEpipe = () => process.exit(0)) {
140
271
  stdout.on("error", (error) => {
141
- if (error?.code === "EPIPE") process.exit(0);
272
+ if (error?.code === "EPIPE") onEpipe();
142
273
  else throw error;
143
274
  });
144
275
  }
@@ -405,7 +536,7 @@ function wireRuntimeSettings(config) {
405
536
 
406
537
  // src/main.tsx
407
538
  async function createLoop(config, modelName, cwd, sessionStore) {
408
- const [{ AgentLoop }, { createModel, reasoningEffortMetadata }, { createDefaultRegistry }] = await Promise.all([import("./loop-BYXBNFFY.js"), import("./provider-EWMUPBLH.js"), import("./tools-BH55BVIS.js")]);
539
+ const [{ AgentLoop }, { createModel, reasoningEffortMetadata }, { createDefaultRegistry }] = await Promise.all([import("./loop-M64D5YII.js"), import("./provider-3Z4B7RMF.js"), import("./tools-5RKM7ZI3.js")]);
409
540
  const model = createModel(config, modelName);
410
541
  const registry = createDefaultRegistry();
411
542
  const modelConfig = resolveModelConfig(config, modelName);
@@ -418,6 +549,7 @@ async function createLoop(config, modelName, cwd, sessionStore) {
418
549
  sessionStore,
419
550
  modelName,
420
551
  contextMaxTokens: contextWindowTokens(config, modelName),
552
+ maxTokens: modelConfig.maxTokens,
421
553
  providerMetadata: reasoningEffortMetadata(config, modelName),
422
554
  temperature: modelConfig.temperature,
423
555
  streamIdleTimeoutSec: modelConfig.streamIdleTimeoutSec,
@@ -425,107 +557,6 @@ async function createLoop(config, modelName, cwd, sessionStore) {
425
557
  vision: modelConfig.vision
426
558
  });
427
559
  }
428
- async function printMode(loop, prompt, cwd, json, imagePaths, sessionStore) {
429
- const flagImages = [];
430
- for (const imagePath of imagePaths) {
431
- if (!imageMimeType(imagePath)) {
432
- process.stderr.write(
433
- `[error] unsupported image type: ${imagePath} (supported: png, jpg, jpeg, gif, webp)
434
- `
435
- );
436
- return 1;
437
- }
438
- const image = await readImageInput(imagePath, cwd);
439
- if (!image) {
440
- process.stderr.write(
441
- `[error] cannot read image: ${imagePath} (missing, unreadable, or larger than ${MAX_IMAGE_BYTES / (1024 * 1024)}MB)
442
- `
443
- );
444
- return 1;
445
- }
446
- flagImages.push(image);
447
- }
448
- const controller = new AbortController();
449
- process.on("SIGINT", () => controller.abort());
450
- loop.onHookWarning = (message) => {
451
- process.stderr.write(`[hook] ${message}
452
- `);
453
- };
454
- const resolved = await resolveMentions(prompt, cwd);
455
- if (resolved.attached.length > 0) {
456
- process.stderr.write(`[attached] ${resolved.attached.join(", ")}
457
- `);
458
- }
459
- for (const skip of resolved.skipped) {
460
- process.stderr.write(`[skipped] @${skip.path}: ${skip.reason}
461
- `);
462
- }
463
- const images = [...flagImages, ...resolved.images];
464
- const input = images.length > 0 ? { text: resolved.input, images } : resolved.input;
465
- const usage = new UsageTracker();
466
- let exitCode = 0;
467
- try {
468
- for await (const event of loop.stream(input, controller.signal, {
469
- persistAs: prompt
470
- })) {
471
- if (json) {
472
- const line = eventToJsonLine(event);
473
- if (line) process.stdout.write(`${line}
474
- `);
475
- } else {
476
- const line = printEventLine(event);
477
- if (line) {
478
- (line.stream === "stdout" ? process.stdout : process.stderr).write(line.text);
479
- }
480
- }
481
- switch (event.type) {
482
- case "finish":
483
- usage.add(event.usage);
484
- if (event.usage) {
485
- sessionStore?.addUsage(event.usage).catch(() => {
486
- });
487
- }
488
- break;
489
- case "error":
490
- exitCode = 1;
491
- break;
492
- }
493
- if (exitCode !== 0) break;
494
- }
495
- } catch (error) {
496
- if (!(error instanceof DOMException && error.name === "AbortError")) {
497
- const message = error instanceof Error ? formatStreamError(error) : String(error);
498
- process.stderr.write(`
499
- [error] ${message}
500
- `);
501
- exitCode = 1;
502
- }
503
- }
504
- const usageLine = usage.toJsonLine();
505
- if (usageLine) {
506
- if (json) {
507
- process.stdout.write(`${usageLine}
508
- `);
509
- } else {
510
- const t = usage.totals;
511
- process.stderr.write(
512
- `[usage] ${t.requests} requests, ${formatTokens(t.promptTokens)} prompt + ${formatTokens(t.completionTokens)} completion = ${formatTokens(t.totalTokens)} tokens
513
- `
514
- );
515
- }
516
- }
517
- if (!json) process.stdout.write("\n");
518
- const killedShells = defaultTaskManager.cleanup();
519
- const killedAgents = defaultAgentTasks.cleanup();
520
- const stopped = killedShells.length + killedAgents.length;
521
- if (stopped > 0) {
522
- process.stderr.write(
523
- `[info] ${stopped} background task(s) were still running and have been stopped.
524
- `
525
- );
526
- }
527
- return exitCode;
528
- }
529
560
  var program = new Command();
530
561
  program.name("star").description("Star CLI \u2014 an AI agent command-line interface").version(VERSION).option("-m, --model <model>", "model to use").option("--permission-mode <mode>", "permission mode: ask | auto | readonly | yolo | plan").option("-p, --print <prompt>", "non-interactive print mode").option("--json", "output NDJSON events on stdout (print mode only)").option(
531
562
  "--image <path>",
@@ -536,7 +567,6 @@ program.name("star").description("Star CLI \u2014 an AI agent command-line inter
536
567
  "--clear-sessions [scope]",
537
568
  "delete stored sessions for the current directory ('all' deletes every session) and exit"
538
569
  ).action(async (opts) => {
539
- installStdoutEpipeGuard();
540
570
  const cwd = process.cwd();
541
571
  if (opts.json && opts.print === void 0) {
542
572
  console.error(
@@ -577,6 +607,17 @@ program.name("star").description("Star CLI \u2014 an AI agent command-line inter
577
607
  process.exit(1);
578
608
  }
579
609
  let sessionStore = null;
610
+ let shuttingDown = false;
611
+ installStdoutEpipeGuard(process.stdout, () => {
612
+ if (shuttingDown) return;
613
+ shuttingDown = true;
614
+ void (async () => {
615
+ defaultTaskManager.cleanup();
616
+ defaultAgentTasks.cleanup();
617
+ await sessionStore?.close();
618
+ process.exit(0);
619
+ })();
620
+ });
580
621
  let resumed = null;
581
622
  let resumeId = null;
582
623
  if (typeof opts.resume === "string") {
@@ -601,8 +642,8 @@ program.name("star").description("Star CLI \u2014 an AI agent command-line inter
601
642
  }
602
643
  const [meta, loaded] = await Promise.all([sessionStore.meta(), sessionStore.starMessages()]);
603
644
  const messages = reconcileStarMessages(loaded);
604
- if (messages.length !== loaded.length) {
605
- await sessionStore.replaceMessages(messages);
645
+ if (messages.length !== loaded.length && !await sessionStore.replaceMessages(messages)) {
646
+ throw new Error(`Could not repair persisted history for session ${resumeId}`);
606
647
  }
607
648
  resumed = { meta, messages };
608
649
  setTodoSession(resumeId);
@@ -621,7 +662,7 @@ program.name("star").description("Star CLI \u2014 an AI agent command-line inter
621
662
  );
622
663
  process.exit(1);
623
664
  }
624
- if (!resumeId && !opts.print) {
665
+ if (!resumeId && opts.print === void 0) {
625
666
  sessionStore = await SessionStore.create(cwd, modelName);
626
667
  setTodoSession(sessionStore.id);
627
668
  }
@@ -639,7 +680,7 @@ program.name("star").description("Star CLI \u2014 an AI agent command-line inter
639
680
  hydrateSnapshots(await loadSessionSnapshots(sessionStore.dir));
640
681
  }
641
682
  }
642
- if (opts.print) {
683
+ if (opts.print !== void 0) {
643
684
  process.exitCode = await printMode(
644
685
  loop,
645
686
  opts.print,
@@ -658,7 +699,7 @@ program.name("star").description("Star CLI \u2014 an AI agent command-line inter
658
699
  console.error(ttyError);
659
700
  process.exit(1);
660
701
  }
661
- const { renderRepl } = await import("./repl-SSCURTAH.js");
702
+ const { renderRepl } = await import("./repl-N2H7JDSH.js");
662
703
  renderRepl(loop, {
663
704
  model: modelName,
664
705
  permissionMode: config.permissionMode,
@@ -669,5 +710,10 @@ program.name("star").description("Star CLI \u2014 an AI agent command-line inter
669
710
  initialUsage: resumed?.meta.usage
670
711
  });
671
712
  });
672
- program.parse(process.argv);
713
+ try {
714
+ await program.parseAsync(process.argv);
715
+ } catch (error) {
716
+ console.error(error instanceof Error ? error.message : String(error));
717
+ process.exitCode = 1;
718
+ }
673
719
  //# sourceMappingURL=main.js.map