@devstationlabs/cli 0.1.3 → 0.1.5

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 (2) hide show
  1. package/devstation.js +412 -167
  2. package/package.json +1 -1
package/devstation.js CHANGED
@@ -12510,7 +12510,7 @@ import { existsSync as existsSync12 } from "fs";
12510
12510
 
12511
12511
  // src/lib/agent/cli/args.ts
12512
12512
  var CLI_NAME = "devstation";
12513
- var VERSION = "0.1.3";
12513
+ var VERSION = "0.1.5";
12514
12514
  var COMMANDS = new Set([
12515
12515
  "chat",
12516
12516
  "run",
@@ -12731,7 +12731,7 @@ var SESSION_HELP = ` /undo rewind the last checkpoint
12731
12731
  import { accessSync, constants as constants2, existsSync as existsSync10 } from "fs";
12732
12732
 
12733
12733
  // src/lib/agent/git.ts
12734
- import { existsSync } from "fs";
12734
+ import { existsSync, readFileSync, statSync } from "fs";
12735
12735
  import { join } from "path";
12736
12736
 
12737
12737
  // src/lib/agent/shell.ts
@@ -12949,7 +12949,16 @@ function cloneTargetProblem(target) {
12949
12949
  // src/lib/agent/git.ts
12950
12950
  var CHECKPOINT_PREFIX = "agent checkpoint:";
12951
12951
  function isRepo(root) {
12952
- return existsSync(join(root, ".git"));
12952
+ const dotGit = join(root, ".git");
12953
+ if (!existsSync(dotGit))
12954
+ return false;
12955
+ try {
12956
+ if (statSync(dotGit).isFile())
12957
+ return readFileSync(dotGit, "utf8").startsWith("gitdir:");
12958
+ } catch {
12959
+ return false;
12960
+ }
12961
+ return existsSync(join(dotGit, "HEAD"));
12953
12962
  }
12954
12963
  function quote(value) {
12955
12964
  return `'${value.replace(/'/g, `'\\''`)}'`;
@@ -13074,21 +13083,25 @@ import {
13074
13083
  cpSync,
13075
13084
  existsSync as existsSync3,
13076
13085
  mkdirSync as mkdirSync2,
13077
- readFileSync as readFileSync2,
13078
- readdirSync as readdirSync2,
13086
+ readFileSync as readFileSync4,
13087
+ readdirSync as readdirSync3,
13079
13088
  rmSync,
13080
13089
  writeFileSync as writeFileSync2
13081
13090
  } from "fs";
