cruo-agent 0.1.0 → 0.1.2

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 CHANGED
@@ -60,6 +60,28 @@ Run it from the repository the agent should work on. If that repository needs
60
60
  installing before its tests will run, add `--prepare 'npm ci'` or whatever your
61
61
  equivalent is — a fresh checkout has your source and none of your dependencies.
62
62
 
63
+ Checkouts go under `~/.cruo-work/<agent>`, one directory per agent, so **running
64
+ several agents at once needs no extra flags**. `--worktree-root` overrides it if
65
+ you want them somewhere else; pointing two agents at one root is safe too, since
66
+ the sweep that clears abandoned checkouts at startup only removes ones too old
67
+ for any run to still be inside.
68
+
69
+ ## When it stops doing anything
70
+
71
+ A supervisor sitting idle costs nothing — it is a database query on a timer. If
72
+ the *harness* refuses to work, though, the supervisor says so and slows down:
73
+
74
+ ```
75
+ ! the harness is refusing to work: the account's spend limit is reached
76
+ No card is charged for this, and polling backs off until it changes.
77
+ ```
78
+
79
+ That distinction matters more than it looks. A refusal is a fact about your
80
+ account, not about the card, so those runs are **not** counted against the
81
+ card's `--max-attempts` — otherwise one billing problem would quietly set aside
82
+ every issue the agent was holding, and you would come back to a board that had
83
+ given up on work nobody abandoned.
84
+
63
85
  ## Common options
64
86
 
65
87
  | flag | |
@@ -74,7 +96,7 @@ equivalent is — a fresh checkout has your source and none of your dependencies
74
96
  | `--harness-timeout <seconds>` | kill a run that wedges (default 600) |
75
97
  | `--max-attempts <n>` | give up on a card after n unproductive runs (default 3) |
76
98
 
77
- The full list, and what each is for: <https://cruo.space/agents>
99
+ The full list, and what each is for: <https://cruo.space/docs#agents>
78
100
 
79
101
  ## Running it somewhere that is not your laptop
80
102
 
package/dist/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.2
package/dist/cli.js CHANGED
@@ -45,6 +45,64 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
45
45
  ));
46
46
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
47
47
 
48
+ // src/options.ts
49
+ function stripTokenFlag(argv2) {
50
+ const at = argv2.indexOf("--token");
51
+ if (at < 0) return [...argv2];
52
+ return [...argv2.slice(0, at), ...argv2.slice(at + 2)];
53
+ }
54
+ function numericIn(argv2, name, spec = {}) {
55
+ const i = argv2.indexOf(`--${name}`);
56
+ if (i < 0) return spec.fallback;
57
+ const raw = argv2[i + 1];
58
+ if (raw === void 0 || raw.startsWith("--")) {
59
+ throw new OptionError(
60
+ `--${name} expects a ${spec.integer ? "whole " : ""}number${spec.unit ? ` of ${spec.unit}` : ""}, but no value followed it.`
61
+ );
62
+ }
63
+ const value = raw.trim() === "" ? NaN : Number(raw);
64
+ const what = `--${name} ${JSON.stringify(raw)}`;
65
+ if (!Number.isFinite(value)) {
66
+ throw new OptionError(
67
+ `${what} is not a number.` + // Reached only by something like `-abc`. A plain `-5` is a finite
68
+ // negative and falls to the range check below, which is the accurate
69
+ // refusal for it — this hint is for the case where the leading dash is
70
+ // the reason it did not parse at all.
71
+ (raw.startsWith("-") ? " A leading `-` reads as a flag, not a sign." : "")
72
+ );
73
+ }
74
+ if (value <= 0) {
75
+ throw new OptionError(`${what} must be greater than zero${spec.unit ? ` ${spec.unit}` : ""}.`);
76
+ }
77
+ if (spec.integer && !Number.isInteger(value)) {
78
+ throw new OptionError(`${what} must be a whole number.`);
79
+ }
80
+ return value;
81
+ }
82
+ function requiredNumericIn(argv2, name, fallback, spec = {}) {
83
+ return numericIn(argv2, name, { ...spec, fallback });
84
+ }
85
+ function secondsIn(argv2, name, fallbackSeconds) {
86
+ return requiredNumericIn(argv2, name, fallbackSeconds, { unit: "seconds" }) * 1e3;
87
+ }
88
+ var OptionError, flagIn, optIn;
89
+ var init_options = __esm({
90
+ "src/options.ts"() {
91
+ "use strict";
92
+ OptionError = class extends Error {
93
+ constructor(message) {
94
+ super(message);
95
+ this.name = "OptionError";
96
+ }
97
+ };
98
+ flagIn = (argv2, name) => argv2.includes(`--${name}`);
99
+ optIn = (argv2, name, fallback) => {
100
+ const i = argv2.indexOf(`--${name}`);
101
+ return i >= 0 && argv2[i + 1] && !argv2[i + 1].startsWith("--") ? argv2[i + 1] : fallback;
102
+ };
103
+ }
104
+ });
105
+
48
106
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/core.js
49
107
  // @__NO_SIDE_EFFECTS__
