@cryer/star-cli 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -3
- package/dist/{chunk-RLQXGX3N.js → chunk-MJ2O7NWI.js} +240 -85
- package/dist/chunk-MJ2O7NWI.js.map +1 -0
- package/dist/{loop-XIILV7GY.js → loop-R3RQCNFT.js} +2 -2
- package/dist/main.js +160 -139
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-RLQXGX3N.js.map +0 -1
- /package/dist/{loop-XIILV7GY.js.map → loop-R3RQCNFT.js.map} +0 -0
package/dist/main.js
CHANGED
|
@@ -13,24 +13,29 @@ import {
|
|
|
13
13
|
compactMessages,
|
|
14
14
|
createDefaultRegistry,
|
|
15
15
|
defaultTaskManager,
|
|
16
|
+
discoverSkills,
|
|
16
17
|
estimateTokens,
|
|
17
18
|
formatTaskFinished,
|
|
18
19
|
formatTaskList,
|
|
19
20
|
formatTaskStarted,
|
|
20
21
|
formatTodos,
|
|
21
22
|
getGitSummary,
|
|
23
|
+
globalConfigPath,
|
|
22
24
|
hydrateSnapshots,
|
|
23
25
|
isAllowedByRules,
|
|
24
26
|
isDangerousCommand,
|
|
25
27
|
isGitRepo,
|
|
26
28
|
isSensitivePath,
|
|
27
29
|
listSnapshots,
|
|
30
|
+
projectConfigPath,
|
|
28
31
|
reconcileToolCalls,
|
|
29
32
|
resolveShell,
|
|
30
33
|
rewindToSnapshot,
|
|
34
|
+
sessionsDir,
|
|
35
|
+
starHome,
|
|
31
36
|
summarizeMessages,
|
|
32
37
|
undoTurnSnapshots
|
|
33
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-MJ2O7NWI.js";
|
|
34
39
|
|
|
35
40
|
// src/main.tsx
|
|
36
41
|
import { Command } from "commander";
|
|
@@ -269,26 +274,8 @@ import { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useSt
|
|
|
269
274
|
|
|
270
275
|
// src/config/save.ts
|
|
271
276
|
import fs from "fs";
|
|
272
|
-
import path3 from "path";
|
|
273
|
-
import { parse, stringify } from "smol-toml";
|
|
274
|
-
|
|
275
|
-
// src/config/paths.ts
|
|
276
|
-
import os from "os";
|
|
277
277
|
import path2 from "path";
|
|
278
|
-
|
|
279
|
-
return process.env.STAR_HOME ?? path2.join(os.homedir(), ".star-cli");
|
|
280
|
-
}
|
|
281
|
-
function globalConfigPath() {
|
|
282
|
-
return path2.join(starHome(), "config.toml");
|
|
283
|
-
}
|
|
284
|
-
function sessionsDir() {
|
|
285
|
-
return path2.join(starHome(), "sessions");
|
|
286
|
-
}
|
|
287
|
-
function projectConfigPath(cwd) {
|
|
288
|
-
return path2.join(cwd, ".star", "config.toml");
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// src/config/save.ts
|
|
278
|
+
import { parse, stringify } from "smol-toml";
|
|
292
279
|
async function addAllowRule(rule) {
|
|
293
280
|
const filePath = globalConfigPath();
|
|
294
281
|
let raw = {};
|
|
@@ -303,7 +290,7 @@ async function addAllowRule(rule) {
|
|
|
303
290
|
if (allow.includes(rule)) return false;
|
|
304
291
|
permissions.allow = [...allow, rule];
|
|
305
292
|
raw.permissions = permissions;
|
|
306
|
-
await fs.promises.mkdir(
|
|
293
|
+
await fs.promises.mkdir(path2.dirname(filePath), { recursive: true });
|
|
307
294
|
await fs.promises.writeFile(filePath, stringify(raw), "utf8");
|
|
308
295
|
return true;
|
|
309
296
|
}
|
|
@@ -317,10 +304,79 @@ async function savePermissionMode(mode) {
|
|
|
317
304
|
if (error.code !== "ENOENT") throw error;
|
|
318
305
|
}
|
|
319
306
|
raw.permissionMode = mode;
|
|
320
|
-
await fs.promises.mkdir(
|
|
307
|
+
await fs.promises.mkdir(path2.dirname(filePath), { recursive: true });
|
|
321
308
|
await fs.promises.writeFile(filePath, stringify(raw), "utf8");
|
|
322
309
|
}
|
|
323
310
|
|
|
311
|
+
// src/config/schema.ts
|
|
312
|
+
import { z } from "zod";
|
|
313
|
+
var ProviderConfigSchema = z.object({
|
|
314
|
+
name: z.string(),
|
|
315
|
+
protocol: z.enum(["openai-compatible", "anthropic", "openai-responses"]).default("openai-compatible"),
|
|
316
|
+
baseURL: z.string(),
|
|
317
|
+
apiKeyEnv: z.string().optional(),
|
|
318
|
+
apiKey: z.string().optional(),
|
|
319
|
+
headers: z.record(z.string()).optional()
|
|
320
|
+
});
|
|
321
|
+
var ModelConfigSchema = z.object({
|
|
322
|
+
name: z.string(),
|
|
323
|
+
provider: z.string(),
|
|
324
|
+
model: z.string(),
|
|
325
|
+
maxTokens: z.number().int().positive().optional(),
|
|
326
|
+
// Per-model context window; overrides the top-level contextMaxTokens for
|
|
327
|
+
// compaction and the status bar when set.
|
|
328
|
+
contextMaxTokens: z.number().int().positive().optional(),
|
|
329
|
+
// Cost estimation needs BOTH prices (USD per 1M tokens); with only one set
|
|
330
|
+
// the model is treated as unpriced.
|
|
331
|
+
promptPrice: z.number().nonnegative().optional(),
|
|
332
|
+
completionPrice: z.number().nonnegative().optional()
|
|
333
|
+
});
|
|
334
|
+
var PermissionsConfigSchema = z.object({
|
|
335
|
+
allow: z.array(z.string()).default([])
|
|
336
|
+
});
|
|
337
|
+
var HookConfigSchema = z.object({
|
|
338
|
+
event: z.enum(["PreToolUse", "PostToolUse", "Stop"]),
|
|
339
|
+
matcher: z.string().refine(
|
|
340
|
+
(pattern) => {
|
|
341
|
+
try {
|
|
342
|
+
new RegExp(pattern);
|
|
343
|
+
return true;
|
|
344
|
+
} catch {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
{ message: "invalid regular expression" }
|
|
349
|
+
).optional(),
|
|
350
|
+
command: z.string().min(1),
|
|
351
|
+
timeoutSec: z.number().positive().default(30)
|
|
352
|
+
});
|
|
353
|
+
var ConfigSchema = z.object({
|
|
354
|
+
defaultModel: z.string().default(""),
|
|
355
|
+
permissionMode: z.enum(["auto", "ask", "readonly", "yolo", "plan"]).default("ask"),
|
|
356
|
+
providers: z.array(ProviderConfigSchema).default([]),
|
|
357
|
+
models: z.array(ModelConfigSchema).default([]),
|
|
358
|
+
maxSteps: z.number().int().positive().default(50),
|
|
359
|
+
contextMaxTokens: z.number().int().positive().default(1e5),
|
|
360
|
+
// Seconds without any stream part before a stalled response is ended
|
|
361
|
+
// gracefully (some relays never send the terminal chunks). Applies once
|
|
362
|
+
// streaming has started; the first part gets a longer, fixed allowance.
|
|
363
|
+
streamIdleTimeoutSec: z.number().positive().default(20),
|
|
364
|
+
contextCompaction: z.enum(["summary", "truncate"]).default("summary"),
|
|
365
|
+
// Optional per-session dollar budget; when set, the REPL stops the session
|
|
366
|
+
// once the estimated cost for the session reaches this amount.
|
|
367
|
+
sessionBudgetUsd: z.number().positive().optional(),
|
|
368
|
+
// Terminal bell (REPL only): ring when a turn takes longer than
|
|
369
|
+
// notifyBellThresholdSec, and when a background task finishes.
|
|
370
|
+
// STAR_NO_NOTIFY=1 disables without touching the config.
|
|
371
|
+
notifyBell: z.boolean().default(true),
|
|
372
|
+
notifyBellThresholdSec: z.number().positive().default(10),
|
|
373
|
+
permissions: PermissionsConfigSchema.default({ allow: [] }),
|
|
374
|
+
hooks: z.array(HookConfigSchema).default([])
|
|
375
|
+
});
|
|
376
|
+
function contextWindowTokens(config, modelName) {
|
|
377
|
+
return config.models.find((m) => m.name === modelName)?.contextMaxTokens ?? config.contextMaxTokens;
|
|
378
|
+
}
|
|
379
|
+
|
|
324
380
|
// src/llm/provider.ts
|
|
325
381
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
326
382
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
@@ -385,15 +441,15 @@ function createModel(config, modelName) {
|
|
|
385
441
|
|
|
386
442
|
// src/session/checkpoints.ts
|
|
387
443
|
import fs2 from "fs/promises";
|
|
388
|
-
import
|
|
444
|
+
import path3 from "path";
|
|
389
445
|
function checkpointsDir(sessionDir) {
|
|
390
|
-
return
|
|
446
|
+
return path3.join(sessionDir, "checkpoints");
|
|
391
447
|
}
|
|
392
448
|
function indexPath(sessionDir) {
|
|
393
|
-
return
|
|
449
|
+
return path3.join(checkpointsDir(sessionDir), "index.json");
|
|
394
450
|
}
|
|
395
451
|
function checkpointContentPath(sessionDir, id) {
|
|
396
|
-
return
|
|
452
|
+
return path3.join(checkpointsDir(sessionDir), `${id}.snapshot`);
|
|
397
453
|
}
|
|
398
454
|
async function listCheckpointRecords(sessionDir) {
|
|
399
455
|
let raw;
|
|
@@ -441,11 +497,11 @@ async function loadSessionSnapshots(sessionDir) {
|
|
|
441
497
|
|
|
442
498
|
// src/session/clear.ts
|
|
443
499
|
import fs4 from "fs/promises";
|
|
444
|
-
import
|
|
500
|
+
import path5 from "path";
|
|
445
501
|
|
|
446
502
|
// src/session/store.ts
|
|
447
503
|
import fs3 from "fs/promises";
|
|
448
|
-
import
|
|
504
|
+
import path4 from "path";
|
|
449
505
|
function generateId(now) {
|
|
450
506
|
const pad = (n) => String(n).padStart(2, "0");
|
|
451
507
|
const stamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
@@ -467,13 +523,13 @@ var SessionStore = class _SessionStore {
|
|
|
467
523
|
initialized = false;
|
|
468
524
|
constructor(id) {
|
|
469
525
|
this.id = id;
|
|
470
|
-
this.dir =
|
|
526
|
+
this.dir = path4.join(sessionsDir(), id);
|
|
471
527
|
}
|
|
472
528
|
metaPath() {
|
|
473
|
-
return
|
|
529
|
+
return path4.join(this.dir, "meta.json");
|
|
474
530
|
}
|
|
475
531
|
messagesPath() {
|
|
476
|
-
return
|
|
532
|
+
return path4.join(this.dir, "messages.jsonl");
|
|
477
533
|
}
|
|
478
534
|
static async create(cwd, model) {
|
|
479
535
|
const store = new _SessionStore(generateId(/* @__PURE__ */ new Date()));
|
|
@@ -507,7 +563,7 @@ var SessionStore = class _SessionStore {
|
|
|
507
563
|
const metas = [];
|
|
508
564
|
for (const entry of entries) {
|
|
509
565
|
try {
|
|
510
|
-
const raw = await fs3.readFile(
|
|
566
|
+
const raw = await fs3.readFile(path4.join(sessionsDir(), entry, "meta.json"), "utf8");
|
|
511
567
|
const meta = JSON.parse(raw);
|
|
512
568
|
if (meta.id === entry && (cwd === void 0 || meta.cwd === cwd)) metas.push(meta);
|
|
513
569
|
} catch {
|
|
@@ -634,7 +690,7 @@ async function clearSessions(cwd, excludeId) {
|
|
|
634
690
|
let removed2 = 0;
|
|
635
691
|
for (const meta of metas) {
|
|
636
692
|
if (meta.id === excludeId) continue;
|
|
637
|
-
await fs4.rm(
|
|
693
|
+
await fs4.rm(path5.join(sessionsDir(), meta.id), { recursive: true, force: true });
|
|
638
694
|
removed2 += 1;
|
|
639
695
|
}
|
|
640
696
|
return removed2;
|
|
@@ -648,7 +704,7 @@ async function clearSessions(cwd, excludeId) {
|
|
|
648
704
|
let removed = 0;
|
|
649
705
|
for (const entry of entries) {
|
|
650
706
|
if (entry === excludeId) continue;
|
|
651
|
-
await fs4.rm(
|
|
707
|
+
await fs4.rm(path5.join(sessionsDir(), entry), { recursive: true, force: true });
|
|
652
708
|
removed += 1;
|
|
653
709
|
}
|
|
654
710
|
return removed;
|
|
@@ -656,11 +712,11 @@ async function clearSessions(cwd, excludeId) {
|
|
|
656
712
|
|
|
657
713
|
// src/session/list.ts
|
|
658
714
|
import fs5 from "fs/promises";
|
|
659
|
-
import
|
|
715
|
+
import path6 from "path";
|
|
660
716
|
async function countMessages(id) {
|
|
661
717
|
let raw;
|
|
662
718
|
try {
|
|
663
|
-
raw = await fs5.readFile(
|
|
719
|
+
raw = await fs5.readFile(path6.join(sessionsDir(), id, "messages.jsonl"), "utf8");
|
|
664
720
|
} catch {
|
|
665
721
|
return 0;
|
|
666
722
|
}
|
|
@@ -744,10 +800,19 @@ function computeCostUsd(usage, model) {
|
|
|
744
800
|
}
|
|
745
801
|
function estimateCost(usage, modelName, model) {
|
|
746
802
|
const cost = computeCostUsd(usage, model);
|
|
747
|
-
if (cost === null
|
|
803
|
+
if (cost === null) {
|
|
804
|
+
if (model !== void 0) {
|
|
805
|
+
const missing = [
|
|
806
|
+
model.promptPrice === void 0 ? "promptPrice" : null,
|
|
807
|
+
model.completionPrice === void 0 ? "completionPrice" : null
|
|
808
|
+
].filter((f) => f !== null);
|
|
809
|
+
if (missing.length === 1) {
|
|
810
|
+
return `Estimated cost: unknown (${model.name} is missing ${missing[0]} \u2014 both prices are needed)`;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
748
813
|
return `Estimated cost: unknown (no price configured for ${modelName})`;
|
|
749
814
|
}
|
|
750
|
-
return `Estimated cost: $${formatDollars(cost)} (${model
|
|
815
|
+
return `Estimated cost: $${formatDollars(cost)} (${model?.name} @ $${model?.promptPrice}/M prompt, $${model?.completionPrice}/M completion)`;
|
|
751
816
|
}
|
|
752
817
|
|
|
753
818
|
// src/session/usage.ts
|
|
@@ -909,11 +974,11 @@ function formatUsageDashboard(stats, models, options = {}) {
|
|
|
909
974
|
|
|
910
975
|
// src/version.ts
|
|
911
976
|
import fs6 from "fs";
|
|
912
|
-
import
|
|
977
|
+
import path7 from "path";
|
|
913
978
|
import { fileURLToPath } from "url";
|
|
914
979
|
function readPackageVersion() {
|
|
915
980
|
try {
|
|
916
|
-
const pkgPath =
|
|
981
|
+
const pkgPath = path7.join(path7.dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
917
982
|
const pkg = JSON.parse(fs6.readFileSync(pkgPath, "utf8"));
|
|
918
983
|
if (typeof pkg === "object" && pkg !== null) {
|
|
919
984
|
const version = pkg.version;
|
|
@@ -938,8 +1003,8 @@ function budgetState(costUsd, budgetUsd) {
|
|
|
938
1003
|
import { execFile } from "child_process";
|
|
939
1004
|
import { randomUUID } from "crypto";
|
|
940
1005
|
import { readFile as readFile2, unlink } from "fs/promises";
|
|
941
|
-
import
|
|
942
|
-
import
|
|
1006
|
+
import os from "os";
|
|
1007
|
+
import path8 from "path";
|
|
943
1008
|
import { promisify } from "util";
|
|
944
1009
|
var CLIPBOARD_TIMEOUT_MS = 5e3;
|
|
945
1010
|
var MAX_CLIPBOARD_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
@@ -1013,7 +1078,7 @@ async function commandExists(command) {
|
|
|
1013
1078
|
}
|
|
1014
1079
|
}
|
|
1015
1080
|
async function readClipboardImage() {
|
|
1016
|
-
const tempFile =
|
|
1081
|
+
const tempFile = path8.join(os.tmpdir(), `star-clipboard-${randomUUID()}.png`);
|
|
1017
1082
|
const hasPngpaste = process.platform === "darwin" ? await commandExists("pngpaste") : false;
|
|
1018
1083
|
const plan = clipboardImagePlan(process.platform, tempFile, hasPngpaste);
|
|
1019
1084
|
if (!plan) return null;
|
|
@@ -1059,7 +1124,7 @@ async function copyText(text) {
|
|
|
1059
1124
|
|
|
1060
1125
|
// src/cli/commands/actions.ts
|
|
1061
1126
|
import fs7 from "fs/promises";
|
|
1062
|
-
import
|
|
1127
|
+
import path9 from "path";
|
|
1063
1128
|
var MIN_COMPACT_MESSAGES = 4;
|
|
1064
1129
|
async function compactSession(opts) {
|
|
1065
1130
|
const { backend, sessionStore, config, model } = opts;
|
|
@@ -1120,9 +1185,9 @@ async function exportSession(opts) {
|
|
|
1120
1185
|
return "Nothing to export: the session has no messages.";
|
|
1121
1186
|
}
|
|
1122
1187
|
const sessionId = sessionStore?.id ?? `unknown-${Date.now()}`;
|
|
1123
|
-
const target = arg ?
|
|
1188
|
+
const target = arg ? path9.resolve(cwd, arg) : path9.resolve(cwd, `star-session-${sessionId}.md`);
|
|
1124
1189
|
const existed = await fileExists(target);
|
|
1125
|
-
await fs7.mkdir(
|
|
1190
|
+
await fs7.mkdir(path9.dirname(target), { recursive: true });
|
|
1126
1191
|
await fs7.writeFile(target, renderSessionMarkdown(sessionId, messages));
|
|
1127
1192
|
return existed ? `Exported ${messages.length} messages to ${target} (overwrote existing file).` : `Exported ${messages.length} messages to ${target}.`;
|
|
1128
1193
|
}
|
|
@@ -1190,7 +1255,7 @@ function stringifyPart(value) {
|
|
|
1190
1255
|
|
|
1191
1256
|
// src/cli/diff-preview.ts
|
|
1192
1257
|
import { readFile as readFile3 } from "fs/promises";
|
|
1193
|
-
import
|
|
1258
|
+
import path10 from "path";
|
|
1194
1259
|
var CONTEXT_LINES = 2;
|
|
1195
1260
|
var MAX_HUNK_LINES = 15;
|
|
1196
1261
|
var MAX_TOTAL_LINES = 12;
|
|
@@ -1351,7 +1416,7 @@ async function generateDiffPreview(toolName, args, cwd) {
|
|
|
1351
1416
|
if (toolName !== "edit_file" && toolName !== "write_file") return null;
|
|
1352
1417
|
const filePath = getStringArg(args, "path");
|
|
1353
1418
|
if (filePath === void 0) return null;
|
|
1354
|
-
const absolute =
|
|
1419
|
+
const absolute = path10.resolve(cwd, filePath);
|
|
1355
1420
|
let existing;
|
|
1356
1421
|
try {
|
|
1357
1422
|
existing = await readFile3(absolute, "utf8");
|
|
@@ -1574,6 +1639,23 @@ ${lines.join("\n")}`);
|
|
|
1574
1639
|
ctx.addSystemMessage(await ctx.runDoctor());
|
|
1575
1640
|
}
|
|
1576
1641
|
});
|
|
1642
|
+
registry.register({
|
|
1643
|
+
name: "skills",
|
|
1644
|
+
description: "List available skills (project scope overrides user scope)",
|
|
1645
|
+
usage: "/skills",
|
|
1646
|
+
run(_args, ctx) {
|
|
1647
|
+
const skills = discoverSkills(ctx.cwd ?? process.cwd());
|
|
1648
|
+
if (skills.length === 0) {
|
|
1649
|
+
ctx.addSystemMessage(
|
|
1650
|
+
"No skills found. Add a SKILL.md under .star/skills/<name>/ (project) or ~/.star-cli/skills/<name>/ (user)."
|
|
1651
|
+
);
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
const lines = skills.map((s) => `${s.name} [${s.scope}] \u2014 ${s.description}`);
|
|
1655
|
+
ctx.addSystemMessage(`Available skills:
|
|
1656
|
+
${lines.join("\n")}`);
|
|
1657
|
+
}
|
|
1658
|
+
});
|
|
1577
1659
|
registry.register({
|
|
1578
1660
|
name: "commit",
|
|
1579
1661
|
description: "Analyze uncommitted changes and create a git commit (Conventional Commits)",
|
|
@@ -1664,7 +1746,7 @@ ${result.diff}${note ? `
|
|
|
1664
1746
|
|
|
1665
1747
|
// src/cli/commands/custom.ts
|
|
1666
1748
|
import { readFileSync, readdirSync } from "fs";
|
|
1667
|
-
import
|
|
1749
|
+
import path11 from "path";
|
|
1668
1750
|
var NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
1669
1751
|
var DESCRIPTION_PATTERN = /^<!--\s*description:\s*(.+?)\s*-->\s*$/;
|
|
1670
1752
|
var DEFAULT_DESCRIPTION = "Custom prompt command";
|
|
@@ -1699,14 +1781,14 @@ function scanDir(dir, into) {
|
|
|
1699
1781
|
if (!entry.endsWith(".md")) continue;
|
|
1700
1782
|
const name = entry.slice(0, -".md".length);
|
|
1701
1783
|
if (!NAME_PATTERN.test(name)) continue;
|
|
1702
|
-
const parsed = parseCommandFile(
|
|
1784
|
+
const parsed = parseCommandFile(path11.join(dir, entry), name);
|
|
1703
1785
|
if (parsed) into.set(name, parsed);
|
|
1704
1786
|
}
|
|
1705
1787
|
}
|
|
1706
1788
|
function loadCustomCommands(cwd, home = starHome()) {
|
|
1707
1789
|
const byName = /* @__PURE__ */ new Map();
|
|
1708
|
-
scanDir(
|
|
1709
|
-
scanDir(
|
|
1790
|
+
scanDir(path11.join(home, "commands"), byName);
|
|
1791
|
+
scanDir(path11.join(cwd, ".star", "commands"), byName);
|
|
1710
1792
|
return [...byName.values()];
|
|
1711
1793
|
}
|
|
1712
1794
|
function registerCustomCommands(registry, cwd, home) {
|
|
@@ -1732,7 +1814,7 @@ function registerCustomCommands(registry, cwd, home) {
|
|
|
1732
1814
|
|
|
1733
1815
|
// src/cli/commands/doctor.ts
|
|
1734
1816
|
import fs8 from "fs/promises";
|
|
1735
|
-
import
|
|
1817
|
+
import path12 from "path";
|
|
1736
1818
|
var MIN_NODE_MAJOR = 20;
|
|
1737
1819
|
var REGISTRY_URL = "https://registry.npmjs.org/";
|
|
1738
1820
|
var REGISTRY_TIMEOUT_MS = 5e3;
|
|
@@ -1784,7 +1866,7 @@ async function runDoctor(opts) {
|
|
|
1784
1866
|
detail: `${spec.label} (${spec.shell})`
|
|
1785
1867
|
});
|
|
1786
1868
|
}
|
|
1787
|
-
const configPath =
|
|
1869
|
+
const configPath = path12.join(home, "config.toml");
|
|
1788
1870
|
const configExists = await pathExists(configPath);
|
|
1789
1871
|
checks.push({
|
|
1790
1872
|
name: "STAR_HOME / config",
|
|
@@ -1821,12 +1903,12 @@ async function runDoctor(opts) {
|
|
|
1821
1903
|
});
|
|
1822
1904
|
}
|
|
1823
1905
|
}
|
|
1824
|
-
const sessions =
|
|
1906
|
+
const sessions = path12.join(home, "sessions");
|
|
1825
1907
|
let writable = false;
|
|
1826
1908
|
let writeError = "";
|
|
1827
1909
|
try {
|
|
1828
1910
|
await fs8.mkdir(sessions, { recursive: true });
|
|
1829
|
-
const probe =
|
|
1911
|
+
const probe = path12.join(sessions, `.doctor-probe-${process.pid}`);
|
|
1830
1912
|
await fs8.writeFile(probe, "ok");
|
|
1831
1913
|
await fs8.unlink(probe);
|
|
1832
1914
|
writable = true;
|
|
@@ -1864,7 +1946,7 @@ function formatDoctorReport(report) {
|
|
|
1864
1946
|
|
|
1865
1947
|
// src/cli/commands/init-project.ts
|
|
1866
1948
|
import fs9 from "fs/promises";
|
|
1867
|
-
import
|
|
1949
|
+
import path13 from "path";
|
|
1868
1950
|
import { generateText } from "ai";
|
|
1869
1951
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "out", "coverage"]);
|
|
1870
1952
|
var MAX_ENTRIES = 20;
|
|
@@ -1879,7 +1961,7 @@ async function fileExists2(target) {
|
|
|
1879
1961
|
}
|
|
1880
1962
|
async function readPackageJson(cwd) {
|
|
1881
1963
|
try {
|
|
1882
|
-
const raw = await fs9.readFile(
|
|
1964
|
+
const raw = await fs9.readFile(path13.join(cwd, "package.json"), "utf8");
|
|
1883
1965
|
const parsed = JSON.parse(raw);
|
|
1884
1966
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1885
1967
|
return parsed;
|
|
@@ -1896,10 +1978,10 @@ function stringRecord(value) {
|
|
|
1896
1978
|
return out;
|
|
1897
1979
|
}
|
|
1898
1980
|
async function detectPackageManager(cwd) {
|
|
1899
|
-
if (await fileExists2(
|
|
1900
|
-
if (await fileExists2(
|
|
1901
|
-
if (await fileExists2(
|
|
1902
|
-
if (await fileExists2(
|
|
1981
|
+
if (await fileExists2(path13.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
1982
|
+
if (await fileExists2(path13.join(cwd, "yarn.lock"))) return "yarn";
|
|
1983
|
+
if (await fileExists2(path13.join(cwd, "package-lock.json"))) return "npm";
|
|
1984
|
+
if (await fileExists2(path13.join(cwd, "bun.lockb"))) return "bun";
|
|
1903
1985
|
return null;
|
|
1904
1986
|
}
|
|
1905
1987
|
function inferCommands(pm, scripts) {
|
|
@@ -1933,7 +2015,7 @@ async function listEntries(cwd) {
|
|
|
1933
2015
|
async function readReadmeHead(cwd) {
|
|
1934
2016
|
for (const name of ["README.md", "readme.md", "README"]) {
|
|
1935
2017
|
try {
|
|
1936
|
-
const raw = await fs9.readFile(
|
|
2018
|
+
const raw = await fs9.readFile(path13.join(cwd, name), "utf8");
|
|
1937
2019
|
return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).slice(0, README_HEAD_LINES);
|
|
1938
2020
|
} catch {
|
|
1939
2021
|
}
|
|
@@ -1946,14 +2028,14 @@ async function scanProject(cwd) {
|
|
|
1946
2028
|
const scripts = stringRecord(pkg?.scripts);
|
|
1947
2029
|
return {
|
|
1948
2030
|
name: typeof pkg?.name === "string" ? pkg.name : null,
|
|
1949
|
-
dirName:
|
|
2031
|
+
dirName: path13.basename(cwd),
|
|
1950
2032
|
packageManager,
|
|
1951
2033
|
scripts,
|
|
1952
2034
|
dependencies: Object.keys(stringRecord(pkg?.dependencies)),
|
|
1953
2035
|
devDependencies: Object.keys(stringRecord(pkg?.devDependencies)),
|
|
1954
2036
|
commands: inferCommands(packageManager, scripts),
|
|
1955
|
-
hasTsconfig: await fileExists2(
|
|
1956
|
-
isGitRepo: await fileExists2(
|
|
2037
|
+
hasTsconfig: await fileExists2(path13.join(cwd, "tsconfig.json")),
|
|
2038
|
+
isGitRepo: await fileExists2(path13.join(cwd, ".git")),
|
|
1957
2039
|
readmeHead: await readReadmeHead(cwd),
|
|
1958
2040
|
entries: await listEntries(cwd)
|
|
1959
2041
|
};
|
|
@@ -2020,7 +2102,7 @@ Write the AGENTS.md.`
|
|
|
2020
2102
|
}
|
|
2021
2103
|
async function initProject(opts) {
|
|
2022
2104
|
const { cwd, args, model } = opts;
|
|
2023
|
-
const target =
|
|
2105
|
+
const target = path13.join(cwd, "AGENTS.md");
|
|
2024
2106
|
const force = args.split(/\s+/).includes("force");
|
|
2025
2107
|
if (!force && await fileExists2(target)) {
|
|
2026
2108
|
return {
|
|
@@ -2096,7 +2178,7 @@ function parseSlashCommand(input) {
|
|
|
2096
2178
|
}
|
|
2097
2179
|
|
|
2098
2180
|
// src/cli/commands/rewind.ts
|
|
2099
|
-
import
|
|
2181
|
+
import path14 from "path";
|
|
2100
2182
|
function planRewind(snapshots, id) {
|
|
2101
2183
|
const target = snapshots.find((snapshot) => snapshot.id === id);
|
|
2102
2184
|
if (!target) return null;
|
|
@@ -2110,7 +2192,7 @@ function planRewind(snapshots, id) {
|
|
|
2110
2192
|
return { target, affected, messageIndex };
|
|
2111
2193
|
}
|
|
2112
2194
|
function displayPath(filePath, cwd) {
|
|
2113
|
-
const relative =
|
|
2195
|
+
const relative = path14.relative(cwd, filePath);
|
|
2114
2196
|
return relative !== "" && !relative.startsWith("..") ? relative : filePath;
|
|
2115
2197
|
}
|
|
2116
2198
|
function formatTime(timestamp) {
|
|
@@ -2164,7 +2246,7 @@ function filterCommands(input, commands) {
|
|
|
2164
2246
|
|
|
2165
2247
|
// src/cli/path-suggest.ts
|
|
2166
2248
|
import { readdir } from "fs/promises";
|
|
2167
|
-
import
|
|
2249
|
+
import path15 from "path";
|
|
2168
2250
|
var MAX_SCAN_ENTRIES = 2e3;
|
|
2169
2251
|
function extractAtToken(value, cursor) {
|
|
2170
2252
|
const before = value.slice(0, cursor);
|
|
@@ -2179,7 +2261,7 @@ async function suggestPaths(token, cwd, maxScanEntries = MAX_SCAN_ENTRIES, maxSu
|
|
|
2179
2261
|
const dirPart = slash >= 0 ? normalized.slice(0, slash + 1) : "";
|
|
2180
2262
|
const basePrefix = slash >= 0 ? normalized.slice(slash + 1) : normalized;
|
|
2181
2263
|
if (dirPart.split("/").some((segment) => SKIP_DIRS.has(segment))) return [];
|
|
2182
|
-
const dirAbs =
|
|
2264
|
+
const dirAbs = path15.resolve(cwd, dirPart === "" ? "." : dirPart);
|
|
2183
2265
|
const entries = await readdir(dirAbs, { withFileTypes: true }).catch(() => null);
|
|
2184
2266
|
if (!entries) return [];
|
|
2185
2267
|
const lower = basePrefix.toLowerCase();
|
|
@@ -3060,7 +3142,8 @@ function Repl({
|
|
|
3060
3142
|
registry: createDefaultRegistry(),
|
|
3061
3143
|
config,
|
|
3062
3144
|
cwd,
|
|
3063
|
-
sessionStore: sessionStoreRef.current
|
|
3145
|
+
sessionStore: sessionStoreRef.current,
|
|
3146
|
+
contextMaxTokens: contextWindowTokens(config, name)
|
|
3064
3147
|
});
|
|
3065
3148
|
const prev = backendRef.current;
|
|
3066
3149
|
if (prev instanceof AgentLoop) {
|
|
@@ -3668,9 +3751,9 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
|
|
|
3668
3751
|
setGitBranch(getGitSummary(cwd)?.branch ?? null);
|
|
3669
3752
|
const current = backendRef.current;
|
|
3670
3753
|
if (current instanceof AgentLoop) {
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
);
|
|
3754
|
+
const window_ = contextWindowTokens(config, modelNameRef.current);
|
|
3755
|
+
const pct = estimateTokens([...current.getMessages()]) / window_ * 100;
|
|
3756
|
+
setContextPercent(pct < 10 ? Math.round(pct * 10) / 10 : Math.round(pct));
|
|
3674
3757
|
} else {
|
|
3675
3758
|
setContextPercent(null);
|
|
3676
3759
|
}
|
|
@@ -3760,69 +3843,6 @@ function renderRepl(backend, opts) {
|
|
|
3760
3843
|
// src/config/loader.ts
|
|
3761
3844
|
import fs10 from "fs";
|
|
3762
3845
|
import { parse as parse2 } from "smol-toml";
|
|
3763
|
-
|
|
3764
|
-
// src/config/schema.ts
|
|
3765
|
-
import { z } from "zod";
|
|
3766
|
-
var ProviderConfigSchema = z.object({
|
|
3767
|
-
name: z.string(),
|
|
3768
|
-
protocol: z.enum(["openai-compatible", "anthropic", "openai-responses"]).default("openai-compatible"),
|
|
3769
|
-
baseURL: z.string(),
|
|
3770
|
-
apiKeyEnv: z.string().optional(),
|
|
3771
|
-
apiKey: z.string().optional(),
|
|
3772
|
-
headers: z.record(z.string()).optional()
|
|
3773
|
-
});
|
|
3774
|
-
var ModelConfigSchema = z.object({
|
|
3775
|
-
name: z.string(),
|
|
3776
|
-
provider: z.string(),
|
|
3777
|
-
model: z.string(),
|
|
3778
|
-
maxTokens: z.number().int().positive().optional(),
|
|
3779
|
-
promptPrice: z.number().nonnegative().optional(),
|
|
3780
|
-
completionPrice: z.number().nonnegative().optional()
|
|
3781
|
-
});
|
|
3782
|
-
var PermissionsConfigSchema = z.object({
|
|
3783
|
-
allow: z.array(z.string()).default([])
|
|
3784
|
-
});
|
|
3785
|
-
var HookConfigSchema = z.object({
|
|
3786
|
-
event: z.enum(["PreToolUse", "PostToolUse", "Stop"]),
|
|
3787
|
-
matcher: z.string().refine(
|
|
3788
|
-
(pattern) => {
|
|
3789
|
-
try {
|
|
3790
|
-
new RegExp(pattern);
|
|
3791
|
-
return true;
|
|
3792
|
-
} catch {
|
|
3793
|
-
return false;
|
|
3794
|
-
}
|
|
3795
|
-
},
|
|
3796
|
-
{ message: "invalid regular expression" }
|
|
3797
|
-
).optional(),
|
|
3798
|
-
command: z.string().min(1),
|
|
3799
|
-
timeoutSec: z.number().positive().default(30)
|
|
3800
|
-
});
|
|
3801
|
-
var ConfigSchema = z.object({
|
|
3802
|
-
defaultModel: z.string().default(""),
|
|
3803
|
-
permissionMode: z.enum(["auto", "ask", "readonly", "yolo", "plan"]).default("ask"),
|
|
3804
|
-
providers: z.array(ProviderConfigSchema).default([]),
|
|
3805
|
-
models: z.array(ModelConfigSchema).default([]),
|
|
3806
|
-
maxSteps: z.number().int().positive().default(50),
|
|
3807
|
-
contextMaxTokens: z.number().int().positive().default(1e5),
|
|
3808
|
-
// Seconds without any stream part before a stalled response is ended
|
|
3809
|
-
// gracefully (some relays never send the terminal chunks). Applies once
|
|
3810
|
-
// streaming has started; the first part gets a longer, fixed allowance.
|
|
3811
|
-
streamIdleTimeoutSec: z.number().positive().default(20),
|
|
3812
|
-
contextCompaction: z.enum(["summary", "truncate"]).default("summary"),
|
|
3813
|
-
// Optional per-session dollar budget; when set, the REPL stops the session
|
|
3814
|
-
// once the estimated cost for the session reaches this amount.
|
|
3815
|
-
sessionBudgetUsd: z.number().positive().optional(),
|
|
3816
|
-
// Terminal bell (REPL only): ring when a turn takes longer than
|
|
3817
|
-
// notifyBellThresholdSec, and when a background task finishes.
|
|
3818
|
-
// STAR_NO_NOTIFY=1 disables without touching the config.
|
|
3819
|
-
notifyBell: z.boolean().default(true),
|
|
3820
|
-
notifyBellThresholdSec: z.number().positive().default(10),
|
|
3821
|
-
permissions: PermissionsConfigSchema.default({ allow: [] }),
|
|
3822
|
-
hooks: z.array(HookConfigSchema).default([])
|
|
3823
|
-
});
|
|
3824
|
-
|
|
3825
|
-
// src/config/loader.ts
|
|
3826
3846
|
var PartialConfigSchema = ConfigSchema.partial();
|
|
3827
3847
|
function parseTomlFile(content, filePath) {
|
|
3828
3848
|
let raw;
|
|
@@ -3874,7 +3894,8 @@ async function createLoop(config, modelName, cwd, sessionStore) {
|
|
|
3874
3894
|
config,
|
|
3875
3895
|
cwd,
|
|
3876
3896
|
system: SYSTEM_PROMPT,
|
|
3877
|
-
sessionStore
|
|
3897
|
+
sessionStore,
|
|
3898
|
+
contextMaxTokens: contextWindowTokens(config, modelName)
|
|
3878
3899
|
});
|
|
3879
3900
|
}
|
|
3880
3901
|
async function printMode(loop, prompt, cwd, json, imagePaths) {
|