tines 0.0.92 → 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 +789 -452
  3. package/package.json +1 -1
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,13 +4213,57 @@ function parseTargetSpec(spec) {
4182
4213
  return { name: name2, tier };
4183
4214
  }
4184
4215
 
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;
4229
+ }
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
+
4185
4260
  // src/common.ts
4186
4261
  var DEFAULT_URL = "https://tines.tbuckley.dev";
4187
4262
  function withCommon(cmd) {
4188
4263
  return cmd.option(
4189
4264
  "-u, --url <url>",
4190
- `base URL of the Tines API (or set TINES_API_URL; default ${DEFAULT_URL})`
4191
- ).option("--api-key <key>", "API key (or set TINES_API_KEY)").option("--json", "output the raw JSON response");
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");
4192
4267
  }
4193
4268
  function withList(cmd) {
4194
4269
  return withCommon(
@@ -4204,11 +4279,25 @@ function withList(cmd) {
4204
4279
  )
4205
4280
  );
4206
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
+ }
4207
4296
  function resolveUrl(opts) {
4208
- return opts.url ?? process.env.TINES_API_URL ?? DEFAULT_URL;
4297
+ return resolveUrlSetting(opts).value;
4209
4298
  }
4210
4299
  function resolveApiKey(opts) {
4211
- return opts.apiKey ?? process.env.TINES_API_KEY;
4300
+ return resolveApiKeySetting(opts).value;
4212
4301
  }
4213
4302
  function client(opts) {
4214
4303
  return createApiClient({ baseUrl: resolveUrl(opts), apiKey: resolveApiKey(opts) });
@@ -4327,7 +4416,8 @@ async function resolveRunner(api, ref) {
4327
4416
  async function resolveScopeFlags(api, opts) {
4328
4417
  const scope = {};
4329
4418
  if (opts.project !== void 0) scope.project_id = (await resolveProject(api, opts.project)).id;
4330
- 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;
4331
4421
  if (opts.issue !== void 0) scope.issue_id = (await resolveIssue(api, opts.issue)).id;
4332
4422
  return scope;
4333
4423
  }
@@ -4357,7 +4447,9 @@ files (seeded at skills/${item.name}/):`);
4357
4447
  console.log(`
4358
4448
  url: ${item.repo_url}`);
4359
4449
  if (item.repo_branch) console.log(`branch: ${item.repo_branch}`);
4360
- 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
+ );
4361
4453
  }
4362
4454
  }
4363
4455
  var SCOPE_FLAGS_HELP = `
@@ -4370,51 +4462,65 @@ function withScopeFlags(cmd) {
4370
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);
4371
4463
  }
4372
4464
  function register(program3) {
4373
- 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
+ );
4374
4468
  withList(
4375
4469
  withScopeFlags(
4376
- 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")
4377
4473
  )
4378
- ).action(async (opts) => {
4379
- const api = client(opts);
4380
- const scope = await resolveScopeFlags(api, opts);
4381
- const res = await fetchList(
4382
- opts,
4383
- (page) => api.listContext({
4384
- kind: opts.kind,
4385
- project: scope.project_id ?? void 0,
4386
- state: scope.workflow_state_id ?? void 0,
4387
- issue: scope.issue_id ?? void 0,
4388
- q: opts.search,
4389
- exact: opts.exact ? true : void 0,
4390
- ...page
4391
- })
4392
- );
4393
- printList(res, opts, (items) => {
4394
- if (items.length === 0) return console.log("no context items");
4395
- table([
4396
- ["KIND", "NAME", "SCOPE", "PAYLOAD", "UPDATED", "ID"],
4397
- ...items.map((i) => [
4398
- i.kind,
4399
- i.name,
4400
- i.scope.label,
4401
- contextItemSummary(i),
4402
- timestamp(i.updated_at),
4403
- i.id
4404
- ])
4405
- ]);
4406
- });
4407
- });
4408
- withCommon(context.command("show <id>").description("Show a context item (skills include their files)")).action(
4409
- async (id, opts) => {
4410
- const item = await client(opts).getContextItem(id);
4411
- if (opts.json) return printJson(item);
4412
- 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
+ });
4413
4504
  }
4414
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
+ });
4415
4513
  withCommon(
4416
4514
  withScopeFlags(
4417
- 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("--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)")
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)")
4418
4524
  )
4419
4525
  ).action(
4420
4526
  async (opts) => {
@@ -4442,7 +4548,12 @@ function register(program3) {
4442
4548
  );
4443
4549
  withCommon(
4444
4550
  withScopeFlags(
4445
- 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("--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(
4446
4557
  "--expect-version <n>",
4447
4558
  "fail (409) unless the item is still at this version",
4448
4559
  (v) => Number.parseInt(v, 10)
@@ -4466,12 +4577,15 @@ function register(program3) {
4466
4577
  if (opts.body !== void 0) body.body = readBodyValue(opts.body);
4467
4578
  if (opts.file.length > 0 || opts.removeFile.length > 0) {
4468
4579
  const current = await api.getContextItem(id);
4469
- 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})`);
4470
4582
  if (body.expected_version === void 0) body.expected_version = current.version;
4471
4583
  const files = new Map((current.files ?? []).map((f) => [f.path, f.content]));
4472
4584
  for (const path2 of opts.removeFile) {
4473
4585
  if (!files.delete(path2)) {
4474
- 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
+ );
4475
4589
  }
4476
4590
  }
4477
4591
  for (const spec of opts.file) {
@@ -4484,7 +4598,9 @@ function register(program3) {
4484
4598
  if (opts.branch !== void 0) body.repo_branch = opts.branch === "" ? null : opts.branch;
4485
4599
  if (opts.dir !== void 0) body.repo_dir = opts.dir === "" ? null : opts.dir;
4486
4600
  if (Object.keys(body).length === 0) {
4487
- 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
+ );
4488
4604
  }
4489
4605
  const item = await api.updateContextItem(id, body);
4490
4606
  if (opts.json) return printJson(item);
@@ -4503,7 +4619,9 @@ function register(program3) {
4503
4619
  context.command("init").description('Seed the global "agent-guidelines" prompt (a no-op if it already exists)')
4504
4620
  ).action(async (opts) => {
4505
4621
  const api = client(opts);
4506
- 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
+ );
4507
4625
  const existing = items.find((i) => i.name === AGENT_GUIDELINES_NAME);
4508
4626
  if (existing) {
4509
4627
  if (opts.json) return printJson(existing);
@@ -4525,8 +4643,8 @@ function register(program3) {
4525
4643
  }
4526
4644
 
4527
4645
  // src/commands/issues.ts
4528
- import { existsSync, mkdirSync, readdirSync, readFileSync as readFileSync3, statSync, writeFileSync } from "node:fs";
4529
- 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";
4530
4648
 
4531
4649
  // src/help-guard.ts
4532
4650
  function helpGuard(command, markdown) {
@@ -4565,12 +4683,15 @@ function buildRecurrence(opts) {
4565
4683
  if (hourly !== void 0) {
4566
4684
  if (opts.on !== void 0) throw new CliError("an hourly recurrence does not take --on");
4567
4685
  const every = typeof hourly === "number" ? hourly : Number.parseInt(hourly, 10);
4568
- 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}"`);
4569
4688
  let minute = 0;
4570
4689
  if (opts.at !== void 0) {
4571
4690
  const m = opts.at.match(/^:?(\d{1,2})$/);
4572
4691
  if (!m || Number.parseInt(m[1], 10) > 59) {
4573
- 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
+ );
4574
4695
  }
4575
4696
  minute = Number.parseInt(m[1], 10);
4576
4697
  }
@@ -4595,7 +4716,9 @@ function buildRecurrence(opts) {
4595
4716
  return { preset: { kind: "monthly", time, day_of_month: day } };
4596
4717
  }
4597
4718
  default:
4598
- 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
+ );
4599
4722
  }
4600
4723
  }
4601
4724
 
@@ -4604,7 +4727,9 @@ var systemTimezone = () => Intl.DateTimeFormat().resolvedOptions().timeZone;
4604
4727
  function printIssueLinks(links) {
4605
4728
  if (links.blocked_by.length > 0) {
4606
4729
  console.log("\nblocked by:");
4607
- 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
+ );
4608
4733
  }
