tines 0.0.91 → 0.0.93

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 (3) hide show
  1. package/README.md +56 -0
  2. package/dist/index.js +807 -476
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -3434,7 +3434,8 @@ function repoDirFromUrl(url) {
3434
3434
  const stripped = url.replace(/[?#].*$/, "").replace(/\/+$/, "");
3435
3435
  const lastSlash = Math.max(stripped.lastIndexOf("/"), stripped.lastIndexOf(":"));
3436
3436
  const base = stripped.slice(lastSlash + 1).replace(/\.git$/, "");
3437
- if (!base || base === "." || base === ".." || base.includes("\\") || base.includes("=")) return "repo";
3437
+ if (!base || base === "." || base === ".." || base.includes("\\") || base.includes("="))
3438
+ return "repo";
3438
3439
  return base;
3439
3440
  }
3440
3441
  var ARTIFACT_FILE_MAX_BYTES = 25 * 1024 * 1024;
@@ -3468,7 +3469,13 @@ var RUN_LOG_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
3468
3469
  var RUN_LOG_RAW_MAX_BYTES = 64 * 1024 * 1024;
3469
3470
  var MODEL_PREDECESSORS = {
3470
3471
  "claude-fable-5": ["claude-opus-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6"],
3471
- "claude-opus-5": ["claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", "claude-opus-4-5", "claude-opus-4-1"],
3472
+ "claude-opus-5": [
3473
+ "claude-opus-4-8",
3474
+ "claude-opus-4-7",
3475
+ "claude-opus-4-6",
3476
+ "claude-opus-4-5",
3477
+ "claude-opus-4-1"
3478
+ ],
3472
3479
  "claude-sonnet-5": ["claude-sonnet-4-6", "claude-sonnet-4-5", "claude-3-7-sonnet-latest"],
3473
3480
  "claude-haiku-4-5": ["claude-3-5-haiku-latest"],
3474
3481
  "gemini-2.5-pro": ["gemini-1.5-pro"],
@@ -3509,7 +3516,9 @@ function utilizationLabel(quota, activeRuns, stateName = (id) => id) {
3509
3516
  if (!counts.has(stateId)) counts.set(stateId, { name: stateName(stateId), n: 0 });
3510
3517
  }
3511
3518
  if (counts.size === 0) return `no active runs (roster default ${quota.default_limit} per state)`;
3512
- return [...counts.entries()].map(([stateId, { name: name2, n }]) => `${name2} ${n}/${quota.overrides[stateId] ?? quota.default_limit}`).join(" \xB7 ");
3519
+ return [...counts.entries()].map(
3520
+ ([stateId, { name: name2, n }]) => `${name2} ${n}/${quota.overrides[stateId] ?? quota.default_limit}`
3521
+ ).join(" \xB7 ");
3513
3522
  }
3514
3523
  var LIBRARY_MAX_BYTES = 5 * 1024 * 1024;
3515
3524
 
@@ -3610,9 +3619,15 @@ var DESCRIBERS = {
3610
3619
  name(p.runner_name),
3611
3620
  text(`fail to launch (${str(p.consecutive_failures)} consecutive): ${str(p.error)}`)
3612
3621
  ],
3613
- "routing_rule.created": (ev, p) => [text(`${action(ev.type)} the ${str(p.scope_label)} routing rule`)],
3614
- "routing_rule.updated": (ev, p) => [text(`${action(ev.type)} the ${str(p.scope_label)} routing rule`)],
3615
- "routing_rule.deleted": (ev, p) => [text(`${action(ev.type)} the ${str(p.scope_label)} routing rule`)],
3622
+ "routing_rule.created": (ev, p) => [
3623
+ text(`${action(ev.type)} the ${str(p.scope_label)} routing rule`)
3624
+ ],
3625
+ "routing_rule.updated": (ev, p) => [
3626
+ text(`${action(ev.type)} the ${str(p.scope_label)} routing rule`)
3627
+ ],
3628
+ "routing_rule.deleted": (ev, p) => [
3629
+ text(`${action(ev.type)} the ${str(p.scope_label)} routing rule`)
3630
+ ],
3616
3631
  "settings.updated": (_ev, p) => [
3617
3632
  text(`updated supervisor settings (${joinChanged(p.changed, ", ") || "no changes"})`)
3618
3633
  ],
@@ -3720,7 +3735,10 @@ function createApiClient(options) {
3720
3735
  async function raw(method, path2, opts = {}) {
3721
3736
  const headers = { ...opts.headers ?? {} };
3722
3737
  if (options.apiKey) headers.authorization = `Bearer ${options.apiKey}`;
3723
- const body = opts.body instanceof Uint8Array ? opts.body.buffer.slice(opts.body.byteOffset, opts.body.byteOffset + opts.body.byteLength) : opts.body;
3738
+ const body = opts.body instanceof Uint8Array ? opts.body.buffer.slice(
3739
+ opts.body.byteOffset,
3740
+ opts.body.byteOffset + opts.body.byteLength
3741
+ ) : opts.body;
3724
3742
  const res = await fetchFn(`${base}${path2}`, { method, headers, body });
3725
3743
  if (!res.ok) {
3726
3744
  let parsed = null;
@@ -3816,7 +3834,11 @@ function createApiClient(options) {
3816
3834
  uploadArtifactFolder: async (issueId, name2, files) => {
3817
3835
  const form = new FormData();
3818
3836
  for (const file of files) {
3819
- form.append("file", new Blob([file.bytes], { type: file.contentType }), file.path);
3837
+ form.append(
3838
+ "file",
3839
+ new Blob([file.bytes], { type: file.contentType }),
3840
+ file.path
3841
+ );
3820
3842
  }
3821
3843
  const res = await raw("PUT", artifactPath(issueId, name2, "/folder"), { body: form });
3822
3844
  return await res.json();
@@ -3871,7 +3893,10 @@ function createApiClient(options) {
3871
3893
  /** Daemon-only: uploads the raw harness stream for a settled run. */
3872
3894
  putRunLogRaw: (id, body) => raw("PUT", `/api/v1/runs/${id}/log/raw`, {
3873
3895
  body,
3874
- headers: { "content-type": "application/x-ndjson", "content-length": String(body.byteLength) }
3896
+ headers: {
3897
+ "content-type": "application/x-ndjson",
3898
+ "content-length": String(body.byteLength)
3899
+ }
3875
3900
  }),
3876
3901
  cancelRun: (id) => request("POST", `/api/v1/runs/${id}/cancel`),
3877
3902
  // Routing rules (one per exact scope; responses carry shadow hints)
@@ -3887,9 +3912,7 @@ function createApiClient(options) {
3887
3912
  createApiKey: (body) => request("POST", "/api/v1/api-keys", body),
3888
3913
  revokeApiKey: (id) => request("DELETE", `/api/v1/api-keys/${id}`),
3889
3914
  // Library export / import (workflows + context; no tracker data, no secrets)
3890
- exportLibrary: (opts = {}) => get(
3891
- `/api/v1/export${opts.journals === false ? "?journals=false" : ""}`
3892
- ),
3915
+ exportLibrary: (opts = {}) => get(`/api/v1/export${opts.journals === false ? "?journals=false" : ""}`),
3893
3916
  /** Plan-then-apply; `dry_run: true` returns the preview the apply follows. */
3894
3917
  importLibrary: (body) => request("POST", "/api/v1/import", body)
3895
3918
  };
@@ -4050,7 +4073,9 @@ function runnerStatusLabel(runner) {
4050
4073
  }
4051
4074
  function ruleTargetsLabel(rule) {
4052
4075
  if (rule.targets.length === 0) return "(no targets)";
4053
- return rule.targets.map((t) => `${t.runner_name}${t.tier ? `:${t.tier}` : ""}${t.runner_status === "paused" ? " (paused)" : ""}`).join(" \u2192 ");
4076
+ return rule.targets.map(
4077
+ (t) => `${t.runner_name}${t.tier ? `:${t.tier}` : ""}${t.runner_status === "paused" ? " (paused)" : ""}`
4078
+ ).join(" \u2192 ");
4054
4079
  }
4055
4080
  function quotaLabel(quota, stateName) {
4056
4081
  if (quota.type === "global_cap") return `global cap: at most ${quota.limit} concurrent runs`;
@@ -4108,7 +4133,9 @@ function keptWorkspaceRow(kept, sizeBytes, now = Date.now()) {
4108
4133
  function formatTable(rows) {
4109
4134
  if (rows.length === 0) return "";
4110
4135
  const widths = rows[0].map((_, i) => Math.max(...rows.map((r) => r[i].length)));
4111
- return rows.map((row) => row.map((cell, i) => cell.padEnd(widths[i])).join(" ").trimEnd()).join("\n");
4136
+ return rows.map(
4137
+ (row) => row.map((cell, i) => cell.padEnd(widths[i])).join(" ").trimEnd()
4138
+ ).join("\n");
4112
4139
  }
4113
4140
 
4114
4141
  // src/refs.ts
@@ -4124,7 +4151,9 @@ function parseJsonObject(raw, source) {
4124
4151
  try {
4125
4152
  value = JSON.parse(raw);
4126
4153
  } catch (err) {
4127
- throw new CliError(`invalid JSON from ${source}: ${err instanceof Error ? err.message : String(err)}`);
4154
+ throw new CliError(
4155
+ `invalid JSON from ${source}: ${err instanceof Error ? err.message : String(err)}`
4156
+ );
4128
4157
  }
4129
4158
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
4130
4159
  throw new CliError(`expected a JSON object from ${source}`);
@@ -4161,7 +4190,9 @@ function parseFileSpec(spec) {
4161
4190
  const path2 = spec.slice(0, sep);
4162
4191
  const source = spec.slice(sep + 1);
4163
4192
  if (!source.startsWith("@")) {
4164
- throw new CliError(`skill file content always comes from a local file: --file ${path2}=@<local-file>`);
4193
+ throw new CliError(
4194
+ `skill file content always comes from a local file: --file ${path2}=@<local-file>`
4195
+ );
4165
4196
  }
4166
4197
  const file = source.slice(1);
4167
4198
  try {
@@ -4182,16 +4213,57 @@ function parseTargetSpec(spec) {
4182
4213
  return { name: name2, tier };
4183
4214
  }
4184
4215
 
4185
- // src/common.ts
4186
- var DEFAULT_URL = "http://localhost:5173";
4187
- function withCommon(cmd, { baseUrlFlag = true } = {}) {
4188
- if (baseUrlFlag) {
4189
- cmd.option(
4190
- "-u, --url <url>",
4191
- `base URL of the Tines API (or set TINES_API_URL; default ${DEFAULT_URL})`
4192
- );
4216
+ // src/config.ts
4217
+ import { existsSync, mkdirSync, readFileSync as readFileSync3, unlinkSync, writeFileSync } from "node:fs";
4218
+ import { homedir } from "node:os";
4219
+ import { dirname, join } from "node:path";
4220
+ function defaultConfigDir() {
4221
+ return process.env.TINES_CONFIG_DIR ?? join(homedir(), ".config", "tines");
4222
+ }
4223
+ function readJsonFile(path2) {
4224
+ if (!existsSync(path2)) return null;
4225
+ try {
4226
+ return JSON.parse(readFileSync3(path2, "utf8"));
4227
+ } catch {
4228
+ return null;
4193
4229
  }
4194
- return cmd.option("--api-key <key>", "API key (or set TINES_API_KEY)").option("--json", "output the raw JSON response");
4230
+ }
4231
+ function writeJsonFile(path2, value, { secret = false } = {}) {
4232
+ mkdirSync(dirname(path2), { recursive: true });
4233
+ writeFileSync(path2, `${JSON.stringify(value, null, 2)}
4234
+ `, secret ? { mode: 384 } : {});
4235
+ }
4236
+ function configPath(dir = defaultConfigDir()) {
4237
+ return join(dir, "config.json");
4238
+ }
4239
+ function loadCliConfig(dir = defaultConfigDir()) {
4240
+ const raw = readJsonFile(configPath(dir));
4241
+ const config = {};
4242
+ if (typeof raw?.url === "string" && raw.url !== "") config.url = raw.url;
4243
+ if (typeof raw?.api_key === "string" && raw.api_key !== "") config.api_key = raw.api_key;
4244
+ return config;
4245
+ }
4246
+ function saveCliConfig(dir, patch) {
4247
+ const next = { ...loadCliConfig(dir) };
4248
+ if (patch.url !== void 0) next.url = patch.url.replace(/\/+$/, "");
4249
+ if (patch.api_key !== void 0) next.api_key = patch.api_key;
4250
+ writeJsonFile(configPath(dir), next, { secret: true });
4251
+ return next;
4252
+ }
4253
+ function clearCliConfig(dir = defaultConfigDir()) {
4254
+ const path2 = configPath(dir);
4255
+ if (!existsSync(path2)) return false;
4256
+ unlinkSync(path2);
4257
+ return true;
4258
+ }
4259
+
4260
+ // src/common.ts
4261
+ var DEFAULT_URL = "https://tines.tbuckley.dev";
4262
+ function withCommon(cmd) {
4263
+ return cmd.option(
4264
+ "-u, --url <url>",
4265
+ `base URL of the Tines API (or set TINES_API_URL, or run \`tines login\`; default ${DEFAULT_URL})`
4266
+ ).option("--api-key <key>", "API key (or set TINES_API_KEY, or run `tines login`)").option("--json", "output the raw JSON response");
4195
4267
  }
4196
4268
  function withList(cmd) {
4197
4269
  return withCommon(
@@ -4207,11 +4279,25 @@ function withList(cmd) {
4207
4279
  )
4208
4280
  );
4209
4281
  }
4282
+ function resolveUrlSetting(opts) {
4283
+ if (opts.url) return { value: opts.url, source: "flag" };
4284
+ if (process.env.TINES_API_URL) return { value: process.env.TINES_API_URL, source: "env" };
4285
+ const stored = loadCliConfig().url;
4286
+ if (stored) return { value: stored, source: "config" };
4287
+ return { value: DEFAULT_URL, source: "default" };
4288
+ }
4289
+ function resolveApiKeySetting(opts) {
4290
+ if (opts.apiKey) return { value: opts.apiKey, source: "flag" };
4291
+ if (process.env.TINES_API_KEY) return { value: process.env.TINES_API_KEY, source: "env" };
4292
+ const stored = loadCliConfig().api_key;
4293
+ if (stored) return { value: stored, source: "config" };
4294
+ return { value: void 0, source: "default" };
4295
+ }
4210
4296
  function resolveUrl(opts) {
4211
- return opts.url ?? process.env.TINES_API_URL ?? DEFAULT_URL;
4297
+ return resolveUrlSetting(opts).value;
4212
4298
  }
4213
4299
  function resolveApiKey(opts) {
4214
- return opts.apiKey ?? process.env.TINES_API_KEY;
4300
+ return resolveApiKeySetting(opts).value;
4215
4301
  }
4216
4302
  function client(opts) {
4217
4303
  return createApiClient({ baseUrl: resolveUrl(opts), apiKey: resolveApiKey(opts) });
@@ -4330,7 +4416,8 @@ async function resolveRunner(api, ref) {
4330
4416
  async function resolveScopeFlags(api, opts) {
4331
4417
  const scope = {};
4332
4418
  if (opts.project !== void 0) scope.project_id = (await resolveProject(api, opts.project)).id;
4333
- if (opts.state !== void 0) scope.workflow_state_id = (await resolveStateFlag(api, opts.state)).state.id;
4419
+ if (opts.state !== void 0)
4420
+ scope.workflow_state_id = (await resolveStateFlag(api, opts.state)).state.id;
4334
4421
  if (opts.issue !== void 0) scope.issue_id = (await resolveIssue(api, opts.issue)).id;
4335
4422
  return scope;
4336
4423
  }
@@ -4360,7 +4447,9 @@ files (seeded at skills/${item.name}/):`);
4360
4447
  console.log(`
4361
4448
  url: ${item.repo_url}`);
4362
4449
  if (item.repo_branch) console.log(`branch: ${item.repo_branch}`);
4363
- console.log(`dir: ${item.repo_dir ?? `${repoDirFromUrl(item.repo_url ?? "")} (derived from the URL)`}`);
4450
+ console.log(
4451
+ `dir: ${item.repo_dir ?? `${repoDirFromUrl(item.repo_url ?? "")} (derived from the URL)`}`
4452
+ );
4364
4453
  }
4365
4454
  }
4366
4455
  var SCOPE_FLAGS_HELP = `
@@ -4373,57 +4462,72 @@ function withScopeFlags(cmd) {
4373
4462
  return cmd.option("-p, --project <name>", "scope: project name or id").option("-s, --state <workflow/state>", "scope: workflow-qualified state").option("-i, --issue <ref>", "scope: issue (<project>/<number>)").addHelpText("after", SCOPE_FLAGS_HELP);
4374
4463
  }
4375
4464
  function register(program3) {
4376
- const context = program3.command("context").description("Manage context items (prompts, skills, repo pointers) scoped to projects, states, and issues");
4465
+ const context = program3.command("context").description(
4466
+ "Manage context items (prompts, skills, repo pointers) scoped to projects, states, and issues"
4467
+ );
4377
4468
  withList(
4378
4469
  withScopeFlags(
4379
- context.command("list").description("List context items (scope filters match every item whose scope includes the element)").option("-k, --kind <kind>", "filter by kind: prompt, skill, or repo").option("--exact", "only items whose scope sets exactly the given dimensions").option("-q, --search <text>", "search names and descriptions")
4470
+ context.command("list").description(
4471
+ "List context items (scope filters match every item whose scope includes the element)"
4472
+ ).option("-k, --kind <kind>", "filter by kind: prompt, skill, or repo").option("--exact", "only items whose scope sets exactly the given dimensions").option("-q, --search <text>", "search names and descriptions")
4380
4473
  )
4381
- ).action(async (opts) => {
4382
- const api = client(opts);
4383
- const scope = await resolveScopeFlags(api, opts);
4384
- const res = await fetchList(
4385
- opts,
4386
- (page) => api.listContext({
4387
- kind: opts.kind,
4388
- project: scope.project_id ?? void 0,
4389
- state: scope.workflow_state_id ?? void 0,
4390
- issue: scope.issue_id ?? void 0,
4391
- q: opts.search,
4392
- exact: opts.exact ? true : void 0,
4393
- ...page
4394
- })
4395
- );
4396
- printList(res, opts, (items) => {
4397
- if (items.length === 0) return console.log("no context items");
4398
- table([
4399
- ["KIND", "NAME", "SCOPE", "PAYLOAD", "UPDATED", "ID"],
4400
- ...items.map((i) => [
4401
- i.kind,
4402
- i.name,
4403
- i.scope.label,
4404
- contextItemSummary(i),
4405
- timestamp(i.updated_at),
4406
- i.id
4407
- ])
4408
- ]);
4409
- });
4410
- });
4411
- withCommon(context.command("show <id>").description("Show a context item (skills include their files)")).action(
4412
- async (id, opts) => {
4413
- const item = await client(opts).getContextItem(id);
4414
- if (opts.json) return printJson(item);
4415
- printContextItem(item);
4474
+ ).action(
4475
+ async (opts) => {
4476
+ const api = client(opts);
4477
+ const scope = await resolveScopeFlags(api, opts);
4478
+ const res = await fetchList(
4479
+ opts,
4480
+ (page) => api.listContext({
4481
+ kind: opts.kind,
4482
+ project: scope.project_id ?? void 0,
4483
+ state: scope.workflow_state_id ?? void 0,
4484
+ issue: scope.issue_id ?? void 0,
4485
+ q: opts.search,
4486
+ exact: opts.exact ? true : void 0,
4487
+ ...page
4488
+ })
4489
+ );
4490
+ printList(res, opts, (items) => {
4491
+ if (items.length === 0) return console.log("no context items");
4492
+ table([
4493
+ ["KIND", "NAME", "SCOPE", "PAYLOAD", "UPDATED", "ID"],
4494
+ ...items.map((i) => [
4495
+ i.kind,
4496
+ i.name,
4497
+ i.scope.label,
4498
+ contextItemSummary(i),
4499
+ timestamp(i.updated_at),
4500
+ i.id
4501
+ ])
4502
+ ]);
4503
+ });
4416
4504
  }
4417
4505
  );
4506
+ withCommon(
4507
+ context.command("show <id>").description("Show a context item (skills include their files)")
4508
+ ).action(async (id, opts) => {
4509
+ const item = await client(opts).getContextItem(id);
4510
+ if (opts.json) return printJson(item);
4511
+ printContextItem(item);
4512
+ });
4418
4513
  withCommon(
4419
4514
  withScopeFlags(
4420
- context.command("create").description("Create a context item scoped to a project, state, and/or issue").requiredOption("-k, --kind <kind>", "prompt, skill, or repo").requiredOption("-n, --name <name>", "item name (slug-like for skills; the dedup/override key)").option("-d, --description <text>", "one-liner shown in lists").option("--body <md>", "prompt body: inline Markdown or @file (escape a literal @ as @@)").option("--file <path>=@<local>", "skill file: workspace path = local file (repeatable)", collect, []).option("--url <url>", "repo: clone URL").option("--branch <branch>", "repo: branch to check out").option("--dir <dir>", "repo: checkout directory (defaults to the URL's basename)")
4421
- ),
4422
- // --url is the repo pointer here; the API base comes from TINES_API_URL.
4423
- { baseUrlFlag: false }
4515
+ context.command("create").description("Create a context item scoped to a project, state, and/or issue").requiredOption("-k, --kind <kind>", "prompt, skill, or repo").requiredOption(
4516
+ "-n, --name <name>",
4517
+ "item name (slug-like for skills; the dedup/override key)"
4518
+ ).option("-d, --description <text>", "one-liner shown in lists").option("--body <md>", "prompt body: inline Markdown or @file (escape a literal @ as @@)").option(
4519
+ "--file <path>=@<local>",
4520
+ "skill file: workspace path = local file (repeatable)",
4521
+ collect,
4522
+ []
4523
+ ).option("--repo-url <url>", "repo: clone URL").option("--branch <branch>", "repo: branch to check out").option("--dir <dir>", "repo: checkout directory (defaults to the URL's basename)")
4524
+ )
4424
4525
  ).action(
4425
4526
  async (opts) => {
4426
- const api = client({ apiKey: opts.apiKey, json: opts.json });
4527
+ if (opts.kind === "repo" && opts.repoUrl === void 0) {
4528
+ die("--kind repo needs --repo-url <clone-url> (--url is the API base URL)");
4529
+ }
4530
+ const api = client(opts);
4427
4531
  const scope = await resolveScopeFlags(api, opts);
4428
4532
  const body = {
4429
4533
  kind: opts.kind,
@@ -4434,7 +4538,7 @@ function register(program3) {
4434
4538
  if (opts.body !== void 0) body.body = readBodyValue(opts.body);
4435
4539
  if (opts.file.length > 0) body.files = opts.file.map(parseFileSpec);
4436
4540
  if (opts.kind === "skill" && body.files === void 0) body.files = [];
4437
- if (opts.url !== void 0) body.repo_url = opts.url;
4541
+ if (opts.repoUrl !== void 0) body.repo_url = opts.repoUrl;
4438
4542
  if (opts.branch !== void 0) body.repo_branch = opts.branch;
4439
4543
  if (opts.dir !== void 0) body.repo_dir = opts.dir;
4440
4544
  const item = await api.createContextItem(body);
@@ -4444,17 +4548,20 @@ function register(program3) {
4444
4548
  );
4445
4549
  withCommon(
4446
4550
  withScopeFlags(
4447
- context.command("edit <id>").description("Edit a context item: payload, name, description, or scope").option("-n, --name <name>", "rename the item").option("-d, --description <text>", "set the description").option("--body <md>", "prompt body: inline Markdown or @file (escape a literal @ as @@)").option("--file <path>=@<local>", "add or replace a skill file (repeatable)", collect, []).option("--remove-file <path>", "remove a skill file (repeatable)", collect, []).option("--url <url>", "repo: clone URL").option("--branch <branch>", "repo: branch (empty string clears it)").option("--dir <dir>", "repo: checkout directory (empty string restores the URL default)").option("--unset <dimension>", "drop a scope dimension: project, state, or issue (repeatable)", collect, []).option(
4551
+ context.command("edit <id>").description("Edit a context item: payload, name, description, or scope").option("-n, --name <name>", "rename the item").option("-d, --description <text>", "set the description").option("--body <md>", "prompt body: inline Markdown or @file (escape a literal @ as @@)").option("--file <path>=@<local>", "add or replace a skill file (repeatable)", collect, []).option("--remove-file <path>", "remove a skill file (repeatable)", collect, []).option("--repo-url <url>", "repo: clone URL").option("--branch <branch>", "repo: branch (empty string clears it)").option("--dir <dir>", "repo: checkout directory (empty string restores the URL default)").option(
4552
+ "--unset <dimension>",
4553
+ "drop a scope dimension: project, state, or issue (repeatable)",
4554
+ collect,
4555
+ []
4556
+ ).option(
4448
4557
  "--expect-version <n>",
4449
4558
  "fail (409) unless the item is still at this version",
4450
4559
  (v) => Number.parseInt(v, 10)
4451
4560
  )
4452
- ),
4453
- // --url is the repo pointer here; the API base comes from TINES_API_URL.
4454
- { baseUrlFlag: false }
4561
+ )
4455
4562
  ).action(
4456
4563
  async (id, opts) => {
4457
- const api = client({ apiKey: opts.apiKey, json: opts.json });
4564
+ const api = client(opts);
4458
4565
  const body = {};
4459
4566
  if (opts.expectVersion !== void 0) body.expected_version = opts.expectVersion;
4460
4567
  if (opts.name !== void 0) body.name = opts.name;
@@ -4470,12 +4577,15 @@ function register(program3) {
4470
4577
  if (opts.body !== void 0) body.body = readBodyValue(opts.body);
4471
4578
  if (opts.file.length > 0 || opts.removeFile.length > 0) {
4472
4579
  const current = await api.getContextItem(id);
4473
- if (current.kind !== "skill") die(`--file/--remove-file only apply to skills (this is a ${current.kind})`);
4580
+ if (current.kind !== "skill")
4581
+ die(`--file/--remove-file only apply to skills (this is a ${current.kind})`);
4474
4582
  if (body.expected_version === void 0) body.expected_version = current.version;
4475
4583
  const files = new Map((current.files ?? []).map((f) => [f.path, f.content]));
4476
4584
  for (const path2 of opts.removeFile) {
4477
4585
  if (!files.delete(path2)) {
4478
- die(`no file "${path2}" in skill "${current.name}" (have: ${[...files.keys()].join(", ") || "none"})`);
4586
+ die(
4587
+ `no file "${path2}" in skill "${current.name}" (have: ${[...files.keys()].join(", ") || "none"})`
4588
+ );
4479
4589
  }
4480
4590
  }
4481
4591
  for (const spec of opts.file) {
@@ -4484,11 +4594,13 @@ function register(program3) {
4484
4594
  }
4485
4595
  body.files = [...files.entries()].map(([path2, content]) => ({ path: path2, content }));
4486
4596
  }
4487
- if (opts.url !== void 0) body.repo_url = opts.url;
4597
+ if (opts.repoUrl !== void 0) body.repo_url = opts.repoUrl;
4488
4598
  if (opts.branch !== void 0) body.repo_branch = opts.branch === "" ? null : opts.branch;
4489
4599
  if (opts.dir !== void 0) body.repo_dir = opts.dir === "" ? null : opts.dir;
4490
4600
  if (Object.keys(body).length === 0) {
4491
- die("nothing to update: pass payload flags, --name/--description, scope flags, and/or --unset");
4601
+ die(
4602
+ "nothing to update: pass payload flags, --name/--description, scope flags, and/or --unset"
4603
+ );
4492
4604
  }
4493
4605
  const item = await api.updateContextItem(id, body);
4494
4606
  if (opts.json) return printJson(item);
@@ -4507,7 +4619,9 @@ function register(program3) {
4507
4619
  context.command("init").description('Seed the global "agent-guidelines" prompt (a no-op if it already exists)')
4508
4620
  ).action(async (opts) => {
4509
4621
  const api = client(opts);
4510
- const items = await listAll((page) => api.listContext({ kind: "prompt", exact: true, ...page }));
4622
+ const items = await listAll(
4623
+ (page) => api.listContext({ kind: "prompt", exact: true, ...page })
4624
+ );
4511
4625
  const existing = items.find((i) => i.name === AGENT_GUIDELINES_NAME);
4512
4626
  if (existing) {
4513
4627
  if (opts.json) return printJson(existing);
@@ -4529,8 +4643,8 @@ function register(program3) {
4529
4643
  }
4530
4644
 
4531
4645
  // src/commands/issues.ts
4532
- import { existsSync, mkdirSync, readdirSync, readFileSync as readFileSync3, statSync, writeFileSync } from "node:fs";
4533
- import { basename, dirname, join } from "node:path";
4646
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync4, statSync, writeFileSync as writeFileSync2 } from "node:fs";
4647
+ import { basename, dirname as dirname2, join as join2 } from "node:path";
4534
4648
 
4535
4649
  // src/help-guard.ts
4536
4650
  function helpGuard(command, markdown) {
@@ -4569,12 +4683,15 @@ function buildRecurrence(opts) {
4569
4683
  if (hourly !== void 0) {
4570
4684
  if (opts.on !== void 0) throw new CliError("an hourly recurrence does not take --on");
4571
4685
  const every = typeof hourly === "number" ? hourly : Number.parseInt(hourly, 10);
4572
- if (every < 1 || every > 23) throw new CliError(`--every <N>h needs N between 1 and 23, got "${opts.every}"`);
4686
+ if (every < 1 || every > 23)
4687
+ throw new CliError(`--every <N>h needs N between 1 and 23, got "${opts.every}"`);
4573
4688
  let minute = 0;
4574
4689
  if (opts.at !== void 0) {
4575
4690
  const m = opts.at.match(/^:?(\d{1,2})$/);
4576
4691
  if (!m || Number.parseInt(m[1], 10) > 59) {
4577
- throw new CliError(`with an hourly recurrence, --at is the minute past the hour (0-59 or :MM), got "${opts.at}"`);
4692
+ throw new CliError(
4693
+ `with an hourly recurrence, --at is the minute past the hour (0-59 or :MM), got "${opts.at}"`
4694
+ );
4578
4695
  }
4579
4696
  minute = Number.parseInt(m[1], 10);
4580
4697
  }
@@ -4599,7 +4716,9 @@ function buildRecurrence(opts) {
4599
4716
  return { preset: { kind: "monthly", time, day_of_month: day } };
4600
4717
  }
4601
4718
  default:
4602
- throw new CliError(`--every must be hourly, <N>h, daily, weekly, or monthly, got "${opts.every}"`);
4719
+ throw new CliError(
4720
+ `--every must be hourly, <N>h, daily, weekly, or monthly, got "${opts.every}"`
4721
+ );
4603
4722
  }
4604
4723
  }
4605
4724
 
@@ -4608,7 +4727,9 @@ var systemTimezone = () => Intl.DateTimeFormat().resolvedOptions().timeZone;
4608
4727
  function printIssueLinks(links) {
4609
4728
  if (links.blocked_by.length > 0) {
4610
4729
  console.log("\nblocked by:");
4611
- table(linkRows(links.blocked_by, (e) => e.effective_state.category === "done" ? "" : "(open)"));
4730
+ table(
4731
+ linkRows(links.blocked_by, (e) => e.effective_state.category === "done" ? "" : "(open)")
4732
+ );
4612
4733
  }
4613
4734
  if (links.blocks.length > 0) {
4614
4735
  console.log("\nblocks:");
@@ -4630,7 +4751,9 @@ function printIssueDetail(issue) {
4630
4751
  `state: ${issue.effective_state.name} (${issue.effective_state.category})${dup ? ` (via ${issueRef(dup)} \u2014 duplicate)` : ""} workflow: ${issue.workflow.name} updated: ${timestamp(issue.updated_at)}`
4631
4752
  );
4632
4753
  if (dup) {
4633
- console.log(`own state: ${issue.state.name} (${issue.state.category}) \u2014 dormant while this is a duplicate`);
4754
+ console.log(
4755
+ `own state: ${issue.state.name} (${issue.state.category}) \u2014 dormant while this is a duplicate`
4756
+ );
4634
4757
  }
4635
4758
  console.log(`id: ${issue.id}`);
4636
4759
  printIssueLinks(issue.links);
@@ -4639,8 +4762,10 @@ function printIssueDetail(issue) {
4639
4762
  ${issue.description}`);
4640
4763
  }
4641
4764
  const allowed = issue.allowed_transitions.map((t) => `"${t.name}" \u2192 ${t.to_state.name}`);
4642
- console.log(`
4643
- allowed actions: ${allowed.length ? allowed.join(", ") : "none (terminal state)"}`);
4765
+ console.log(
4766
+ `
4767
+ allowed actions: ${allowed.length ? allowed.join(", ") : "none (terminal state)"}`
4768
+ );
4644
4769
  if (issue.comments.length > 0) {
4645
4770
  console.log(`
4646
4771
  comments (${issue.comments.length}):`);
@@ -4653,11 +4778,15 @@ function walkFolder(dir) {
4653
4778
  const files = [];
4654
4779
  const walk = (abs, rel) => {
4655
4780
  for (const entry of readdirSync(abs, { withFileTypes: true })) {
4656
- const nextAbs = join(abs, entry.name);
4781
+ const nextAbs = join2(abs, entry.name);
4657
4782
  const nextRel = rel ? `${rel}/${entry.name}` : entry.name;
4658
4783
  if (entry.isDirectory()) walk(nextAbs, nextRel);
4659
4784
  else if (entry.isFile()) {
4660
- files.push({ path: nextRel, contentType: sniffContentType(entry.name), bytes: readFileSync3(nextAbs) });
4785
+ files.push({
4786
+ path: nextRel,
4787
+ contentType: sniffContentType(entry.name),
4788
+ bytes: readFileSync4(nextAbs)
4789
+ });
4661
4790
  }
4662
4791
  }
4663
4792
  };
@@ -4691,7 +4820,9 @@ matched rule: ${ex.matched_rule.scope_label}`);
4691
4820
  );
4692
4821
  }
4693
4822
  if (ex.queue_position !== null && ex.queue_position > 0) {
4694
- console.log(`queue: ${ex.queue_position} eligible issue${ex.queue_position === 1 ? "" : "s"} ahead of this one`);
4823
+ console.log(
4824
+ `queue: ${ex.queue_position} eligible issue${ex.queue_position === 1 ? "" : "s"} ahead of this one`
4825
+ );
4695
4826
  }
4696
4827
  if (ex.active_run) {
4697
4828
  console.log(
@@ -4707,7 +4838,10 @@ matched rule: ${ex.matched_rule.scope_label}`);
4707
4838
  function register2(program3) {
4708
4839
  const issues = program3.command("issues").description("Work with issues");
4709
4840
  withList(
4710
- issues.command("list").description("List issues across projects (hides done issues unless --all)").option("-p, --project <name>", "filter by project name or id").option("-s, --state <name>", "filter by state name or id").option("-c, --category <cat>", "filter by state category").option("-w, --workflow <id-or-name>", "filter by workflow").option("-a, --all", "include issues in done states").option("--ready", "only issues that are actionable now (not done, not a duplicate, no open blockers)").option("-q, --search <text>", "search titles and descriptions")
4841
+ issues.command("list").description("List issues across projects (hides done issues unless --all)").option("-p, --project <name>", "filter by project name or id").option("-s, --state <name>", "filter by state name or id").option("-c, --category <cat>", "filter by state category").option("-w, --workflow <id-or-name>", "filter by workflow").option("-a, --all", "include issues in done states").option(
4842
+ "--ready",
4843
+ "only issues that are actionable now (not done, not a duplicate, no open blockers)"
4844
+ ).option("-q, --search <text>", "search titles and descriptions")
4711
4845
  ).action(
4712
4846
  async (opts) => {
4713
4847
  const api = client(opts);
@@ -4743,7 +4877,19 @@ function register2(program3) {
4743
4877
  }
4744
4878
  );
4745
4879
  withCommon(
4746
- issues.command("create <project>").description("Create an issue in a project, optionally with a recurrence (a scheduled task)").requiredOption("-t, --title <title>", "issue title (doubles as the title template with a recurrence)").option("-d, --description <markdown>", `issue description (Markdown) \u2014 ${BODY_VALUE_HELP}`).option("-w, --workflow <id-or-name>", "workflow (defaults to project default, else standard)").option("-s, --state <name>", "starting state (defaults to the workflow's initial state)").option("--every <preset>", 'repeat hourly (or every N hours: "6h"), daily, weekly, or monthly').option("--at <when>", "preset time of day HH:MM (default 09:00); for hourly, the minute past the hour :MM (default :00)").option("--on <when>", "weekday (weekly) or day of month (monthly)").option("--cron <expr>", "5-field cron expression (alternative to --every/--at/--on)").option("--tz <iana>", "schedule timezone (defaults to the system timezone)").option("--if-closed", "only create a new instance when all previous instances are closed").option("--schedule-name <name>", "schedule name, unique per project (defaults to the title)")
4880
+ issues.command("create <project>").description("Create an issue in a project, optionally with a recurrence (a scheduled task)").requiredOption(
4881
+ "-t, --title <title>",
4882
+ "issue title (doubles as the title template with a recurrence)"
4883
+ ).option("-d, --description <markdown>", `issue description (Markdown) \u2014 ${BODY_VALUE_HELP}`).option(
4884
+ "-w, --workflow <id-or-name>",
4885
+ "workflow (defaults to project default, else standard)"
4886
+ ).option("-s, --state <name>", "starting state (defaults to the workflow's initial state)").option(
4887
+ "--every <preset>",
4888
+ 'repeat hourly (or every N hours: "6h"), daily, weekly, or monthly'
4889
+ ).option(
4890
+ "--at <when>",
4891
+ "preset time of day HH:MM (default 09:00); for hourly, the minute past the hour :MM (default :00)"
4892
+ ).option("--on <when>", "weekday (weekly) or day of month (monthly)").option("--cron <expr>", "5-field cron expression (alternative to --every/--at/--on)").option("--tz <iana>", "schedule timezone (defaults to the system timezone)").option("--if-closed", "only create a new instance when all previous instances are closed").option("--schedule-name <name>", "schedule name, unique per project (defaults to the title)")
4747
4893
  ).action(
4748
4894
  async (projectRef, opts) => {
4749
4895
  const description = opts.description !== void 0 ? readBodyValue(opts.description) : void 0;
@@ -4799,7 +4945,8 @@ function register2(program3) {
4799
4945
  if (opts.title !== void 0) body.title = opts.title;
4800
4946
  if (description !== void 0) body.description = description;
4801
4947
  if (opts.state !== void 0) body.state = opts.state;
4802
- if (opts.workflow !== void 0) body.workflow_id = (await resolveWorkflow(api, opts.workflow)).id;
4948
+ if (opts.workflow !== void 0)
4949
+ body.workflow_id = (await resolveWorkflow(api, opts.workflow)).id;
4803
4950
  if (Object.keys(body).length === 0) {
4804
4951
  die("nothing to update: pass --title, --description, --state, and/or --workflow");
4805
4952
  }
@@ -4841,15 +4988,17 @@ function register2(program3) {
4841
4988
  });
4842
4989
  withCommon(
4843
4990
  issues.command("comment-edit <ref> <comment-id> <markdown>").description(`Replace the body of your own comment \u2014 Markdown body: ${BODY_VALUE_HELP}`).passThroughOptions()
4844
- ).action(async (ref, commentId, markdown, opts, command) => {
4845
- if (helpGuard(command, markdown)) return;
4846
- const body = readBodyValue(markdown);
4847
- const api = client(opts);
4848
- const issue = await resolveIssue(api, ref);
4849
- const comment = await api.updateComment(issue.id, commentId, { body });
4850
- if (opts.json) return printJson(comment);
4851
- console.log(`edited comment ${comment.id} on ${issue.project_name}/#${issue.number}`);
4852
- });
4991
+ ).action(
4992
+ async (ref, commentId, markdown, opts, command) => {
4993
+ if (helpGuard(command, markdown)) return;
4994
+ const body = readBodyValue(markdown);
4995
+ const api = client(opts);
4996
+ const issue = await resolveIssue(api, ref);
4997
+ const comment = await api.updateComment(issue.id, commentId, { body });
4998
+ if (opts.json) return printJson(comment);
4999
+ console.log(`edited comment ${comment.id} on ${issue.project_name}/#${issue.number}`);
5000
+ }
5001
+ );
4853
5002
  withCommon(
4854
5003
  issues.command("comment-delete <ref> <comment-id>").description("Delete your own comment (the event keeps the record of the deletion)")
4855
5004
  ).action(async (ref, commentId, opts) => {
@@ -4902,7 +5051,12 @@ function register2(program3) {
4902
5051
  );
4903
5052
  });
4904
5053
  withCommon(
4905
- issues.command("context <ref>").description("Print an issue's effective context (the assembled bundle for its current state)").option("--out <dir>", "write the bundle to a directory: prompt.md, skills/<name>/\u2026, repos.json").option("--force", "allow --out into a non-empty directory")
5054
+ issues.command("context <ref>").description(
5055
+ "Print an issue's effective context (the assembled bundle for its current state)"
5056
+ ).option(
5057
+ "--out <dir>",
5058
+ "write the bundle to a directory: prompt.md, skills/<name>/\u2026, repos.json"
5059
+ ).option("--force", "allow --out into a non-empty directory")
4906
5060
  ).action(async (ref, opts) => {
4907
5061
  const api = client(opts);
4908
5062
  const issue = await resolveIssue(api, ref);
@@ -4915,13 +5069,19 @@ function register2(program3) {
4915
5069
  skills: ${context.skills.map((s) => s.name).join(", ")}`);
4916
5070
  }
4917
5071
  for (const repo of context.repos) {
4918
- console.log(`repo: ${repo.name} ${repo.url}${repo.branch ? `#${repo.branch}` : ""} \u2192 ${repo.dir}/`);
5072
+ console.log(
5073
+ `repo: ${repo.name} ${repo.url}${repo.branch ? `#${repo.branch}` : ""} \u2192 ${repo.dir}/`
5074
+ );
4919
5075
  }
4920
5076
  for (const o of context.overridden) {
4921
- console.log(`overridden: ${o.kind} "${o.name}" [${o.scope.label}] (overridden by ${o.overridden_by})`);
5077
+ console.log(
5078
+ `overridden: ${o.kind} "${o.name}" [${o.scope.label}] (overridden by ${o.overridden_by})`
5079
+ );
4922
5080
  }
4923
5081
  for (const c of context.conflicts) {
4924
- console.log(`conflict: repos ${c.item_ids.join(", ")} all resolve to checkout dir "${c.dir}"`);
5082
+ console.log(
5083
+ `conflict: repos ${c.item_ids.join(", ")} all resolve to checkout dir "${c.dir}"`
5084
+ );
4925
5085
  }
4926
5086
  return;
4927
5087
  }
@@ -4930,20 +5090,23 @@ skills: ${context.skills.map((s) => s.name).join(", ")}`);
4930
5090
  `refusing to write: checkout-directory conflict${context.conflicts.length === 1 ? "" : "s"} among the effective repos (${context.conflicts.map((c) => `"${c.dir}": ${c.item_ids.join(", ")}`).join("; ")}); rename or re-dir the items first`
4931
5091
  );
4932
5092
  }
4933
- if (existsSync(opts.out) && readdirSync(opts.out).length > 0 && !opts.force) {
5093
+ if (existsSync2(opts.out) && readdirSync(opts.out).length > 0 && !opts.force) {
4934
5094
  die(`refusing to write into non-empty directory ${opts.out} (pass --force to override)`);
4935
5095
  }
4936
- mkdirSync(opts.out, { recursive: true });
4937
- writeFileSync(join(opts.out, "prompt.md"), context.prompt.text ? `${context.prompt.text}
4938
- ` : "");
5096
+ mkdirSync2(opts.out, { recursive: true });
5097
+ writeFileSync2(
5098
+ join2(opts.out, "prompt.md"),
5099
+ context.prompt.text ? `${context.prompt.text}
5100
+ ` : ""
5101
+ );
4939
5102
  for (const skill of context.skills) {
4940
5103
  for (const file of skill.files) {
4941
- const target = join(opts.out, "skills", skill.name, file.path);
4942
- mkdirSync(dirname(target), { recursive: true });
4943
- writeFileSync(target, file.content);
5104
+ const target = join2(opts.out, "skills", skill.name, file.path);
5105
+ mkdirSync2(dirname2(target), { recursive: true });
5106
+ writeFileSync2(target, file.content);
4944
5107
  }
4945
5108
  }
4946
- writeFileSync(join(opts.out, "repos.json"), `${JSON.stringify(context.repos, null, 2)}
5109
+ writeFileSync2(join2(opts.out, "repos.json"), `${JSON.stringify(context.repos, null, 2)}
4947
5110
  `);
4948
5111
  console.log(
4949
5112
  `wrote ${opts.out}/prompt.md, ${context.skills.length} skill${context.skills.length === 1 ? "" : "s"}, repos.json (${context.repos.length} repo${context.repos.length === 1 ? "" : "s"})`
@@ -4971,27 +5134,29 @@ skills: ${context.skills.map((s) => s.name).join(", ")}`);
4971
5134
  if (opts.json) return printJson(prompt);
4972
5135
  console.log(prompt.text);
4973
5136
  });
4974
- const artifactsCmd = issues.command("artifacts").description("Typed, versioned attachments on an issue \u2014 the work products transition requirements gate on");
4975
- withCommon(artifactsCmd.command("list <ref>").description("List the artifacts attached to an issue")).action(
4976
- async (ref, opts) => {
4977
- const api = client(opts);
4978
- const issue = await resolveIssue(api, ref);
4979
- const res = await api.listArtifacts(issue.id);
4980
- if (opts.json) return printJson(res);
4981
- if (res.items.length === 0) return console.log("no artifacts attached");
4982
- table([
4983
- ["NAME", "TYPE", "VERSION", "FRESH", "SUMMARY", "ATTACHED"],
4984
- ...res.items.map((a) => [
4985
- a.name,
4986
- a.artifact_type,
4987
- `v${a.current_version.version}`,
4988
- a.fresh ? "yes" : "no",
4989
- artifactSummary(a),
4990
- timestamp(a.current_version.created_at)
4991
- ])
4992
- ]);
4993
- }
5137
+ const artifactsCmd = issues.command("artifacts").description(
5138
+ "Typed, versioned attachments on an issue \u2014 the work products transition requirements gate on"
4994
5139
  );
5140
+ withCommon(
5141
+ artifactsCmd.command("list <ref>").description("List the artifacts attached to an issue")
5142
+ ).action(async (ref, opts) => {
5143
+ const api = client(opts);
5144
+ const issue = await resolveIssue(api, ref);
5145
+ const res = await api.listArtifacts(issue.id);
5146
+ if (opts.json) return printJson(res);
5147
+ if (res.items.length === 0) return console.log("no artifacts attached");
5148
+ table([
5149
+ ["NAME", "TYPE", "VERSION", "FRESH", "SUMMARY", "ATTACHED"],
5150
+ ...res.items.map((a) => [
5151
+ a.name,
5152
+ a.artifact_type,
5153
+ `v${a.current_version.version}`,
5154
+ a.fresh ? "yes" : "no",
5155
+ artifactSummary(a),
5156
+ timestamp(a.current_version.created_at)
5157
+ ])
5158
+ ]);
5159
+ });
4995
5160
  withCommon(
4996
5161
  artifactsCmd.command("show <ref> <name>").description("Show an artifact with its full version history")
4997
5162
  ).action(async (ref, name2, opts) => {
@@ -4999,7 +5164,9 @@ skills: ${context.skills.map((s) => s.name).join(", ")}`);
4999
5164
  const issue = await resolveIssue(api, ref);
5000
5165
  const artifact = await api.getArtifact(issue.id, name2);
5001
5166
  if (opts.json) return printJson(artifact);
5002
- console.log(`${artifact.artifact_type} artifact "${artifact.name}" on ${issue.project_name}/${issue.number}`);
5167
+ console.log(
5168
+ `${artifact.artifact_type} artifact "${artifact.name}" on ${issue.project_name}/${issue.number}`
5169
+ );
5003
5170
  if (artifact.description) console.log(artifact.description);
5004
5171
  console.log(
5005
5172
  `current: v${artifact.current_version.version} (${artifact.fresh ? "fresh" : "attached before the current state \u2014 reaffirm or attach a new version to satisfy gates"})`
@@ -5022,25 +5189,27 @@ files (v${artifact.current_version.version}):`);
5022
5189
  }
5023
5190
  });
5024
5191
  withCommon(
5025
- artifactsCmd.command("attach <ref> <name>").description("Attach content to a named artifact slot (creates it, or appends the next version)").option("-f, --file <path>", "upload a file (MIME sniffed from the extension)").option(
5192
+ artifactsCmd.command("attach <ref> <name>").description(
5193
+ "Attach content to a named artifact slot (creates it, or appends the next version)"
5194
+ ).option("-f, --file <path>", "upload a file (MIME sniffed from the extension)").option(
5026
5195
  "--folder <dir>",
5027
5196
  "snapshot a directory tree as one version (collect locally, attach once; MIME per file sniffed)"
5028
- ).option("-t, --text <md|@file>", "inline text document: inline Markdown or @file").option("--url <url>", "link: the URL to attach").option("--pr <spec>", "PR reference: owner/repo#N or a GitHub PR URL").option("--content-type <mime>", "declared MIME type (with --file or --text)").option("--filename <name>", "display filename (with --text; defaults to <name>.md)").option("--title <title>", "display title (with --url)").option("-d, --description <text>", "artifact description, shown in lists and launch prompts"),
5029
- // --url is the link payload here; the API base comes from TINES_API_URL.
5030
- { baseUrlFlag: false }
5197
+ ).option("-t, --text <md|@file>", "inline text document: inline Markdown or @file").option("--link <url>", "link: the URL to attach").option("--pr <spec>", "PR reference: owner/repo#N or a GitHub PR URL").option("--content-type <mime>", "declared MIME type (with --file or --text)").option("--filename <name>", "display filename (with --text; defaults to <name>.md)").option("--title <title>", "display title (with --link)").option("-d, --description <text>", "artifact description, shown in lists and launch prompts")
5031
5198
  ).action(
5032
5199
  async (ref, name2, opts) => {
5033
- const api = client({ apiKey: opts.apiKey, json: opts.json });
5034
- const sources = [opts.file, opts.folder, opts.text, opts.url, opts.pr].filter((v) => v !== void 0);
5200
+ const api = client(opts);
5201
+ const sources = [opts.file, opts.folder, opts.text, opts.link, opts.pr].filter(
5202
+ (v) => v !== void 0
5203
+ );
5035
5204
  if (sources.length !== 1) {
5036
5205
  die(
5037
- "pass exactly one content source: --file <path>, --folder <dir>, --text <md|@file>, --url <url>, or --pr <spec>"
5206
+ "pass exactly one content source: --file <path>, --folder <dir>, --text <md|@file>, --link <url>, or --pr <spec> (a link goes in --link; --url is the API base URL)"
5038
5207
  );
5039
5208
  }
5040
5209
  const issue = await resolveIssue(api, ref);
5041
5210
  let artifact;
5042
5211
  if (opts.folder !== void 0) {
5043
- if (!existsSync(opts.folder) || !statSync(opts.folder).isDirectory()) {
5212
+ if (!existsSync2(opts.folder) || !statSync(opts.folder).isDirectory()) {
5044
5213
  die(`--folder needs a directory, got "${opts.folder}"`);
5045
5214
  }
5046
5215
  const files = walkFolder(opts.folder);
@@ -5052,7 +5221,7 @@ files (v${artifact.current_version.version}):`);
5052
5221
  } else if (opts.file !== void 0) {
5053
5222
  let bytes;
5054
5223
  try {
5055
- bytes = readFileSync3(opts.file);
5224
+ bytes = readFileSync4(opts.file);
5056
5225
  } catch (err) {
5057
5226
  die(`cannot read ${opts.file}: ${err instanceof Error ? err.message : String(err)}`);
5058
5227
  }
@@ -5071,10 +5240,10 @@ files (v${artifact.current_version.version}):`);
5071
5240
  ...opts.contentType !== void 0 ? { content_type: opts.contentType } : {},
5072
5241
  ...opts.description !== void 0 ? { description: opts.description } : {}
5073
5242
  });
5074
- } else if (opts.url !== void 0) {
5243
+ } else if (opts.link !== void 0) {
5075
5244
  artifact = await api.putArtifact(issue.id, name2, {
5076
5245
  type: "link",
5077
- url: opts.url,
5246
+ url: opts.link,
5078
5247
  ...opts.title !== void 0 ? { title: opts.title } : {},
5079
5248
  ...opts.description !== void 0 ? { description: opts.description } : {}
5080
5249
  });
@@ -5097,7 +5266,9 @@ files (v${artifact.current_version.version}):`);
5097
5266
  }
5098
5267
  );
5099
5268
  withCommon(
5100
- artifactsCmd.command("reaffirm <ref> <name>").description("Bless the current content as fresh (appends a version reusing the same payload)")
5269
+ artifactsCmd.command("reaffirm <ref> <name>").description(
5270
+ "Bless the current content as fresh (appends a version reusing the same payload)"
5271
+ )
5101
5272
  ).action(async (ref, name2, opts) => {
5102
5273
  const api = client(opts);
5103
5274
  const issue = await resolveIssue(api, ref);
@@ -5108,7 +5279,14 @@ files (v${artifact.current_version.version}):`);
5108
5279
  );
5109
5280
  });
5110
5281
  withCommon(
5111
- artifactsCmd.command("get <ref> <name>").description("Fetch content (current version by default); a link/pr prints its URL").option("--version <n>", "fetch a specific version from the history", (v) => Number.parseInt(v, 10)).option("--out <path>", "write to this file, or into this directory (keeps the stored filename)")
5282
+ artifactsCmd.command("get <ref> <name>").description("Fetch content (current version by default); a link/pr prints its URL").option(
5283
+ "--version <n>",
5284
+ "fetch a specific version from the history",
5285
+ (v) => Number.parseInt(v, 10)
5286
+ ).option(
5287
+ "--out <path>",
5288
+ "write to this file, or into this directory (keeps the stored filename)"
5289
+ )
5112
5290
  ).action(
5113
5291
  async (ref, name2, opts) => {
5114
5292
  const api = client(opts);
@@ -5129,7 +5307,7 @@ files (v${artifact.current_version.version}):`);
5129
5307
  if (opts.out === void 0) {
5130
5308
  die(`artifact "${name2}" is a folder \u2014 pass --out <dir> to write its tree`);
5131
5309
  }
5132
- if (existsSync(opts.out) && !statSync(opts.out).isDirectory()) {
5310
+ if (existsSync2(opts.out) && !statSync(opts.out).isDirectory()) {
5133
5311
  die(`--out for a folder must be a directory, and "${opts.out}" is a file`);
5134
5312
  }
5135
5313
  const files = version.files ?? [];
@@ -5139,9 +5317,9 @@ files (v${artifact.current_version.version}):`);
5139
5317
  version: opts.version,
5140
5318
  path: file.path
5141
5319
  });
5142
- const target2 = join(opts.out, file.path);
5143
- mkdirSync(dirname(target2), { recursive: true });
5144
- writeFileSync(target2, Buffer.from(content2.bytes));
5320
+ const target2 = join2(opts.out, file.path);
5321
+ mkdirSync2(dirname2(target2), { recursive: true });
5322
+ writeFileSync2(target2, Buffer.from(content2.bytes));
5145
5323
  total += content2.bytes.byteLength;
5146
5324
  }
5147
5325
  return console.log(
@@ -5152,10 +5330,10 @@ files (v${artifact.current_version.version}):`);
5152
5330
  const bytes = Buffer.from(content.bytes);
5153
5331
  if (opts.out !== void 0) {
5154
5332
  let target2 = opts.out;
5155
- if (existsSync(target2) && statSync(target2).isDirectory()) {
5156
- target2 = join(target2, version.filename ?? name2);
5333
+ if (existsSync2(target2) && statSync(target2).isDirectory()) {
5334
+ target2 = join2(target2, version.filename ?? name2);
5157
5335
  }
5158
- writeFileSync(target2, bytes);
5336
+ writeFileSync2(target2, bytes);
5159
5337
  return console.log(`wrote ${target2} (${bytes.byteLength} bytes, ${content.content_type})`);
5160
5338
  }
5161
5339
  if ((content.content_type ?? "").startsWith("text/")) {
@@ -5163,12 +5341,14 @@ files (v${artifact.current_version.version}):`);
5163
5341
  return;
5164
5342
  }
5165
5343
  const target = version.filename ?? name2;
5166
- writeFileSync(target, bytes);
5344
+ writeFileSync2(target, bytes);
5167
5345
  console.log(`wrote ${target} (${bytes.byteLength} bytes, ${content.content_type})`);
5168
5346
  }
5169
5347
  );
5170
5348
  withCommon(
5171
- artifactsCmd.command("delete <ref> <name>").description("Delete an artifact \u2014 every version and its stored files (history is not recoverable)")
5349
+ artifactsCmd.command("delete <ref> <name>").description(
5350
+ "Delete an artifact \u2014 every version and its stored files (history is not recoverable)"
5351
+ )
5172
5352
  ).action(async (ref, name2, opts) => {
5173
5353
  const api = client(opts);
5174
5354
  const issue = await resolveIssue(api, ref);
@@ -5179,30 +5359,38 @@ files (v${artifact.current_version.version}):`);
5179
5359
  );
5180
5360
  });
5181
5361
  withCommon(
5182
- issues.command("assign <ref> [runner]").description("Pin an issue to a runner (<runner>[:tier]) \u2014 replaces routing rules for it; --clear unpins").option("--clear", "remove the pin")
5183
- ).action(async (ref, runnerSpec, opts) => {
5184
- const api = client(opts);
5185
- const issue = await resolveIssue(api, ref);
5186
- if (opts.clear) {
5187
- if (runnerSpec !== void 0) die("--clear does not take a runner");
5188
- const updated2 = await api.updateIssue(issue.id, { pinned_runner_id: null });
5189
- if (opts.json) return printJson(updated2);
5190
- return console.log(`unpinned ${updated2.project_name}/#${updated2.number} \u2014 routing rules apply again`);
5191
- }
5192
- if (runnerSpec === void 0) die("pass <runner>[:tier] to pin, or --clear to unpin");
5193
- const { name: name2, tier } = parseTargetSpec(runnerSpec);
5194
- const runner = await resolveRunner(api, name2);
5195
- const updated = await api.updateIssue(issue.id, {
5196
- pinned_runner_id: runner.id,
5197
- pinned_tier: tier ?? null
5198
- });
5199
- if (opts.json) return printJson(updated);
5200
- console.log(
5201
- `pinned ${updated.project_name}/#${updated.number} to ${runner.name}${tier ? ` (tier ${tier})` : ""} \u2014 only this runner will take it`
5202
- );
5203
- });
5362
+ issues.command("assign <ref> [runner]").description(
5363
+ "Pin an issue to a runner (<runner>[:tier]) \u2014 replaces routing rules for it; --clear unpins"
5364
+ ).option("--clear", "remove the pin")
5365
+ ).action(
5366
+ async (ref, runnerSpec, opts) => {
5367
+ const api = client(opts);
5368
+ const issue = await resolveIssue(api, ref);
5369
+ if (opts.clear) {
5370
+ if (runnerSpec !== void 0) die("--clear does not take a runner");
5371
+ const updated2 = await api.updateIssue(issue.id, { pinned_runner_id: null });
5372
+ if (opts.json) return printJson(updated2);
5373
+ return console.log(
5374
+ `unpinned ${updated2.project_name}/#${updated2.number} \u2014 routing rules apply again`
5375
+ );
5376
+ }
5377
+ if (runnerSpec === void 0) die("pass <runner>[:tier] to pin, or --clear to unpin");
5378
+ const { name: name2, tier } = parseTargetSpec(runnerSpec);
5379
+ const runner = await resolveRunner(api, name2);
5380
+ const updated = await api.updateIssue(issue.id, {
5381
+ pinned_runner_id: runner.id,
5382
+ pinned_tier: tier ?? null
5383
+ });
5384
+ if (opts.json) return printJson(updated);
5385
+ console.log(
5386
+ `pinned ${updated.project_name}/#${updated.number} to ${runner.name}${tier ? ` (tier ${tier})` : ""} \u2014 only this runner will take it`
5387
+ );
5388
+ }
5389
+ );
5204
5390
  withCommon(
5205
- issues.command("dispatch <ref>").description("Explain why an issue is (not) dispatching: eligibility, routing, per-runner verdicts")
5391
+ issues.command("dispatch <ref>").description(
5392
+ "Explain why an issue is (not) dispatching: eligibility, routing, per-runner verdicts"
5393
+ )
5206
5394
  ).action(async (ref, opts) => {
5207
5395
  const api = client(opts);
5208
5396
  const issue = await resolveIssue(api, ref);
@@ -5270,7 +5458,9 @@ function printNote(note) {
5270
5458
  if (note) console.error(`note: ${note}`);
5271
5459
  }
5272
5460
  function register3(program3) {
5273
- const journal = program3.command("journal").description("An issue's stage journal: shared notes for its project + the stage your run was launched in");
5461
+ const journal = program3.command("journal").description(
5462
+ "An issue's stage journal: shared notes for its project + the stage your run was launched in"
5463
+ );
5274
5464
  withCommon(
5275
5465
  journal.command("show <ref>").description("Print the journal for the stage your run was launched in").option("--state <workflow>/<state>", STATE_FLAG_HELP)
5276
5466
  ).action(async (ref, opts) => {
@@ -5329,29 +5519,96 @@ start one: tines journal append ${issue.project_name}/${issue.number} "- <date>:
5329
5519
  "the version being replaced (from the prompt or journal show)",
5330
5520
  (v) => Number.parseInt(v, 10)
5331
5521
  )
5332
- ).action(async (ref, opts) => {
5333
- const api = client(opts);
5334
- const { scope, note, item } = await resolveJournal(api, ref, opts.state);
5335
- printNote(note);
5336
- if (!item) die(`no journal exists yet for ${scope.label}; nothing to rewrite`);
5337
- const updated = await api.updateContextItem(item.id, {
5338
- body: readBodyValue(opts.body),
5339
- expected_version: opts.expectVersion
5340
- });
5341
- if (opts.json) return printJson(updated);
5342
- console.log(`rewrote the ${scope.label} journal (now v${updated.version})`);
5522
+ ).action(
5523
+ async (ref, opts) => {
5524
+ const api = client(opts);
5525
+ const { scope, note, item } = await resolveJournal(api, ref, opts.state);
5526
+ printNote(note);
5527
+ if (!item) die(`no journal exists yet for ${scope.label}; nothing to rewrite`);
5528
+ const updated = await api.updateContextItem(item.id, {
5529
+ body: readBodyValue(opts.body),
5530
+ expected_version: opts.expectVersion
5531
+ });
5532
+ if (opts.json) return printJson(updated);
5533
+ console.log(`rewrote the ${scope.label} journal (now v${updated.version})`);
5534
+ }
5535
+ );
5536
+ }
5537
+
5538
+ // src/commands/login.ts
5539
+ import { readFileSync as readFileSync5 } from "node:fs";
5540
+ function maskKey(key) {
5541
+ return `${key.slice(0, 14)}\u2026`;
5542
+ }
5543
+ function readKeyArg(value) {
5544
+ if (value !== "-") return value;
5545
+ const key = readFileSync5(0, "utf8").trim();
5546
+ if (!key) die("no API key on stdin");
5547
+ return key;
5548
+ }
5549
+ function register4(program3) {
5550
+ program3.command("login").description("Store the API URL and key for this machine, so commands need no env vars").option("-u, --url <url>", `base URL of the Tines API to store (default ${DEFAULT_URL})`).option("--api-key <key>", 'API key from Settings \u2192 API keys ("-" reads it from stdin)').option("--no-verify", "store without checking the key against the API").action(async (opts) => {
5551
+ if (!opts.url && !opts.apiKey) die("pass --url and/or --api-key");
5552
+ const dir = defaultConfigDir();
5553
+ const apiKey = opts.apiKey ? readKeyArg(opts.apiKey) : void 0;
5554
+ const url = (opts.url ?? resolveUrlSetting({}).value ?? DEFAULT_URL).replace(/\/+$/, "");
5555
+ const key = apiKey ?? resolveApiKeySetting({}).value;
5556
+ if (opts.verify && key) {
5557
+ try {
5558
+ await createApiClient({ baseUrl: url, apiKey: key }).listProjects({ limit: 1 });
5559
+ } catch (err) {
5560
+ if (err instanceof ApiError && err.status === 401) {
5561
+ die(`${url} rejected the API key (${err.message}); nothing stored`);
5562
+ }
5563
+ if (err instanceof ApiError) throw err;
5564
+ const cause = err.cause?.code;
5565
+ die(
5566
+ `could not reach ${url}${cause ? ` (${cause})` : ""}; nothing stored (pass --no-verify to store anyway)`
5567
+ );
5568
+ }
5569
+ }
5570
+ const stored = saveCliConfig(dir, { url: opts.url, api_key: apiKey });
5571
+ console.log(`stored in ${configPath(dir)}`);
5572
+ console.log(`url: ${stored.url ?? `${DEFAULT_URL} (default)`}`);
5573
+ console.log(`api key: ${stored.api_key ? maskKey(stored.api_key) : "none"}`);
5574
+ });
5575
+ program3.command("logout").description("Forget the stored API URL and key").action(() => {
5576
+ const dir = defaultConfigDir();
5577
+ console.log(
5578
+ clearCliConfig(dir) ? `removed ${configPath(dir)}` : `nothing stored in ${configPath(dir)}`
5579
+ );
5580
+ });
5581
+ withCommon(
5582
+ program3.command("config").description("Show the API URL and key in effect, and where each comes from")
5583
+ ).action((opts) => {
5584
+ const url = resolveUrlSetting(opts);
5585
+ const key = resolveApiKeySetting(opts);
5586
+ const report = {
5587
+ url: { value: url.value, source: url.source },
5588
+ api_key: {
5589
+ value: key.value ? maskKey(key.value) : null,
5590
+ source: key.value ? key.source : null
5591
+ },
5592
+ config_path: configPath(defaultConfigDir())
5593
+ };
5594
+ if (opts.json) return printJson(report);
5595
+ console.log(`url: ${report.url.value} (${report.url.source})`);
5596
+ console.log(
5597
+ report.api_key.value ? `api key: ${report.api_key.value} (${report.api_key.source})` : "api key: none (pass --api-key, set TINES_API_KEY, or run `tines login`)"
5598
+ );
5599
+ console.log(`config file: ${report.config_path}`);
5343
5600
  });
5344
5601
  }
5345
5602
 
5346
5603
  // src/commands/misc.ts
5347
5604
  function registerTime(program3) {
5348
- withCommon(program3.command("time").description("Fetch the current time from the Tines API")).action(
5349
- async (opts) => {
5350
- const result = await client(opts).getTime();
5351
- if (opts.json) printJson(result);
5352
- else console.log(`Server time: ${result.time} (unix ${result.unix})`);
5353
- }
5354
- );
5605
+ withCommon(
5606
+ program3.command("time").description("Fetch the current time from the Tines API")
5607
+ ).action(async (opts) => {
5608
+ const result = await client(opts).getTime();
5609
+ if (opts.json) printJson(result);
5610
+ else console.log(`Server time: ${result.time} (unix ${result.unix})`);
5611
+ });
5355
5612
  }
5356
5613
  function registerEvents(program3) {
5357
5614
  const events = program3.command("events").description("Read the activity log");
@@ -5380,7 +5637,7 @@ function registerEvents(program3) {
5380
5637
  }
5381
5638
 
5382
5639
  // src/commands/projects.ts
5383
- function register4(program3) {
5640
+ function register5(program3) {
5384
5641
  const projects = program3.command("projects").description("Manage projects");
5385
5642
  withList(projects.command("list").description("List projects")).action(async (opts) => {
5386
5643
  const api = client(opts);
@@ -5394,7 +5651,10 @@ function register4(program3) {
5394
5651
  });
5395
5652
  });
5396
5653
  withCommon(
5397
- projects.command("create <name>").description("Create a project (with its initial context prompt)").option("-d, --description <text>", "project description").option("-w, --default-workflow <id-or-name>", "default workflow for new issues").option("--prompt <md>", "initial conventions prompt, stitched into every issue's agent prompt: inline Markdown or @file").option("--no-prompt", "create without an initial prompt")
5654
+ projects.command("create <name>").description("Create a project (with its initial context prompt)").option("-d, --description <text>", "project description").option("-w, --default-workflow <id-or-name>", "default workflow for new issues").option(
5655
+ "--prompt <md>",
5656
+ "initial conventions prompt, stitched into every issue's agent prompt: inline Markdown or @file"
5657
+ ).option("--no-prompt", "create without an initial prompt")
5398
5658
  ).action(
5399
5659
  async (name2, opts) => {
5400
5660
  if (opts.prompt === void 0 || opts.prompt === true) {
@@ -5471,8 +5731,10 @@ async function resolveRoutingScope(api, opts) {
5471
5731
  if (opts.state) parts.push(`state ${opts.state}`);
5472
5732
  return { projectId, stateId, label: parts.length > 0 ? parts.join(" \xB7 ") : "global" };
5473
5733
  }
5474
- function register5(program3) {
5475
- const routing = program3.command("routing").description("Scoped routing rules: which runner takes which issues (most specific scope wins)");
5734
+ function register6(program3) {
5735
+ const routing = program3.command("routing").description(
5736
+ "Scoped routing rules: which runner takes which issues (most specific scope wins)"
5737
+ );
5476
5738
  withCommon(routing.command("list").description("List routing rules, most specific first")).action(
5477
5739
  async (opts) => {
5478
5740
  const res = await client(opts).listRoutingRules();
@@ -5485,7 +5747,9 @@ function register5(program3) {
5485
5747
  }
5486
5748
  );
5487
5749
  withCommon(
5488
- routing.command("set <target...>").description("Create or replace the rule at a scope: an ordered list of <runner>[:tier] targets").option("-p, --project <name>", "scope: project name or id").option("-s, --state <workflow/state>", "scope: workflow-qualified state")
5750
+ routing.command("set <target...>").description(
5751
+ "Create or replace the rule at a scope: an ordered list of <runner>[:tier] targets"
5752
+ ).option("-p, --project <name>", "scope: project name or id").option("-s, --state <workflow/state>", "scope: workflow-qualified state")
5489
5753
  ).action(async (targetSpecs, opts) => {
5490
5754
  const api = client(opts);
5491
5755
  const scope = await resolveRoutingScope(api, opts);
@@ -5499,7 +5763,11 @@ function register5(program3) {
5499
5763
  const existing = items.find(
5500
5764
  (r) => r.scope.project_id === scope.projectId && r.scope.workflow_state_id === scope.stateId
5501
5765
  );
5502
- const rule = existing ? await api.updateRoutingRule(existing.id, { targets }) : await api.createRoutingRule({ project_id: scope.projectId, workflow_state_id: scope.stateId, targets });
5766
+ const rule = existing ? await api.updateRoutingRule(existing.id, { targets }) : await api.createRoutingRule({
5767
+ project_id: scope.projectId,
5768
+ workflow_state_id: scope.stateId,
5769
+ targets
5770
+ });
5503
5771
  if (opts.json) return printJson(rule);
5504
5772
  console.log(
5505
5773
  `${existing ? "updated" : "created"} the ${rule.scope.label} rule: ${ruleTargetsLabel(rule)}`
@@ -5530,21 +5798,21 @@ import {
5530
5798
  createWriteStream,
5531
5799
  existsSync as existsSync4,
5532
5800
  mkdirSync as mkdirSync4,
5533
- readFileSync as readFileSync7,
5801
+ readFileSync as readFileSync9,
5534
5802
  rmSync as rmSync2,
5535
5803
  statSync as statSync3,
5536
- unlinkSync,
5804
+ unlinkSync as unlinkSync2,
5537
5805
  writeFileSync as writeFileSync3
5538
5806
  } from "node:fs";
5539
5807
  import { hostname, platform, arch } from "node:os";
5540
- import { dirname as dirname4, join as join4 } from "node:path";
5808
+ import { dirname as dirname4, join as join5 } from "node:path";
5541
5809
 
5542
5810
  // src/version.ts
5543
- import { readFileSync as readFileSync4 } from "node:fs";
5811
+ import { readFileSync as readFileSync6 } from "node:fs";
5544
5812
  function cliVersion() {
5545
5813
  try {
5546
5814
  const manifest = new URL("../package.json", import.meta.url);
5547
- return JSON.parse(readFileSync4(manifest, "utf8")).version ?? "0.0.0-unknown";
5815
+ return JSON.parse(readFileSync6(manifest, "utf8")).version ?? "0.0.0-unknown";
5548
5816
  } catch {
5549
5817
  return "0.0.0-unknown";
5550
5818
  }
@@ -5592,7 +5860,8 @@ function renderStreamEvent(event) {
5592
5860
  case "result": {
5593
5861
  const parts = [];
5594
5862
  if (typeof event.num_turns === "number") parts.push(`${event.num_turns} turns`);
5595
- if (typeof event.total_cost_usd === "number") parts.push(`$${event.total_cost_usd.toFixed(2)}`);
5863
+ if (typeof event.total_cost_usd === "number")
5864
+ parts.push(`$${event.total_cost_usd.toFixed(2)}`);
5596
5865
  const detail = parts.length > 0 ? ` (${parts.join(", ")})` : "";
5597
5866
  const lines = [`[session] result: ${event.subtype ?? "done"}${detail}`];
5598
5867
  if (event.is_error && event.result) lines.push(`[error] ${clip(event.result, 2e3)}`);
@@ -5647,8 +5916,8 @@ var ClaudeStreamRenderer = class {
5647
5916
 
5648
5917
  // src/daemon/cli-refresh.ts
5649
5918
  import { spawn } from "node:child_process";
5650
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "node:fs";
5651
- import { dirname as dirname2, join as join2 } from "node:path";
5919
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync7 } from "node:fs";
5920
+ import { dirname as dirname3, join as join3 } from "node:path";
5652
5921
 
5653
5922
  // src/daemon/support.ts
5654
5923
  import { delimiter } from "node:path";
@@ -5923,14 +6192,14 @@ function buildSpawnEnv(base, opts) {
5923
6192
  var PACKAGE = "tines";
5924
6193
  var NPM_ARGS = ["--min-release-age=0", "--no-audit", "--no-fund", "--loglevel=error"];
5925
6194
  function agentCliPrefix(configDir) {
5926
- return join2(configDir, "cli");
6195
+ return join3(configDir, "cli");
5927
6196
  }
5928
6197
  function binDirOf(prefix) {
5929
- return join2(prefix, "node_modules", ".bin");
6198
+ return join3(prefix, "node_modules", ".bin");
5930
6199
  }
5931
6200
  function installedVersion(prefix) {
5932
6201
  try {
5933
- const pkg = readFileSync5(join2(prefix, "node_modules", PACKAGE, "package.json"), "utf8");
6202
+ const pkg = readFileSync7(join3(prefix, "node_modules", PACKAGE, "package.json"), "utf8");
5934
6203
  const version = JSON.parse(pkg).version;
5935
6204
  return typeof version === "string" ? version : null;
5936
6205
  } catch {
@@ -5939,7 +6208,7 @@ function installedVersion(prefix) {
5939
6208
  }
5940
6209
  function lastGood(prefix) {
5941
6210
  const binDir = binDirOf(prefix);
5942
- if (!existsSync2(join2(binDir, PACKAGE))) return AMBIENT_CLI;
6211
+ if (!existsSync3(join3(binDir, PACKAGE))) return AMBIENT_CLI;
5943
6212
  return { binDir, version: installedVersion(prefix), source: "stale" };
5944
6213
  }
5945
6214
  function runNpm(file, args, cwd, timeoutMs) {
@@ -5976,7 +6245,7 @@ async function installAgentCli(opts) {
5976
6245
  const prefix = agentCliPrefix(opts.configDir);
5977
6246
  const timeoutMs = opts.timeoutMs ?? 6e4;
5978
6247
  try {
5979
- mkdirSync2(prefix, { recursive: true });
6248
+ mkdirSync3(prefix, { recursive: true });
5980
6249
  } catch (err) {
5981
6250
  opts.log(`agent CLI refresh: cannot create ${prefix} (${message(err)})`);
5982
6251
  return AMBIENT_CLI;
@@ -5984,8 +6253,8 @@ async function installAgentCli(opts) {
5984
6253
  const args = ["install", "--prefix", prefix, `${PACKAGE}@latest`, ...NPM_ARGS];
5985
6254
  let result = await runNpm("npm", args, prefix, timeoutMs);
5986
6255
  if (result.spawnError?.code === "ENOENT") {
5987
- const sibling = join2(dirname2(process.execPath), "npm");
5988
- if (existsSync2(sibling)) result = await runNpm(sibling, args, prefix, timeoutMs);
6256
+ const sibling = join3(dirname3(process.execPath), "npm");
6257
+ if (existsSync3(sibling)) result = await runNpm(sibling, args, prefix, timeoutMs);
5989
6258
  }
5990
6259
  if (result.code !== 0) {
5991
6260
  const fallback = lastGood(prefix);
@@ -6007,38 +6276,13 @@ function message(err) {
6007
6276
  }
6008
6277
 
6009
6278
  // src/daemon/store.ts
6010
- import {
6011
- existsSync as existsSync3,
6012
- mkdirSync as mkdirSync3,
6013
- readdirSync as readdirSync2,
6014
- readFileSync as readFileSync6,
6015
- rmSync,
6016
- statSync as statSync2,
6017
- writeFileSync as writeFileSync2
6018
- } from "node:fs";
6019
- import { homedir } from "node:os";
6020
- import { dirname as dirname3, join as join3 } from "node:path";
6021
- function defaultConfigDir() {
6022
- return process.env.TINES_CONFIG_DIR ?? join3(homedir(), ".config", "tines");
6023
- }
6024
- function readJsonFile(path2) {
6025
- if (!existsSync3(path2)) return null;
6026
- try {
6027
- return JSON.parse(readFileSync6(path2, "utf8"));
6028
- } catch {
6029
- return null;
6030
- }
6031
- }
6032
- function writeJsonFile(path2, value, { secret = false } = {}) {
6033
- mkdirSync3(dirname3(path2), { recursive: true });
6034
- writeFileSync2(path2, `${JSON.stringify(value, null, 2)}
6035
- `, secret ? { mode: 384 } : {});
6036
- }
6279
+ import { readdirSync as readdirSync2, readFileSync as readFileSync8, rmSync, statSync as statSync2 } from "node:fs";
6280
+ import { join as join4 } from "node:path";
6037
6281
  function credentialsKey(url, name2) {
6038
6282
  return `${url.replace(/\/+$/, "")}#${name2}`;
6039
6283
  }
6040
6284
  function credentialsPath(dir) {
6041
- return join3(dir, "runners.json");
6285
+ return join4(dir, "runners.json");
6042
6286
  }
6043
6287
  function loadRunnerCredentials(dir, url, name2) {
6044
6288
  const all = readJsonFile(credentialsPath(dir));
@@ -6061,7 +6305,7 @@ function clearRunnerCredentials(dir, url, name2) {
6061
6305
  writeJsonFile(path2, all, { secret: true });
6062
6306
  }
6063
6307
  function daemonStatePath(dir, runnerId) {
6064
- return join3(dir, `daemon-state-${runnerId}.json`);
6308
+ return join4(dir, `daemon-state-${runnerId}.json`);
6065
6309
  }
6066
6310
  function loadDaemonState(path2) {
6067
6311
  const state = readJsonFile(path2);
@@ -6075,10 +6319,10 @@ function saveDaemonState(path2, runs) {
6075
6319
  }
6076
6320
  function processStartTimeMs(pid) {
6077
6321
  try {
6078
- const stat = readFileSync6(`/proc/${pid}/stat`, "utf8");
6322
+ const stat = readFileSync8(`/proc/${pid}/stat`, "utf8");
6079
6323
  const afterComm = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
6080
6324
  const startTicks = Number(afterComm[19]);
6081
- const btimeLine = readFileSync6("/proc/stat", "utf8").split("\n").find((line) => line.startsWith("btime "));
6325
+ const btimeLine = readFileSync8("/proc/stat", "utf8").split("\n").find((line) => line.startsWith("btime "));
6082
6326
  const btime = Number(btimeLine?.slice("btime ".length));
6083
6327
  if (!Number.isFinite(startTicks) || !Number.isFinite(btime)) return null;
6084
6328
  return btime * 1e3 + startTicks / 100 * 1e3;
@@ -6087,10 +6331,10 @@ function processStartTimeMs(pid) {
6087
6331
  }
6088
6332
  }
6089
6333
  function workspacesDir(configDir) {
6090
- return join3(configDir, "workspaces");
6334
+ return join4(configDir, "workspaces");
6091
6335
  }
6092
6336
  function keptMarkerPath(workspace) {
6093
- return join3(workspace, "kept.json");
6337
+ return join4(workspace, "kept.json");
6094
6338
  }
6095
6339
  function writeKeptMarker(workspace, marker) {
6096
6340
  writeJsonFile(keptMarkerPath(workspace), marker);
@@ -6112,7 +6356,7 @@ function listKeptWorkspaces(configDir) {
6112
6356
  }
6113
6357
  const kept = [];
6114
6358
  for (const name2 of names) {
6115
- const path2 = join3(root, name2);
6359
+ const path2 = join4(root, name2);
6116
6360
  const marker = readKeptMarker(path2);
6117
6361
  if (marker) kept.push({ ...marker, path: path2 });
6118
6362
  }
@@ -6149,7 +6393,7 @@ function directorySizeBytes(path2) {
6149
6393
  return 0;
6150
6394
  }
6151
6395
  for (const entry of entries) {
6152
- const child = join3(path2, entry.name);
6396
+ const child = join4(path2, entry.name);
6153
6397
  if (entry.isDirectory()) total += directorySizeBytes(child);
6154
6398
  else if (entry.isFile()) {
6155
6399
  try {
@@ -6172,20 +6416,23 @@ async function uploadRawLog(run) {
6172
6416
  await new Promise((resolve) => run.rawSpool?.end(resolve) ?? resolve());
6173
6417
  const size = statSync3(path2).size;
6174
6418
  if (size > 0) {
6175
- let body = readFileSync7(path2);
6419
+ let body = readFileSync9(path2);
6176
6420
  if (body.byteLength > RUN_LOG_RAW_MAX_BYTES) {
6177
6421
  const marker = Buffer.from(
6178
6422
  `{"type":"tines_truncated","dropped_bytes":${body.byteLength - RUN_LOG_RAW_MAX_BYTES}}
6179
6423
  `
6180
6424
  );
6181
- body = Buffer.concat([marker, body.subarray(body.byteLength - RUN_LOG_RAW_MAX_BYTES + marker.byteLength)]);
6425
+ body = Buffer.concat([
6426
+ marker,
6427
+ body.subarray(body.byteLength - RUN_LOG_RAW_MAX_BYTES + marker.byteLength)
6428
+ ]);
6182
6429
  }
6183
6430
  await run.rawUpload(body);
6184
6431
  }
6185
6432
  } catch {
6186
6433
  } finally {
6187
6434
  try {
6188
- unlinkSync(path2);
6435
+ unlinkSync2(path2);
6189
6436
  } catch {
6190
6437
  }
6191
6438
  }
@@ -6242,7 +6489,9 @@ async function runDaemon(opts) {
6242
6489
  };
6243
6490
  let creds = loadRunnerCredentials(opts.configDir, opts.url, opts.name);
6244
6491
  if (creds) {
6245
- log(`reconnecting as runner "${opts.name}" (${creds.runner_id}) \u2014 token from ${opts.configDir}`);
6492
+ log(
6493
+ `reconnecting as runner "${opts.name}" (${creds.runner_id}) \u2014 token from ${opts.configDir}`
6494
+ );
6246
6495
  } else {
6247
6496
  if (!opts.apiKey) {
6248
6497
  throw new Error(
@@ -6271,37 +6520,40 @@ async function runDaemon(opts) {
6271
6520
  const ensureCli = () => opts.cliRefresh ? refresher.ensure() : Promise.resolve(AMBIENT_CLI);
6272
6521
  const cliLabel = (cli) => cli.source === "ambient" ? `ambient PATH (${opts.cliRefresh ? "refresh failed" : "refresh disabled"})` : `tines ${cli.version ?? "unknown"} (daemon-managed${cli.source === "stale" ? ", last-good copy" : ""})`;
6273
6522
  log(`agent CLI: ${cliLabel(await ensureCli())}`);
6274
- const table2 = new RunTable({
6275
- finish: async (run, status, error) => {
6276
- await client2.finishRun(run.runId, { status, ...error ? { error } : {} });
6277
- },
6278
- release: (run, { keep, outcome }) => {
6279
- if (run.timeout) clearTimeout(run.timeout);
6280
- run.renderer?.finish();
6281
- settleWorkspace(run.workspace, keep, {
6282
- run_id: run.runId,
6283
- ...run.issueLabel ? { issue_ref: run.issueLabel } : {},
6284
- status: outcome,
6285
- ...run.endNote ? { error: run.endNote } : {}
6286
- });
6287
- void uploadRawLog(run);
6288
- sweepKeptWorkspaces();
6289
- },
6290
- noteKept: (run) => run.batcher.append(`workspace kept at ${run.workspace}
6523
+ const table2 = new RunTable(
6524
+ {
6525
+ finish: async (run, status, error) => {
6526
+ await client2.finishRun(run.runId, { status, ...error ? { error } : {} });
6527
+ },
6528
+ release: (run, { keep, outcome }) => {
6529
+ if (run.timeout) clearTimeout(run.timeout);
6530
+ run.renderer?.finish();
6531
+ settleWorkspace(run.workspace, keep, {
6532
+ run_id: run.runId,
6533
+ ...run.issueLabel ? { issue_ref: run.issueLabel } : {},
6534
+ status: outcome,
6535
+ ...run.endNote ? { error: run.endNote } : {}
6536
+ });
6537
+ void uploadRawLog(run);
6538
+ sweepKeptWorkspaces();
6539
+ },
6540
+ noteKept: (run) => run.batcher.append(`workspace kept at ${run.workspace}
6291
6541
  `),
6292
- persist: () => {
6293
- const entries = table2.values().filter((run) => run.child?.pid !== void 0).map((run) => ({
6294
- run_id: run.runId,
6295
- pid: run.child.pid,
6296
- workspace: run.workspace,
6297
- key_fingerprint: run.keyFingerprint,
6298
- started_at: run.spawnedAt,
6299
- ...run.issueLabel ? { issue_ref: run.issueLabel } : {}
6300
- }));
6301
- saveDaemonState(statePath, entries);
6542
+ persist: () => {
6543
+ const entries = table2.values().filter((run) => run.child?.pid !== void 0).map((run) => ({
6544
+ run_id: run.runId,
6545
+ pid: run.child.pid,
6546
+ workspace: run.workspace,
6547
+ key_fingerprint: run.keyFingerprint,
6548
+ started_at: run.spawnedAt,
6549
+ ...run.issueLabel ? { issue_ref: run.issueLabel } : {}
6550
+ }));
6551
+ saveDaemonState(statePath, entries);
6552
+ },
6553
+ log
6302
6554
  },
6303
- log
6304
- }, { keep: (outcome) => keepWorkspace(opts.keepWorkspaces, outcome) });
6555
+ { keep: (outcome) => keepWorkspace(opts.keepWorkspaces, outcome) }
6556
+ );
6305
6557
  for (const orphan of loadDaemonState(statePath)) {
6306
6558
  if (pidAlive(orphan.pid)) {
6307
6559
  const processStart = processStartTimeMs(orphan.pid);
@@ -6309,7 +6561,9 @@ async function runDaemon(opts) {
6309
6561
  if (reused) {
6310
6562
  log(`state-file pid ${orphan.pid} (run ${orphan.run_id}) was recycled; not killing it`);
6311
6563
  } else {
6312
- log(`killing orphaned harness from a previous life: run ${orphan.run_id} (pid ${orphan.pid})`);
6564
+ log(
6565
+ `killing orphaned harness from a previous life: run ${orphan.run_id} (pid ${orphan.pid})`
6566
+ );
6313
6567
  killTree(orphan.pid, "SIGKILL");
6314
6568
  }
6315
6569
  }
@@ -6342,7 +6596,7 @@ async function runDaemon(opts) {
6342
6596
  const launch = async (assignment) => {
6343
6597
  const runId = assignment.run.id;
6344
6598
  if (table2.has(runId)) return;
6345
- const workspace = join4(workspacesDir(opts.configDir), runId);
6599
+ const workspace = join5(workspacesDir(opts.configDir), runId);
6346
6600
  const issueLabel = assignment.run.issue_ref ? `${assignment.run.issue_ref.project_name}/${assignment.run.issue_ref.number}` : void 0;
6347
6601
  const run = {
6348
6602
  runId,
@@ -6360,32 +6614,43 @@ async function runDaemon(opts) {
6360
6614
  };
6361
6615
  run.flush = () => run.batcher.flush();
6362
6616
  table2.track(run);
6363
- log(`run ${runId} assigned (issue ${issueLabel ?? assignment.run.issue_id}); materializing workspace`);
6617
+ log(
6618
+ `run ${runId} assigned (issue ${issueLabel ?? assignment.run.issue_id}); materializing workspace`
6619
+ );
6364
6620
  try {
6365
6621
  rmSync2(workspace, { recursive: true, force: true });
6366
6622
  mkdirSync4(workspace, { recursive: true });
6367
- writeFileSync3(join4(workspace, "prompt.md"), `${assignment.prompt}
6623
+ writeFileSync3(join5(workspace, "prompt.md"), `${assignment.prompt}
6368
6624
  `);
6369
6625
  for (const skill of assignment.bundle.skills) {
6370
6626
  for (const file of skill.files) {
6371
- const target = join4(workspace, "skills", skill.name, file.path);
6627
+ const target = join5(workspace, "skills", skill.name, file.path);
6372
6628
  mkdirSync4(dirname4(target), { recursive: true });
6373
6629
  writeFileSync3(target, file.content);
6374
6630
  }
6375
6631
  }
6376
6632
  writeFileSync3(
6377
- join4(workspace, "repos.json"),
6633
+ join5(workspace, "repos.json"),
6378
6634
  `${JSON.stringify(assignment.bundle.repos, null, 2)}
6379
6635
  `
6380
6636
  );
6381
6637
  for (const repo of assignment.bundle.repos) {
6382
6638
  if (run.settled) return table2.cleanup(run);
6383
- const args = ["clone", ...repo.branch ? ["--branch", repo.branch] : [], repo.url, repo.dir];
6639
+ const args = [
6640
+ "clone",
6641
+ ...repo.branch ? ["--branch", repo.branch] : [],
6642
+ repo.url,
6643
+ repo.dir
6644
+ ];
6384
6645
  run.batcher.append(`$ git ${args.join(" ")}
6385
6646
  `);
6386
6647
  const result = await runGit(args, workspace, run.batcher);
6387
6648
  if (result !== 0) {
6388
- return table2.finishAndCleanup(run, "failed", `git clone failed for ${repo.url} (exit ${result})`);
6649
+ return table2.finishAndCleanup(
6650
+ run,
6651
+ "failed",
6652
+ `git clone failed for ${repo.url} (exit ${result})`
6653
+ );
6389
6654
  }
6390
6655
  }
6391
6656
  if (run.settled) return table2.cleanup(run);
@@ -6399,7 +6664,7 @@ async function runDaemon(opts) {
6399
6664
  );
6400
6665
  const harnessInput = {
6401
6666
  workspace,
6402
- promptFile: join4(workspace, "prompt.md"),
6667
+ promptFile: join5(workspace, "prompt.md"),
6403
6668
  prompt: assignment.prompt,
6404
6669
  model: assignment.run.model
6405
6670
  };
@@ -6432,7 +6697,7 @@ async function runDaemon(opts) {
6432
6697
  const renderer = new ClaudeStreamRenderer((line) => run.batcher.append(line));
6433
6698
  run.renderer = renderer;
6434
6699
  run.drain = () => renderer.finish();
6435
- const spoolPath = join4(opts.configDir, "rawlogs", `${runId}.ndjson`);
6700
+ const spoolPath = join5(opts.configDir, "rawlogs", `${runId}.ndjson`);
6436
6701
  mkdirSync4(dirname4(spoolPath), { recursive: true });
6437
6702
  run.rawSpoolPath = spoolPath;
6438
6703
  run.rawSpool = createWriteStream(spoolPath);
@@ -6445,16 +6710,13 @@ async function runDaemon(opts) {
6445
6710
  child.stdout?.on("data", (data) => run.batcher.append(data.toString("utf8")));
6446
6711
  }
6447
6712
  child.stderr?.on("data", (data) => run.batcher.append(data.toString("utf8")));
6448
- run.timeout = setTimeout(
6449
- () => {
6450
- if (run.settled) return;
6451
- log(`run ${runId} hit its ${assignment.timeout_minutes}m timeout; killing`);
6452
- run.timedOut = true;
6453
- if (child.pid) killTree(child.pid, "SIGTERM");
6454
- if (child.pid) setTimeout(() => killTree(child.pid, "SIGKILL"), 5e3).unref?.();
6455
- },
6456
- assignment.timeout_minutes * 6e4
6457
- );
6713
+ run.timeout = setTimeout(() => {
6714
+ if (run.settled) return;
6715
+ log(`run ${runId} hit its ${assignment.timeout_minutes}m timeout; killing`);
6716
+ run.timedOut = true;
6717
+ if (child.pid) killTree(child.pid, "SIGTERM");
6718
+ if (child.pid) setTimeout(() => killTree(child.pid, "SIGKILL"), 5e3).unref?.();
6719
+ }, assignment.timeout_minutes * 6e4);
6458
6720
  child.on("error", (err) => {
6459
6721
  void table2.finishAndCleanup(run, "failed", `failed to launch harness: ${message2(err)}`);
6460
6722
  });
@@ -6578,27 +6840,31 @@ function printTierTable(runner) {
6578
6840
  override?.effort ? `effort ${override.effort}` : null,
6579
6841
  stale ? `stale \u2014 built-in is now ${builtin}` : null
6580
6842
  ].filter(Boolean);
6581
- console.log(` ${tier}: ${model} [${source}]${marks.length > 0 ? ` (${marks.join(", ")})` : ""}`);
6843
+ console.log(
6844
+ ` ${tier}: ${model} [${source}]${marks.length > 0 ? ` (${marks.join(", ")})` : ""}`
6845
+ );
6582
6846
  }
6583
6847
  }
6584
- function register6(program3) {
6848
+ function register7(program3) {
6585
6849
  const runners = program3.command("runners").description("Manage the runner registry");
6586
- withCommon(runners.command("list").description("List runners")).action(async (opts) => {
6587
- const res = await client(opts).listRunners();
6588
- if (opts.json) return printJson(res);
6589
- if (res.items.length === 0) return console.log("no runners");
6590
- table([
6591
- ["NAME", "TYPE", "STATUS", "RUNS", "TIER", "LAST SEEN"],
6592
- ...res.items.map((r) => [
6593
- r.name,
6594
- r.type,
6595
- runnerStatusLabel(r),
6596
- `${r.active_runs}/${r.max_concurrent}`,
6597
- r.default_tier,
6598
- r.last_seen_at ? timestamp(r.last_seen_at) : "\u2014"
6599
- ])
6600
- ]);
6601
- });
6850
+ withCommon(runners.command("list").description("List runners")).action(
6851
+ async (opts) => {
6852
+ const res = await client(opts).listRunners();
6853
+ if (opts.json) return printJson(res);
6854
+ if (res.items.length === 0) return console.log("no runners");
6855
+ table([
6856
+ ["NAME", "TYPE", "STATUS", "RUNS", "TIER", "LAST SEEN"],
6857
+ ...res.items.map((r) => [
6858
+ r.name,
6859
+ r.type,
6860
+ runnerStatusLabel(r),
6861
+ `${r.active_runs}/${r.max_concurrent}`,
6862
+ r.default_tier,
6863
+ r.last_seen_at ? timestamp(r.last_seen_at) : "\u2014"
6864
+ ])
6865
+ ]);
6866
+ }
6867
+ );
6602
6868
  withCommon(runners.command("show <name>").description("Show a runner")).action(
6603
6869
  async (ref, opts) => {
6604
6870
  const runner = await resolveRunner(client(opts), ref);
@@ -6622,7 +6888,8 @@ function register6(program3) {
6622
6888
  const b = runner.budget;
6623
6889
  const parts = [];
6624
6890
  if (b.max_run_cost_usd !== void 0) parts.push(`$${b.max_run_cost_usd}/run`);
6625
- if (b.max_run_tokens !== void 0) parts.push(`${b.max_run_tokens.toLocaleString()} tok/run`);
6891
+ if (b.max_run_tokens !== void 0)
6892
+ parts.push(`${b.max_run_tokens.toLocaleString()} tok/run`);
6626
6893
  if (b.daily_usd !== void 0) parts.push(`$${b.daily_usd}/day`);
6627
6894
  if (b.daily_tokens !== void 0) parts.push(`${b.daily_tokens.toLocaleString()} tok/day`);
6628
6895
  if (parts.length > 0) console.log(`budget: ${parts.join(" ")}`);
@@ -6676,7 +6943,12 @@ function register6(program3) {
6676
6943
  }
6677
6944
  );
6678
6945
  withCommon(
6679
- runners.command("budget <name>").description("Set or clear a runner's money limits (per-run caps enforce now; daily limits arrive with the budgets milestone)").option("--max-run-usd <n>", "hard per-run cost cap (platform-enforced on Claude runners)").option("--max-run-tokens <n>", "hard per-run token cap (input + output)").option("--daily-usd <n>", "daily USD limit (stored now, enforced by the budgets milestone)").option("--daily-tokens <n>", "daily token limit (stored now, enforced by the budgets milestone)").option("--clear", "remove all limits")
6946
+ runners.command("budget <name>").description(
6947
+ "Set or clear a runner's money limits (per-run caps enforce now; daily limits arrive with the budgets milestone)"
6948
+ ).option("--max-run-usd <n>", "hard per-run cost cap (platform-enforced on Claude runners)").option("--max-run-tokens <n>", "hard per-run token cap (input + output)").option("--daily-usd <n>", "daily USD limit (stored now, enforced by the budgets milestone)").option(
6949
+ "--daily-tokens <n>",
6950
+ "daily token limit (stored now, enforced by the budgets milestone)"
6951
+ ).option("--clear", "remove all limits")
6680
6952
  ).action(
6681
6953
  async (ref, opts) => {
6682
6954
  const api = client(opts);
@@ -6691,7 +6963,8 @@ function register6(program3) {
6691
6963
  } else if (flags) {
6692
6964
  const num = (value, flag) => {
6693
6965
  const n = Number(value);
6694
- if (!Number.isFinite(n) || n <= 0) die(`${flag} must be a positive number, got "${value}"`);
6966
+ if (!Number.isFinite(n) || n <= 0)
6967
+ die(`${flag} must be a positive number, got "${value}"`);
6695
6968
  return n;
6696
6969
  };
6697
6970
  updated = await api.updateRunner(runner.id, {
@@ -6709,10 +6982,14 @@ function register6(program3) {
6709
6982
  if (!b) return console.log(`no limits on "${updated.name}"`);
6710
6983
  console.log(`limits on "${updated.name}":`);
6711
6984
  if (b.max_run_cost_usd !== void 0) console.log(` $${b.max_run_cost_usd} per run`);
6712
- if (b.max_run_tokens !== void 0) console.log(` ${b.max_run_tokens.toLocaleString()} tokens per run`);
6713
- if (b.daily_usd !== void 0) console.log(` $${b.daily_usd} per day (enforced by the budgets milestone)`);
6985
+ if (b.max_run_tokens !== void 0)
6986
+ console.log(` ${b.max_run_tokens.toLocaleString()} tokens per run`);
6987
+ if (b.daily_usd !== void 0)
6988
+ console.log(` $${b.daily_usd} per day (enforced by the budgets milestone)`);
6714
6989
  if (b.daily_tokens !== void 0) {
6715
- console.log(` ${b.daily_tokens.toLocaleString()} tokens per day (enforced by the budgets milestone)`);
6990
+ console.log(
6991
+ ` ${b.daily_tokens.toLocaleString()} tokens per day (enforced by the budgets milestone)`
6992
+ );
6716
6993
  }
6717
6994
  }
6718
6995
  );
@@ -6735,7 +7012,12 @@ function register6(program3) {
6735
7012
  }
6736
7013
  );
6737
7014
  withCommon(
6738
- runners.command("remove <name>").description("Remove a runner (refused while routing rules or pins reference it, unless --force)").option("--force", "strip the runner from routing rules and clear issue pins (emptied rules are kept, flagged)")
7015
+ runners.command("remove <name>").description(
7016
+ "Remove a runner (refused while routing rules or pins reference it, unless --force)"
7017
+ ).option(
7018
+ "--force",
7019
+ "strip the runner from routing rules and clear issue pins (emptied rules are kept, flagged)"
7020
+ )
6739
7021
  ).action(async (ref, opts) => {
6740
7022
  const api = client(opts);
6741
7023
  const runner = await resolveRunner(api, ref);
@@ -6757,14 +7039,28 @@ function register6(program3) {
6757
7039
  runner_id: rotated.runner.id,
6758
7040
  token: rotated.runner_token
6759
7041
  });
6760
- console.log(`stored it for the daemon on this machine (${defaultConfigDir()}); restart the daemon to adopt it.`);
7042
+ console.log(
7043
+ `stored it for the daemon on this machine (${defaultConfigDir()}); restart the daemon to adopt it.`
7044
+ );
6761
7045
  } else {
6762
- console.log("drop it into the daemon machine's config \u2014 its next poll gets a 401 until it adopts the new token.");
7046
+ console.log(
7047
+ "drop it into the daemon machine's config \u2014 its next poll gets a 401 until it adopts the new token."
7048
+ );
6763
7049
  }
6764
7050
  });
6765
7051
  const runnerCmd = program3.command("runner").description("The local runner daemon");
6766
7052
  withCommon(
6767
- runnerCmd.command("daemon").description("Run the local runner daemon: register/reconnect, poll for assigned runs, execute them").option("--name <name>", "runner name, unique per user (default: this hostname)").option("--harness <harness>", "claude-code | codex | custom", "claude-code").option("--command <template>", "custom harness command template ({prompt_file}, {workspace}, {model})").option("--max-concurrent <n>", "maximum simultaneous runs", (v) => Number.parseInt(v, 10), 1).option("--poll-interval <seconds>", "seconds between polls", (v) => Number.parseInt(v, 10), 15).option(
7053
+ runnerCmd.command("daemon").description(
7054
+ "Run the local runner daemon: register/reconnect, poll for assigned runs, execute them"
7055
+ ).option("--name <name>", "runner name, unique per user (default: this hostname)").option("--harness <harness>", "claude-code | codex | custom", "claude-code").option(
7056
+ "--command <template>",
7057
+ "custom harness command template ({prompt_file}, {workspace}, {model})"
7058
+ ).option("--max-concurrent <n>", "maximum simultaneous runs", (v) => Number.parseInt(v, 10), 1).option(
7059
+ "--poll-interval <seconds>",
7060
+ "seconds between polls",
7061
+ (v) => Number.parseInt(v, 10),
7062
+ 15
7063
+ ).option(
6768
7064
  "--no-cli-refresh",
6769
7065
  "do not install/refresh the agent-facing tines CLI from npm (harnesses use the ambient PATH)"
6770
7066
  ).option(
@@ -6789,7 +7085,9 @@ function register6(program3) {
6789
7085
  die(`--harness must be claude-code, codex, or custom, got "${opts.harness}"`);
6790
7086
  }
6791
7087
  if (harness === "custom" && !opts.command) {
6792
- die('the custom harness needs --command "<template>" ({prompt_file}, {workspace}, {model})');
7088
+ die(
7089
+ 'the custom harness needs --command "<template>" ({prompt_file}, {workspace}, {model})'
7090
+ );
6793
7091
  }
6794
7092
  if (harness !== "custom" && opts.command) die("--command only applies to --harness custom");
6795
7093
  if (!Number.isInteger(opts.maxConcurrent) || opts.maxConcurrent < 1 || opts.maxConcurrent > 100) {
@@ -6800,7 +7098,9 @@ function register6(program3) {
6800
7098
  }
6801
7099
  const keepWorkspaces = opts.keepWorkspaces;
6802
7100
  if (!KEEP_WORKSPACES_MODES.includes(keepWorkspaces)) {
6803
- die(`--keep-workspaces must be ${KEEP_WORKSPACES_MODES.join(", ")}, got "${opts.keepWorkspaces}"`);
7101
+ die(
7102
+ `--keep-workspaces must be ${KEEP_WORKSPACES_MODES.join(", ")}, got "${opts.keepWorkspaces}"`
7103
+ );
6804
7104
  }
6805
7105
  if (!Number.isFinite(opts.keepWorkspacesFor) || opts.keepWorkspacesFor <= 0) {
6806
7106
  die("--keep-workspaces-for must be a positive number of hours");
@@ -6831,9 +7131,7 @@ function register6(program3) {
6831
7131
  if (opts.json) return printJson({ items: sized });
6832
7132
  if (sized.length === 0) {
6833
7133
  console.log(`no kept workspaces in ${workspacesDir(configDir)}`);
6834
- return console.log(
6835
- "the daemon keeps them only with --keep-workspaces failed (or always)."
6836
- );
7134
+ return console.log("the daemon keeps them only with --keep-workspaces failed (or always).");
6837
7135
  }
6838
7136
  table([
6839
7137
  ["RUN", "ISSUE", "STATUS", "AGE", "SIZE", "PATH"],
@@ -6844,7 +7142,8 @@ function register6(program3) {
6844
7142
  if (opts.all === void 0 && opts.olderThan === void 0) {
6845
7143
  die("pass --all or --older-than <hours>");
6846
7144
  }
6847
- if (opts.all && opts.olderThan !== void 0) die("--all cannot be combined with --older-than");
7145
+ if (opts.all && opts.olderThan !== void 0)
7146
+ die("--all cannot be combined with --older-than");
6848
7147
  if (opts.olderThan !== void 0 && (!Number.isFinite(opts.olderThan) || opts.olderThan < 0)) {
6849
7148
  die("--older-than must be a non-negative number of hours");
6850
7149
  }
@@ -6874,61 +7173,69 @@ function register6(program3) {
6874
7173
  );
6875
7174
  printList(res, opts, (items) => {
6876
7175
  if (items.length === 0) return console.log(opts.active ? "no active runs" : "no runs");
6877
- table([["ID", "ISSUE", "RUNNER", "TIER", "STATUS", "DURATION", "COST", "CREATED"], ...items.map(runRow)]);
7176
+ table([
7177
+ ["ID", "ISSUE", "RUNNER", "TIER", "STATUS", "DURATION", "COST", "CREATED"],
7178
+ ...items.map(runRow)
7179
+ ]);
6878
7180
  });
6879
7181
  });
6880
7182
  withCommon(
6881
7183
  runsCmd.command("show <id>").description("Show a run; --logs prints the captured log tail, --logs --full the whole log").option("--logs", "print the log tail").option("--full", "with --logs: print the complete log, not the 256 KB tail").option("--raw", "with --logs --full: print the unrendered harness stream instead")
6882
- ).action(async (id, opts) => {
6883
- const api = client(opts);
6884
- const run = await api.getRun(id);
6885
- if (opts.json) return printJson(run);
6886
- console.log(`${run.id} ${run.status} on ${run.runner_name}`);
6887
- if (run.issue_ref) console.log(`issue: ${issueRef(run.issue_ref)} \u2014 ${run.issue_ref.title}`);
6888
- console.log(`tier: ${run.tier} model: ${run.model ?? "(n/a)"}`);
6889
- console.log(
6890
- `states: ${run.state_at_start_name ?? run.state_id_at_start} \u2192 ${run.state_at_end_name ?? run.state_id_at_end ?? "\u2026"}`
6891
- );
6892
- console.log(
6893
- `created: ${timestamp(run.created_at)} started: ${run.started_at ? timestamp(run.started_at) : "\u2014"} ended: ${run.ended_at ? timestamp(run.ended_at) : "\u2014"} duration: ${runDurationLabel(run)}`
6894
- );
6895
- if (run.usage) {
6896
- const u = run.usage;
6897
- const parts = [];
6898
- if (u.input_tokens !== void 0 || u.output_tokens !== void 0) {
6899
- parts.push(`${(u.input_tokens ?? 0).toLocaleString()} in / ${(u.output_tokens ?? 0).toLocaleString()} out tokens`);
6900
- }
6901
- if (u.cost_usd !== void 0) parts.push(`$${u.cost_usd.toFixed(2)}`);
6902
- if (u.cost_source) parts.push(`(${u.cost_source === "provider" ? "provider-reported" : u.cost_source})`);
6903
- if (parts.length > 0) console.log(`usage: ${parts.join(" ")}`);
6904
- }
6905
- if (run.provider_session_id) console.log(`provider session: ${run.provider_session_id}`);
6906
- if (run.provider_url) console.log(`provider console: ${run.provider_url}`);
6907
- if (run.error) console.log(`error: ${run.error}`);
6908
- if (opts.logs) {
6909
- console.log("");
6910
- if (opts.full || opts.raw) {
6911
- const res = await api.getRunLogFull(id, { raw: opts.raw });
6912
- const body = res.body;
6913
- if (!body) return;
6914
- const reader = body.getReader();
6915
- const decoder = new TextDecoder();
6916
- for (; ; ) {
6917
- const { done, value } = await reader.read();
6918
- if (done) break;
6919
- if (value) process.stdout.write(decoder.decode(value, { stream: true }));
7184
+ ).action(
7185
+ async (id, opts) => {
7186
+ const api = client(opts);
7187
+ const run = await api.getRun(id);
7188
+ if (opts.json) return printJson(run);
7189
+ console.log(`${run.id} ${run.status} on ${run.runner_name}`);
7190
+ if (run.issue_ref) console.log(`issue: ${issueRef(run.issue_ref)} \u2014 ${run.issue_ref.title}`);
7191
+ console.log(`tier: ${run.tier} model: ${run.model ?? "(n/a)"}`);
7192
+ console.log(
7193
+ `states: ${run.state_at_start_name ?? run.state_id_at_start} \u2192 ${run.state_at_end_name ?? run.state_id_at_end ?? "\u2026"}`
7194
+ );
7195
+ console.log(
7196
+ `created: ${timestamp(run.created_at)} started: ${run.started_at ? timestamp(run.started_at) : "\u2014"} ended: ${run.ended_at ? timestamp(run.ended_at) : "\u2014"} duration: ${runDurationLabel(run)}`
7197
+ );
7198
+ if (run.usage) {
7199
+ const u = run.usage;
7200
+ const parts = [];
7201
+ if (u.input_tokens !== void 0 || u.output_tokens !== void 0) {
7202
+ parts.push(
7203
+ `${(u.input_tokens ?? 0).toLocaleString()} in / ${(u.output_tokens ?? 0).toLocaleString()} out tokens`
7204
+ );
6920
7205
  }
6921
- process.stdout.write(decoder.decode());
6922
- return;
7206
+ if (u.cost_usd !== void 0) parts.push(`$${u.cost_usd.toFixed(2)}`);
7207
+ if (u.cost_source)
7208
+ parts.push(`(${u.cost_source === "provider" ? "provider-reported" : u.cost_source})`);
7209
+ if (parts.length > 0) console.log(`usage: ${parts.join(" ")}`);
6923
7210
  }
6924
- if (run.log_bytes_dropped > 0) {
6925
- console.log(
6926
- `[${Math.round(run.log_bytes_dropped / 1024)} KB truncated from the head \u2014 ` + (run.log_expired ? "past its retention window; only this tail remains]" : `run \`tines runs show ${run.id} --logs --full\` for the complete ${Math.round(run.log_full_bytes / 1024)} KB log]`)
6927
- );
7211
+ if (run.provider_session_id) console.log(`provider session: ${run.provider_session_id}`);
7212
+ if (run.provider_url) console.log(`provider console: ${run.provider_url}`);
7213
+ if (run.error) console.log(`error: ${run.error}`);
7214
+ if (opts.logs) {
7215
+ console.log("");
7216
+ if (opts.full || opts.raw) {
7217
+ const res = await api.getRunLogFull(id, { raw: opts.raw });
7218
+ const body = res.body;
7219
+ if (!body) return;
7220
+ const reader = body.getReader();
7221
+ const decoder = new TextDecoder();
7222
+ for (; ; ) {
7223
+ const { done, value } = await reader.read();
7224
+ if (done) break;
7225
+ if (value) process.stdout.write(decoder.decode(value, { stream: true }));
7226
+ }
7227
+ process.stdout.write(decoder.decode());
7228
+ return;
7229
+ }
7230
+ if (run.log_bytes_dropped > 0) {
7231
+ console.log(
7232
+ `[${Math.round(run.log_bytes_dropped / 1024)} KB truncated from the head \u2014 ` + (run.log_expired ? "past its retention window; only this tail remains]" : `run \`tines runs show ${run.id} --logs --full\` for the complete ${Math.round(run.log_full_bytes / 1024)} KB log]`)
7233
+ );
7234
+ }
7235
+ console.log(run.log || "(no log output captured)");
6928
7236
  }
6929
- console.log(run.log || "(no log output captured)");
6930
7237
  }
6931
- });
7238
+ );
6932
7239
  withCommon(
6933
7240
  runsCmd.command("cancel <id>").description("Cancel a run (judged like any other end: usually a strike)")
6934
7241
  ).action(async (id, opts) => {
@@ -6971,7 +7278,7 @@ title template: ${s.title_template}`);
6971
7278
  for (const line of s.description_template.split("\n")) console.log(` ${line}`);
6972
7279
  }
6973
7280
  }
6974
- function register7(program3) {
7281
+ function register8(program3) {
6975
7282
  const schedules = program3.command("schedules").description("Manage scheduled tasks (addressed as <project>/<name>)");
6976
7283
  withList(
6977
7284
  schedules.command("list").description("List scheduled tasks (hides paused schedules unless --all)").option("-p, --project <name>", "filter by project name or id").option("-a, --all", "include paused schedules")
@@ -7022,7 +7329,9 @@ recent instances:`);
7022
7329
  }
7023
7330
  });
7024
7331
  withCommon(
7025
- schedules.command("edit <ref>").description("Edit a schedule: templates, workflow, start state, recurrence, timezone, gate, or name").option("-t, --title <template>", "set the title template").option(
7332
+ schedules.command("edit <ref>").description(
7333
+ "Edit a schedule: templates, workflow, start state, recurrence, timezone, gate, or name"
7334
+ ).option("-t, --title <template>", "set the title template").option(
7026
7335
  "-d, --description <markdown>",
7027
7336
  `set the description template (Markdown) \u2014 ${BODY_VALUE_HELP}`
7028
7337
  ).option(
@@ -7031,7 +7340,13 @@ recent instances:`);
7031
7340
  ).option(
7032
7341
  "-s, --state <id-or-name>",
7033
7342
  "start state for future instances (the workflow's initial state = the default)"
7034
- ).option("--every <preset>", 'repeat hourly (or every N hours: "6h"), daily, weekly, or monthly').option("--at <when>", "preset time of day HH:MM (default 09:00); for hourly, the minute past the hour :MM (default :00)").option("--on <when>", "weekday (weekly) or day of month (monthly)").option("--cron <expr>", "5-field cron expression (alternative to --every/--at/--on)").option("--tz <iana>", "set the schedule timezone").option("--if-closed", "only create a new instance when all previous instances are closed").option("--no-if-closed", "clear the only-when-closed gate").option("--name <new-name>", "rename the schedule")
7343
+ ).option(
7344
+ "--every <preset>",
7345
+ 'repeat hourly (or every N hours: "6h"), daily, weekly, or monthly'
7346
+ ).option(
7347
+ "--at <when>",
7348
+ "preset time of day HH:MM (default 09:00); for hourly, the minute past the hour :MM (default :00)"
7349
+ ).option("--on <when>", "weekday (weekly) or day of month (monthly)").option("--cron <expr>", "5-field cron expression (alternative to --every/--at/--on)").option("--tz <iana>", "set the schedule timezone").option("--if-closed", "only create a new instance when all previous instances are closed").option("--no-if-closed", "clear the only-when-closed gate").option("--name <new-name>", "rename the schedule")
7035
7350
  ).action(
7036
7351
  async (ref, opts) => {
7037
7352
  const descriptionTemplate = opts.description !== void 0 ? readBodyValue(opts.description) : void 0;
@@ -7040,7 +7355,8 @@ recent instances:`);
7040
7355
  const body = {};
7041
7356
  if (opts.title !== void 0) body.title_template = opts.title;
7042
7357
  if (descriptionTemplate !== void 0) body.description_template = descriptionTemplate;
7043
- if (opts.workflow !== void 0) body.workflow_id = (await resolveWorkflow(api, opts.workflow)).id;
7358
+ if (opts.workflow !== void 0)
7359
+ body.workflow_id = (await resolveWorkflow(api, opts.workflow)).id;
7044
7360
  if (opts.state !== void 0) body.state = opts.state;
7045
7361
  const recurrence = buildRecurrence(opts);
7046
7362
  if (recurrence?.preset) body.preset = recurrence.preset;
@@ -7060,15 +7376,15 @@ recent instances:`);
7060
7376
  printScheduleDetail(updated);
7061
7377
  }
7062
7378
  );
7063
- withCommon(schedules.command("pause <ref>").description("Pause a schedule (keeps config and history)")).action(
7064
- async (ref, opts) => {
7065
- const api = client(opts);
7066
- const schedule = await resolveSchedule(api, ref);
7067
- const updated = await api.updateSchedule(schedule.id, { enabled: false });
7068
- if (opts.json) return printJson(updated);
7069
- console.log(`paused schedule "${scheduleRef(updated)}"`);
7070
- }
7071
- );
7379
+ withCommon(
7380
+ schedules.command("pause <ref>").description("Pause a schedule (keeps config and history)")
7381
+ ).action(async (ref, opts) => {
7382
+ const api = client(opts);
7383
+ const schedule = await resolveSchedule(api, ref);
7384
+ const updated = await api.updateSchedule(schedule.id, { enabled: false });
7385
+ if (opts.json) return printJson(updated);
7386
+ console.log(`paused schedule "${scheduleRef(updated)}"`);
7387
+ });
7072
7388
  withCommon(
7073
7389
  schedules.command("resume <ref>").description("Resume a paused schedule (recomputes the next occurrence from now)")
7074
7390
  ).action(async (ref, opts) => {
@@ -7076,7 +7392,9 @@ recent instances:`);
7076
7392
  const schedule = await resolveSchedule(api, ref);
7077
7393
  const updated = await api.updateSchedule(schedule.id, { enabled: true });
7078
7394
  if (opts.json) return printJson(updated);
7079
- console.log(`resumed schedule "${scheduleRef(updated)}" \u2014 next run ${timestamp(updated.next_run_at)}`);
7395
+ console.log(
7396
+ `resumed schedule "${scheduleRef(updated)}" \u2014 next run ${timestamp(updated.next_run_at)}`
7397
+ );
7080
7398
  });
7081
7399
  withCommon(
7082
7400
  schedules.command("run <ref>").description("Create an instance now (respects the only-when-closed gate)")
@@ -7108,57 +7426,61 @@ recent instances:`);
7108
7426
  }
7109
7427
 
7110
7428
  // src/commands/supervisor.ts
7111
- function register8(program3) {
7429
+ function register9(program3) {
7112
7430
  const supervisor = program3.command("supervisor").description("The automation kill switch, quota policy, and attempt limit");
7113
- withCommon(supervisor.command("status").description("One-screen overview: kill switch, quota, utilization, runners")).action(
7114
- async (opts) => {
7115
- const api = client(opts);
7116
- const [settings, runnersRes, workflows, activeRunItems] = await Promise.all([
7117
- api.getSupervisorSettings(),
7118
- api.listRunners(),
7119
- api.listWorkflows(),
7120
- listAll((page) => api.listRuns({ active: true, ...page }))
7121
- ]);
7122
- if (opts.json) {
7123
- return printJson({ settings, runners: runnersRes.items, active_runs: activeRunItems });
7124
- }
7125
- const stateNames = /* @__PURE__ */ new Map();
7126
- for (const wf of workflows.items) {
7127
- for (const s of wf.states) stateNames.set(s.id, `${wf.name}/${s.name}`);
7128
- }
7129
- console.log(`automation: ${settings.enabled ? "ON" : "OFF (kill switch \u2014 nothing dispatches)"}`);
7130
- console.log(quotaLabel(settings.quota, (id) => stateNames.get(id) ?? id));
7131
- console.log(`utilization: ${utilizationLabel(settings.quota, activeRunItems, (id) => stateNames.get(id) ?? id)}`);
7132
- console.log(`attempt limit: ${settings.attempt_limit} strikes, then the issue parks`);
7133
- if (runnersRes.items.length === 0) {
7134
- console.log("runners: none");
7135
- } else {
7136
- console.log("runners:");
7137
- table(
7138
- runnersRes.items.map((r) => [
7139
- ` ${r.name}`,
7140
- r.type,
7141
- runnerStatusLabel(r),
7142
- `${r.active_runs}/${r.max_concurrent}`
7143
- ])
7144
- );
7145
- }
7431
+ withCommon(
7432
+ supervisor.command("status").description("One-screen overview: kill switch, quota, utilization, runners")
7433
+ ).action(async (opts) => {
7434
+ const api = client(opts);
7435
+ const [settings, runnersRes, workflows, activeRunItems] = await Promise.all([
7436
+ api.getSupervisorSettings(),
7437
+ api.listRunners(),
7438
+ api.listWorkflows(),
7439
+ listAll((page) => api.listRuns({ active: true, ...page }))
7440
+ ]);
7441
+ if (opts.json) {
7442
+ return printJson({ settings, runners: runnersRes.items, active_runs: activeRunItems });
7146
7443
  }
7147
- );
7148
- withCommon(supervisor.command("enable").description("Arm automation (the kill switch on)")).action(
7149
- async (opts) => {
7150
- const settings = await client(opts).updateSupervisorSettings({ enabled: true });
7151
- if (opts.json) return printJson(settings);
7152
- console.log("automation is ON \u2014 eligible issues with a matching rule will dispatch");
7444
+ const stateNames = /* @__PURE__ */ new Map();
7445
+ for (const wf of workflows.items) {
7446
+ for (const s of wf.states) stateNames.set(s.id, `${wf.name}/${s.name}`);
7153
7447
  }
7154
- );
7155
- withCommon(supervisor.command("disable").description("Pause all automation at once (the kill switch off)")).action(
7156
- async (opts) => {
7157
- const settings = await client(opts).updateSupervisorSettings({ enabled: false });
7158
- if (opts.json) return printJson(settings);
7159
- console.log("automation is OFF \u2014 nothing new dispatches until re-enabled");
7448
+ console.log(
7449
+ `automation: ${settings.enabled ? "ON" : "OFF (kill switch \u2014 nothing dispatches)"}`
7450
+ );
7451
+ console.log(quotaLabel(settings.quota, (id) => stateNames.get(id) ?? id));
7452
+ console.log(
7453
+ `utilization: ${utilizationLabel(settings.quota, activeRunItems, (id) => stateNames.get(id) ?? id)}`
7454
+ );
7455
+ console.log(`attempt limit: ${settings.attempt_limit} strikes, then the issue parks`);
7456
+ if (runnersRes.items.length === 0) {
7457
+ console.log("runners: none");
7458
+ } else {
7459
+ console.log("runners:");
7460
+ table(
7461
+ runnersRes.items.map((r) => [
7462
+ ` ${r.name}`,
7463
+ r.type,
7464
+ runnerStatusLabel(r),
7465
+ `${r.active_runs}/${r.max_concurrent}`
7466
+ ])
7467
+ );
7160
7468
  }
7161
- );
7469
+ });
7470
+ withCommon(
7471
+ supervisor.command("enable").description("Arm automation (the kill switch on)")
7472
+ ).action(async (opts) => {
7473
+ const settings = await client(opts).updateSupervisorSettings({ enabled: true });
7474
+ if (opts.json) return printJson(settings);
7475
+ console.log("automation is ON \u2014 eligible issues with a matching rule will dispatch");
7476
+ });
7477
+ withCommon(
7478
+ supervisor.command("disable").description("Pause all automation at once (the kill switch off)")
7479
+ ).action(async (opts) => {
7480
+ const settings = await client(opts).updateSupervisorSettings({ enabled: false });
7481
+ if (opts.json) return printJson(settings);
7482
+ console.log("automation is OFF \u2014 nothing new dispatches until re-enabled");
7483
+ });
7162
7484
  const quota = supervisor.command("quota").description("Pick and configure the quota policy");
7163
7485
  withCommon(
7164
7486
  quota.command("global <n>").description("Use the global cap: at most <n> concurrent runs in total")
@@ -7171,7 +7493,11 @@ function register8(program3) {
7171
7493
  console.log(quotaLabel(settings.quota));
7172
7494
  });
7173
7495
  withCommon(
7174
- quota.command("roster").description("Use the per-state roster: at most N concurrent runs per workflow state").requiredOption("--default <n>", "limit for states without an override", (v) => Number.parseInt(v, 10)).option(
7496
+ quota.command("roster").description("Use the per-state roster: at most N concurrent runs per workflow state").requiredOption(
7497
+ "--default <n>",
7498
+ "limit for states without an override",
7499
+ (v) => Number.parseInt(v, 10)
7500
+ ).option(
7175
7501
  "--state <workflow/state=n>",
7176
7502
  "per-state override (repeatable), counted by the state a run started in",
7177
7503
  collect,
@@ -7203,7 +7529,7 @@ function register8(program3) {
7203
7529
  }
7204
7530
 
7205
7531
  // src/commands/workflows.ts
7206
- import { readFileSync as readFileSync8 } from "node:fs";
7532
+ import { readFileSync as readFileSync10 } from "node:fs";
7207
7533
  function readJsonBody(inline, file) {
7208
7534
  if (inline !== void 0 && file !== void 0) {
7209
7535
  die("pass the JSON inline or with --file, not both");
@@ -7212,14 +7538,14 @@ function readJsonBody(inline, file) {
7212
7538
  if (file !== void 0 && file !== "-") {
7213
7539
  let raw;
7214
7540
  try {
7215
- raw = readFileSync8(file, "utf8");
7541
+ raw = readFileSync10(file, "utf8");
7216
7542
  } catch (err) {
7217
7543
  die(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
7218
7544
  }
7219
7545
  return parseJsonObject(raw, file);
7220
7546
  }
7221
7547
  if (file === "-" || !process.stdin.isTTY) {
7222
- const raw = readFileSync8(0, "utf8");
7548
+ const raw = readFileSync10(0, "utf8");
7223
7549
  if (raw.trim() === "") {
7224
7550
  if (file === "-") die("no JSON on stdin");
7225
7551
  return void 0;
@@ -7295,7 +7621,7 @@ function printWorkflowDetail(wf) {
7295
7621
  for (const w of wf.warnings ?? []) console.log(`
7296
7622
  warning: ${w}`);
7297
7623
  }
7298
- function register9(program3) {
7624
+ function register10(program3) {
7299
7625
  const workflows = program3.command("workflows").description("Manage the workflow library");
7300
7626
  withList(workflows.command("list").description("List the workflow library")).action(
7301
7627
  async (opts) => {
@@ -7325,22 +7651,26 @@ function register9(program3) {
7325
7651
  printWorkflowDetail(wf);
7326
7652
  });
7327
7653
  withCommon(
7328
- workflows.command("create [json]").description('Create a workflow from a JSON definition (states carry initial "prompt" instructions)').option("-f, --file <path>", 'read the JSON definition from a file ("-" for stdin)').option("--no-prompts", 'allow states without initial "prompt" instructions').addHelpText("after", WORKFLOW_JSON_HELP)
7329
- ).action(async (inline, opts) => {
7330
- const body = readJsonBody(inline, opts.file);
7331
- if (!body) {
7332
- die(
7333
- `missing workflow JSON: pass it inline, with --file <path>, or pipe it on stdin
7654
+ workflows.command("create [json]").description(
7655
+ 'Create a workflow from a JSON definition (states carry initial "prompt" instructions)'
7656
+ ).option("-f, --file <path>", 'read the JSON definition from a file ("-" for stdin)').option("--no-prompts", 'allow states without initial "prompt" instructions').addHelpText("after", WORKFLOW_JSON_HELP)
7657
+ ).action(
7658
+ async (inline, opts) => {
7659
+ const body = readJsonBody(inline, opts.file);
7660
+ if (!body) {
7661
+ die(
7662
+ `missing workflow JSON: pass it inline, with --file <path>, or pipe it on stdin
7334
7663
  see \`tines workflows create --help\` for the expected shape`
7335
- );
7336
- }
7337
- assertNewStatesHavePrompts(body.states, opts.prompts);
7338
- const wf = await client(opts).createWorkflow(body);
7339
- if (opts.json) return printJson(wf);
7340
- console.log(`created workflow "${wf.name}" (${wf.id})
7664
+ );
7665
+ }
7666
+ assertNewStatesHavePrompts(body.states, opts.prompts);
7667
+ const wf = await client(opts).createWorkflow(body);
7668
+ if (opts.json) return printJson(wf);
7669
+ console.log(`created workflow "${wf.name}" (${wf.id})
7341
7670
  `);
7342
- printWorkflowDetail(wf);
7343
- });
7671
+ printWorkflowDetail(wf);
7672
+ }
7673
+ );
7344
7674
  withCommon(
7345
7675
  workflows.command("edit <id-or-name> [json]").description("Update a workflow from a JSON definition and/or flags").option("-f, --file <path>", 'read the JSON definition from a file ("-" for stdin)').option("-n, --name <name>", "rename the workflow").option("-d, --description <text>", "set the description").option("--initial-state <id-or-name>", "set the initial state").option("--no-prompts", 'allow new states without initial "prompt" instructions').addHelpText("after", WORKFLOW_JSON_HELP)
7346
7676
  ).action(
@@ -7377,14 +7707,15 @@ var program2 = new Command();
7377
7707
  program2.name("tines").description("CLI for Tines").version(cliVersion()).enablePositionalOptions();
7378
7708
  registerTime(program2);
7379
7709
  register4(program2);
7380
- register9(program2);
7710
+ register5(program2);
7711
+ register10(program2);
7381
7712
  register2(program2);
7382
7713
  register(program2);
7383
7714
  register3(program2);
7715
+ register8(program2);
7384
7716
  register7(program2);
7385
7717
  register6(program2);
7386
- register5(program2);
7387
- register8(program2);
7718
+ register9(program2);
7388
7719
  registerEvents(program2);
7389
7720
 
7390
7721
  // src/index.ts