@pebblehouse/odin-cli 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +75 -33
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command24 } from "commander";
4
+ import { Command as Command25 } from "commander";
5
5
 
6
6
  // src/auth/login.ts
7
7
  import open from "open";
@@ -759,7 +759,7 @@ plansCmd.command("create <slug> <title>").description("Create a plan").option("-
759
759
  process.stdout.write(JSON.stringify(res) + "\n");
760
760
  if (res.error) process.exit(1);
761
761
  });
762
- plansCmd.command("update <slug> <id>").description("Update a plan").option("--title <title>", "New title").option("--description <description>", "New description").option("--status <status>", "Plan status (active|done|archived)").option("--bet-id <uuid>", "Link plan to a bet").option("--task-id <uuid>", "Link plan to a task").option("--clear-bet", "Clear the bet link").option("--clear-task", "Clear the task link").action(async (slug, id, opts) => {
762
+ plansCmd.command("update <slug> <id>").description("Update a plan").option("--title <title>", "New title").option("--description <description>", "New description").option("--status <status>", "Plan status (active|done|archived)").option("--bet-id <uuid>", "Link plan to a bet").option("--task-id <uuid>", "Link plan to a task").option("--clear-bet", "Clear the bet link").option("--clear-task", "Clear the task link").option("--pr-url <url>", "Bind the plan's pull request URL (claim-guarded on bet-linked plans)").option("--claim-id <id>", "Held delivery claim id").option("--claim-generation <n>", "Held claim generation").option("--skills-version <v>", "odin-skills version").action(async (slug, id, opts) => {
763
763
  if (opts.betId && opts.clearBet) {
764
764
  process.stderr.write("Cannot combine --bet-id with --clear-bet\n");
765
765
  process.exit(1);
@@ -776,6 +776,11 @@ plansCmd.command("update <slug> <id>").description("Update a plan").option("--ti
776
776
  else if (opts.clearBet) body.bet_id = null;
777
777
  if (opts.taskId) body.task_id = opts.taskId;
778
778
  else if (opts.clearTask) body.task_id = null;
779
+ if (opts.prUrl) body.pr_url = opts.prUrl;
780
+ Object.assign(body, {
781
+ ...opts.claimId ? { claim_id: opts.claimId, claim_generation: Number(opts.claimGeneration) } : {},
782
+ ...opts.skillsVersion ? { skills_version: opts.skillsVersion } : {}
783
+ });
779
784
  const res = await apiRequest(`/pebbles/${slug}/plans/${id}`, {
780
785
  method: "PUT",
781
786
  body
@@ -802,12 +807,16 @@ plansCmd.command("add-phase <slug> <planId> <title>").description("Add a phase t
802
807
  process.stdout.write(JSON.stringify(res) + "\n");
803
808
  if (res.error) process.exit(1);
804
809
  });
805
- plansCmd.command("update-phase <slug> <planId> <phaseId>").description("Update a phase").option("--title <title>", "Phase title").option("--status <status>", "Phase status (pending|in_progress|done|skipped)").option("--notes <notes>", "Phase notes").option("--description <description>", "Phase description").action(async (slug, planId, phaseId, opts) => {
810
+ plansCmd.command("update-phase <slug> <planId> <phaseId>").description("Update a phase").option("--title <title>", "Phase title").option("--status <status>", "Phase status (pending|in_progress|done|skipped)").option("--notes <notes>", "Phase notes").option("--description <description>", "Phase description").option("--claim-id <id>", "Held delivery claim id").option("--claim-generation <n>", "Held claim generation").option("--skills-version <v>", "odin-skills version").action(async (slug, planId, phaseId, opts) => {
806
811
  const body = {};
807
812
  if (opts.title) body.title = opts.title;
808
813
  if (opts.status) body.status = opts.status;
809
814
  if (opts.notes) body.notes = opts.notes;
810
815
  if (opts.description) body.description = opts.description;
816
+ Object.assign(body, {
817
+ ...opts.claimId ? { claim_id: opts.claimId, claim_generation: Number(opts.claimGeneration) } : {},
818
+ ...opts.skillsVersion ? { skills_version: opts.skillsVersion } : {}
819
+ });
811
820
  const res = await apiRequest(`/pebbles/${slug}/plans/${planId}/phases/${phaseId}`, {
812
821
  method: "PUT",
813
822
  body
@@ -1276,7 +1285,7 @@ betsCmd.command("delete <slug> <id>").description("Delete a bet").action(async (
1276
1285
  if (res.error) process.exit(1);
1277
1286
  });
1278
1287
  betsCmd.addCommand(depsCmd);
1279
- betsCmd.command("transition <slug> <id>").description("Apply a guarded lifecycle operation (never a raw status write)").requiredOption("--op <operation>", "start_shaping | approve_ready | handoff | complete | return_to_shaping | archive | correct_stage | open_blocker | resolve_blocker | set_queued | clear_queued").requiredOption("--expected-revision <n>", "lifecycle_revision from `odin bets get`").option("--to <stage>", `Target stage for handoff/correct_stage (${STAGE_HELP})`).option("--reason <text>", "Reason (required for return loops, skips and return_to_shaping)").option("--note <text>", "Optional correction/resolution note").option("--actor <label>", "Persona slug or handle recorded on the event", "cli").option("--actor-kind <kind>", "human | persona", "human").option("--idempotency-key <key>", "Reuse the same key when retrying after a timeout").option("--payload <json>", "Extra payload JSON merged last (check_token/exceptions, category, blocker_id, owner_persona\u2026)").action(async (slug, id, opts) => {
1288
+ betsCmd.command("transition <slug> <id>").description("Apply a guarded lifecycle operation (never a raw status write)").requiredOption("--op <operation>", "start_shaping | approve_ready | handoff | complete | return_to_shaping | archive | correct_stage | open_blocker | resolve_blocker | set_queued | clear_queued").requiredOption("--expected-revision <n>", "lifecycle_revision from `odin bets get`").option("--to <stage>", `Target stage for handoff/correct_stage (${STAGE_HELP})`).option("--reason <text>", "Reason (required for return loops, skips and return_to_shaping)").option("--note <text>", "Optional correction/resolution note").option("--actor <label>", "Persona slug or handle recorded on the event", "cli").option("--actor-kind <kind>", "human | persona", "human").option("--idempotency-key <key>", "Reuse the same key when retrying after a timeout").option("--payload <json>", "Extra payload JSON merged last (check_token/exceptions, category, blocker_id, owner_persona\u2026)").option("--claim-id <id>", "Held delivery claim id").option("--claim-generation <n>", "Held claim generation").option("--skills-version <v>", "odin-skills version").action(async (slug, id, opts) => {
1280
1289
  let extra = {};
1281
1290
  if (opts.payload) {
1282
1291
  try {
@@ -1287,7 +1296,16 @@ betsCmd.command("transition <slug> <id>").description("Apply a guarded lifecycle
1287
1296
  }
1288
1297
  }
1289
1298
  const payload = { ...opts.to ? { to_stage: opts.to } : {}, ...opts.reason ? { reason: opts.reason } : {}, ...opts.note ? { note: opts.note } : {}, ...extra };
1290
- const body = { operation: opts.op, expected_revision: Number(opts.expectedRevision), idempotency_key: opts.idempotencyKey ?? randomUUID(), actor_label: opts.actor, actor_kind: opts.actorKind, payload };
1299
+ const body = {
1300
+ operation: opts.op,
1301
+ expected_revision: Number(opts.expectedRevision),
1302
+ idempotency_key: opts.idempotencyKey ?? randomUUID(),
1303
+ actor_label: opts.actor,
1304
+ actor_kind: opts.actorKind,
1305
+ payload,
1306
+ ...opts.claimId ? { claim_id: opts.claimId, claim_generation: Number(opts.claimGeneration) } : {},
1307
+ ...opts.skillsVersion ? { skills_version: opts.skillsVersion } : {}
1308
+ };
1291
1309
  const res = await apiRequest(`/pebbles/${slug}/bets/${id}/lifecycle`, { method: "POST", body });
1292
1310
  process.stdout.write(JSON.stringify(res) + "\n");
1293
1311
  if (res.error) process.exit(1);
@@ -1308,9 +1326,32 @@ betsCmd.command("lifecycle-contract").description("Stages, transition matrix, Re
1308
1326
  if (res.error) process.exit(1);
1309
1327
  });
1310
1328
 
1311
- // src/commands/tickets.ts
1329
+ // src/commands/delivery.ts
1312
1330
  import { Command as Command15 } from "commander";
1313
- var ticketsCmd = new Command15("tickets").description("Resolve human-readable ticket IDs");
1331
+ var deliveryCmd = new Command15("delivery").description("Delivery admission: claim, validate, release (DELIVERY-ADMISSION-REFERENCE.md)");
1332
+ var out = (res, okWhen) => {
1333
+ process.stdout.write(JSON.stringify(res) + "\n");
1334
+ if (res.error || !okWhen(res.data)) process.exit(1);
1335
+ };
1336
+ deliveryCmd.command("claim <slug>").option("--bet <id>").requiredOption("--persona <slug>").requiredOption("--attempt-id <id>").option("--host <host>").action(async (slug, o) => {
1337
+ const res = await apiRequest(`/pebbles/${slug}/delivery/claim`, {
1338
+ method: "POST",
1339
+ body: { ...o.bet ? { bet_id: o.bet } : {}, persona: o.persona, attempt_id: o.attemptId, ...o.host ? { host: o.host } : {} }
1340
+ });
1341
+ out(res, (d) => ["granted", "no_work", "resource_wait"].includes(d?.outcome));
1342
+ });
1343
+ deliveryCmd.command("validate <slug> <betId>").requiredOption("--claim-id <id>").requiredOption("--claim-generation <n>").action(async (slug, betId, o) => {
1344
+ const res = await apiRequest(`/pebbles/${slug}/delivery/validate`, { method: "POST", body: { bet_id: betId, claim_id: o.claimId, claim_generation: Number(o.claimGeneration) } });
1345
+ out(res, (d) => d?.valid === true);
1346
+ });
1347
+ deliveryCmd.command("release <slug>").requiredOption("--claim-id <id>").requiredOption("--claim-generation <n>").option("--scope <scope>", "claim | migration", "claim").option("--actor <label>", "persona slug", "cli").action(async (slug, o) => {
1348
+ const res = await apiRequest(`/pebbles/${slug}/delivery/release`, { method: "POST", body: { claim_id: o.claimId, claim_generation: Number(o.claimGeneration), scope: o.scope, actor_label: o.actor } });
1349
+ out(res, (d) => d?.ok === true);
1350
+ });
1351
+
1352
+ // src/commands/tickets.ts
1353
+ import { Command as Command16 } from "commander";
1354
+ var ticketsCmd = new Command16("tickets").description("Resolve human-readable ticket IDs");
1314
1355
  ticketsCmd.command("resolve <key>").description("Resolve a ticket key (e.g. ODIN-12, RATE-36A) to its bet or task, with historical candidates").action(async (key) => {
1315
1356
  const res = await apiRequest(`/tickets/${encodeURIComponent(key.trim().toUpperCase())}`);
1316
1357
  process.stdout.write(JSON.stringify(res) + "\n");
@@ -1323,9 +1364,9 @@ ticketsCmd.command("resolve <key>").description("Resolve a ticket key (e.g. ODIN
1323
1364
  });
1324
1365
 
1325
1366
  // src/commands/bet-comments.ts
1326
- import { Command as Command16 } from "commander";
1367
+ import { Command as Command17 } from "commander";
1327
1368
  import { readFileSync as readFileSync11 } from "fs";
1328
- var betCommentsCmd = new Command16("bet-comments").description("Manage bet comments");
1369
+ var betCommentsCmd = new Command17("bet-comments").description("Manage bet comments");
1329
1370
  betCommentsCmd.command("list <slug> <betId>").description("List comments for a bet (oldest first)").action(async (slug, betId) => {
1330
1371
  const res = await apiRequest(`/pebbles/${slug}/bets/${betId}/comments`);
1331
1372
  process.stdout.write(JSON.stringify(res) + "\n");
@@ -1352,9 +1393,9 @@ betCommentsCmd.command("create <slug> <betId> [body]").description("Post a comme
1352
1393
  });
1353
1394
 
1354
1395
  // src/commands/tasks.ts
1355
- import { Command as Command17 } from "commander";
1396
+ import { Command as Command18 } from "commander";
1356
1397
  import { readFileSync as readFileSync12 } from "fs";
1357
- var tasksCmd = new Command17("tasks").description("Manage tasks");
1398
+ var tasksCmd = new Command18("tasks").description("Manage tasks");
1358
1399
  withListFlags(
1359
1400
  tasksCmd.command("list <slug>").description("List tasks for a pebble").option("--status <status>", "Filter by status").option("--bet <bet-id>", "Filter by bet ID")
1360
1401
  ).action(async (slug, opts) => {
@@ -1415,7 +1456,7 @@ tasksCmd.command("delete <slug> <id>").description("Delete a task").action(async
1415
1456
  });
1416
1457
 
1417
1458
  // src/commands/roadmaps.ts
1418
- import { Command as Command18 } from "commander";
1459
+ import { Command as Command19 } from "commander";
1419
1460
  import { readFileSync as readFileSync14 } from "fs";
1420
1461
 
1421
1462
  // src/resolve-pebble.ts
@@ -1438,7 +1479,7 @@ function resolvePebbleSlug(explicit, cwd = process.cwd()) {
1438
1479
  }
1439
1480
 
1440
1481
  // src/commands/roadmaps.ts
1441
- var roadmapsCmd = new Command18("roadmaps").description("Manage roadmaps");
1482
+ var roadmapsCmd = new Command19("roadmaps").description("Manage roadmaps");
1442
1483
  roadmapsCmd.command("list <slug>").description("List roadmaps for a pebble").option("--status <status>", "Filter by status").option("--all", "Include child roadmaps (default: top-level only)").action(async (slug, opts) => {
1443
1484
  const params = { nesting: opts.all ? "all" : "top" };
1444
1485
  if (opts.status) params.status = opts.status;
@@ -1524,11 +1565,11 @@ clientDraftCmd.command("save <roadmap-id>").description("Save the draft; --file
1524
1565
  });
1525
1566
 
1526
1567
  // src/commands/persona-runs.ts
1527
- import { Command as Command19 } from "commander";
1568
+ import { Command as Command20 } from "commander";
1528
1569
  import { readFileSync as readFileSync15 } from "fs";
1529
1570
  import { join as join3 } from "path";
1530
1571
  import { homedir as homedir2 } from "os";
1531
- var personaRunsCmd = new Command19("persona-runs").description(
1572
+ var personaRunsCmd = new Command20("persona-runs").description(
1532
1573
  "Manage per-persona per-session token usage rows"
1533
1574
  );
1534
1575
  personaRunsCmd.command("list <slug>").description("List persona runs for a pebble").option("--bet-id <id>", "Filter by bet_id").option("--plan-id <id>", "Filter by plan_id").option("--persona <name>", "Filter by persona name").option("--limit <n>", "Max rows", "50").action(async (slug, opts) => {
@@ -1625,9 +1666,9 @@ personaRunsCmd.command("capture").description("Read marker + transcript, compute
1625
1666
  });
1626
1667
 
1627
1668
  // src/commands/blog.ts
1628
- import { Command as Command20 } from "commander";
1669
+ import { Command as Command21 } from "commander";
1629
1670
  import { readFileSync as readFileSync16 } from "fs";
1630
- var blogCmd = new Command20("blog").description("Manage blog posts");
1671
+ var blogCmd = new Command21("blog").description("Manage blog posts");
1631
1672
  blogCmd.command("list <slug>").description("List blog posts for a pebble").option("--status <status>", "Filter by status (draft, review, scheduled, published, archived)").option("--tag <tag>", "Filter by tag").option("--page <page>", "Page number", "1").option("--per-page <perPage>", "Results per page", "20").action(async (slug, opts) => {
1632
1673
  const params = {};
1633
1674
  if (opts.status) params.status = opts.status;
@@ -1692,8 +1733,8 @@ blogCmd.command("delete <slug> <id>").description("Delete a blog post").action(a
1692
1733
  });
1693
1734
 
1694
1735
  // src/commands/api-keys.ts
1695
- import { Command as Command21 } from "commander";
1696
- var apiKeysCmd = new Command21("api-keys").description("Manage API keys for server-to-server access");
1736
+ import { Command as Command22 } from "commander";
1737
+ var apiKeysCmd = new Command22("api-keys").description("Manage API keys for server-to-server access");
1697
1738
  apiKeysCmd.command("list <slug>").description("List API keys for a pebble").action(async (slug) => {
1698
1739
  const res = await apiRequest(`/pebbles/${slug}/api-keys`);
1699
1740
  process.stdout.write(JSON.stringify(res) + "\n");
@@ -1735,7 +1776,7 @@ apiKeysCmd.command("delete <slug> <id>").description("Permanently delete an API
1735
1776
  });
1736
1777
 
1737
1778
  // src/commands/pebble/index.ts
1738
- import { Command as Command22 } from "commander";
1779
+ import { Command as Command23 } from "commander";
1739
1780
 
1740
1781
  // src/commands/pebble/provisioner.ts
1741
1782
  async function runProvisioner(ctx, steps) {
@@ -2148,7 +2189,7 @@ Hand off: /odin:eng-manager bet:${bet.data.id}
2148
2189
  }
2149
2190
 
2150
2191
  // src/commands/pebble/index.ts
2151
- var pebbleCmd = new Command22("pebble").description(
2192
+ var pebbleCmd = new Command23("pebble").description(
2152
2193
  "Provision and initialize pebbles (eng-infra, run-once)"
2153
2194
  );
2154
2195
  pebbleCmd.command("new <name>").description("Provision a new pebble from pebble-template (day-one)").option("--run", "Hand off to eng-manager on the init bet").option("--dir <path>", "Parent dir to clone into").option("--dry-run", "Print the plan; mutate nothing").action(async (name, opts) => {
@@ -2169,7 +2210,7 @@ pebbleCmd.command("provision <provider> <slug>").description("Run one provider s
2169
2210
  });
2170
2211
 
2171
2212
  // src/commands/create/index.ts
2172
- import { Command as Command23 } from "commander";
2213
+ import { Command as Command24 } from "commander";
2173
2214
 
2174
2215
  // src/commands/create/topology.ts
2175
2216
  var ENVS = ["prd", "stg", "dev"];
@@ -2269,9 +2310,9 @@ var tokensStep = {
2269
2310
  const supabaseAccessToken = await io.secret(
2270
2311
  "Supabase Access Token (TF_VAR_SUPABASE_ACCESS_TOKEN):"
2271
2312
  );
2272
- const out = { vercelApiToken, vercelToken, supabaseAccessToken };
2273
- if (vercelTeamId) out.vercelTeamId = vercelTeamId;
2274
- return out;
2313
+ const out2 = { vercelApiToken, vercelToken, supabaseAccessToken };
2314
+ if (vercelTeamId) out2.vercelTeamId = vercelTeamId;
2315
+ return out2;
2275
2316
  }
2276
2317
  };
2277
2318
  var vercelStep2 = {
@@ -2595,17 +2636,17 @@ var ENV_KEY_MAP = {
2595
2636
  SUPABASE_DB_PASSWORD: "supabaseDbPassword"
2596
2637
  };
2597
2638
  function capturedFromDoppler(prd, stg) {
2598
- const out = {};
2639
+ const out2 = {};
2599
2640
  for (const [dopplerKey, capKey] of Object.entries(SHARED_KEY_MAP)) {
2600
- if (prd[dopplerKey]) out[capKey] = prd[dopplerKey];
2641
+ if (prd[dopplerKey]) out2[capKey] = prd[dopplerKey];
2601
2642
  }
2602
- if (prd.TF_VAR_APP_DOMAIN) out.appDomain = prd.TF_VAR_APP_DOMAIN.replace(/^app\./, "");
2643
+ if (prd.TF_VAR_APP_DOMAIN) out2.appDomain = prd.TF_VAR_APP_DOMAIN.replace(/^app\./, "");
2603
2644
  for (const [env, cfg] of [["prd", prd], ["stg", stg]]) {
2604
2645
  for (const [dopplerKey, capKey] of Object.entries(ENV_KEY_MAP)) {
2605
- if (cfg[dopplerKey]) out[`${env}_${capKey}`] = cfg[dopplerKey];
2646
+ if (cfg[dopplerKey]) out2[`${env}_${capKey}`] = cfg[dopplerKey];
2606
2647
  }
2607
2648
  }
2608
- return out;
2649
+ return out2;
2609
2650
  }
2610
2651
  function assembleInputs(ctx) {
2611
2652
  const c = ctx.captured;
@@ -2634,13 +2675,13 @@ function assembleInputs(ctx) {
2634
2675
  }
2635
2676
 
2636
2677
  // src/commands/create/index.ts
2637
- var createCmd = new Command23("create").description(
2678
+ var createCmd = new Command24("create").description(
2638
2679
  "Create and provision a new project end to end"
2639
2680
  );
2640
2681
  createCmd.command("project [name]").description("Guided self-serve project + parallel-env provisioning").option("--dry-run", "Print the plan; mutate nothing").action(
2641
2682
  async (name, opts) => process.exit(await runCreate(name, { dryRun: opts.dryRun }))
2642
2683
  );
2643
- var setupCmd = new Command23("setup").description("Alias for `create project`").arguments("[name]").option("--dry-run", "Print the plan; mutate nothing").action(
2684
+ var setupCmd = new Command24("setup").description("Alias for `create project`").arguments("[name]").option("--dry-run", "Print the plan; mutate nothing").action(
2644
2685
  async (name, opts) => process.exit(await runCreate(name, { dryRun: opts.dryRun }))
2645
2686
  );
2646
2687
 
@@ -2667,7 +2708,7 @@ ${RESET}
2667
2708
  ${DIM}${cwd}${RESET}
2668
2709
  `);
2669
2710
  }
2670
- var program = new Command24();
2711
+ var program = new Command25();
2671
2712
  program.name("odin").description("CLI for Odin \u2014 the knowledge backbone for Pebble House").version(VERSION);
2672
2713
  program.command("login").description("Authenticate with Odin via browser").option("--device", "Use the device-code flow (for headless or remote machines)").action(async (options) => {
2673
2714
  printBanner();
@@ -2696,6 +2737,7 @@ program.addCommand(rulesCmd);
2696
2737
  program.addCommand(briefsCmd);
2697
2738
  program.addCommand(blueprintsCmd);
2698
2739
  program.addCommand(betsCmd);
2740
+ program.addCommand(deliveryCmd);
2699
2741
  program.addCommand(ticketsCmd);
2700
2742
  program.addCommand(betCommentsCmd);
2701
2743
  program.addCommand(tasksCmd);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pebblehouse/odin-cli",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "type": "module",
5
5
  "description": "CLI for Odin — the knowledge backbone for Pebble House",
6
6
  "bin": {