@pebblehouse/odin-cli 0.18.3 → 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.
- package/README.md +9 -0
- package/dist/index.js +142 -49
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,6 +73,15 @@ odin tasks promote <slug> <task-id> --file ./bet.md # Promote a task to a bet
|
|
|
73
73
|
odin tasks create <slug> "Fix" --follow-up-of ODIN-12 # Allocate ODIN-12A as a follow-up
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
### Roadmaps
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
odin roadmaps client-draft get <roadmap-id> [--pebble <slug>]
|
|
80
|
+
odin roadmaps client-draft save <roadmap-id> --expected-revision <n> --file <payload.json> [--pebble <slug>]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Reads or saves the client-page draft of a master roadmap. `--pebble` defaults to the nearest `CLAUDE.md` `pebble:` line. Requires `odin login` as Kenny; API keys get `dashboard_approval_required`. Prints the `{ data, error, issues? }` JSON envelope and exits 1 on any error.
|
|
84
|
+
|
|
76
85
|
### Auth
|
|
77
86
|
|
|
78
87
|
```bash
|
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
|
|
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 = {
|
|
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/
|
|
1329
|
+
// src/commands/delivery.ts
|
|
1312
1330
|
import { Command as Command15 } from "commander";
|
|
1313
|
-
var
|
|
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
|
|
1367
|
+
import { Command as Command17 } from "commander";
|
|
1327
1368
|
import { readFileSync as readFileSync11 } from "fs";
|
|
1328
|
-
var betCommentsCmd = new
|
|
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
|
|
1396
|
+
import { Command as Command18 } from "commander";
|
|
1356
1397
|
import { readFileSync as readFileSync12 } from "fs";
|
|
1357
|
-
var tasksCmd = new
|
|
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,9 +1456,30 @@ 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
|
|
1419
|
-
import { readFileSync as
|
|
1420
|
-
|
|
1459
|
+
import { Command as Command19 } from "commander";
|
|
1460
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
1461
|
+
|
|
1462
|
+
// src/resolve-pebble.ts
|
|
1463
|
+
import { existsSync as existsSync3, readFileSync as readFileSync13 } from "fs";
|
|
1464
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
1465
|
+
var PEBBLE_LINE = /^pebble:\s*([a-z0-9][a-z0-9-]*)\s*$/m;
|
|
1466
|
+
function resolvePebbleSlug(explicit, cwd = process.cwd()) {
|
|
1467
|
+
if (explicit) return explicit;
|
|
1468
|
+
let dir = cwd;
|
|
1469
|
+
for (; ; ) {
|
|
1470
|
+
const file = join2(dir, "CLAUDE.md");
|
|
1471
|
+
if (existsSync3(file)) {
|
|
1472
|
+
const m = readFileSync13(file, "utf-8").match(PEBBLE_LINE);
|
|
1473
|
+
if (m) return m[1];
|
|
1474
|
+
}
|
|
1475
|
+
const parent = dirname2(dir);
|
|
1476
|
+
if (parent === dir) return null;
|
|
1477
|
+
dir = parent;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
// src/commands/roadmaps.ts
|
|
1482
|
+
var roadmapsCmd = new Command19("roadmaps").description("Manage roadmaps");
|
|
1421
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) => {
|
|
1422
1484
|
const params = { nesting: opts.all ? "all" : "top" };
|
|
1423
1485
|
if (opts.status) params.status = opts.status;
|
|
@@ -1431,7 +1493,7 @@ roadmapsCmd.command("get <slug> <id>").description("Get a roadmap by ID (include
|
|
|
1431
1493
|
if (res.error) process.exit(1);
|
|
1432
1494
|
});
|
|
1433
1495
|
roadmapsCmd.command("create <slug> <title>").description("Create a roadmap").option("--source <source>", "Source agent", "claude-code").option("--content <content>", "Roadmap content").option("--file <path>", "Read content from file").option("--status <status>", "Roadmap status").option("--parent <id>", "Parent roadmap UUID (nesting is capped at 2 levels)").option("--starts-on <date>", "Start date, YYYY-MM-DD").option("--ends-on <date>", "End date, YYYY-MM-DD").action(async (slug, title, opts) => {
|
|
1434
|
-
const content = opts.file ?
|
|
1496
|
+
const content = opts.file ? readFileSync14(opts.file, "utf-8") : opts.content ?? "";
|
|
1435
1497
|
const res = await apiRequest(`/pebbles/${slug}/roadmaps`, {
|
|
1436
1498
|
method: "POST",
|
|
1437
1499
|
body: {
|
|
@@ -1450,7 +1512,7 @@ roadmapsCmd.command("create <slug> <title>").description("Create a roadmap").opt
|
|
|
1450
1512
|
roadmapsCmd.command("update <slug> <id>").description("Update a roadmap").option("--title <title>", "New title").option("--content <content>", "New content").option("--file <path>", "Read content from file").option("--status <status>", "New status").option("--parent <id>", 'New parent roadmap UUID, or the literal "null" to promote to top level').option("--starts-on <date>", 'New start date YYYY-MM-DD, or the literal "null" to clear').option("--ends-on <date>", 'New end date YYYY-MM-DD, or the literal "null" to clear').action(async (slug, id, opts) => {
|
|
1451
1513
|
const body = {};
|
|
1452
1514
|
if (opts.title) body.title = opts.title;
|
|
1453
|
-
if (opts.file) body.content =
|
|
1515
|
+
if (opts.file) body.content = readFileSync14(opts.file, "utf-8");
|
|
1454
1516
|
else if (opts.content) body.content = opts.content;
|
|
1455
1517
|
if (opts.status) body.status = opts.status;
|
|
1456
1518
|
const nullable = (v) => v === void 0 ? void 0 : v === "null" ? null : v;
|
|
@@ -1471,13 +1533,43 @@ roadmapsCmd.command("delete <slug> <id>").description("Delete a roadmap").action
|
|
|
1471
1533
|
process.stdout.write(JSON.stringify(res) + "\n");
|
|
1472
1534
|
if (res.error) process.exit(1);
|
|
1473
1535
|
});
|
|
1536
|
+
function emit(res, failed) {
|
|
1537
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
1538
|
+
if (failed) process.exit(1);
|
|
1539
|
+
}
|
|
1540
|
+
function draftPath(pebble, roadmapId) {
|
|
1541
|
+
const slug = resolvePebbleSlug(pebble);
|
|
1542
|
+
return slug ? `/pebbles/${slug}/roadmaps/${roadmapId}/client-page/draft` : null;
|
|
1543
|
+
}
|
|
1544
|
+
var clientDraftCmd = roadmapsCmd.command("client-draft").description("Read or save the client-page draft of a master roadmap (Kenny's session only; draft only)");
|
|
1545
|
+
clientDraftCmd.command("get <roadmap-id>").description("Print the draft as { data: { revision, payload } | null, error }").option("--pebble <slug>", "Pebble slug (default: nearest CLAUDE.md `pebble:` line)").action(async (roadmapId, opts) => {
|
|
1546
|
+
const path = draftPath(opts.pebble, roadmapId);
|
|
1547
|
+
if (!path) return emit({ data: null, error: "pebble_unresolved" }, true);
|
|
1548
|
+
const res = await apiRequest(path);
|
|
1549
|
+
emit(res, Boolean(res.error));
|
|
1550
|
+
});
|
|
1551
|
+
clientDraftCmd.command("save <roadmap-id>").description("Save the draft; --file holds the payload only. Prints { data, error, issues? }").requiredOption("--expected-revision <n>", "Revision you read (0 creates the first draft)").requiredOption("--file <path>", "JSON file containing the RoadmapClientPayload").option("--pebble <slug>", "Pebble slug (default: nearest CLAUDE.md `pebble:` line)").action(async (roadmapId, opts) => {
|
|
1552
|
+
const path = draftPath(opts.pebble, roadmapId);
|
|
1553
|
+
if (!path) return emit({ data: null, error: "pebble_unresolved" }, true);
|
|
1554
|
+
if (!/^\d+$/.test(opts.expectedRevision) || !Number.isSafeInteger(Number(opts.expectedRevision))) {
|
|
1555
|
+
return emit({ data: null, error: "invalid_payload", issues: [{ path: "expectedRevision", message: "must be a non-negative integer" }] }, true);
|
|
1556
|
+
}
|
|
1557
|
+
let payload;
|
|
1558
|
+
try {
|
|
1559
|
+
payload = JSON.parse(readFileSync14(opts.file, "utf-8"));
|
|
1560
|
+
} catch (e) {
|
|
1561
|
+
return emit({ data: null, error: "invalid_payload", issues: [{ path: "(root)", message: `payload file is not valid JSON: ${e.message}` }] }, true);
|
|
1562
|
+
}
|
|
1563
|
+
const res = await apiRequest(path, { method: "PUT", body: { expectedRevision: Number(opts.expectedRevision), payload } });
|
|
1564
|
+
emit(res, Boolean(res.error));
|
|
1565
|
+
});
|
|
1474
1566
|
|
|
1475
1567
|
// src/commands/persona-runs.ts
|
|
1476
|
-
import { Command as
|
|
1477
|
-
import { readFileSync as
|
|
1478
|
-
import { join as
|
|
1568
|
+
import { Command as Command20 } from "commander";
|
|
1569
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
1570
|
+
import { join as join3 } from "path";
|
|
1479
1571
|
import { homedir as homedir2 } from "os";
|
|
1480
|
-
var personaRunsCmd = new
|
|
1572
|
+
var personaRunsCmd = new Command20("persona-runs").description(
|
|
1481
1573
|
"Manage per-persona per-session token usage rows"
|
|
1482
1574
|
);
|
|
1483
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) => {
|
|
@@ -1501,7 +1593,7 @@ personaRunsCmd.command("get <slug> <id>").description("Get a persona run by id")
|
|
|
1501
1593
|
if (res.error) process.exit(1);
|
|
1502
1594
|
});
|
|
1503
1595
|
personaRunsCmd.command("create <slug>").description("Create/upsert a persona run from a JSON file").requiredOption("--file <path>", "JSON payload matching PersonaRunCreate").action(async (slug, opts) => {
|
|
1504
|
-
const body = JSON.parse(
|
|
1596
|
+
const body = JSON.parse(readFileSync15(opts.file, "utf-8"));
|
|
1505
1597
|
const res = await apiRequest(`/pebbles/${slug}/persona-runs`, {
|
|
1506
1598
|
method: "POST",
|
|
1507
1599
|
body
|
|
@@ -1510,10 +1602,10 @@ personaRunsCmd.command("create <slug>").description("Create/upsert a persona run
|
|
|
1510
1602
|
if (res.error) process.exit(1);
|
|
1511
1603
|
});
|
|
1512
1604
|
personaRunsCmd.command("capture").description("Read marker + transcript, compute tokens, POST to endpoint (hook path)").requiredOption("--session-id <id>", "Claude Code session id").requiredOption("--transcript <path>", "Path to transcript JSONL").option("--marker <path>", "Explicit marker path (default: ~/.claude/odin-context/<session-id>.json)").action(async (opts) => {
|
|
1513
|
-
const markerPath = opts.marker ??
|
|
1605
|
+
const markerPath = opts.marker ?? join3(homedir2(), ".claude", "odin-context", `${opts.sessionId}.json`);
|
|
1514
1606
|
let marker = {};
|
|
1515
1607
|
try {
|
|
1516
|
-
marker = JSON.parse(
|
|
1608
|
+
marker = JSON.parse(readFileSync15(markerPath, "utf-8"));
|
|
1517
1609
|
} catch {
|
|
1518
1610
|
process.exit(0);
|
|
1519
1611
|
}
|
|
@@ -1523,7 +1615,7 @@ personaRunsCmd.command("capture").description("Read marker + transcript, compute
|
|
|
1523
1615
|
let input = 0, output = 0, cacheRead = 0, cacheCreate = 0;
|
|
1524
1616
|
let minTs = null, maxTs = null;
|
|
1525
1617
|
try {
|
|
1526
|
-
const raw =
|
|
1618
|
+
const raw = readFileSync15(opts.transcript, "utf-8");
|
|
1527
1619
|
for (const line of raw.split("\n")) {
|
|
1528
1620
|
if (!line.trim()) continue;
|
|
1529
1621
|
let obj;
|
|
@@ -1574,9 +1666,9 @@ personaRunsCmd.command("capture").description("Read marker + transcript, compute
|
|
|
1574
1666
|
});
|
|
1575
1667
|
|
|
1576
1668
|
// src/commands/blog.ts
|
|
1577
|
-
import { Command as
|
|
1578
|
-
import { readFileSync as
|
|
1579
|
-
var blogCmd = new
|
|
1669
|
+
import { Command as Command21 } from "commander";
|
|
1670
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
1671
|
+
var blogCmd = new Command21("blog").description("Manage blog posts");
|
|
1580
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) => {
|
|
1581
1673
|
const params = {};
|
|
1582
1674
|
if (opts.status) params.status = opts.status;
|
|
@@ -1595,7 +1687,7 @@ blogCmd.command("get <slug> <idOrSlug>").description("Get a blog post by UUID or
|
|
|
1595
1687
|
blogCmd.command("create <slug> <title>").description("Create a blog post").requiredOption("--post-slug <postSlug>", "URL slug for the post").option("--blocks <blocks>", "Content blocks as JSON string").option("--file <path>", "Read content blocks from JSON file").option("--status <status>", "Initial status (draft, review, scheduled)", "draft").option("--scheduled-publish-at <iso>", "UTC ISO timestamp for scheduled publishing (required when --status scheduled)").option("--metadata <metadata>", "Post metadata as JSON string").option("--campaign-id <campaignId>", "Link to a marketing campaign").action(async (slug, title, opts) => {
|
|
1596
1688
|
let blocks = [];
|
|
1597
1689
|
if (opts.file) {
|
|
1598
|
-
blocks = JSON.parse(
|
|
1690
|
+
blocks = JSON.parse(readFileSync16(opts.file, "utf-8"));
|
|
1599
1691
|
} else if (opts.blocks) {
|
|
1600
1692
|
blocks = JSON.parse(opts.blocks);
|
|
1601
1693
|
}
|
|
@@ -1619,7 +1711,7 @@ blogCmd.command("update <slug> <id>").description("Update a blog post").option("
|
|
|
1619
1711
|
const body = {};
|
|
1620
1712
|
if (opts.title) body.title = opts.title;
|
|
1621
1713
|
if (opts.postSlug) body.slug = opts.postSlug;
|
|
1622
|
-
if (opts.file) body.blocks = JSON.parse(
|
|
1714
|
+
if (opts.file) body.blocks = JSON.parse(readFileSync16(opts.file, "utf-8"));
|
|
1623
1715
|
else if (opts.blocks) body.blocks = JSON.parse(opts.blocks);
|
|
1624
1716
|
if (opts.status) body.status = opts.status;
|
|
1625
1717
|
if (opts.scheduledPublishAt !== void 0)
|
|
@@ -1641,8 +1733,8 @@ blogCmd.command("delete <slug> <id>").description("Delete a blog post").action(a
|
|
|
1641
1733
|
});
|
|
1642
1734
|
|
|
1643
1735
|
// src/commands/api-keys.ts
|
|
1644
|
-
import { Command as
|
|
1645
|
-
var apiKeysCmd = new
|
|
1736
|
+
import { Command as Command22 } from "commander";
|
|
1737
|
+
var apiKeysCmd = new Command22("api-keys").description("Manage API keys for server-to-server access");
|
|
1646
1738
|
apiKeysCmd.command("list <slug>").description("List API keys for a pebble").action(async (slug) => {
|
|
1647
1739
|
const res = await apiRequest(`/pebbles/${slug}/api-keys`);
|
|
1648
1740
|
process.stdout.write(JSON.stringify(res) + "\n");
|
|
@@ -1684,7 +1776,7 @@ apiKeysCmd.command("delete <slug> <id>").description("Permanently delete an API
|
|
|
1684
1776
|
});
|
|
1685
1777
|
|
|
1686
1778
|
// src/commands/pebble/index.ts
|
|
1687
|
-
import { Command as
|
|
1779
|
+
import { Command as Command23 } from "commander";
|
|
1688
1780
|
|
|
1689
1781
|
// src/commands/pebble/provisioner.ts
|
|
1690
1782
|
async function runProvisioner(ctx, steps) {
|
|
@@ -2097,7 +2189,7 @@ Hand off: /odin:eng-manager bet:${bet.data.id}
|
|
|
2097
2189
|
}
|
|
2098
2190
|
|
|
2099
2191
|
// src/commands/pebble/index.ts
|
|
2100
|
-
var pebbleCmd = new
|
|
2192
|
+
var pebbleCmd = new Command23("pebble").description(
|
|
2101
2193
|
"Provision and initialize pebbles (eng-infra, run-once)"
|
|
2102
2194
|
);
|
|
2103
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) => {
|
|
@@ -2118,7 +2210,7 @@ pebbleCmd.command("provision <provider> <slug>").description("Run one provider s
|
|
|
2118
2210
|
});
|
|
2119
2211
|
|
|
2120
2212
|
// src/commands/create/index.ts
|
|
2121
|
-
import { Command as
|
|
2213
|
+
import { Command as Command24 } from "commander";
|
|
2122
2214
|
|
|
2123
2215
|
// src/commands/create/topology.ts
|
|
2124
2216
|
var ENVS = ["prd", "stg", "dev"];
|
|
@@ -2218,9 +2310,9 @@ var tokensStep = {
|
|
|
2218
2310
|
const supabaseAccessToken = await io.secret(
|
|
2219
2311
|
"Supabase Access Token (TF_VAR_SUPABASE_ACCESS_TOKEN):"
|
|
2220
2312
|
);
|
|
2221
|
-
const
|
|
2222
|
-
if (vercelTeamId)
|
|
2223
|
-
return
|
|
2313
|
+
const out2 = { vercelApiToken, vercelToken, supabaseAccessToken };
|
|
2314
|
+
if (vercelTeamId) out2.vercelTeamId = vercelTeamId;
|
|
2315
|
+
return out2;
|
|
2224
2316
|
}
|
|
2225
2317
|
};
|
|
2226
2318
|
var vercelStep2 = {
|
|
@@ -2544,17 +2636,17 @@ var ENV_KEY_MAP = {
|
|
|
2544
2636
|
SUPABASE_DB_PASSWORD: "supabaseDbPassword"
|
|
2545
2637
|
};
|
|
2546
2638
|
function capturedFromDoppler(prd, stg) {
|
|
2547
|
-
const
|
|
2639
|
+
const out2 = {};
|
|
2548
2640
|
for (const [dopplerKey, capKey] of Object.entries(SHARED_KEY_MAP)) {
|
|
2549
|
-
if (prd[dopplerKey])
|
|
2641
|
+
if (prd[dopplerKey]) out2[capKey] = prd[dopplerKey];
|
|
2550
2642
|
}
|
|
2551
|
-
if (prd.TF_VAR_APP_DOMAIN)
|
|
2643
|
+
if (prd.TF_VAR_APP_DOMAIN) out2.appDomain = prd.TF_VAR_APP_DOMAIN.replace(/^app\./, "");
|
|
2552
2644
|
for (const [env, cfg] of [["prd", prd], ["stg", stg]]) {
|
|
2553
2645
|
for (const [dopplerKey, capKey] of Object.entries(ENV_KEY_MAP)) {
|
|
2554
|
-
if (cfg[dopplerKey])
|
|
2646
|
+
if (cfg[dopplerKey]) out2[`${env}_${capKey}`] = cfg[dopplerKey];
|
|
2555
2647
|
}
|
|
2556
2648
|
}
|
|
2557
|
-
return
|
|
2649
|
+
return out2;
|
|
2558
2650
|
}
|
|
2559
2651
|
function assembleInputs(ctx) {
|
|
2560
2652
|
const c = ctx.captured;
|
|
@@ -2583,22 +2675,22 @@ function assembleInputs(ctx) {
|
|
|
2583
2675
|
}
|
|
2584
2676
|
|
|
2585
2677
|
// src/commands/create/index.ts
|
|
2586
|
-
var createCmd = new
|
|
2678
|
+
var createCmd = new Command24("create").description(
|
|
2587
2679
|
"Create and provision a new project end to end"
|
|
2588
2680
|
);
|
|
2589
2681
|
createCmd.command("project [name]").description("Guided self-serve project + parallel-env provisioning").option("--dry-run", "Print the plan; mutate nothing").action(
|
|
2590
2682
|
async (name, opts) => process.exit(await runCreate(name, { dryRun: opts.dryRun }))
|
|
2591
2683
|
);
|
|
2592
|
-
var setupCmd = new
|
|
2684
|
+
var setupCmd = new Command24("setup").description("Alias for `create project`").arguments("[name]").option("--dry-run", "Print the plan; mutate nothing").action(
|
|
2593
2685
|
async (name, opts) => process.exit(await runCreate(name, { dryRun: opts.dryRun }))
|
|
2594
2686
|
);
|
|
2595
2687
|
|
|
2596
2688
|
// src/index.ts
|
|
2597
|
-
import { readFileSync as
|
|
2689
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
2598
2690
|
import { fileURLToPath } from "url";
|
|
2599
|
-
import { dirname as
|
|
2600
|
-
var __dirname =
|
|
2601
|
-
var pkg = JSON.parse(
|
|
2691
|
+
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
2692
|
+
var __dirname = dirname3(fileURLToPath(import.meta.url));
|
|
2693
|
+
var pkg = JSON.parse(readFileSync17(resolve2(__dirname, "../package.json"), "utf-8"));
|
|
2602
2694
|
var GOLD = "\x1B[33m";
|
|
2603
2695
|
var DIM = "\x1B[2m";
|
|
2604
2696
|
var RESET = "\x1B[0m";
|
|
@@ -2616,7 +2708,7 @@ ${RESET}
|
|
|
2616
2708
|
${DIM}${cwd}${RESET}
|
|
2617
2709
|
`);
|
|
2618
2710
|
}
|
|
2619
|
-
var program = new
|
|
2711
|
+
var program = new Command25();
|
|
2620
2712
|
program.name("odin").description("CLI for Odin \u2014 the knowledge backbone for Pebble House").version(VERSION);
|
|
2621
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) => {
|
|
2622
2714
|
printBanner();
|
|
@@ -2645,6 +2737,7 @@ program.addCommand(rulesCmd);
|
|
|
2645
2737
|
program.addCommand(briefsCmd);
|
|
2646
2738
|
program.addCommand(blueprintsCmd);
|
|
2647
2739
|
program.addCommand(betsCmd);
|
|
2740
|
+
program.addCommand(deliveryCmd);
|
|
2648
2741
|
program.addCommand(ticketsCmd);
|
|
2649
2742
|
program.addCommand(betCommentsCmd);
|
|
2650
2743
|
program.addCommand(tasksCmd);
|