50
108
  function $constructor(name, initializer3, params) {
@@ -15238,10 +15296,13 @@ var init_plans = __esm({
15238
15296
  });
15239
15297
 
15240
15298
  // ../../packages/core/dist/mcp-connect.js
15241
- var CRUO_CLOUD;
15299
+ var DOCS_URL, AGENT_DOCS_URL, MCP_DOCS_URL, CRUO_CLOUD;
15242
15300
  var init_mcp_connect = __esm({
15243
15301
  "../../packages/core/dist/mcp-connect.js"() {
15244
15302
  "use strict";
15303
+ DOCS_URL = "https://cruo.space/docs";
15304
+ AGENT_DOCS_URL = `${DOCS_URL}#agents`;
15305
+ MCP_DOCS_URL = `${DOCS_URL}#mcp`;
15245
15306
  CRUO_CLOUD = {
15246
15307
  apiUrl: "https://szsutuujxbldkykcvdrm.supabase.co",
15247
15308
  publishableKey: "sb_publishable_hpnQFQ8IV0JXTch51XAstA_-S8H1eaD",
@@ -45940,62 +46001,34 @@ var init_auth = __esm({
45940
46001
  }
45941
46002
  });
45942
46003
 
45943
- // src/options.ts
45944
- function numericIn(argv2, name, spec = {}) {
45945
- const i = argv2.indexOf(`--${name}`);
45946
- if (i < 0) return spec.fallback;
45947
- const raw = argv2[i + 1];
45948
- if (raw === void 0 || raw.startsWith("--")) {
45949
- throw new OptionError(
45950
- `--${name} expects a ${spec.integer ? "whole " : ""}number${spec.unit ? ` of ${spec.unit}` : ""}, but no value followed it.`
45951
- );
46004
+ // src/harness-signal.ts
46005
+ function refusalIn(stdout) {
46006
+ if (!stdout.trim()) return null;
46007
+ for (const [pattern, reason] of REFUSALS) {
46008
+ if (pattern.test(stdout)) return reason;
45952
46009
  }
45953
- const value = raw.trim() === "" ? NaN : Number(raw);
45954
- const what = `--${name} ${JSON.stringify(raw)}`;
45955
- if (!Number.isFinite(value)) {
45956
- throw new OptionError(
45957
- `${what} is not a number.` + // Reached only by something like `-abc`. A plain `-5` is a finite
45958
- // negative and falls to the range check below, which is the accurate
45959
- // refusal for it — this hint is for the case where the leading dash is
45960
- // the reason it did not parse at all.
45961
- (raw.startsWith("-") ? " A leading `-` reads as a flag, not a sign." : "")
45962
- );
45963
- }
45964
- if (value <= 0) {
45965
- throw new OptionError(`${what} must be greater than zero${spec.unit ? ` ${spec.unit}` : ""}.`);
45966
- }
45967
- if (spec.integer && !Number.isInteger(value)) {
45968
- throw new OptionError(`${what} must be a whole number.`);
45969
- }
45970
- return value;
45971
- }
45972
- function requiredNumericIn(argv2, name, fallback, spec = {}) {
45973
- return numericIn(argv2, name, { ...spec, fallback });
46010
+ return null;
45974
46011
  }
45975
- function secondsIn(argv2, name, fallbackSeconds) {
45976
- return requiredNumericIn(argv2, name, fallbackSeconds, { unit: "seconds" }) * 1e3;
46012
+ function neverRan(run) {
46013
+ return run.refusal !== null || run.stdoutBytes === 0;
45977
46014
  }
45978
- var OptionError, flagIn, optIn;
45979
- var init_options = __esm({
45980
- "src/options.ts"() {
46015
+ var REFUSALS;
46016
+ var init_harness_signal = __esm({
46017
+ "src/harness-signal.ts"() {
45981
46018
  "use strict";
45982
- OptionError = class extends Error {
45983
- constructor(message) {
45984
- super(message);
45985
- this.name = "OptionError";
45986
- }
45987
- };
45988
- flagIn = (argv2, name) => argv2.includes(`--${name}`);
45989
- optIn = (argv2, name, fallback) => {
45990
- const i = argv2.indexOf(`--${name}`);
45991
- return i >= 0 && argv2[i + 1] && !argv2[i + 1].startsWith("--") ? argv2[i + 1] : fallback;
45992
- };
46019
+ REFUSALS = [
46020
+ [/hit your (monthly |weekly |daily )?(spend|usage) limit/i, "the account's spend limit is reached"],
46021
+ [/insufficient credit|out of credit|credit balance is too low/i, "the account is out of credit"],
46022
+ [/upgrade to (a paid plan|claude pro)/i, "the plan does not cover this"],
46023
+ [/invalid api key|authentication[_ ]error|please run \/login/i, "the harness is not signed in"],
46024
+ [/rate limit(ed| exceeded|s? reached)\b.*\btry again/i, "the account is rate limited"]
46025
+ ];
45993
46026
  }
45994
46027
  });
45995
46028
 
45996
46029
  // src/worktree.ts
45997
46030
  import { execFile } from "node:child_process";
45998
- import { mkdir, readdir, rm } from "node:fs/promises";
46031
+ import { mkdir, readdir, rm, stat } from "node:fs/promises";
45999
46032
  import { join } from "node:path";
46000
46033
  import { promisify } from "node:util";
46001
46034
  async function git(repo, args) {
@@ -46078,7 +46111,7 @@ async function createWorktree(ref, config3) {
46078
46111
  }
46079
46112
  };
46080
46113
  }
46081
- async function sweep(config3) {
46114
+ async function sweep(config3, maxRunMs = 0) {
46082
46115
  await git(config3.repo, ["worktree", "prune"]);
46083
46116
  let entries;
46084
46117
  try {
@@ -46086,9 +46119,17 @@ async function sweep(config3) {
46086
46119
  } catch {
46087
46120
  return [];
46088
46121
  }
46122
+ const cutoff = Date.now() - Math.max(maxRunMs + 5 * 6e4, 60 * 6e4);
46089
46123
  const removed = [];
46090
46124
  for (const name of entries) {
46091
46125
  const path = join(config3.root, name);
46126
+ let touched;
46127
+ try {
46128
+ touched = (await stat(path)).mtimeMs;
46129
+ } catch {
46130
+ continue;
46131
+ }
46132
+ if (touched >= cutoff) continue;
46092
46133
  await git(config3.repo, ["worktree", "remove", "--force", path]).catch(async () => {
46093
46134
  await rm(path, { recursive: true, force: true });
46094
46135
  });
@@ -46109,7 +46150,7 @@ var init_worktree = __esm({
46109
46150
  var supervisor_exports = {};
46110
46151
  import { spawn } from "node:child_process";
46111
46152
  import { createRequire } from "node:module";
46112
- import { mkdtemp, readdir as readdir2, readFile, rm as rm2, stat, writeFile } from "node:fs/promises";
46153
+ import { mkdtemp, readdir as readdir2, readFile, rm as rm2, stat as stat2, writeFile } from "node:fs/promises";
46113
46154
  import { homedir, tmpdir } from "node:os";
46114
46155
  import { join as join2 } from "node:path";
46115
46156
  import { fileURLToPath } from "node:url";
@@ -46132,6 +46173,12 @@ async function identify(ctx) {
46132
46173
  capabilities: member.capabilities
46133
46174
  };
46134
46175
  }
46176
+ function resolveWorktreeRoot(identity) {
46177
+ if (options.worktreeRoot) return options.worktreeRoot;
46178
+ const slug = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
46179
+ const name = slug(identity.name) || slug(identity.email.split("@")[0] ?? "") || "agent";
46180
+ return join2(homedir(), ".cruo-work", name);
46181
+ }
46135
46182
  async function pollAssigned(ctx) {
46136
46183
  const { data: issues, error: error51 } = await ctx.client.from("issues").select("*").eq("assignee_id", ctx.userId).order("created_at", { ascending: true });
46137
46184
  if (error51) throw new Error(`Assignment poll failed: ${error51.message}`);
@@ -46247,17 +46294,25 @@ async function pollMentions(ctx, identity) {
46247
46294
  );
46248
46295
  }
46249
46296
  async function claim(ctx, hit) {
46250
- const expiresAt = new Date(Date.now() + options.harnessTimeoutMs + 3e4);
46251
- const { error: error51 } = await ctx.client.schema("pm").from("issue_claims").upsert(
46252
- {
46253
- issue_id: hit.issue.id,
46254
- claimed_by: ctx.userId,
46255
- claimed_at: (/* @__PURE__ */ new Date()).toISOString(),
46256
- expires_at: expiresAt.toISOString()
46257
- },
46258
- { onConflict: "issue_id" }
46259
- );
46260
- if (error51) log(` could not claim ${hit.ref}: ${error51.message}`);
46297
+ const ttlMs = options.harnessTimeoutMs + 3e4;
46298
+ const { data, error: error51 } = await ctx.client.schema("public").rpc("claim_issue", {
46299
+ issue: hit.issue.id,
46300
+ ttl_ms: ttlMs
46301
+ });
46302
+ if (error51) {
46303
+ log(` could not claim ${hit.ref}: ${error51.message} \u2014 leaving it`);
46304
+ return false;
46305
+ }
46306
+ return data === true;
46307
+ }
46308
+ async function claimedElsewhere(ctx, hits) {
46309
+ if (hits.length === 0) return /* @__PURE__ */ new Set();
46310
+ const { data, error: error51 } = await ctx.client.schema("pm").from("issue_claims").select("issue_id, claimed_by").in("issue_id", hits.map((h) => h.issue.id)).gt("expires_at", (/* @__PURE__ */ new Date()).toISOString()).neq("claimed_by", ctx.userId);
46311
+ if (error51) {
46312
+ log(` could not read claims: ${error51.message} \u2014 relying on the claim itself`);
46313
+ return /* @__PURE__ */ new Set();
46314
+ }
46315
+ return new Set((data ?? []).map((r) => r.issue_id));
46261
46316
  }
46262
46317
  async function release(ctx, hit) {
46263
46318
  const { error: error51 } = await ctx.client.schema("pm").from("issue_claims").delete().eq("issue_id", hit.issue.id).eq("claimed_by", ctx.userId);
@@ -46317,7 +46372,7 @@ async function sweepStaleConfigs() {
46317
46372
  if (!name.startsWith(CONFIG_DIR_PREFIX)) continue;
46318
46373
  const path = join2(dir, name);
46319
46374
  try {
46320
- const info = await stat(path);
46375
+ const info = await stat2(path);
46321
46376
  if (info.mtimeMs >= cutoff) continue;
46322
46377
  await rm2(path, { recursive: true, force: true });
46323
46378
  removed += 1;
@@ -46447,9 +46502,6 @@ function userPrompt(hit) {
46447
46502
  `which your function owns. Pick it up and do your part.`
46448
46503
  ].join(" ");
46449
46504
  }
46450
- function neverRan(run) {
46451
- return run.code !== 0 && run.stdoutBytes === 0;
46452
- }
46453
46505
  async function invokeHarness(ctx, identity, hit, worktree) {
46454
46506
  const cwd = worktree?.path ?? options.cwd;
46455
46507
  const { path: mcpConfig, cleanup } = await writeMcpConfig();
@@ -46479,8 +46531,10 @@ async function invokeHarness(ctx, identity, hit, worktree) {
46479
46531
  env: harnessEnv()
46480
46532
  });
46481
46533
  let stdoutBytes = 0;
46534
+ let head2 = "";
46482
46535
  child.stdout?.on("data", (c) => {
46483
46536
  stdoutBytes += c.length;
46537
+ if (head2.length < 2048) head2 += c.toString("utf8").slice(0, 2048 - head2.length);
46484
46538
  process.stdout.write(c);
46485
46539
  });
46486
46540
  child.stderr?.on("data", (c) => process.stderr.write(c));
@@ -46492,11 +46546,11 @@ async function invokeHarness(ctx, identity, hit, worktree) {
46492
46546
  child.on("error", (e) => {
46493
46547
  clearTimeout(timer);
46494
46548
  log(` harness failed to start: ${e.message}`);
46495
- resolve2({ code: 127, stdoutBytes });
46549
+ resolve2({ code: 127, stdoutBytes, refusal: refusalIn(head2) });
46496
46550
  });
46497
46551
  child.on("close", (code) => {
46498
46552
  clearTimeout(timer);
46499
- resolve2({ code: code ?? 1, stdoutBytes });
46553
+ resolve2({ code: code ?? 1, stdoutBytes, refusal: refusalIn(head2) });
46500
46554
  });
46501
46555
  });
46502
46556
  } finally {
@@ -46588,11 +46642,17 @@ async function tick(ctx, identity, deadTicks) {
46588
46642
  seen.add(h.ref);
46589
46643
  return true;
46590
46644
  });
46591
- const attemptsByHit = await loadAttempts(ctx, hits);
46645
+ const heldByOthers = await claimedElsewhere(ctx, hits);
46646
+ const free = hits.filter((h) => !heldByOthers.has(h.issue.id));
46647
+ if (heldByOthers.size > 0) {
46648
+ log(`${heldByOthers.size} issue(s) already being worked by someone else`);
46649
+ }
46650
+ const attemptsByHit = await loadAttempts(ctx, free);
46592
46651
  const attemptsFor = (h) => attemptsByHit.get(`${h.reason}:${h.issue.id}`) ?? 0;
46593
- const actionable = hits.filter((h) => attemptsFor(h) < options.maxAttempts);
46652
+ const actionable = free.filter((h) => attemptsFor(h) < options.maxAttempts);
46594
46653
  if (actionable.length === 0) {
46595
- if (hits.length > 0) log(`${hits.length} issue(s) waiting, all set aside`);
46654
+ if (free.length > 0) log(`${free.length} issue(s) waiting, all set aside`);
46655
+ else if (heldByOthers.size > 0) log(`nothing free \u2014 everything waiting is claimed`);
46596
46656
  await beat(ctx, { holding: 0, deadTicks, intervalMs: options.intervalMs });
46597
46657
  return { invoked: 0, failedToRun: 0 };
46598
46658
  }
@@ -46621,6 +46681,10 @@ async function tick(ctx, identity, deadTicks) {
46621
46681
  invoked += 1;
46622
46682
  const before = { status: hit.issue.status_id, assignee: hit.issue.assignee_id };
46623
46683
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
46684
+ if (!await claim(ctx, hit)) {
46685
+ log(` ${hit.ref} is being worked by another agent \u2014 skipping`);
46686
+ continue;
46687
+ }
46624
46688
  let worktree = null;
46625
46689
  if (worktreeConfig) {
46626
46690
  try {
@@ -46628,12 +46692,12 @@ async function tick(ctx, identity, deadTicks) {
46628
46692
  log(` ${hit.ref} worktree ${worktree.path} on ${worktree.branch}`);
46629
46693
  } catch (error51) {
46630
46694
  failedToRun += 1;
46695
+ await release(ctx, hit);
46631
46696
  log(` ${hit.ref} could not prepare a worktree \u2014 not counted against this issue`);
46632
46697
  log(` ${error51 instanceof Error ? error51.message.split("\n")[0] : String(error51)}`);
46633
46698
  continue;
46634
46699
  }
46635
46700
  }
46636
- await claim(ctx, hit);
46637
46701
  let run;
46638
46702
  let committed = 0;
46639
46703
  try {
@@ -46645,9 +46709,17 @@ async function tick(ctx, identity, deadTicks) {
46645
46709
  const code = run.code;
46646
46710
  if (neverRan(run)) {
46647
46711
  failedToRun += 1;
46648
- log(
46649
- ` ${hit.ref} the harness exited ${code} without reaching a model \u2014 not counted against this issue`
46650
- );
46712
+ if (run.refusal) {
46713
+ if (run.refusal !== lastRefusal) {
46714
+ log(`! the harness is refusing to work: ${run.refusal}`);
46715
+ log(` No card is charged for this, and polling backs off until it changes.`);
46716
+ lastRefusal = run.refusal;
46717
+ }
46718
+ } else {
46719
+ log(
46720
+ ` ${hit.ref} the harness exited ${code} without reaching a model \u2014 not counted against this issue`
46721
+ );
46722
+ }
46651
46723
  continue;
46652
46724
  }
46653
46725
  const { data: after } = await ctx.client.from("issues").select("status_id, assignee_id").eq("id", hit.issue.id).maybeSingle();
@@ -46684,6 +46756,7 @@ async function tick(ctx, identity, deadTicks) {
46684
46756
  }
46685
46757
  }
46686
46758
  }
46759
+ if (failedToRun < invoked) lastRefusal = null;
46687
46760
  const allDead = invoked > 0 && failedToRun === invoked;
46688
46761
  if (allDead) {
46689
46762
  log(`! nothing reached a model this tick (${invoked}/${invoked} failed to run).`);
@@ -46722,20 +46795,20 @@ async function main() {
46722
46795
  const base = await resolveBase(options.repo, options.base);
46723
46796
  worktreeConfig = {
46724
46797
  repo: options.repo,
46725
- root: options.worktreeRoot,
46798
+ root: resolveWorktreeRoot(identity),
46726
46799
  base,
46727
46800
  prepare: options.prepare,
46728
46801
  prepareTimeoutMs: options.prepareTimeout,
46729
46802
  keepFailed: options.keepFailed
46730
46803
  };
46731
- log(`worktrees in ${options.worktreeRoot}, branching from ${base} in ${options.repo}`);
46804
+ log(`worktrees in ${worktreeConfig.root}, branching from ${base} in ${options.repo}`);
46732
46805
  if (base === "HEAD") {
46733
46806
  log(`! no origin/main or main \u2014 branching from HEAD, so tasks inherit your working state`);
46734
46807
  }
46735
46808
  if (!options.prepare) {
46736
46809
  log(` no --prepare: a fresh worktree has no dependencies and no built packages`);
46737
46810
  }
46738
- const swept = await sweep(worktreeConfig);
46811
+ const swept = await sweep(worktreeConfig, options.harnessTimeoutMs + options.prepareTimeout);
46739
46812
  if (swept.length) log(` swept ${swept.length} worktree(s) left by an earlier run: ${swept.join(", ")}`);
46740
46813
  if (options.push) log(` publishing to ${options.pushRemote} after any run that commits`);
46741
46814
  if (options.allow === "mcp__cruo") {
@@ -46770,7 +46843,7 @@ async function main() {
46770
46843
  await new Promise((r) => setTimeout(r, wait));
46771
46844
  }
46772
46845
  }
46773
- var argv, flag, opt, num, ms, readOptions, options, log, worktreeConfig, PRIORITY_RANK, CONFIG_DIR_PREFIX, CRUO_OWNED_ENV;
46846
+ var argv, flag, opt, num, ms, readOptions, options, log, worktreeConfig, lastRefusal, PRIORITY_RANK, CONFIG_DIR_PREFIX, CRUO_OWNED_ENV;
46774
46847
  var init_supervisor = __esm({
46775
46848
  "src/supervisor.ts"() {
46776
46849
  "use strict";
@@ -46779,6 +46852,7 @@ var init_supervisor = __esm({
46779
46852
  init_env2();
46780
46853
  init_auth();
46781
46854
  init_options();
46855
+ init_harness_signal();
46782
46856
  init_worktree();
46783
46857
  argv = process.argv.slice(2);
46784
46858
  flag = (name) => flagIn(argv, name);
@@ -46845,7 +46919,13 @@ var init_supervisor = __esm({
46845
46919
  * legitimate setup, and so is a PM agent with no filesystem at all.
46846
46920
  */
46847
46921
  worktree: flag("worktree"),
46848
- worktreeRoot: opt("worktree-root", join2(homedir(), ".cruo-work")),
46922
+ /**
46923
+ * Where worktrees are created. Defaults to `~/.cruo-work/<agent>` — per
46924
+ * AGENT, not one shared directory — which needs this agent's name and so is
46925
+ * resolved in `main` rather than here. Two supervisors sharing a root is a
46926
+ * setup someone has to ask for; see `resolveWorktreeRoot`.
46927
+ */
46928
+ worktreeRoot: opt("worktree-root"),
46849
46929
  /** The repo to branch from. Defaults to wherever the supervisor was started. */
46850
46930
  repo: opt("repo", process.cwd()),
46851
46931
  /** Ref new task branches are cut from. Resolved at startup; see `resolveBase`. */
@@ -46890,6 +46970,7 @@ cruo-supervisor: ${error51.message}
46890
46970
  })();
46891
46971
  log = (...parts) => console.log(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB", { hour12: false })}]`, ...parts);
46892
46972
  worktreeConfig = null;
46973
+ lastRefusal = null;
46893
46974
  PRIORITY_RANK = {
46894
46975
  urgent: 0,
46895
46976
  high: 1,
@@ -46919,6 +47000,7 @@ cruo-supervisor: ${error51.message}
46919
47000
  });
46920
47001
 
46921
47002
  // src/cli.ts
47003
+ init_options();
46922
47004
  import { chmod, mkdir as mkdir2, readFile as readFile2, rm as rm3, writeFile as writeFile2 } from "node:fs/promises";
46923
47005
  import { homedir as homedir2 } from "node:os";
46924
47006
  import { join as join3 } from "node:path";
@@ -46942,7 +47024,7 @@ Common options
46942
47024
  --push publish the branch after a run that commits
46943
47025
  --interval <seconds> how often to poll (default 20)
46944
47026
 
46945
- Full list and what each one is for: https://cruo.space/agents
47027
+ Full list and what each one is for: https://cruo.space/docs#agents
46946
47028
 
46947
47029
  The token comes from CRUO_TOKEN, then --token, then whatever \`login\` stored.
46948
47030
  `;
@@ -47014,7 +47096,7 @@ Get one from Cruo \u2192 Settings \u2192 Members \u2192 Add an agent.
47014
47096
  process.exit(2);
47015
47097
  }
47016
47098
  process.env.CRUO_TOKEN = token;
47017
- const passthrough = argv2.filter((a, i) => i !== flagIndex && i !== flagIndex + 1);
47099
+ const passthrough = stripTokenFlag(argv2);
47018
47100
  process.argv = [process.argv[0], process.argv[1], ...passthrough];
47019
47101
  await Promise.resolve().then(() => (init_supervisor(), supervisor_exports));
47020
47102
  }
package/dist/index.js CHANGED
@@ -34986,6 +34986,9 @@ var signupSchema = external_exports.object({
34986
34986
  });
34987
34987
 
34988
34988
  // ../../packages/core/dist/mcp-connect.js
34989
+ var DOCS_URL = "https://cruo.space/docs";
34990
+ var AGENT_DOCS_URL = `${DOCS_URL}#agents`;
34991
+ var MCP_DOCS_URL = `${DOCS_URL}#mcp`;
34989
34992
  var CRUO_CLOUD = {
34990
34993
  apiUrl: "https://szsutuujxbldkykcvdrm.supabase.co",
34991
34994
  publishableKey: "sb_publishable_hpnQFQ8IV0JXTch51XAstA_-S8H1eaD",
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "cruo-agent",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Run a Cruo agent: it watches your board, picks up the cards you assign it, and works them.",
5
5
  "license": "MIT",
6
- "homepage": "https://cruo.space/agents",
6
+ "homepage": "https://cruo.space/docs#agents",
7
7
  "keywords": [
8
8
  "cruo",
9
9
  "agent",
@@ -29,12 +29,13 @@
29
29
  "access": "public"
30
30
  },
31
31
  "scripts": {
32
- "build": "node scripts/build.mjs"
32
+ "build": "node scripts/build.mjs",
33
+ "prepublishOnly": "node scripts/build.mjs"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@cruo/mcp": "workspace:*"
36
37
  },
37
38
  "bugs": {
38
- "url": "https://cruo.space/agents"
39
+ "url": "https://cruo.space/docs#agents"
39
40
  }
40
41
  }