4609
4734
  if (links.blocks.length > 0) {
4610
4735
  console.log("\nblocks:");
@@ -4626,7 +4751,9 @@ function printIssueDetail(issue) {
4626
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)}`
4627
4752
  );
4628
4753
  if (dup) {
4629
- 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
+ );
4630
4757
  }
4631
4758
  console.log(`id: ${issue.id}`);
4632
4759
  printIssueLinks(issue.links);
@@ -4635,8 +4762,10 @@ function printIssueDetail(issue) {
4635
4762
  ${issue.description}`);
4636
4763
  }
4637
4764
  const allowed = issue.allowed_transitions.map((t) => `"${t.name}" \u2192 ${t.to_state.name}`);
4638
- console.log(`
4639
- allowed actions: ${allowed.length ? allowed.join(", ") : "none (terminal state)"}`);
4765
+ console.log(
4766
+ `
4767
+ allowed actions: ${allowed.length ? allowed.join(", ") : "none (terminal state)"}`
4768
+ );
4640
4769
  if (issue.comments.length > 0) {
4641
4770
  console.log(`
4642
4771
  comments (${issue.comments.length}):`);
@@ -4649,11 +4778,15 @@ function walkFolder(dir) {
4649
4778
  const files = [];
4650
4779
  const walk = (abs, rel) => {
4651
4780
  for (const entry of readdirSync(abs, { withFileTypes: true })) {
4652
- const nextAbs = join(abs, entry.name);
4781
+ const nextAbs = join2(abs, entry.name);
4653
4782
  const nextRel = rel ? `${rel}/${entry.name}` : entry.name;
4654
4783
  if (entry.isDirectory()) walk(nextAbs, nextRel);
4655
4784
  else if (entry.isFile()) {
4656
- 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
+ });
4657
4790
  }
4658
4791
  }
4659
4792
  };
@@ -4687,7 +4820,9 @@ matched rule: ${ex.matched_rule.scope_label}`);
4687
4820
  );
4688
4821
  }
4689
4822
  if (ex.queue_position !== null && ex.queue_position > 0) {
4690
- 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
+ );
4691
4826
  }
4692
4827
  if (ex.active_run) {
4693
4828
  console.log(
@@ -4703,7 +4838,10 @@ matched rule: ${ex.matched_rule.scope_label}`);
4703
4838
  function register2(program3) {
4704
4839
  const issues = program3.command("issues").description("Work with issues");
4705
4840
  withList(
4706
- 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")
4707
4845
  ).action(
4708
4846
  async (opts) => {
4709
4847
  const api = client(opts);
@@ -4739,7 +4877,19 @@ function register2(program3) {
4739
4877
  }
4740
4878
  );
4741
4879
  withCommon(
4742
- 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)")
4743
4893
  ).action(
4744
4894
  async (projectRef, opts) => {
4745
4895
  const description = opts.description !== void 0 ? readBodyValue(opts.description) : void 0;
@@ -4795,7 +4945,8 @@ function register2(program3) {
4795
4945
  if (opts.title !== void 0) body.title = opts.title;
4796
4946
  if (description !== void 0) body.description = description;
4797
4947
  if (opts.state !== void 0) body.state = opts.state;
4798
- 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;
4799
4950
  if (Object.keys(body).length === 0) {
4800
4951
  die("nothing to update: pass --title, --description, --state, and/or --workflow");
4801
4952
  }
@@ -4837,15 +4988,17 @@ function register2(program3) {
4837
4988
  });
4838
4989
  withCommon(
4839
4990
  issues.command("comment-edit <ref> <comment-id> <markdown>").description(`Replace the body of your own comment \u2014 Markdown body: ${BODY_VALUE_HELP}`).passThroughOptions()
4840
- ).action(async (ref, commentId, markdown, opts, command) => {
4841
- if (helpGuard(command, markdown)) return;
4842
- const body = readBodyValue(markdown);
4843
- const api = client(opts);
4844
- const issue = await resolveIssue(api, ref);
4845
- const comment = await api.updateComment(issue.id, commentId, { body });
4846
- if (opts.json) return printJson(comment);
4847
- console.log(`edited comment ${comment.id} on ${issue.project_name}/#${issue.number}`);
4848
- });
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
+ );
4849
5002
  withCommon(
4850
5003
  issues.command("comment-delete <ref> <comment-id>").description("Delete your own comment (the event keeps the record of the deletion)")
4851
5004
  ).action(async (ref, commentId, opts) => {
@@ -4898,7 +5051,12 @@ function register2(program3) {
4898
5051
  );
4899
5052
  });
4900
5053
  withCommon(
4901
- 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")
4902
5060
  ).action(async (ref, opts) => {
4903
5061
  const api = client(opts);
4904
5062
  const issue = await resolveIssue(api, ref);
@@ -4911,13 +5069,19 @@ function register2(program3) {
4911
5069
  skills: ${context.skills.map((s) => s.name).join(", ")}`);
4912
5070
  }
4913
5071
  for (const repo of context.repos) {
4914
- 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
+ );
4915
5075
  }
4916
5076
  for (const o of context.overridden) {
4917
- 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
+ );
4918
5080
  }
4919
5081
  for (const c of context.conflicts) {
4920
- 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
+ );
4921
5085
  }
4922
5086
  return;
4923
5087
  }
@@ -4926,20 +5090,23 @@ skills: ${context.skills.map((s) => s.name).join(", ")}`);
4926
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`
4927
5091
  );
4928
5092
  }
4929
- if (existsSync(opts.out) && readdirSync(opts.out).length > 0 && !opts.force) {
5093
+ if (existsSync2(opts.out) && readdirSync(opts.out).length > 0 && !opts.force) {
4930
5094
  die(`refusing to write into non-empty directory ${opts.out} (pass --force to override)`);
4931
5095
  }
4932
- mkdirSync(opts.out, { recursive: true });
4933
- writeFileSync(join(opts.out, "prompt.md"), context.prompt.text ? `${context.prompt.text}
4934
- ` : "");
5096
+ mkdirSync2(opts.out, { recursive: true });
5097
+ writeFileSync2(
5098
+ join2(opts.out, "prompt.md"),
5099
+ context.prompt.text ? `${context.prompt.text}
5100
+ ` : ""
5101
+ );
4935
5102
  for (const skill of context.skills) {
4936
5103
  for (const file of skill.files) {
4937
- const target = join(opts.out, "skills", skill.name, file.path);
4938
- mkdirSync(dirname(target), { recursive: true });
4939
- 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);
4940
5107
  }
4941
5108
  }
4942
- 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)}
4943
5110
  `);
4944
5111
  console.log(
4945
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"})`
@@ -4967,27 +5134,29 @@ skills: ${context.skills.map((s) => s.name).join(", ")}`);
4967
5134
  if (opts.json) return printJson(prompt);
4968
5135
  console.log(prompt.text);
4969
5136
  });
4970
- const artifactsCmd = issues.command("artifacts").description("Typed, versioned attachments on an issue \u2014 the work products transition requirements gate on");
4971
- withCommon(artifactsCmd.command("list <ref>").description("List the artifacts attached to an issue")).action(
4972
- async (ref, opts) => {
4973
- const api = client(opts);
4974
- const issue = await resolveIssue(api, ref);
4975
- const res = await api.listArtifacts(issue.id);
4976
- if (opts.json) return printJson(res);
4977
- if (res.items.length === 0) return console.log("no artifacts attached");
4978
- table([
4979
- ["NAME", "TYPE", "VERSION", "FRESH", "SUMMARY", "ATTACHED"],
4980
- ...res.items.map((a) => [
4981
- a.name,
4982
- a.artifact_type,
4983
- `v${a.current_version.version}`,
4984
- a.fresh ? "yes" : "no",
4985
- artifactSummary(a),
4986
- timestamp(a.current_version.created_at)
4987
- ])
4988
- ]);
4989
- }
5137
+ const artifactsCmd = issues.command("artifacts").description(
5138
+ "Typed, versioned attachments on an issue \u2014 the work products transition requirements gate on"
4990
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
+ });
4991
5160
  withCommon(
4992
5161
  artifactsCmd.command("show <ref> <name>").description("Show an artifact with its full version history")
4993
5162
  ).action(async (ref, name2, opts) => {
@@ -4995,7 +5164,9 @@ skills: ${context.skills.map((s) => s.name).join(", ")}`);
4995
5164
  const issue = await resolveIssue(api, ref);
4996
5165
  const artifact = await api.getArtifact(issue.id, name2);
4997
5166
  if (opts.json) return printJson(artifact);
4998
- 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
+ );
4999
5170
  if (artifact.description) console.log(artifact.description);
5000
5171
  console.log(
5001
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"})`
@@ -5018,14 +5189,18 @@ files (v${artifact.current_version.version}):`);
5018
5189
  }
5019
5190
  });
5020
5191
  withCommon(
5021
- 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(
5022
5195
  "--folder <dir>",
5023
5196
  "snapshot a directory tree as one version (collect locally, attach once; MIME per file sniffed)"
5024
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")
5025
5198
  ).action(
5026
5199
  async (ref, name2, opts) => {
5027
5200
  const api = client(opts);
5028
- const sources = [opts.file, opts.folder, opts.text, opts.link, opts.pr].filter((v) => v !== void 0);
5201
+ const sources = [opts.file, opts.folder, opts.text, opts.link, opts.pr].filter(
5202
+ (v) => v !== void 0
5203
+ );
5029
5204
  if (sources.length !== 1) {
5030
5205
  die(
5031
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)"
@@ -5034,7 +5209,7 @@ files (v${artifact.current_version.version}):`);
5034
5209
  const issue = await resolveIssue(api, ref);
5035
5210
  let artifact;
5036
5211
  if (opts.folder !== void 0) {
5037
- if (!existsSync(opts.folder) || !statSync(opts.folder).isDirectory()) {
5212
+ if (!existsSync2(opts.folder) || !statSync(opts.folder).isDirectory()) {
5038
5213
  die(`--folder needs a directory, got "${opts.folder}"`);
5039
5214
  }
5040
5215
  const files = walkFolder(opts.folder);
@@ -5046,7 +5221,7 @@ files (v${artifact.current_version.version}):`);
5046
5221
  } else if (opts.file !== void 0) {
5047
5222
  let bytes;
5048
5223
  try {
5049
- bytes = readFileSync3(opts.file);
5224
+ bytes = readFileSync4(opts.file);
5050
5225
  } catch (err) {
5051
5226
  die(`cannot read ${opts.file}: ${err instanceof Error ? err.message : String(err)}`);
5052
5227
  }
@@ -5091,7 +5266,9 @@ files (v${artifact.current_version.version}):`);
5091
5266
  }
5092
5267
  );
5093
5268
  withCommon(
5094
- 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
+ )
5095
5272
  ).action(async (ref, name2, opts) => {
5096
5273
  const api = client(opts);
5097
5274
  const issue = await resolveIssue(api, ref);
@@ -5102,7 +5279,14 @@ files (v${artifact.current_version.version}):`);
5102
5279
  );
5103
5280
  });
5104
5281
  withCommon(
5105
- 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
+ )
5106
5290
  ).action(
5107
5291
  async (ref, name2, opts) => {
5108
5292
  const api = client(opts);
@@ -5123,7 +5307,7 @@ files (v${artifact.current_version.version}):`);
5123
5307
  if (opts.out === void 0) {
5124
5308
  die(`artifact "${name2}" is a folder \u2014 pass --out <dir> to write its tree`);
5125
5309
  }
5126
- if (existsSync(opts.out) && !statSync(opts.out).isDirectory()) {
5310
+ if (existsSync2(opts.out) && !statSync(opts.out).isDirectory()) {
5127
5311
  die(`--out for a folder must be a directory, and "${opts.out}" is a file`);
5128
5312
  }
5129
5313
  const files = version.files ?? [];
@@ -5133,9 +5317,9 @@ files (v${artifact.current_version.version}):`);
5133
5317
  version: opts.version,
5134
5318
  path: file.path
5135
5319
  });
5136
- const target2 = join(opts.out, file.path);
5137
- mkdirSync(dirname(target2), { recursive: true });
5138
- 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));
5139
5323
  total += content2.bytes.byteLength;
5140
5324
  }
5141
5325
  return console.log(
@@ -5146,10 +5330,10 @@ files (v${artifact.current_version.version}):`);
5146
5330
  const bytes = Buffer.from(content.bytes);
5147
5331
  if (opts.out !== void 0) {
5148
5332
  let target2 = opts.out;
5149
- if (existsSync(target2) && statSync(target2).isDirectory()) {
5150
- target2 = join(target2, version.filename ?? name2);
5333
+ if (existsSync2(target2) && statSync(target2).isDirectory()) {
5334
+ target2 = join2(target2, version.filename ?? name2);
5151
5335
  }
5152
- writeFileSync(target2, bytes);
5336
+ writeFileSync2(target2, bytes);
5153
5337
  return console.log(`wrote ${target2} (${bytes.byteLength} bytes, ${content.content_type})`);
5154
5338
  }
5155
5339
  if ((content.content_type ?? "").startsWith("text/")) {
@@ -5157,12 +5341,14 @@ files (v${artifact.current_version.version}):`);
5157
5341
  return;
5158
5342
  }
5159
5343
  const target = version.filename ?? name2;
5160
- writeFileSync(target, bytes);
5344
+ writeFileSync2(target, bytes);
5161
5345
  console.log(`wrote ${target} (${bytes.byteLength} bytes, ${content.content_type})`);
5162
5346
  }
5163
5347
  );
5164
5348
  withCommon(
5165
- 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
+ )
5166
5352
  ).action(async (ref, name2, opts) => {
5167
5353
  const api = client(opts);
5168
5354
  const issue = await resolveIssue(api, ref);
@@ -5173,30 +5359,38 @@ files (v${artifact.current_version.version}):`);
5173
5359
  );
5174
5360
  });
