@pebblehouse/odin-cli 0.18.0 → 0.18.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +42 -9
  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";
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>", `Stage (${STAGE_HELP})`)
1228
1231
  ).action(async (slug, opts) => {
1229
1232
  const resolved = resolveListParams(opts);
1230
1233
  if (resolved.fatal) {
@@ -1242,21 +1245,20 @@ 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("--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",
1249
- body: { title, source: opts.source, content, status: opts.status, priority: opts.priority, appetite: opts.appetite, follow_up_of: opts.followUpOf }
1252
+ body: { title, source: opts.source, content, priority: opts.priority, appetite: opts.appetite, follow_up_of: opts.followUpOf }
1250
1253
  });
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's title, content, priority or appetite (stage changes: odin bets transition)").option("--title <title>", "New title").option("--content <content>", "New content").option("--file <path>", "Read content from file").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");
1258
1261
  else if (opts.content) body.content = opts.content;
1259
- if (opts.status) body.status = opts.status;
1260
1262
  if (opts.priority) body.priority = opts.priority;
1261
1263
  if (opts.appetite) body.appetite = opts.appetite;
1262
1264
  const res = await apiRequest(`/pebbles/${slug}/bets/${id}`, {
@@ -1274,6 +1276,37 @@ betsCmd.command("delete <slug> <id>").description("Delete a bet").action(async (
1274
1276
  if (res.error) process.exit(1);
1275
1277
  });
1276
1278
  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) => {
1280
+ let extra = {};
1281
+ if (opts.payload) {
1282
+ try {
1283
+ extra = JSON.parse(opts.payload);
1284
+ } catch {
1285
+ process.stderr.write("--payload must be valid JSON\n");
1286
+ process.exit(2);
1287
+ }
1288
+ }
1289
+ 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 };
1291
+ const res = await apiRequest(`/pebbles/${slug}/bets/${id}/lifecycle`, { method: "POST", body });
1292
+ process.stdout.write(JSON.stringify(res) + "\n");
1293
+ if (res.error) process.exit(1);
1294
+ });
1295
+ betsCmd.command("ready-check <slug> <id>").description("Evaluate Ready prerequisites").action(async (slug, id) => {
1296
+ const res = await apiRequest(`/pebbles/${slug}/bets/${id}/ready-check`);
1297
+ process.stdout.write(JSON.stringify(res) + "\n");
1298
+ if (res.error) process.exit(1);
1299
+ });
1300
+ 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) => {
1301
+ const res = await apiRequest(`/pebbles/${slug}/bets/${id}/activity`, { params: { ...opts.cursor ? { cursor: opts.cursor } : {}, limit: opts.limit } });
1302
+ process.stdout.write(JSON.stringify(res) + "\n");
1303
+ if (res.error) process.exit(1);
1304
+ });
1305
+ betsCmd.command("lifecycle-contract").description("Stages, transition matrix, Ready requirements, blocker categories").action(async () => {
1306
+ const res = await apiRequest(`/bet-lifecycle/contract`);
1307
+ process.stdout.write(JSON.stringify(res) + "\n");
1308
+ if (res.error) process.exit(1);
1309
+ });
1277
1310
 
1278
1311
  // src/commands/tickets.ts
1279
1312
  import { Command as Command15 } from "commander";
@@ -1364,11 +1397,11 @@ tasksCmd.command("update <slug> <id>").description("Update a task").option("--ti
1364
1397
  process.stdout.write(JSON.stringify(res) + "\n");
1365
1398
  if (res.error) process.exit(1);
1366
1399
  });
1367
- tasksCmd.command("promote <slug> <id>").description("Promote a task to a bet, keeping its ticket ID and history").option("--content <content>", "Shaped bet markdown").option("--file <path>", "Read bet markdown from file").option("--status <status>", "Bet status (pitch | shaped)", "shaped").option("--priority <priority>", "Bet priority").action(async (slug, id, opts) => {
1400
+ tasksCmd.command("promote <slug> <id>").description("Promote a task to a bet, keeping its ticket ID and history (the bet starts in pitch)").option("--content <content>", "Shaped bet markdown").option("--file <path>", "Read bet markdown from file").option("--priority <priority>", "Bet priority").action(async (slug, id, opts) => {
1368
1401
  const content = opts.file ? readFileSync12(opts.file, "utf-8") : opts.content;
1369
1402
  const res = await apiRequest(`/pebbles/${slug}/tasks/${id}/promote`, {
1370
1403
  method: "POST",
1371
- body: { content, status: opts.status, priority: opts.priority }
1404
+ body: { content, priority: opts.priority }
1372
1405
  });
1373
1406
  process.stdout.write(JSON.stringify(res) + "\n");
1374
1407
  if (res.error) process.exit(1);
@@ -2040,7 +2073,7 @@ Re-run \`odin pebble new ${name}\` after acting.
2040
2073
  const body = renderInitBetBody(slug, report);
2041
2074
  const bet = await apiRequest(`/pebbles/${slug}/bets`, {
2042
2075
  method: "POST",
2043
- body: { title: `Initialize ${slug}`, source: "odin-cli", content: body, status: "shaped" }
2076
+ body: { title: `Initialize ${slug}`, source: "odin-cli", content: body }
2044
2077
  });
2045
2078
  process.stdout.write(JSON.stringify(bet) + "\n");
2046
2079
  if (opts.run && bet.data?.id) {
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.2",
4
4
  "type": "module",
5
5
  "description": "CLI for Odin — the knowledge backbone for Pebble House",
6
6
  "bin": {