@pebblehouse/odin-cli 0.18.0 → 0.18.1

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 +38 -4
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -384,7 +384,8 @@ async function apiRequest(path, options = {}) {
384
384
  }
385
385
  const headers = {
386
386
  Authorization: `Bearer ${token}`,
387
- "Content-Type": "application/json"
387
+ "Content-Type": "application/json",
388
+ "X-Odin-Client": "cli"
388
389
  };
389
390
  const res = await fetch(url, {
390
391
  method,
@@ -1190,6 +1191,7 @@ blueprintsCmd.command("assets <id>").description("List assets for a blueprint").
1190
1191
  // src/commands/bets.ts
1191
1192
  import { Command as Command14 } from "commander";
1192
1193
  import { readFileSync as readFileSync10 } from "fs";
1194
+ import { randomUUID } from "crypto";
1193
1195
 
1194
1196
  // src/commands/bet-dependencies.ts
1195
1197
  import { Command as Command13 } from "commander";
@@ -1223,8 +1225,9 @@ depsCmd.command("remove <slug> <bet-id>").description("Remove a blocked-by depen
1223
1225
 
1224
1226
  // src/commands/bets.ts
1225
1227
  var betsCmd = new Command14("bets").description("Manage bets");
1228
+ var STAGE_HELP = "pitch, shaping, ready, planning, building, review, qa, shipping, done, archived (legacy: shaped, betting, active, blocked)";
1226
1229
  withListFlags(
1227
- betsCmd.command("list <slug>").description("List bets for a pebble").option("--status <status>", "Filter by status (pitch, shaped, betting, active, blocked, building, done, archived)")
1230
+ betsCmd.command("list <slug>").description("List bets for a pebble").option("--status <status>", `Status (${STAGE_HELP})`)
1228
1231
  ).action(async (slug, opts) => {
1229
1232
  const resolved = resolveListParams(opts);
1230
1233
  if (resolved.fatal) {
@@ -1242,7 +1245,7 @@ betsCmd.command("get <slug> <id>").description("Get a bet by ID").action(async (
1242
1245
  process.stdout.write(JSON.stringify(res) + "\n");
1243
1246
  if (res.error) process.exit(1);
1244
1247
  });
1245
- betsCmd.command("create <slug> <title>").description("Create a bet").option("--source <source>", "Source agent", "claude-code").option("--content <content>", "Bet content").option("--file <path>", "Read content from file").option("--status <status>", "Bet status (pitch, shaped, betting, active, blocked, building, done, archived)").option("--priority <priority>", "Bet priority").option("--appetite <appetite>", "Bet appetite").option("--follow-up-of <key-or-uuid>", "Allocate a suffixed follow-up ticket in this ticket's family (e.g. ODIN-12 \u2192 ODIN-12A)").action(async (slug, title, opts) => {
1248
+ betsCmd.command("create <slug> <title>").description("Create a bet").option("--source <source>", "Source agent", "claude-code").option("--content <content>", "Bet content").option("--file <path>", "Read content from file").option("--status <status>", "pitch (default) or shaped (transitional)").option("--priority <priority>", "Bet priority").option("--appetite <appetite>", "Bet appetite").option("--follow-up-of <key-or-uuid>", "Allocate a suffixed follow-up ticket in this ticket's family (e.g. ODIN-12 \u2192 ODIN-12A)").action(async (slug, title, opts) => {
1246
1249
  const content = opts.file ? readFileSync10(opts.file, "utf-8") : opts.content ?? "";
1247
1250
  const res = await apiRequest(`/pebbles/${slug}/bets`, {
1248
1251
  method: "POST",
@@ -1251,7 +1254,7 @@ betsCmd.command("create <slug> <title>").description("Create a bet").option("--s
1251
1254
  process.stdout.write(JSON.stringify(res) + "\n");
1252
1255
  if (res.error) process.exit(1);
1253
1256
  });
1254
- betsCmd.command("update <slug> <id>").description("Update a bet").option("--title <title>", "New title").option("--content <content>", "New content").option("--file <path>", "Read content from file").option("--status <status>", "New status (pitch, shaped, betting, active, blocked, building, done, archived)").option("--priority <priority>", "New priority").option("--appetite <appetite>", "New appetite").action(async (slug, id, opts) => {
1257
+ betsCmd.command("update <slug> <id>").description("Update a bet").option("--title <title>", "New title").option("--content <content>", "New content").option("--file <path>", "Read content from file").option("--status <status>", `New status \u2014 legacy-governed bets only; lifecycle-governed bets use \`odin bets transition\` (${STAGE_HELP})`).option("--priority <priority>", "New priority").option("--appetite <appetite>", "New appetite").action(async (slug, id, opts) => {
1255
1258
  const body = {};
1256
1259
  if (opts.title) body.title = opts.title;
1257
1260
  if (opts.file) body.content = readFileSync10(opts.file, "utf-8");
@@ -1274,6 +1277,37 @@ betsCmd.command("delete <slug> <id>").description("Delete a bet").action(async (
1274
1277
  if (res.error) process.exit(1);
1275
1278
  });
1276
1279
  betsCmd.addCommand(depsCmd);
1280
+ 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) => {
1281
+ let extra = {};
1282
+ if (opts.payload) {
1283
+ try {
1284
+ extra = JSON.parse(opts.payload);
1285
+ } catch {
1286
+ process.stderr.write("--payload must be valid JSON\n");
1287
+ process.exit(2);
1288
+ }
1289
+ }
1290
+ const payload = { ...opts.to ? { to_stage: opts.to } : {}, ...opts.reason ? { reason: opts.reason } : {}, ...opts.note ? { note: opts.note } : {}, ...extra };
1291
+ const body = { operation: opts.op, expected_revision: Number(opts.expectedRevision), idempotency_key: opts.idempotencyKey ?? randomUUID(), actor_label: opts.actor, actor_kind: opts.actorKind, payload };
1292
+ const res = await apiRequest(`/pebbles/${slug}/bets/${id}/lifecycle`, { method: "POST", body });
1293
+ process.stdout.write(JSON.stringify(res) + "\n");
1294
+ if (res.error) process.exit(1);
1295
+ });
1296
+ betsCmd.command("ready-check <slug> <id>").description("Evaluate Ready prerequisites").action(async (slug, id) => {
1297
+ const res = await apiRequest(`/pebbles/${slug}/bets/${id}/ready-check`);
1298
+ process.stdout.write(JSON.stringify(res) + "\n");
1299
+ if (res.error) process.exit(1);
1300
+ });
1301
+ betsCmd.command("activity <slug> <id>").description("Unified Activity (events + comments), newest first").option("--cursor <cursor>").option("--limit <n>", "1-100", "30").action(async (slug, id, opts) => {
1302
+ const res = await apiRequest(`/pebbles/${slug}/bets/${id}/activity`, { params: { ...opts.cursor ? { cursor: opts.cursor } : {}, limit: opts.limit } });
1303
+ process.stdout.write(JSON.stringify(res) + "\n");
1304
+ if (res.error) process.exit(1);
1305
+ });
1306
+ betsCmd.command("lifecycle-contract").description("Stages, transition matrix, Ready requirements, blocker categories").action(async () => {
1307
+ const res = await apiRequest(`/bet-lifecycle/contract`);
1308
+ process.stdout.write(JSON.stringify(res) + "\n");
1309
+ if (res.error) process.exit(1);
1310
+ });
1277
1311
 
1278
1312
  // src/commands/tickets.ts
1279
1313
  import { Command as Command15 } from "commander";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pebblehouse/odin-cli",
3
- "version": "0.18.0",
3
+ "version": "0.18.1",
4
4
  "type": "module",
5
5
  "description": "CLI for Odin — the knowledge backbone for Pebble House",
6
6
  "bin": {