5175
5361
  withCommon(
5176
- 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")
5177
- ).action(async (ref, runnerSpec, opts) => {
5178
- const api = client(opts);
5179
- const issue = await resolveIssue(api, ref);
5180
- if (opts.clear) {
5181
- if (runnerSpec !== void 0) die("--clear does not take a runner");
5182
- const updated2 = await api.updateIssue(issue.id, { pinned_runner_id: null });
5183
- if (opts.json) return printJson(updated2);
5184
- return console.log(`unpinned ${updated2.project_name}/#${updated2.number} \u2014 routing rules apply again`);
5185
- }
5186
- if (runnerSpec === void 0) die("pass <runner>[:tier] to pin, or --clear to unpin");
5187
- const { name: name2, tier } = parseTargetSpec(runnerSpec);
5188
- const runner = await resolveRunner(api, name2);
5189
- const updated = await api.updateIssue(issue.id, {
5190
- pinned_runner_id: runner.id,
5191
- pinned_tier: tier ?? null
5192
- });
5193
- if (opts.json) return printJson(updated);
5194
- console.log(
5195
- `pinned ${updated.project_name}/#${updated.number} to ${runner.name}${tier ? ` (tier ${tier})` : ""} \u2014 only this runner will take it`
5196
- );
5197
- });
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
+ );
5198
5390
  withCommon(
5199
- 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
+ )
5200
5394
  ).action(async (ref, opts) => {
5201
5395
  const api = client(opts);
5202
5396
  const issue = await resolveIssue(api, ref);
@@ -5264,7 +5458,9 @@ function printNote(note) {
5264
5458
  if (note) console.error(`note: ${note}`);
5265
5459
  }
5266
5460
  function register3(program3) {
5267
- 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
+ );
5268
5464
  withCommon(
5269
5465
  journal.command("show <ref>").description("Print the journal for the stage your run was launched in").option("--state <workflow>/<state>", STATE_FLAG_HELP)
5270
5466
  ).action(async (ref, opts) => {
@@ -5323,29 +5519,96 @@ start one: tines journal append ${issue.project_name}/${issue.number} "- <date>:
5323
5519
  "the version being replaced (from the prompt or journal show)",
5324
5520
  (v) => Number.parseInt(v, 10)
5325
5521
  )
5326
- ).action(async (ref, opts) => {
5327
- const api = client(opts);
5328
- const { scope, note, item } = await resolveJournal(api, ref, opts.state);
5329
- printNote(note);
5330
- if (!item) die(`no journal exists yet for ${scope.label}; nothing to rewrite`);
5331
- const updated = await api.updateContextItem(item.id, {
5332
- body: readBodyValue(opts.body),
5333
- expected_version: opts.expectVersion
5334
- });
5335
- if (opts.json) return printJson(updated);
5336
- 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}`);
5337
5600
  });
5338
5601
  }
5339
5602
 
5340
5603
  // src/commands/misc.ts
5341
5604
  function registerTime(program3) {
5342
- withCommon(program3.command("time").description("Fetch the current time from the Tines API")).action(
5343
- async (opts) => {
5344
- const result = await client(opts).getTime();
5345
- if (opts.json) printJson(result);
5346
- else console.log(`Server time: ${result.time} (unix ${result.unix})`);
5347
- }
5348
- );
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
+ });
5349
5612
  }
5350
5613
  function registerEvents(program3) {
5351
5614
  const events = program3.command("events").description("Read the activity log");
@@ -5374,7 +5637,7 @@ function registerEvents(program3) {
5374
5637
  }
5375
5638
 
5376
5639
  // src/commands/projects.ts
5377
- function register4(program3) {
5640
+ function register5(program3) {
5378
5641
  const projects = program3.command("projects").description("Manage projects");
5379
5642
  withList(projects.command("list").description("List projects")).action(async (opts) => {
5380
5643
  const api = client(opts);
@@ -5388,7 +5651,10 @@ function register4(program3) {
5388
5651
  });
5389
5652
  });
5390
5653
  withCommon(
5391
- 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")
5392
5658
  ).action(
5393
5659
  async (name2, opts) => {
5394
5660
  if (opts.prompt === void 0 || opts.prompt === true) {
@@ -5465,8 +5731,10 @@ async function resolveRoutingScope(api, opts) {
5465
5731
  if (opts.state) parts.push(`state ${opts.state}`);
5466
5732
  return { projectId, stateId, label: parts.length > 0 ? parts.join(" \xB7 ") : "global" };
5467
5733
  }
5468
- function register5(program3) {
5469
- 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
+ );
5470
5738
  withCommon(routing.command("list").description("List routing rules, most specific first")).action(
5471
5739
  async (opts) => {
5472
5740
  const res = await client(opts).listRoutingRules();
@@ -5479,7 +5747,9 @@ function register5(program3) {
5479
5747
  }
5480
5748
  );
5481
5749
  withCommon(
5482
- 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")
5483
5753
  ).action(async (targetSpecs, opts) => {
5484
5754
  const api = client(opts);
5485
5755
  const scope = await resolveRoutingScope(api, opts);
@@ -5493,7 +5763,11 @@ function register5(program3) {
5493
5763
  const existing = items.find(
5494
5764
  (r) => r.scope.project_id === scope.projectId && r.scope.workflow_state_id === scope.stateId
5495
5765
  );
5496
- 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
+ });
5497
5771
  if (opts.json) return printJson(rule);
5498
5772
  console.log(
5499
5773
  `${existing ? "updated" : "created"} the ${rule.scope.label} rule: ${ruleTargetsLabel(rule)}`
@@ -5524,21 +5798,21 @@ import {
5524
5798
  createWriteStream,
5525
5799
  existsSync as existsSync4,
5526
5800
  mkdirSync as mkdirSync4,
5527
- readFileSync as readFileSync7,
5801
+ readFileSync as readFileSync9,
5528
5802
  rmSync as rmSync2,
5529
5803
  statSync as statSync3,
5530
- unlinkSync,
5804
+ unlinkSync as unlinkSync2,
5531
5805
  writeFileSync as writeFileSync3
5532
5806
  } from "node:fs";
5533
5807
  import { hostname, platform, arch } from "node:os";
5534
- import { dirname as dirname4, join as join4 } from "node:path";
5808
+ import { dirname as dirname4, join as join5 } from "node:path";
5535
5809
 
5536
5810
  // src/version.ts
5537
- import { readFileSync as readFileSync4 } from "node:fs";
5811
+ import { readFileSync as readFileSync6 } from "node:fs";
5538
5812
  function cliVersion() {
5539
5813
  try {
5540
5814
  const manifest = new URL("../package.json", import.meta.url);
5541
- return JSON.parse(readFileSync4(manifest, "utf8")).version ?? "0.0.0-unknown";
5815
+ return JSON.parse(readFileSync6(manifest, "utf8")).version ?? "0.0.0-unknown";
5542
5816
  } catch {
5543
5817
  return "0.0.0-unknown";
5544
5818
  }
@@ -5586,7 +5860,8 @@ function renderStreamEvent(event) {
5586
5860
  case "result": {
5587
5861
  const parts = [];
5588
5862
  if (typeof event.num_turns === "number") parts.push(`${event.num_turns} turns`);
5589
- 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)}`);
5590
5865
  const detail = parts.length > 0 ? ` (${parts.join(", ")})` : "";
5591
5866
  const lines = [`[session] result: ${event.subtype ?? "done"}${detail}`];
5592
5867
  if (event.is_error && event.result) lines.push(`[error] ${clip(event.result, 2e3)}`);
@@ -5641,8 +5916,8 @@ var ClaudeStreamRenderer = class {
5641
5916
 
5642
5917
  // src/daemon/cli-refresh.ts
5643
5918
  import { spawn } from "node:child_process";
5644
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "node:fs";
5645
- 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";
5646
5921
 
5647
5922
  // src/daemon/support.ts
5648
5923
  import { delimiter } from "node:path";
@@ -5917,14 +6192,14 @@ function buildSpawnEnv(base, opts) {
5917
6192
  var PACKAGE = "tines";
5918
6193
  var NPM_ARGS = ["--min-release-age=0", "--no-audit", "--no-fund", "--loglevel=error"];
5919
6194
  function agentCliPrefix(configDir) {
5920
- return join2(configDir, "cli");
6195
+ return join3(configDir, "cli");
5921
6196
  }
5922
6197
  function binDirOf(prefix) {
5923
- return join2(prefix, "node_modules", ".bin");
6198
+ return join3(prefix, "node_modules", ".bin");
5924
6199
  }
5925
6200
  function installedVersion(prefix) {
5926
6201
  try {
5927
- const pkg = readFileSync5(join2(prefix, "node_modules", PACKAGE, "package.json"), "utf8");
6202
+ const pkg = readFileSync7(join3(prefix, "node_modules", PACKAGE, "package.json"), "utf8");
5928
6203
  const version = JSON.parse(pkg).version;
5929
6204
  return typeof version === "string" ? version : null;
5930
6205
  } catch {
@@ -5933,7 +6208,7 @@ function installedVersion(prefix) {
5933
6208
  }
5934
6209
  function lastGood(prefix) {
5935
6210
  const binDir = binDirOf(prefix);
5936
- if (!existsSync2(join2(binDir, PACKAGE))) return AMBIENT_CLI;
6211
+ if (!existsSync3(join3(binDir, PACKAGE))) return AMBIENT_CLI;
5937
6212
  return { binDir, version: installedVersion(prefix), source: "stale" };
5938
6213
  }
5939
6214
  function runNpm(file, args, cwd, timeoutMs) {
@@ -5970,7 +6245,7 @@ async function installAgentCli(opts) {
5970
6245
  const prefix = agentCliPrefix(opts.configDir);
5971
6246
  const timeoutMs = opts.timeoutMs ?? 6e4;
5972
6247
  try {
5973
- mkdirSync2(prefix, { recursive: true });
6248
+ mkdirSync3(prefix, { recursive: true });
5974
6249
  } catch (err) {
5975
6250
  opts.log(`agent CLI refresh: cannot create ${prefix} (${message(err)})`);
5976
6251
  return AMBIENT_CLI;
@@ -5978,8 +6253,8 @@ async function installAgentCli(opts) {
5978
6253
  const args = ["install", "--prefix", prefix, `${PACKAGE}@latest`, ...NPM_ARGS];
5979
6254
  let result = await runNpm("npm", args, prefix, timeoutMs);
5980
6255
  if (result.spawnError?.code === "ENOENT") {
5981
- const sibling = join2(dirname2(process.execPath), "npm");
5982
- 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);
5983
6258
  }
5984
6259
  if (result.code !== 0) {
5985
6260
  const fallback = lastGood(prefix);
@@ -6001,38 +6276,13 @@ function message(err) {
6001
6276
  }
6002
6277
 
6003
6278
  // src/daemon/store.ts
6004
- import {
6005
- existsSync as existsSync3,
6006
- mkdirSync as mkdirSync3,
6007
- readdirSync as readdirSync2,
6008
- readFileSync as readFileSync6,
6009
- rmSync,
6010
- statSync as statSync2,
6011
- writeFileSync as writeFileSync2
6012
- } from "node:fs";
6013
- import { homedir } from "node:os";
6014
- import { dirname as dirname3, join as join3 } from "node:path";
6015
- function defaultConfigDir() {
6016
- return process.env.TINES_CONFIG_DIR ?? join3(homedir(), ".config", "tines");
6017
- }
6018
- function readJsonFile(path2) {
6019
- if (!existsSync3(path2)) return null;
6020
- try {
6021
- return JSON.parse(readFileSync6(path2, "utf8"));
6022
- } catch {
6023
- return null;
6024
- }
6025
- }
6026
- function writeJsonFile(path2, value, { secret = false } = {}) {
6027
- mkdirSync3(dirname3(path2), { recursive: true });
6028
- writeFileSync2(path2, `${JSON.stringify(value, null, 2)}
6029
- `, secret ? { mode: 384 } : {});
6030
- }
6279
+ import { readdirSync as readdirSync2, readFileSync as readFileSync8, rmSync, statSync as statSync2 } from "node:fs";
6280
+ import { join as join4 } from "node:path";
6031
6281
  function credentialsKey(url, name2) {
6032
6282
  return `${url.replace(/\/+$/, "")}#${name2}`;
6033
6283
  }
6034
6284
  function credentialsPath(dir) {
6035
- return join3(dir, "runners.json");
6285
+ return join4(dir, "runners.json");
6036
6286
  }
6037
6287
  function loadRunnerCredentials(dir, url, name2) {
6038
6288
  const all = readJsonFile(credentialsPath(dir));
@@ -6055,7 +6305,7 @@ function clearRunnerCredentials(dir, url, name2) {
6055
6305
  writeJsonFile(path2, all, { secret: true });
6056
6306
  }
6057
6307
  function daemonStatePath(dir, runnerId) {
6058
- return join3(dir, `daemon-state-${runnerId}.json`);
6308
+ return join4(dir, `daemon-state-${runnerId}.json`);
6059
6309
  }
6060
6310
  function loadDaemonState(path2) {
6061
6311
  const state = readJsonFile(path2);
@@ -6069,10 +6319,10 @@ function saveDaemonState(path2, runs) {
6069
6319
  }
6070
6320
  function processStartTimeMs(pid) {
6071
6321
  try {
6072
- const stat = readFileSync6(`/proc/${pid}/stat`, "utf8");
6322
+ const stat = readFileSync8(`/proc/${pid}/stat`, "utf8");
6073
6323
  const afterComm = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
6074
6324
  const startTicks = Number(afterComm[19]);
6075
- 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 "));
6076
6326
  const btime = Number(btimeLine?.slice("btime ".length));
6077
6327
  if (!Number.isFinite(startTicks) || !Number.isFinite(btime)) return null;
6078
6328
  return btime * 1e3 + startTicks / 100 * 1e3;
@@ -6081,10 +6331,10 @@ function processStartTimeMs(pid) {
6081
6331
  }
6082
6332
  }
6083
6333
  function workspacesDir(configDir) {
6084
- return join3(configDir, "workspaces");
6334
+ return join4(configDir, "workspaces");
6085
6335
  }
6086
6336
  function keptMarkerPath(workspace) {
6087
- return join3(workspace, "kept.json");
6337
+ return join4(workspace, "kept.json");
6088
6338
  }
6089
6339
  function writeKeptMarker(workspace, marker) {
6090
6340
  writeJsonFile(keptMarkerPath(workspace), marker);
@@ -6106,7 +6356,7 @@ function listKeptWorkspaces(configDir) {
6106
6356
  }
6107
6357
  const kept = [];
6108
6358
  for (const name2 of names) {
6109
- const path2 = join3(root, name2);
6359
+ const path2 = join4(root, name2);
6110
6360
  const marker = readKeptMarker(path2);
6111
6361
  if (marker) kept.push({ ...marker, path: path2 });
6112
6362
  }
@@ -6143,7 +6393,7 @@ function directorySizeBytes(path2) {
6143
6393
  return 0;
6144
6394
  }
6145
6395
  for (const entry of entries) {
6146
- const child = join3(path2, entry.name);
6396
+ const child = join4(path2, entry.name);
6147
6397
  if (entry.isDirectory()) total += directorySizeBytes(child);
6148
6398
  else if (entry.isFile()) {
6149
6399
  try {
@@ -6166,20 +6416,23 @@ async function uploadRawLog(run) {
6166
6416
  await new Promise((resolve) => run.rawSpool?.end(resolve) ?? resolve());
6167
6417
  const size = statSync3(path2).size;
6168
6418
  if (size > 0) {
6169
- let body = readFileSync7(path2);
6419
+ let body = readFileSync9(path2);
6170
6420
  if (body.byteLength > RUN_LOG_RAW_MAX_BYTES) {
6171
6421
  const marker = Buffer.from(
6172
6422
  `{"type":"tines_truncated","dropped_bytes":${body.byteLength - RUN_LOG_RAW_MAX_BYTES}}
6173
6423
  `
6174
6424
  );
6175
- 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
+ ]);
6176
6429
  }
6177
6430
  await run.rawUpload(body);
6178
6431
  }
6179
6432
  } catch {
6180
6433
  } finally {
6181
6434
  try {
6182
- unlinkSync(path2);
6435
+ unlinkSync2(path2);
6183
6436
  } catch {
6184
6437
  }
6185
6438
  }
@@ -6236,7 +6489,9 @@ async function runDaemon(opts) {
6236
6489
  };
6237
6490
  let creds = loadRunnerCredentials(opts.configDir, opts.url, opts.name);
6238
6491
  if (creds) {
6239
- 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
+ );
6240
6495
  } else {
6241
6496
  if (!opts.apiKey) {
6242
6497
  throw new Error(
@@ -6265,37 +6520,40 @@ async function runDaemon(opts) {
6265
6520
  const ensureCli = () => opts.cliRefresh ? refresher.ensure() : Promise.resolve(AMBIENT_CLI);
6266
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" : ""})`;
6267
6522
  log(`agent CLI: ${cliLabel(await ensureCli())}`);
6268
- const table2 = new RunTable({
6269
- finish: async (run, status, error) => {
6270
- await client2.finishRun(run.runId, { status, ...error ? { error } : {} });
6271
- },
6272
- release: (run, { keep, outcome }) => {
6273
- if (run.timeout) clearTimeout(run.timeout);
6274
- run.renderer?.finish();
6275
- settleWorkspace(run.workspace, keep, {
6276
- run_id: run.runId,
6277
- ...run.issueLabel ? { issue_ref: run.issueLabel } : {},
6278
- status: outcome,
6279
- ...run.endNote ? { error: run.endNote } : {}
6280
- });
6281
- void uploadRawLog(run);
6282
- sweepKeptWorkspaces();
6283
- },
6284
- 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}
6285
6541
  `),
6286
- persist: () => {
6287
- const entries = table2.values().filter((run) => run.child?.pid !== void 0).map((run) => ({
6288
- run_id: run.runId,
6289
- pid: run.child.pid,
6290
- workspace: run.workspace,
6291
- key_fingerprint: run.keyFingerprint,
6292
- started_at: run.spawnedAt,
6293
- ...run.issueLabel ? { issue_ref: run.issueLabel } : {}
6294
- }));
6295
- 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
6296
6554
  },
6297
- log
6298
- }, { keep: (outcome) => keepWorkspace(opts.keepWorkspaces, outcome) });
6555
+ { keep: (outcome) => keepWorkspace(opts.keepWorkspaces, outcome) }
6556
+ );
6299
6557
  for (const orphan of loadDaemonState(statePath)) {
6300
6558
  if (pidAlive(orphan.pid)) {
6301
6559
  const processStart = processStartTimeMs(orphan.pid);
@@ -6303,7 +6561,9 @@ async function runDaemon(opts) {
6303
6561
  if (reused) {
6304
6562
  log(`state-file pid ${orphan.pid} (run ${orphan.run_id}) was recycled; not killing it`);
6305
6563
  } else {
6306
- 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
+ );
6307
6567
  killTree(orphan.pid, "SIGKILL");
6308
6568
  }
6309
6569
  }
@@ -6336,7 +6596,7 @@ async function runDaemon(opts) {
6336
6596
  const launch = async (assignment) => {
6337
6597
  const runId = assignment.run.id;
6338
6598
  if (table2.has(runId)) return;
6339
- const workspace = join4(workspacesDir(opts.configDir), runId);
6599
+ const workspace = join5(workspacesDir(opts.configDir), runId);
6340
6600
  const issueLabel = assignment.run.issue_ref ? `${assignment.run.issue_ref.project_name}/${assignment.run.issue_ref.number}` : void 0;
6341
6601
  const run = {
6342
6602
  runId,
@@ -6354,32 +6614,43 @@ async function runDaemon(opts) {
6354
6614
  };
6355
6615
  run.flush = () => run.batcher.flush();
6356
6616
  table2.track(run);
6357
- 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
+ );
6358
6620
  try {
6359
6621
  rmSync2(workspace, { recursive: true, force: true });
6360
6622
  mkdirSync4(workspace, { recursive: true });
6361
- writeFileSync3(join4(workspace, "prompt.md"), `${assignment.prompt}
6623
+ writeFileSync3(join5(workspace, "prompt.md"), `${assignment.prompt}
6362
6624
  `);
6363
6625
  for (const skill of assignment.bundle.skills) {
6364
6626
  for (const file of skill.files) {
6365
- const target = join4(workspace, "skills", skill.name, file.path);
6627
+ const target = join5(workspace, "skills", skill.name, file.path);
6366
6628
  mkdirSync4(dirname4(target), { recursive: true });
6367
6629
  writeFileSync3(target, file.content);
6368
6630
  }
6369
6631
  }
6370
6632
  writeFileSync3(
6371
- join4(workspace, "repos.json"),
6633
+ join5(workspace, "repos.json"),
6372
6634
  `${JSON.stringify(assignment.bundle.repos, null, 2)}
6373
6635
  `
6374
6636
  );
6375
6637
  for (const repo of assignment.bundle.repos) {
6376
6638
  if (run.settled) return table2.cleanup(run);
6377
- 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
+ ];
6378
6645
  run.batcher.append(`$ git ${args.join(" ")}
6379
6646
  `);
6380
6647
  const result = await runGit(args, workspace, run.batcher);
6381
6648
  if (result !== 0) {
6382
- 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
+ );
6383
6654
  }
6384
6655
  }
6385
6656
  if (run.settled) return table2.cleanup(run);
@@ -6393,7 +6664,7 @@ async function runDaemon(opts) {
6393
6664
  );
6394
6665
  const harnessInput = {
6395
6666
  workspace,
6396
- promptFile: join4(workspace, "prompt.md"),
6667
+ promptFile: join5(workspace, "prompt.md"),
6397
6668
  prompt: assignment.prompt,
6398
6669
  model: assignment.run.model
6399
6670
  };
@@ -6426,7 +6697,7 @@ async function runDaemon(opts) {
6426
6697
  const renderer = new ClaudeStreamRenderer((line) => run.batcher.append(line));
6427
6698
  run.renderer = renderer;
6428
6699
  run.drain = () => renderer.finish();
6429
- const spoolPath = join4(opts.configDir, "rawlogs", `${runId}.ndjson`);
6700
+ const spoolPath = join5(opts.configDir, "rawlogs", `${runId}.ndjson`);
6430
6701
  mkdirSync4(dirname4(spoolPath), { recursive: true });
6431
6702
  run.rawSpoolPath = spoolPath;
6432
6703
  run.rawSpool = createWriteStream(spoolPath);
@@ -6439,16 +6710,13 @@ async function runDaemon(opts) {
6439
6710
  child.stdout?.on("data", (data) => run.batcher.append(data.toString("utf8")));
6440
6711
  }
6441
6712
  child.stderr?.on("data", (data) => run.batcher.append(data.toString("utf8")));
6442
- run.timeout = setTimeout(
6443
- () => {
6444
- if (run.settled) return;
6445
- log(`run ${runId} hit its ${assignment.timeout_minutes}m timeout; killing`);
6446
- run.timedOut = true;
6447
- if (child.pid) killTree(child.pid, "SIGTERM");
6448
- if (child.pid) setTimeout(() => killTree(child.pid, "SIGKILL"), 5e3).unref?.();
6449
- },
6450
- assignment.timeout_minutes * 6e4
6451
- );
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);
6452
6720
  child.on("error", (err) => {
6453
6721
  void table2.finishAndCleanup(run, "failed", `failed to launch harness: ${message2(err)}`);
6454
6722
  });
@@ -6572,27 +6840,31 @@ function printTierTable(runner) {
6572
6840
  override?.effort ? `effort ${override.effort}` : null,
6573
6841
  stale ? `stale \u2014 built-in is now ${builtin}` : null
6574
6842
  ].filter(Boolean);
6575
- console.log(` ${tier}: ${model} [${source}]${marks.length > 0 ? ` (${marks.join(", ")})` : ""}`);
6843
+ console.log(
6844
+ ` ${tier}: ${model} [${source}]${marks.length > 0 ? ` (${marks.join(", ")})` : ""}`
6845
+ );
6576
6846
  }
6577
6847
  }
6578
- function register6(program3) {
6848
+ function register7(program3) {
6579
6849
  const runners = program3.command("runners").description("Manage the runner registry");
6580
- withCommon(runners.command("list").description("List runners")).action(async (opts) => {
6581
- const res = await client(opts).listRunners();
6582
- if (opts.json) return printJson(res);
6583
- if (res.items.length === 0) return console.log("no runners");
6584
- table([
6585
- ["NAME", "TYPE", "STATUS", "RUNS", "TIER", "LAST SEEN"],
6586
- ...res.items.map((r) => [
6587
- r.name,
6588
- r.type,
6589
- runnerStatusLabel(r),
6590
- `${r.active_runs}/${r.max_concurrent}`,
6591
- r.default_tier,
6592
- r.last_seen_at ? timestamp(r.last_seen_at) : "\u2014"
6593
- ])
6594
- ]);
6595
- });
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
+ );
6596
6868
  withCommon(runners.command("show <name>").description("Show a runner")).action(
6597
6869
  async (ref, opts) => {
6598
6870
  const runner = await resolveRunner(client(opts), ref);
@@ -6616,7 +6888,8 @@ function register6(program3) {
6616
6888
  const b = runner.budget;
6617
6889
  const parts = [];
6618
6890
  if (b.max_run_cost_usd !== void 0) parts.push(`$${b.max_run_cost_usd}/run`);
6619
- 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`);
6620
6893
  if (b.daily_usd !== void 0) parts.push(`$${b.daily_usd}/day`);
6621
6894
  if (b.daily_tokens !== void 0) parts.push(`${b.daily_tokens.toLocaleString()} tok/day`);
6622
6895
  if (parts.length > 0) console.log(`budget: ${parts.join(" ")}`);
@@ -6670,7 +6943,12 @@ function register6(program3) {
6670
6943
  }
6671
6944
  );
6672
6945
  withCommon(
6673
- 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")
6674
6952
  ).action(
6675
6953
  async (ref, opts) => {
6676
6954
  const api = client(opts);
@@ -6685,7 +6963,8 @@ function register6(program3) {
6685
6963
  } else if (flags) {
6686
6964
  const num = (value, flag) => {
6687
6965
  const n = Number(value);
6688
- 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}"`);
6689
6968
  return n;
6690
6969
  };
6691
6970
  updated = await api.updateRunner(runner.id, {
@@ -6703,10 +6982,14 @@ function register6(program3) {
6703
6982
  if (!b) return console.log(`no limits on "${updated.name}"`);
6704
6983
  console.log(`limits on "${updated.name}":`);
6705
6984
  if (b.max_run_cost_usd !== void 0) console.log(` $${b.max_run_cost_usd} per run`);
6706
- if (b.max_run_tokens !== void 0) console.log(` ${b.max_run_tokens.toLocaleString()} tokens per run`);
6707
- 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)`);
6708
6989
  if (b.daily_tokens !== void 0) {
6709
- 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
+ );
6710
6993
  }
6711
6994
  }
6712
6995
  );
@@ -6729,7 +7012,12 @@ function register6(program3) {
6729
7012
  }
6730
7013
  );
6731
7014
  withCommon(
6732
- 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
+ )
6733
7021
  ).action(async (ref, opts) => {
6734
7022
  const api = client(opts);
6735
7023
  const runner = await resolveRunner(api, ref);
@@ -6751,14 +7039,28 @@ function register6(program3) {
6751
7039
  runner_id: rotated.runner.id,
6752
7040
  token: rotated.runner_token
6753
7041
  });
6754
- 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
+ );
6755
7045
  } else {
6756
- 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
+ );
6757
7049
  }
6758
7050
  });
6759
7051
  const runnerCmd = program3.command("runner").description("The local runner daemon");
6760
7052
  withCommon(
6761
- 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(
6762
7064
  "--no-cli-refresh",
6763
7065
  "do not install/refresh the agent-facing tines CLI from npm (harnesses use the ambient PATH)"
6764
7066
  ).option(
@@ -6783,7 +7085,9 @@ function register6(program3) {
6783
7085
  die(`--harness must be claude-code, codex, or custom, got "${opts.harness}"`);
6784
7086
  }
6785
7087
  if (harness === "custom" && !opts.command) {
6786
- 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
+ );
6787
7091
  }
6788
7092
  if (harness !== "custom" && opts.command) die("--command only applies to --harness custom");
6789
7093
  if (!Number.isInteger(opts.maxConcurrent) || opts.maxConcurrent < 1 || opts.maxConcurrent > 100) {
@@ -6794,7 +7098,9 @@ function register6(program3) {
6794
7098
  }
6795
7099
  const keepWorkspaces = opts.keepWorkspaces;
6796
7100
  if (!KEEP_WORKSPACES_MODES.includes(keepWorkspaces)) {
6797
- 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
+ );
6798
7104
  }
6799
7105
  if (!Number.isFinite(opts.keepWorkspacesFor) || opts.keepWorkspacesFor <= 0) {
6800
7106
  die("--keep-workspaces-for must be a positive number of hours");
@@ -6825,9 +7131,7 @@ function register6(program3) {
6825
7131
  if (opts.json) return printJson({ items: sized });
6826
7132
  if (sized.length === 0) {
6827
7133
  console.log(`no kept workspaces in ${workspacesDir(configDir)}`);
6828
- return console.log(
6829
- "the daemon keeps them only with --keep-workspaces failed (or always)."
6830
- );
7134
+ return console.log("the daemon keeps them only with --keep-workspaces failed (or always).");
6831
7135
  }
6832
7136
  table([
6833
7137
  ["RUN", "ISSUE", "STATUS", "AGE", "SIZE", "PATH"],
@@ -6838,7 +7142,8 @@ function register6(program3) {
6838
7142
  if (opts.all === void 0 && opts.olderThan === void 0) {
6839
7143
  die("pass --all or --older-than <hours>");
6840
7144
  }
6841
- 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");
6842
7147
  if (opts.olderThan !== void 0 && (!Number.isFinite(opts.olderThan) || opts.olderThan < 0)) {
6843
7148
  die("--older-than must be a non-negative number of hours");
6844
7149
  }
@@ -6868,61 +7173,69 @@ function register6(program3) {
6868
7173
  );
6869
7174
  printList(res, opts, (items) => {
6870
7175
  if (items.length === 0) return console.log(opts.active ? "no active runs" : "no runs");
6871
- 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
+ ]);
6872
7180
  });
6873
7181
  });
6874
7182
  withCommon(
6875
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")
6876
- ).action(async (id, opts) => {
6877
- const api = client(opts);
6878
- const run = await api.getRun(id);
6879
- if (opts.json) return printJson(run);
6880
- console.log(`${run.id} ${run.status} on ${run.runner_name}`);
6881
- if (run.issue_ref) console.log(`issue: ${issueRef(run.issue_ref)} \u2014 ${run.issue_ref.title}`);
6882
- console.log(`tier: ${run.tier} model: ${run.model ?? "(n/a)"}`);
6883
- console.log(
6884
- `states: ${run.state_at_start_name ?? run.state_id_at_start} \u2192 ${run.state_at_end_name ?? run.state_id_at_end ?? "\u2026"}`
6885
- );
6886
- console.log(
6887
- `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)}`
6888
- );
6889
- if (run.usage) {
6890
- const u = run.usage;
6891
- const parts = [];
6892
- if (u.input_tokens !== void 0 || u.output_tokens !== void 0) {
6893
- parts.push(`${(u.input_tokens ?? 0).toLocaleString()} in / ${(u.output_tokens ?? 0).toLocaleString()} out tokens`);
6894
- }
6895
- if (u.cost_usd !== void 0) parts.push(`$${u.cost_usd.toFixed(2)}`);
6896
- if (u.cost_source) parts.push(`(${u.cost_source === "provider" ? "provider-reported" : u.cost_source})`);
6897
- if (parts.length > 0) console.log(`usage: ${parts.join(" ")}`);
6898
- }
6899
- if (run.provider_session_id) console.log(`provider session: ${run.provider_session_id}`);
6900
- if (run.provider_url) console.log(`provider console: ${run.provider_url}`);
6901
- if (run.error) console.log(`error: ${run.error}`);
6902
- if (opts.logs) {
6903
- console.log("");
6904
- if (opts.full || opts.raw) {
6905
- const res = await api.getRunLogFull(id, { raw: opts.raw });
6906
- const body = res.body;
6907
- if (!body) return;
6908
- const reader = body.getReader();
6909
- const decoder = new TextDecoder();
6910
- for (; ; ) {
6911
- const { done, value } = await reader.read();
6912
- if (done) break;
6913
- 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
+ );
6914
7205
  }
6915
- process.stdout.write(decoder.decode());
6916
- 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(" ")}`);
6917
7210
  }
6918
- if (run.log_bytes_dropped > 0) {
6919
- console.log(
6920
- `[${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]`)
6921
- );
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)");
6922
7236
  }
6923
- console.log(run.log || "(no log output captured)");
6924
7237
  }
6925
- });
7238
+ );
6926
7239
  withCommon(
6927
7240
  runsCmd.command("cancel <id>").description("Cancel a run (judged like any other end: usually a strike)")
6928
7241
  ).action(async (id, opts) => {
@@ -6965,7 +7278,7 @@ title template: ${s.title_template}`);
6965
7278
  for (const line of s.description_template.split("\n")) console.log(` ${line}`);
6966
7279
  }
6967
7280
  }
6968
- function register7(program3) {
7281
+ function register8(program3) {
6969
7282
  const schedules = program3.command("schedules").description("Manage scheduled tasks (addressed as <project>/<name>)");
6970
7283
  withList(
6971
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")
@@ -7016,7 +7329,9 @@ recent instances:`);
7016
7329
  }
7017
7330
  });
7018
7331
  withCommon(
7019
- 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(
7020
7335
  "-d, --description <markdown>",
7021
7336
  `set the description template (Markdown) \u2014 ${BODY_VALUE_HELP}`
7022
7337
  ).option(
@@ -7025,7 +7340,13 @@ recent instances:`);
7025
7340
  ).option(
7026
7341
  "-s, --state <id-or-name>",
7027
7342
  "start state for future instances (the workflow's initial state = the default)"
7028
- ).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")
7029
7350
  ).action(
7030
7351
  async (ref, opts) => {
7031
7352
  const descriptionTemplate = opts.description !== void 0 ? readBodyValue(opts.description) : void 0;
@@ -7034,7 +7355,8 @@ recent instances:`);
7034
7355
  const body = {};
7035
7356
  if (opts.title !== void 0) body.title_template = opts.title;
7036
7357
  if (descriptionTemplate !== void 0) body.description_template = descriptionTemplate;
7037
- 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;
7038
7360
  if (opts.state !== void 0) body.state = opts.state;
7039
7361
  const recurrence = buildRecurrence(opts);
7040
7362
  if (recurrence?.preset) body.preset = recurrence.preset;
@@ -7054,15 +7376,15 @@ recent instances:`);
7054
7376
  printScheduleDetail(updated);
7055
7377
  }
7056
7378
  );
7057
- withCommon(schedules.command("pause <ref>").description("Pause a schedule (keeps config and history)")).action(
7058
- async (ref, opts) => {
7059
- const api = client(opts);
7060
- const schedule = await resolveSchedule(api, ref);
7061
- const updated = await api.updateSchedule(schedule.id, { enabled: false });
7062
- if (opts.json) return printJson(updated);
7063
- console.log(`paused schedule "${scheduleRef(updated)}"`);
7064
- }
7065
- );
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
+ });
7066
7388
  withCommon(
7067
7389
  schedules.command("resume <ref>").description("Resume a paused schedule (recomputes the next occurrence from now)")
7068
7390
  ).action(async (ref, opts) => {
@@ -7070,7 +7392,9 @@ recent instances:`);
7070
7392
  const schedule = await resolveSchedule(api, ref);
7071
7393
  const updated = await api.updateSchedule(schedule.id, { enabled: true });
7072
7394
  if (opts.json) return printJson(updated);
7073
- 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
+ );
7074
7398
  });
7075
7399
  withCommon(
7076
7400
  schedules.command("run <ref>").description("Create an instance now (respects the only-when-closed gate)")
@@ -7102,57 +7426,61 @@ recent instances:`);
7102
7426
  }
7103
7427
 
7104
7428
  // src/commands/supervisor.ts
7105
- function register8(program3) {
7429
+ function register9(program3) {
7106
7430
  const supervisor = program3.command("supervisor").description("The automation kill switch, quota policy, and attempt limit");
7107
- withCommon(supervisor.command("status").description("One-screen overview: kill switch, quota, utilization, runners")).action(
7108
- async (opts) => {
7109
- const api = client(opts);
7110
- const [settings, runnersRes, workflows, activeRunItems] = await Promise.all([
7111
- api.getSupervisorSettings(),
7112
- api.listRunners(),
7113
- api.listWorkflows(),
7114
- listAll((page) => api.listRuns({ active: true, ...page }))
7115
- ]);
7116
- if (opts.json) {
7117
- return printJson({ settings, runners: runnersRes.items, active_runs: activeRunItems });
7118
- }
7119
- const stateNames = /* @__PURE__ */ new Map();
7120
- for (const wf of workflows.items) {
7121
- for (const s of wf.states) stateNames.set(s.id, `${wf.name}/${s.name}`);
7122
- }
7123
- console.log(`automation: ${settings.enabled ? "ON" : "OFF (kill switch \u2014 nothing dispatches)"}`);
7124
- console.log(quotaLabel(settings.quota, (id) => stateNames.get(id) ?? id));
7125
- console.log(`utilization: ${utilizationLabel(settings.quota, activeRunItems, (id) => stateNames.get(id) ?? id)}`);
7126
- console.log(`attempt limit: ${settings.attempt_limit} strikes, then the issue parks`);
7127
- if (runnersRes.items.length === 0) {
7128
- console.log("runners: none");
7129
- } else {
7130
- console.log("runners:");
7131
- table(
7132
- runnersRes.items.map((r) => [
7133
- ` ${r.name}`,
7134
- r.type,
7135
- runnerStatusLabel(r),
7136
- `${r.active_runs}/${r.max_concurrent}`
7137
- ])
7138
- );
7139
- }
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 });
7140
7443
  }
7141
- );
7142
- withCommon(supervisor.command("enable").description("Arm automation (the kill switch on)")).action(
7143
- async (opts) => {
7144
- const settings = await client(opts).updateSupervisorSettings({ enabled: true });
7145
- if (opts.json) return printJson(settings);
7146
- 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}`);
7147
7447
  }
7148
- );
7149
- withCommon(supervisor.command("disable").description("Pause all automation at once (the kill switch off)")).action(
7150
- async (opts) => {
7151
- const settings = await client(opts).updateSupervisorSettings({ enabled: false });
7152
- if (opts.json) return printJson(settings);
7153
- 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
+ );
7154
7468
  }
7155
- );
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
+ });
7156
7484
  const quota = supervisor.command("quota").description("Pick and configure the quota policy");