13082
- import { dirname as dirname2, join as join3 } from "path";
13091
+ import { dirname as dirname2, join as join4 } from "path";
13092
+
13093
+ // src/lib/agent/repo-session.ts
13094
+ import { readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
13095
+ import { join as join3, relative as relative2, sep as sep2 } from "path";
13083
13096
 
13084
13097
  // src/lib/agent/workspace.ts
13085
13098
  import {
13086
13099
  existsSync as existsSync2,
13087
13100
  mkdirSync,
13088
- readFileSync,
13101
+ readFileSync as readFileSync2,
13089
13102
  readdirSync,
13090
13103
  realpathSync,
13091
- statSync,
13104
+ statSync as statSync2,
13092
13105
  writeFileSync
13093
13106
  } from "fs";
13094
13107
  import { dirname, isAbsolute, join as join2, relative, resolve, sep } from "path";
@@ -13165,10 +13178,10 @@ class Workspace {
13165
13178
  if (!existsSync2(resolved.absolute)) {
13166
13179
  return { ok: false, reason: `There is no file at ${relativePath}.` };
13167
13180
  }
13168
- if (statSync(resolved.absolute).isDirectory()) {
13181
+ if (statSync2(resolved.absolute).isDirectory()) {
13169
13182
  return { ok: false, reason: `${relativePath} is a directory, not a file.` };
13170
13183
  }
13171
- const buffer = readFileSync(resolved.absolute);
13184
+ const buffer = readFileSync2(resolved.absolute);
13172
13185
  if (looksBinary(buffer)) {
13173
13186
  return {
13174
13187
  ok: false,
@@ -13188,10 +13201,10 @@ class Workspace {
13188
13201
  if (!resolved.ok)
13189
13202
  return { ok: false, reason: resolved.reason };
13190
13203
  if (existsSync2(resolved.absolute)) {
13191
- if (statSync(resolved.absolute).isDirectory()) {
13204
+ if (statSync2(resolved.absolute).isDirectory()) {
13192
13205
  return { ok: false, reason: `${relativePath} is a directory.` };
13193
13206
  }
13194
- if (looksBinary(readFileSync(resolved.absolute))) {
13207
+ if (looksBinary(readFileSync2(resolved.absolute))) {
13195
13208
  return { ok: false, reason: `${relativePath} is a binary file and was not overwritten.` };
13196
13209
  }
13197
13210
  }
@@ -13203,7 +13216,7 @@ class Workspace {
13203
13216
  const resolved = this.resolve(relativePath);
13204
13217
  return resolved.ok && existsSync2(resolved.absolute);
13205
13218
  }
13206
- list(subdir = ".") {
13219
+ list(subdir = ".", limit = Number.POSITIVE_INFINITY) {
13207
13220
  const base = subdir === "." || subdir === "" ? { ok: true, absolute: this.root } : this.resolve(subdir);
13208
13221
  if (!base.ok || !existsSync2(base.absolute))
13209
13222
  return [];
@@ -13212,6 +13225,8 @@ class Workspace {
13212
13225
  const out = [];
13213
13226
  const walk = (dir) => {
13214
13227
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
13228
+ if (out.length >= limit)
13229
+ return;
13215
13230
  if (entry.name.startsWith(".") && skip.has(entry.name))
13216
13231
  continue;
13217
13232
  if (skip.has(entry.name))
@@ -13228,23 +13243,219 @@ class Workspace {
13228
13243
  }
13229
13244
  }
13230
13245
 
13246
+ // src/lib/agent/repo-session.ts
13247
+ var SKIP_DIRS = new Set([
13248
+ ".git",
13249
+ ".agent",
13250
+ ".devstation",
13251
+ "node_modules",
13252
+ ".next",
13253
+ "dist",
13254
+ "build",
13255
+ ".turbo"
13256
+ ]);
13257
+ function materialise(files, root) {
13258
+ const workspace = new Workspace(root);
13259
+ const refused = [];
13260
+ let written = 0;
13261
+ for (const [path, content] of Object.entries(files)) {
13262
+ const result = workspace.write(path, content);
13263
+ if (result.ok)
13264
+ written++;
13265
+ else
13266
+ refused.push({ path, why: result.reason });
13267
+ }
13268
+ return { written, refused };
13269
+ }
13270
+ async function initLocalRepo(root, ref) {
13271
+ const result = await runShell("git init -q && git add -A && " + "git -c user.name='DevStation agent' -c user.email='agent@devstation.online' " + `commit -q -m ${JSON.stringify(`base: ${ref}`)} --allow-empty`, { cwd: root, timeoutMs: 120000 });
13272
+ return result.ok;
13273
+ }
13274
+ function readWorkspace(root, maxFileBytes = 1024 * 1024) {
13275
+ const files = {};
13276
+ const walk = (dir) => {
13277
+ for (const item of readdirSync2(dir, { withFileTypes: true })) {
13278
+ if (item.isSymbolicLink())
13279
+ continue;
13280
+ const full = join3(dir, item.name);
13281
+ if (item.isDirectory()) {
13282
+ if (SKIP_DIRS.has(item.name))
13283
+ continue;
13284
+ walk(full);
13285
+ continue;
13286
+ }
13287
+ if (!item.isFile())
13288
+ continue;
13289
+ if (statSync3(full).size > maxFileBytes)
13290
+ continue;
13291
+ const buffer = readFileSync3(full);
13292
+ if (looksBinary(buffer))
13293
+ continue;
13294
+ files[relative2(root, full).split(sep2).join("/")] = buffer.toString("utf8");
13295
+ }
13296
+ };
13297
+ walk(root);
13298
+ return files;
13299
+ }
13300
+ var INDEX_SKIP_DIRS = new Set([
13301
+ ...SKIP_DIRS,
13302
+ "target",
13303
+ "coverage",
13304
+ "__pycache__",
13305
+ ".venv",
13306
+ "venv",
13307
+ ".gradle",
13308
+ "out"
13309
+ ]);
13310
+ var HIDDEN_KEEP = new Set([".github"]);
13311
+ function readWorkspaceBounded(root, opts = {}) {
13312
+ const maxFiles = opts.maxFiles ?? 20000;
13313
+ const until = Date.now() + (opts.deadlineMs ?? 1e4);
13314
+ const maxBytes = opts.maxFileBytes ?? 1024 * 1024;
13315
+ const files = {};
13316
+ let seen = 0;
13317
+ let stopped = null;
13318
+ const walk = (dir) => {
13319
+ let entries;
13320
+ try {
13321
+ entries = readdirSync2(dir, { withFileTypes: true });
13322
+ } catch {
13323
+ return;
13324
+ }
13325
+ for (const item of entries) {
13326
+ if (stopped)
13327
+ return;
13328
+ if (item.isSymbolicLink())
13329
+ continue;
13330
+ const full = join3(dir, item.name);
13331
+ if (item.isDirectory()) {
13332
+ if (INDEX_SKIP_DIRS.has(item.name))
13333
+ continue;
13334
+ if (item.name.startsWith(".") && !HIDDEN_KEEP.has(item.name))
13335
+ continue;
13336
+ walk(full);
13337
+ continue;
13338
+ }
13339
+ if (!item.isFile())
13340
+ continue;
13341
+ if (seen >= maxFiles) {
13342
+ stopped = "max-files";
13343
+ return;
13344
+ }
13345
+ if ((seen & 63) === 0 && Date.now() > until) {
13346
+ stopped = "deadline";
13347
+ return;
13348
+ }
13349
+ seen++;
13350
+ try {
13351
+ if (statSync3(full).size > maxBytes)
13352
+ continue;
13353
+ const buffer = readFileSync3(full);
13354
+ if (looksBinary(buffer))
13355
+ continue;
13356
+ files[relative2(root, full).split(sep2).join("/")] = buffer.toString("utf8");
13357
+ } catch {}
13358
+ }
13359
+ };
13360
+ walk(root);
13361
+ return { files, stopped };
13362
+ }
13363
+ var SNAPSHOT_SKIP_DIRS = new Set([...SKIP_DIRS, ".output", ".venv"]);
13364
+ function listWorkspaceBounded(root, opts = {}) {
13365
+ const maxFiles = opts.maxFiles ?? Number.POSITIVE_INFINITY;
13366
+ const maxBytes = opts.maxBytes ?? Number.POSITIVE_INFINITY;
13367
+ const until = Date.now() + (opts.deadlineMs ?? 1e4);
13368
+ const paths = [];
13369
+ let bytes = 0;
13370
+ let stopped = null;
13371
+ const walk = (dir) => {
13372
+ let entries;
13373
+ try {
13374
+ entries = readdirSync2(dir, { withFileTypes: true });
13375
+ } catch {
13376
+ return;
13377
+ }
13378
+ for (const item of entries) {
13379
+ if (stopped)
13380
+ return;
13381
+ if (item.isSymbolicLink())
13382
+ continue;
13383
+ const full = join3(dir, item.name);
13384
+ if (item.isDirectory()) {
13385
+ if (SNAPSHOT_SKIP_DIRS.has(item.name))
13386
+ continue;
13387
+ if (opts.skipHidden && item.name.startsWith(".") && !HIDDEN_KEEP.has(item.name))
13388
+ continue;
13389
+ walk(full);
13390
+ continue;
13391
+ }
13392
+ if (!item.isFile())
13393
+ continue;
13394
+ if (paths.length >= maxFiles) {
13395
+ stopped = "max-files";
13396
+ return;
13397
+ }
13398
+ if ((paths.length & 63) === 0 && Date.now() > until) {
13399
+ stopped = "deadline";
13400
+ return;
13401
+ }
13402
+ try {
13403
+ bytes += statSync3(full).size;
13404
+ } catch {
13405
+ continue;
13406
+ }
13407
+ if (bytes > maxBytes) {
13408
+ stopped = "max-bytes";
13409
+ return;
13410
+ }
13411
+ paths.push(relative2(root, full).split(sep2).join("/"));
13412
+ }
13413
+ };
13414
+ walk(root);
13415
+ return { paths: paths.sort(), bytes, stopped };
13416
+ }
13417
+ var MAX_TITLE = 72;
13418
+ function proposalFor(goal, result, files) {
13419
+ const firstLine = (goal || result.summary).split(`
13420
+ `)[0].trim();
13421
+ const title = firstLine.length > MAX_TITLE ? `${firstLine.slice(0, MAX_TITLE - 3)}...` : firstLine;
13422
+ const fileList = files.length === 0 ? "_No files changed._" : files.slice(0, 50).map((f) => `- \`${f}\``).join(`
13423
+ `) + (files.length > 50 ? `
13424
+ - ...and ${files.length - 50} more` : "");
13425
+ const body = [
13426
+ `**Asked for:** ${goal}`,
13427
+ "",
13428
+ result.summary || "No summary was produced.",
13429
+ "",
13430
+ "### Files changed",
13431
+ fileList,
13432
+ "",
13433
+ "---",
13434
+ `Opened by the DevStation coding agent after ${result.steps} step(s), ` + `about $${result.costUsd.toFixed(2)}. Review it as you would any other change: ` + "nothing here has been merged, and the agent could not open this itself."
13435
+ ].join(`
13436
+ `);
13437
+ return { title, body, message: `${title}
13438
+
13439
+ ${goal}` };
13440
+ }
13441
+
13231
13442
  // src/lib/agent/snapshots.ts
13232
13443
  var DIR = ".devstation/checkpoints";
13233
13444
  var KEEP = 20;
13234
13445
  function storeDir(root) {
13235
- return join3(root, DIR);
13446
+ return join4(root, DIR);
13236
13447
  }
13237
13448
  function listSnapshots(root) {
13238
13449
  const dir = storeDir(root);
13239
13450
  if (!existsSync3(dir))
13240
13451
  return [];
13241
13452
  const found = [];
13242
- for (const entry of readdirSync2(dir)) {
13243
- const manifest = join3(dir, entry, "manifest.json");
13453
+ for (const entry of readdirSync3(dir)) {
13454
+ const manifest = join4(dir, entry, "manifest.json");
13244
13455
  if (!existsSync3(manifest))
13245
13456
  continue;
13246
13457
  try {
13247
- found.push(JSON.parse(readFileSync2(manifest, "utf8")));
13458
+ found.push(JSON.parse(readFileSync4(manifest, "utf8")));
13248
13459
  } catch {}
13249
13460
  }
13250
13461
  return found.sort((a, b) => b.id.localeCompare(a.id));
@@ -13256,20 +13467,36 @@ function nextId(root) {
13256
13467
  }, 0);
13257
13468
  return `${String(highest + 1).padStart(6, "0")}-${Date.now()}`;
13258
13469
  }
13259
- function takeSnapshot(root, message) {
13260
- const workspace = new Workspace(root);
13261
- const files = workspace.list(".").filter((p) => !p.startsWith(`${DIR}/`) && !p.startsWith(".devstation/"));
13470
+ var SNAPSHOT_MAX_FILES = 5000;
13471
+ var SNAPSHOT_MAX_BYTES = 50 * 1024 * 1024;
13472
+ function takeSnapshot(root, message, opts = {}) {
13473
+ const home = opts.home ?? process.env.HOME ?? "";
13474
+ const strip = (p) => p.replace(/\/+$/, "");
13475
+ if (home && strip(root) === strip(home)) {
13476
+ opts.onSkip?.("a home directory is never copied aside. cd into a project to get undo.");
13477
+ return null;
13478
+ }
13479
+ const listed = listWorkspaceBounded(root, {
13480
+ maxFiles: opts.maxFiles ?? SNAPSHOT_MAX_FILES,
13481
+ maxBytes: opts.maxBytes ?? SNAPSHOT_MAX_BYTES
13482
+ });
13483
+ if (listed.stopped) {
13484
+ const limit = listed.stopped === "max-bytes" ? `${Math.round((opts.maxBytes ?? SNAPSHOT_MAX_BYTES) / 1024 / 1024)}MB` : listed.stopped === "max-files" ? `${(opts.maxFiles ?? SNAPSHOT_MAX_FILES).toLocaleString("en-US")} files` : "the time limit";
13485
+ opts.onSkip?.(`this workspace is larger than ${limit}, too large to copy aside each turn. Put it under git for undo.`);
13486
+ return null;
13487
+ }
13488
+ const files = listed.paths.filter((p) => !p.startsWith(`${DIR}/`) && !p.startsWith(".devstation/"));
13262
13489
  if (files.length === 0)
13263
13490
  return null;
13264
13491
  const id = nextId(root);
13265
- const dir = join3(storeDir(root), id);
13266
- mkdirSync2(join3(dir, "files"), { recursive: true });
13492
+ const dir = join4(storeDir(root), id);
13493
+ mkdirSync2(join4(dir, "files"), { recursive: true });
13267
13494
  const captured = [];
13268
13495
  for (const path of files) {
13269
- const from = join3(root, path);
13496
+ const from = join4(root, path);
13270
13497
  if (!existsSync3(from))
13271
13498
  continue;
13272
- const to = join3(dir, "files", path);
13499
+ const to = join4(dir, "files", path);
13273
13500
  mkdirSync2(dirname2(to), { recursive: true });
13274
13501
  try {
13275
13502
  cpSync(from, to);
@@ -13282,7 +13509,7 @@ function takeSnapshot(root, message) {
13282
13509
  message,
13283
13510
  files: captured
13284
13511
  };
13285
- writeFileSync2(join3(dir, "manifest.json"), `${JSON.stringify(snapshot, null, 2)}
13512
+ writeFileSync2(join4(dir, "manifest.json"), `${JSON.stringify(snapshot, null, 2)}
13286
13513
  `);
13287
13514
  prune(root);
13288
13515
  return snapshot;
@@ -13290,7 +13517,7 @@ function takeSnapshot(root, message) {
13290
13517
  function prune(root) {
13291
13518
  const all = listSnapshots(root);
13292
13519
  for (const old of all.slice(KEEP)) {
13293
- rmSync(join3(storeDir(root), old.id), { recursive: true, force: true });
13520
+ rmSync(join4(storeDir(root), old.id), { recursive: true, force: true });
13294
13521
  }
13295
13522
  }
13296
13523
  function undoSnapshot(root) {
@@ -13298,19 +13525,18 @@ function undoSnapshot(root) {
13298
13525
  if (!latest) {
13299
13526
  return { ok: false, message: "There is no checkpoint to undo." };
13300
13527
  }
13301
- const workspace = new Workspace(root);
13302
- const dir = join3(storeDir(root), latest.id);
13528
+ const dir = join4(storeDir(root), latest.id);
13303
13529
  const kept = new Set(latest.files);
13304
- for (const path of workspace.list(".")) {
13530
+ for (const path of listWorkspaceBounded(root).paths) {
13305
13531
  if (kept.has(path) || path.startsWith(".devstation/"))
13306
13532
  continue;
13307
- rmSync(join3(root, path), { force: true });
13533
+ rmSync(join4(root, path), { force: true });
13308
13534
  }
13309
13535
  for (const path of latest.files) {
13310
- const from = join3(dir, "files", path);
13536
+ const from = join4(dir, "files", path);
13311
13537
  if (!existsSync3(from))
13312
13538
  continue;
13313
- const to = join3(root, path);
13539
+ const to = join4(root, path);
13314
13540
  mkdirSync2(dirname2(to), { recursive: true });
13315
13541
  cpSync(from, to);
13316
13542
  }
@@ -13318,7 +13544,7 @@ function undoSnapshot(root) {
13318
13544
  return { ok: true, message: `Undid ${latest.message}` };
13319
13545
  }
13320
13546
  function discardSnapshot(root, id) {
13321
- rmSync(join3(storeDir(root), id), { recursive: true, force: true });
13547
+ rmSync(join4(storeDir(root), id), { recursive: true, force: true });
13322
13548
  }
13323
13549
 
13324
13550
  // src/lib/agent/policy.ts
@@ -17477,9 +17703,9 @@ var REDACTED = "[redacted]";
17477
17703
  function redact(input) {
17478
17704
  let text = input;
17479
17705
  const kinds = new Set;
17480
- text = text.replace(SECRET_KEY, (_m, key, sep2, q) => {
17706
+ text = text.replace(SECRET_KEY, (_m, key, sep3, q) => {
17481
17707
  kinds.add("assignment");
17482
- return `${key}${sep2}${q}${REDACTED}${q}`;
17708
+ return `${key}${sep3}${q}${REDACTED}${q}`;
17483
17709
  });
17484
17710
  for (const { name, re } of PATTERNS) {
17485
17711
  text = text.replace(re, () => {
@@ -17957,6 +18183,26 @@ class AnthropicProvider {
17957
18183
 
17958
18184
  // src/lib/agent/providers/openrouter.ts
17959
18185
  var OPENROUTER_BASE = "https://openrouter.ai/api/v1";
18186
+ var DEFAULT_MAX_TOKENS2 = Number(process.env.DEVSTATION_MAX_TOKENS) || 16000;
18187
+ var MIN_RETRY_TOKENS = 1024;
18188
+ function affordableTokens(detail) {
18189
+ const match = /can only afford (\d+)/i.exec(detail);
18190
+ return match ? Number(match[1]) : null;
18191
+ }
18192
+ function describeFailure(label, status, detail) {
18193
+ let message = detail;
18194
+ try {
18195
+ const parsed = JSON.parse(detail);
18196
+ if (parsed.error?.message)
18197
+ message = parsed.error.message;
18198
+ } catch {}
18199
+ if (status === 402 && /credit/i.test(message)) {
18200
+ return `${label} is out of credit for this request. Add credit at https://openrouter.ai/settings/credits, ` + "or use a cheaper model: devstation config set model anthropic/claude-sonnet-5";
18201
+ }
18202
+ if (status === 401)
18203
+ return `${label} rejected the API key. Run \`devstation login\` to store a new one.`;
18204
+ return `${label} request failed (${status}). ${message.slice(0, 300)}`;
18205
+ }
17960
18206
  function stopReasonOf2(raw) {
17961
18207
  switch (raw) {
17962
18208
  case "stop":
@@ -18019,7 +18265,7 @@ class OpenRouterProvider {
18019
18265
  }
18020
18266
  async generate(input) {
18021
18267
  const openRouter = this.name === "openrouter";
18022
- const res = await fetch(this.endpoint, {
18268
+ const send = (maxTokens) => fetch(this.endpoint, {
18023
18269
  method: "POST",
18024
18270
  headers: {
18025
18271
  "content-type": "application/json",
@@ -18030,7 +18276,7 @@ class OpenRouterProvider {
18030
18276
  model: this.model,
18031
18277
  messages: toOpenAiMessages(input.system, input.messages),
18032
18278
  stream: true,
18033
- max_tokens: input.maxTokens ?? 64000,
18279
+ max_tokens: maxTokens,
18034
18280
  temperature: 0.2,
18035
18281
  ...input.tools?.length ? {
18036
18282
  tools: input.tools.map((t) => ({
@@ -18046,10 +18292,21 @@ class OpenRouterProvider {
18046
18292
  }),
18047
18293
  signal: input.signal
18048
18294
  });
18295
+ const label = openRouter ? "OpenRouter" : `The endpoint ${this.endpoint}`;
18296
+ const requested = input.maxTokens ?? DEFAULT_MAX_TOKENS2;
18297
+ let res = await send(requested);
18298
+ if (res.status === 402 && openRouter) {
18299
+ const detail = await res.text().catch(() => "");
18300
+ const afford = affordableTokens(detail);
18301
+ if (afford !== null && afford >= MIN_RETRY_TOKENS && afford < requested) {
18302
+ res = await send(Math.floor(afford * 0.95));
18303
+ } else {
18304
+ throw new Error(describeFailure(label, res.status, detail));
18305
+ }
18306
+ }
18049
18307
  if (!res.ok || !res.body) {
18050
18308
  const detail = await res.text().catch(() => "");
18051
- const label = openRouter ? "OpenRouter" : `The endpoint ${this.endpoint}`;
18052
- throw new Error(`${label} request failed (${res.status}). ${detail.slice(0, 200)}`);
18309
+ throw new Error(describeFailure(label, res.status, detail));
18053
18310
  }
18054
18311
  let text = "";
18055
18312
  let finish = null;
@@ -18122,12 +18379,12 @@ import {
18122
18379
  chmodSync,
18123
18380
  existsSync as existsSync4,
18124
18381
  mkdirSync as mkdirSync3,
18125
- readFileSync as readFileSync3,
18382
+ readFileSync as readFileSync5,
18126
18383
  renameSync,
18127
- statSync as statSync2,
18384
+ statSync as statSync4,
18128
18385
  writeFileSync as writeFileSync3
18129
18386
  } from "fs";
18130
- import { dirname as dirname4, join as join5 } from "path";
18387
+ import { dirname as dirname4, join as join6 } from "path";
18131
18388
  var PROVIDER_IDS = ["anthropic", "openrouter", "openai"];
18132
18389
  var SETTING_KEYS = ["provider", "model", "baseUrl"];
18133
18390
  var KEY_ENV = {
@@ -18141,16 +18398,16 @@ var DEFAULT_BASE_URL = {
18141
18398
  openai: "https://api.openai.com/v1"
18142
18399
  };
18143
18400
  function globalDir(home) {
18144
- return join5(home, ".devstation");
18401
+ return join6(home, ".devstation");
18145
18402
  }
18146
18403
  function globalConfigPath(home) {
18147
- return join5(globalDir(home), "config.json");
18404
+ return join6(globalDir(home), "config.json");
18148
18405
  }
18149
18406
  function projectConfigPath(root) {
18150
- return join5(root, ".devstation", "config.json");
18407
+ return join6(root, ".devstation", "config.json");
18151
18408
  }
18152
18409
  function credentialsPath(home) {
18153
- return join5(globalDir(home), "credentials.json");
18410
+ return join6(globalDir(home), "credentials.json");
18154
18411
  }
18155
18412
  function isProvider(value) {
18156
18413
  return typeof value === "string" && PROVIDER_IDS.includes(value);
@@ -18159,7 +18416,7 @@ function readSettingsFile(path4, problems = []) {
18159
18416
  if (!existsSync4(path4))
18160
18417
  return {};
18161
18418
  try {
18162
- const raw = JSON.parse(readFileSync3(path4, "utf8"));
18419
+ const raw = JSON.parse(readFileSync5(path4, "utf8"));
18163
18420
  const out = {};
18164
18421
  if (raw.provider !== undefined) {
18165
18422
  const named = typeof raw.provider === "string" ? raw.provider.toLowerCase() : raw.provider;
@@ -18204,11 +18461,11 @@ function readCredentials(home, problems = []) {
18204
18461
  if (!existsSync4(path4))
18205
18462
  return {};
18206
18463
  try {
18207
- const mode = statSync2(path4).mode & 511;
18464
+ const mode = statSync4(path4).mode & 511;
18208
18465
  if (mode & 63) {
18209
18466
  problems.push(`${path4} is readable by other users (mode ${mode.toString(8)}). Fix it: chmod 600 ${path4}`);
18210
18467
  }
18211
- const raw = JSON.parse(readFileSync3(path4, "utf8"));
18468
+ const raw = JSON.parse(readFileSync5(path4, "utf8"));
18212
18469
  const out = {};
18213
18470
  for (const id of PROVIDER_IDS) {
18214
18471
  if (typeof raw[id] === "string" && raw[id].trim())
@@ -18455,87 +18712,6 @@ async function embedMissing(store, provider, options = {}) {
18455
18712
  // src/lib/agent/memory/workspace-index.ts
18456
18713
  import { join as join7 } from "path";
18457
18714
 
18458
- // src/lib/agent/repo-session.ts
18459
- import { readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
18460
- import { join as join6, relative as relative2, sep as sep2 } from "path";
18461
- var SKIP_DIRS = new Set([
18462
- ".git",
18463
- ".agent",
18464
- ".devstation",
18465
- "node_modules",
18466
- ".next",
18467
- "dist",
18468
- "build",
18469
- ".turbo"
18470
- ]);
18471
- function materialise(files, root) {
18472
- const workspace = new Workspace(root);
18473
- const refused = [];
18474
- let written = 0;
18475
- for (const [path4, content] of Object.entries(files)) {
18476
- const result = workspace.write(path4, content);
18477
- if (result.ok)
18478
- written++;
18479
- else
18480
- refused.push({ path: path4, why: result.reason });
18481
- }
18482
- return { written, refused };
18483
- }
18484
- async function initLocalRepo(root, ref) {
18485
- const result = await runShell("git init -q && git add -A && " + "git -c user.name='DevStation agent' -c user.email='agent@devstation.online' " + `commit -q -m ${JSON.stringify(`base: ${ref}`)} --allow-empty`, { cwd: root, timeoutMs: 120000 });
18486
- return result.ok;
18487
- }
18488
- function readWorkspace(root, maxFileBytes = 1024 * 1024) {
18489
- const files = {};
18490
- const walk3 = (dir) => {
18491
- for (const item of readdirSync3(dir, { withFileTypes: true })) {
18492
- if (item.isSymbolicLink())
18493
- continue;
18494
- const full = join6(dir, item.name);
18495
- if (item.isDirectory()) {
18496
- if (SKIP_DIRS.has(item.name))
18497
- continue;
18498
- walk3(full);
18499
- continue;
18500
- }
18501
- if (!item.isFile())
18502
- continue;
18503
- if (statSync3(full).size > maxFileBytes)
18504
- continue;
18505
- const buffer = readFileSync4(full);
18506
- if (looksBinary(buffer))
18507
- continue;
18508
- files[relative2(root, full).split(sep2).join("/")] = buffer.toString("utf8");
18509
- }
18510
- };
18511
- walk3(root);
18512
- return files;
18513
- }
18514
- var MAX_TITLE = 72;
18515
- function proposalFor(goal, result, files) {
18516
- const firstLine = (goal || result.summary).split(`
18517
- `)[0].trim();
18518
- const title = firstLine.length > MAX_TITLE ? `${firstLine.slice(0, MAX_TITLE - 3)}...` : firstLine;
18519
- const fileList = files.length === 0 ? "_No files changed._" : files.slice(0, 50).map((f) => `- \`${f}\``).join(`
18520
- `) + (files.length > 50 ? `
18521
- - ...and ${files.length - 50} more` : "");
18522
- const body = [
18523
- `**Asked for:** ${goal}`,
18524
- "",
18525
- result.summary || "No summary was produced.",
18526
- "",
18527
- "### Files changed",
18528
- fileList,
18529
- "",
18530
- "---",
18531
- `Opened by the DevStation coding agent after ${result.steps} step(s), ` + `about $${result.costUsd.toFixed(2)}. Review it as you would any other change: ` + "nothing here has been merged, and the agent could not open this itself."
18532
- ].join(`
18533
- `);
18534
- return { title, body, message: `${title}
18535
-
18536
- ${goal}` };
18537
- }
18538
-
18539
18715
  // src/lib/agent/memory/store.ts
18540
18716
  import { Database } from "bun:sqlite";
18541
18717
  import { createHash } from "crypto";
@@ -18879,7 +19055,7 @@ class MemoryStore {
18879
19055
  this.db.run("DELETE FROM chunks WHERE path = ?", [path4]);
18880
19056
  this.db.run("DELETE FROM files WHERE path = ?", [path4]);
18881
19057
  }
18882
- reindex(files) {
19058
+ reindex(files, options = {}) {
18883
19059
  const result = { scanned: 0, reindexed: 0, removed: 0, chunks: 0 };
18884
19060
  this.db.run("BEGIN");
18885
19061
  try {
@@ -18892,9 +19068,11 @@ class MemoryStore {
18892
19068
  result.chunks += this.replaceFile(path4, content);
18893
19069
  result.reindexed++;
18894
19070
  }
18895
- for (const gone of known) {
18896
- this.removeFile(gone);
18897
- result.removed++;
19071
+ if (!options.partial) {
19072
+ for (const gone of known) {
19073
+ this.removeFile(gone);
19074
+ result.removed++;
19075
+ }
18898
19076
  }
18899
19077
  this.db.run("COMMIT");
18900
19078
  } catch (error2) {
@@ -18979,8 +19157,27 @@ function openStore(root) {
18979
19157
  return new MemoryStore(storePath(root));
18980
19158
  }
18981
19159
  async function indexWorkspace(root, options = {}) {
19160
+ const started = Date.now();
19161
+ const home = options.home ?? process.env.HOME ?? "";
19162
+ const strip2 = (p) => p.replace(/\/+$/, "");
19163
+ if (home && strip2(root) === strip2(home)) {
19164
+ return {
19165
+ scanned: 0,
19166
+ reindexed: 0,
19167
+ removed: 0,
19168
+ chunks: 0,
19169
+ embedded: 0,
19170
+ embeddingModel: null,
19171
+ stopped: "home",
19172
+ ms: 0
19173
+ };
19174
+ }
18982
19175
  const store = options.store ?? openStore(root);
18983
- const result = store.reindex(readWorkspace(root));
19176
+ const read = readWorkspaceBounded(root, {
19177
+ maxFiles: options.maxFiles,
19178
+ deadlineMs: options.deadlineMs
19179
+ });
19180
+ const result = store.reindex(read.files, { partial: read.stopped !== null });
18984
19181
  let embedded = 0;
18985
19182
  if (options.embeddings) {
18986
19183
  try {
@@ -18989,11 +19186,17 @@ async function indexWorkspace(root, options = {}) {
18989
19186
  embedded = 0;
18990
19187
  }
18991
19188
  }
18992
- return { ...result, embedded, embeddingModel: options.embeddings?.model ?? null };
19189
+ return {
19190
+ ...result,
19191
+ embedded,
19192
+ embeddingModel: options.embeddings?.model ?? null,
19193
+ stopped: read.stopped,
19194
+ ms: Date.now() - started
19195
+ };
18993
19196
  }
18994
19197
 
18995
19198
  // src/lib/agent/memory/project-memory.ts
18996
- import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
19199
+ import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
18997
19200
  import { dirname as dirname6, join as join8 } from "path";
18998
19201
  var DEFAULT_FILE = "PROJECT_MEMORY.md";
18999
19202
  var HEADER = `# Project memory
@@ -19012,7 +19215,7 @@ function readMemory(root) {
19012
19215
  const path4 = memoryPath(root);
19013
19216
  if (!existsSync5(path4))
19014
19217
  return [];
19015
- return parseMemory(readFileSync5(path4, "utf8"));
19218
+ return parseMemory(readFileSync6(path4, "utf8"));
19016
19219
  }
19017
19220
  var ENTRY = /^- \[([^\]]+)\](?:\s*\(([^)]*)\))?\s+([\s\S]*)$/;
19018
19221
  function parseMemory(text) {
@@ -19036,7 +19239,7 @@ function remember(root, note, tag = null, now = new Date) {
19036
19239
  if (!text) {
19037
19240
  return { ok: false, path: path4, message: "There was nothing to remember." };
19038
19241
  }
19039
- const existing = existsSync5(path4) ? readFileSync5(path4, "utf8") : "";
19242
+ const existing = existsSync5(path4) ? readFileSync6(path4, "utf8") : "";
19040
19243
  const entries = parseMemory(existing);
19041
19244
  const normal = (value) => value.toLowerCase().replace(/\s+/g, " ").trim();
19042
19245
  if (entries.some((entry) => normal(entry.note) === normal(text))) {
@@ -19072,7 +19275,7 @@ function renderMemory(entries) {
19072
19275
 
19073
19276
  // src/lib/agent/mcp.ts
19074
19277
  import { spawn as spawn3 } from "child_process";
19075
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
19278
+ import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
19076
19279
  import { join as join9 } from "path";
19077
19280
  function configPaths(root, home = process.env.HOME ?? "") {
19078
19281
  return [
@@ -19087,7 +19290,7 @@ function loadConfig(root, home = process.env.HOME ?? "") {
19087
19290
  if (!existsSync6(path4))
19088
19291
  continue;
19089
19292
  try {
19090
- const parsed = JSON.parse(readFileSync6(path4, "utf8"));
19293
+ const parsed = JSON.parse(readFileSync7(path4, "utf8"));
19091
19294
  for (const [name, server] of Object.entries(parsed.mcpServers ?? {})) {
19092
19295
  if (server && typeof server.command === "string")
19093
19296
  merged.mcpServers[name] = server;
@@ -19347,11 +19550,11 @@ function failedResult(message) {
19347
19550
 
19348
19551
  // src/lib/agent/sandbox-exec.ts
19349
19552
  import { randomBytes } from "crypto";
19350
- import { existsSync as existsSync8, statSync as statSync5, unlinkSync } from "fs";
19553
+ import { existsSync as existsSync8, statSync as statSync6, unlinkSync } from "fs";
19351
19554
  import { isAbsolute as isAbsolute3, join as join11, relative as relative4, resolve as resolve3, sep as sep4 } from "path";
19352
19555
 
19353
19556
  // src/lib/agent/project.ts
19354
- import { existsSync as existsSync7, readFileSync as readFileSync7, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
19557
+ import { existsSync as existsSync7, readFileSync as readFileSync8, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
19355
19558
  import { dirname as dirname7, join as join10, relative as relative3, sep as sep3 } from "path";
19356
19559
  var SKIP = new Set([
19357
19560
  "node_modules",
@@ -19378,7 +19581,7 @@ var MANIFESTS = [
19378
19581
  var MAX_DEPTH = 2;
19379
19582
  function readScripts(absolute) {
19380
19583
  try {
19381
- const parsed = JSON.parse(readFileSync7(absolute, "utf8"));
19584
+ const parsed = JSON.parse(readFileSync8(absolute, "utf8"));
19382
19585
  return parsed.scripts ?? {};
19383
19586
  } catch {
19384
19587
  return {};
@@ -19411,7 +19614,7 @@ function detectManifests(root) {
19411
19614
  continue;
19412
19615
  const child = join10(dir, entry);
19413
19616
  try {
19414
- if (statSync4(child).isDirectory())
19617
+ if (statSync5(child).isDirectory())
19415
19618
  scan(child, depth + 1);
19416
19619
  } catch {}
19417
19620
  }
@@ -19685,7 +19888,7 @@ function sandboxExecutor(options) {
19685
19888
  return "The sandbox could not write to the workspace. Check the mount and try --no-sandbox.";
19686
19889
  }
19687
19890
  try {
19688
- const stat2 = statSync5(path4);
19891
+ const stat2 = statSync6(path4);
19689
19892
  const [uid, gid] = user.split(":").map(Number);
19690
19893
  if (stat2.uid !== uid || stat2.gid !== gid) {
19691
19894
  return `The sandbox writes files as ${stat2.uid}:${stat2.gid} but this account is ${user}. ` + "Files it creates would not be yours to edit, and git would stop trusting the " + "repository, so it was not started. Run with --no-sandbox, or set " + "DEVSTATION_SANDBOX_USER.";
@@ -19829,9 +20032,11 @@ import { readdirSync as readdirSync5 } from "fs";
19829
20032
 
19830
20033
  // src/lib/agent/pricing.ts
19831
20034
  var TABLE = [
19832
- { match: /opus/i, rates: { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 } },
19833
- { match: /haiku/i, rates: { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 } },
19834
- { match: /sonnet/i, rates: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 } }
20035
+ { match: /fable-5[.-]1/i, rates: { input: 10, output: 50, cacheRead: 0.25, cacheWrite: 12.5 } },
20036
+ { match: /fable/i, rates: { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 } },
20037
+ { match: /opus/i, rates: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 } },
20038
+ { match: /haiku/i, rates: { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 } },
20039
+ { match: /sonnet/i, rates: { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 } }
19835
20040
  ];
19836
20041
  function ratesFor(model, env2 = process.env) {
19837
20042
  const override = {
@@ -19953,8 +20158,20 @@ function fail(reason) {
19953
20158
  function executeFileTool(workspace, name, args) {
19954
20159
  switch (name) {
19955
20160
  case "list_files": {
19956
- const files = workspace.list(String(args.path ?? "."));
19957
- return files.length === 0 ? { ok: true, output: "The workspace is empty." } : { ok: true, output: files.join(`
20161
+ const LIMIT = 2000;
20162
+ const files = workspace.list(String(args.path ?? "."), LIMIT + 1);
20163
+ if (files.length === 0)
20164
+ return { ok: true, output: "The workspace is empty." };
20165
+ if (files.length > LIMIT) {
20166
+ return {
20167
+ ok: true,
20168
+ output: `${files.slice(0, LIMIT).join(`
20169
+ `)}
20170
+
20171
+ ` + `(Stopped at ${LIMIT} files: this directory is very large. List a subdirectory, or search for what you need.)`
20172
+ };
20173
+ }
20174
+ return { ok: true, output: files.join(`
19958
20175
  `) };
19959
20176
  }
19960
20177
  case "read_file": {
@@ -19991,7 +20208,10 @@ function executeFileTool(workspace, name, args) {
19991
20208
  return fail("No search query was given.");
19992
20209
  const hits = [];
19993
20210
  let truncated = false;
19994
- for (const path4 of workspace.list()) {
20211
+ const MAX_SCAN = 20000;
20212
+ const scanned = workspace.list(".", MAX_SCAN + 1);
20213
+ const scanLimited = scanned.length > MAX_SCAN;
20214
+ for (const path4 of scanned.slice(0, MAX_SCAN)) {
19995
20215
  const file = workspace.read(path4);
19996
20216
  if (!file.ok)
19997
20217
  continue;
@@ -20010,14 +20230,18 @@ function executeFileTool(workspace, name, args) {
20010
20230
  if (truncated)
20011
20231
  break;
20012
20232
  }
20013
- if (hits.length === 0)
20014
- return { ok: true, output: `No match for "${args.query}".` };
20233
+ const scanNote = scanLimited ? `
20234
+
20235
+ (Searched the first ${MAX_SCAN} files only: this workspace is very large. Search a subdirectory.)` : "";
20236
+ if (hits.length === 0) {
20237
+ return { ok: true, output: `No match for "${args.query}".${scanNote}` };
20238
+ }
20015
20239
  return {
20016
20240
  ok: true,
20017
- output: truncated ? `${hits.join(`
20241
+ output: `${truncated ? `${hits.join(`
20018
20242
  `)}
20019
20243
  \u2026 more matches not shown` : hits.join(`
20020
- `)
20244
+ `)}${scanNote}`
20021
20245
  };
20022
20246
  }
20023
20247
  default:
@@ -20026,7 +20250,7 @@ function executeFileTool(workspace, name, args) {
20026
20250
  }
20027
20251
 
20028
20252
  // src/lib/agent/memory/retrieve.ts
20029
- var DEFAULT_MAX_TOKENS2 = 6000;
20253
+ var DEFAULT_MAX_TOKENS3 = 6000;
20030
20254
  var DEFAULT_CANDIDATES = 40;
20031
20255
  var RRF_K = 60;
20032
20256
  var TEST_PATH = /(^|\/)(?:tests?|__tests__|spec|e2e)(\/|$)|\.(?:test|spec)\.[a-z]+$/i;
@@ -20056,7 +20280,7 @@ async function retrieve(store, query2, options = {}) {
20056
20280
  score: entry.score * weightFor(entry.chunk.path),
20057
20281
  from: [...entry.from]
20058
20282
  })).sort((a, b) => b.score - a.score);
20059
- const budget = (options.maxTokens ?? DEFAULT_MAX_TOKENS2) - PREAMBLE_TOKENS;
20283
+ const budget = (options.maxTokens ?? DEFAULT_MAX_TOKENS3) - PREAMBLE_TOKENS;
20060
20284
  return withinBudget(ordered, budget, HEADER_TOKENS);
20061
20285
  }
20062
20286
  async function vectorSearch(store, query2, limit2, options) {
@@ -20339,6 +20563,7 @@ ${goal}` : goal }
20339
20563
  let summary = "";
20340
20564
  let stoppedBecause = "finished";
20341
20565
  const usesGit = isRepo(this.opts.workspace.root);
20566
+ let snapshotSkip = null;
20342
20567
  for (;; ) {
20343
20568
  const budget = this.budgetCheck(steps, started);
20344
20569
  if (budget) {
@@ -20372,7 +20597,15 @@ ${goal}` : goal }
20372
20597
  }
20373
20598
  messages.push({ role: "assistant", content: result.text, toolCalls: result.toolCalls });
20374
20599
  this.progress(steps, summary, messages);
20375
- const pending = usesGit ? null : takeSnapshot(this.opts.workspace.root, goal.slice(0, 80));
20600
+ let pending = null;
20601
+ if (!usesGit && snapshotSkip === null) {
20602
+ pending = takeSnapshot(this.opts.workspace.root, goal.slice(0, 80), {
20603
+ onSkip: (reason) => {
20604
+ snapshotSkip = reason;
20605
+ this.emit("plan", `No undo for this run: ${reason}`);
20606
+ }
20607
+ });
20608
+ }
20376
20609
  const turn = { applied: [], failed: [] };
20377
20610
  for (const call of result.toolCalls) {
20378
20611
  steps++;
@@ -20738,10 +20971,10 @@ import {
20738
20971
  appendFileSync,
20739
20972
  existsSync as existsSync9,
20740
20973
  mkdirSync as mkdirSync6,
20741
- readFileSync as readFileSync8,
20974
+ readFileSync as readFileSync9,
20742
20975
  readdirSync as readdirSync6,
20743
20976
  renameSync as renameSync2,
20744
- statSync as statSync6,
20977
+ statSync as statSync7,
20745
20978
  writeFileSync as writeFileSync5
20746
20979
  } from "fs";
20747
20980
  import { join as join12 } from "path";
@@ -20804,7 +21037,7 @@ class SessionStore {
20804
21037
  if (!existsSync9(path4))
20805
21038
  return null;
20806
21039
  try {
20807
- return JSON.parse(readFileSync8(path4, "utf8"));
21040
+ return JSON.parse(readFileSync9(path4, "utf8"));
20808
21041
  } catch {
20809
21042
  return null;
20810
21043
  }
@@ -20826,10 +21059,10 @@ class SessionStore {
20826
21059
  const path4 = this.eventPath(id);
20827
21060
  if (!existsSync9(path4))
20828
21061
  return { events: [], offset: 0 };
20829
- const size = statSync6(path4).size;
21062
+ const size = statSync7(path4).size;
20830
21063
  if (size <= fromByte)
20831
21064
  return { events: [], offset: size };
20832
- const text = readFileSync8(path4, "utf8").slice(fromByte);
21065
+ const text = readFileSync9(path4, "utf8").slice(fromByte);
20833
21066
  const events = [];
20834
21067
  let consumed = 0;
20835
21068
  for (const line of text.split(`
@@ -20973,9 +21206,21 @@ async function runCommand(context, goal, options = {}) {
20973
21206
  executor = built.executor;
20974
21207
  }
20975
21208
  const embeddings = embeddingsFromEnv();
21209
+ const firstIndex = !existsSync10(storePath(context.root));
21210
+ if (firstIndex && !options.quiet)
21211
+ terminal.out("Indexing the workspace so the agent can search it\u2026");
20976
21212
  const memory = openStore(context.root);
20977
21213
  try {
20978
- await indexWorkspace(context.root, { store: memory, embeddings });
21214
+ const indexed = await indexWorkspace(context.root, { store: memory, embeddings });
21215
+ if (!options.quiet) {
21216
+ if (indexed.stopped === "home") {
21217
+ terminal.err("Not indexing your home directory. The agent can still read and list files; cd into a project for a real index.");
21218
+ } else if (indexed.stopped) {
21219
+ terminal.err(`Indexed ${indexed.scanned} files and stopped at the ${indexed.stopped === "deadline" ? "time" : "file"} limit: this workspace is very large. cd into the project you mean.`);
21220
+ } else if (firstIndex || indexed.ms > 1500) {
21221
+ terminal.out(`Indexed ${indexed.scanned} files in ${(indexed.ms / 1000).toFixed(1)}s.`);
21222
+ }
21223
+ }
20979
21224
  } catch {}
20980
21225
  const mcp = await McpHub.start(loadConfig(context.root));
20981
21226
  for (const status of mcp.status) {
@@ -22160,7 +22405,7 @@ function lineReader(rl, write) {
22160
22405
  // src/lib/agent/cli/upgrade.ts
22161
22406
  import { spawnSync } from "child_process";
22162
22407
  import { createHash as createHash2 } from "crypto";
22163
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync8, readFileSync as readFileSync9, renameSync as renameSync3, writeFileSync as writeFileSync7 } from "fs";
22408
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync8, readFileSync as readFileSync10, renameSync as renameSync3, writeFileSync as writeFileSync7 } from "fs";
22164
22409
  import { dirname as dirname8, join as join14 } from "path";
22165
22410
  var PACKAGE = "@devstationlabs/cli";
22166
22411
  var REPO = "linoxbt/dev-shipyard";
@@ -22301,7 +22546,7 @@ function notifyIfOutdated(terminal, opts = {}) {
22301
22546
  const path4 = join14(homeDir, ".devstation", "update-check.json");
22302
22547
  let cached = {};
22303
22548
  try {
22304
- cached = JSON.parse(readFileSync9(path4, "utf8"));
22549
+ cached = JSON.parse(readFileSync10(path4, "utf8"));
22305
22550
  } catch {}
22306
22551
  if (cached.latest && compareVersions(cached.latest, VERSION) > 0) {
22307
22552
  terminal.err(`devstation ${cached.latest} is available (you have ${VERSION}). Run: devstation upgrade`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devstationlabs/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "The DevStation coding agent, in your terminal.",
5
5
  "license": "MIT",
6
6
  "type": "module",