7157
7485
  withCommon(
7158
7486
  quota.command("global <n>").description("Use the global cap: at most <n> concurrent runs in total")
@@ -7165,7 +7493,11 @@ function register8(program3) {
7165
7493
  console.log(quotaLabel(settings.quota));
7166
7494
  });
7167
7495
  withCommon(
7168
- 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(
7169
7501
  "--state <workflow/state=n>",
7170
7502
  "per-state override (repeatable), counted by the state a run started in",
7171
7503
  collect,
@@ -7197,7 +7529,7 @@ function register8(program3) {
7197
7529
  }
7198
7530
 
7199
7531
  // src/commands/workflows.ts
7200
- import { readFileSync as readFileSync8 } from "node:fs";
7532
+ import { readFileSync as readFileSync10 } from "node:fs";
7201
7533
  function readJsonBody(inline, file) {
7202
7534
  if (inline !== void 0 && file !== void 0) {
7203
7535
  die("pass the JSON inline or with --file, not both");
@@ -7206,14 +7538,14 @@ function readJsonBody(inline, file) {
7206
7538
  if (file !== void 0 && file !== "-") {
7207
7539
  let raw;
7208
7540
  try {
7209
- raw = readFileSync8(file, "utf8");
7541
+ raw = readFileSync10(file, "utf8");
7210
7542
  } catch (err) {
7211
7543
  die(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
7212
7544
  }
7213
7545
  return parseJsonObject(raw, file);
7214
7546
  }
7215
7547
  if (file === "-" || !process.stdin.isTTY) {
7216
- const raw = readFileSync8(0, "utf8");
7548
+ const raw = readFileSync10(0, "utf8");
7217
7549
  if (raw.trim() === "") {
7218
7550
  if (file === "-") die("no JSON on stdin");
7219
7551
  return void 0;
@@ -7289,7 +7621,7 @@ function printWorkflowDetail(wf) {
7289
7621
  for (const w of wf.warnings ?? []) console.log(`
7290
7622
  warning: ${w}`);
7291
7623
  }
7292
- function register9(program3) {
7624
+ function register10(program3) {
7293
7625
  const workflows = program3.command("workflows").description("Manage the workflow library");
7294
7626
  withList(workflows.command("list").description("List the workflow library")).action(
7295
7627
  async (opts) => {
@@ -7319,22 +7651,26 @@ function register9(program3) {
7319
7651
  printWorkflowDetail(wf);
7320
7652
  });
7321
7653
  withCommon(
7322
- 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)
7323
- ).action(async (inline, opts) => {
7324
- const body = readJsonBody(inline, opts.file);
7325
- if (!body) {
7326
- die(
7327
- `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
7328
7663
  see \`tines workflows create --help\` for the expected shape`
7329
- );
7330
- }
7331
- assertNewStatesHavePrompts(body.states, opts.prompts);
7332
- const wf = await client(opts).createWorkflow(body);
7333
- if (opts.json) return printJson(wf);
7334
- 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})
7335
7670
  `);
7336
- printWorkflowDetail(wf);
7337
- });
7671
+ printWorkflowDetail(wf);
7672
+ }
7673
+ );
7338
7674
  withCommon(
7339
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)
7340
7676
  ).action(
@@ -7371,14 +7707,15 @@ var program2 = new Command();
7371
7707
  program2.name("tines").description("CLI for Tines").version(cliVersion()).enablePositionalOptions();
7372
7708
  registerTime(program2);
7373
7709
  register4(program2);
7374
- register9(program2);
7710
+ register5(program2);
7711
+ register10(program2);
7375
7712
  register2(program2);
7376
7713
  register(program2);
7377
7714
  register3(program2);
7715
+ register8(program2);
7378
7716
  register7(program2);
7379
7717
  register6(program2);
7380
- register5(program2);
7381
- register8(program2);
7718
+ register9(program2);
7382
7719
  registerEvents(program2);
7383
7720
 
7384
7721
  // src/index.ts