@pebblehouse/odin-cli 0.17.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.
- package/README.md +8 -0
- package/dist/index.js +99 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,6 +65,14 @@ odin search "authentication" # Cross-pebble
|
|
|
65
65
|
odin search "RLS policies" --pebble odin # Scoped
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
### Tickets
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
odin tickets resolve ODIN-12 # Resolve a ticket key to its bet or task
|
|
72
|
+
odin tasks promote <slug> <task-id> --file ./bet.md # Promote a task to a bet (keeps its ticket ID)
|
|
73
|
+
odin tasks create <slug> "Fix" --follow-up-of ODIN-12 # Allocate ODIN-12A as a follow-up
|
|
74
|
+
```
|
|
75
|
+
|
|
68
76
|
### Auth
|
|
69
77
|
|
|
70
78
|
```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 Command24 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/auth/login.ts
|
|
7
7
|
import open from "open";
|
|
@@ -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,
|
|
@@ -407,10 +408,10 @@ pebblesCmd.command("get <slug>").description("Get pebble details").action(async
|
|
|
407
408
|
process.stdout.write(JSON.stringify(res) + "\n");
|
|
408
409
|
if (res.error) process.exit(1);
|
|
409
410
|
});
|
|
410
|
-
pebblesCmd.command("create <slug>").description("Create a pebble").requiredOption("--name <name>", "Pebble name").option("--status <status>", "Initial status", "idea").option("--summary <summary>", "Summary").action(async (slug, opts) => {
|
|
411
|
+
pebblesCmd.command("create <slug>").description("Create a pebble").requiredOption("--name <name>", "Pebble name").option("--status <status>", "Initial status", "idea").option("--summary <summary>", "Summary").option("--ticket-prefix <PREFIX>", "Four uppercase letters for ticket IDs (derived from slug when omitted)").action(async (slug, opts) => {
|
|
411
412
|
const res = await apiRequest("/pebbles", {
|
|
412
413
|
method: "POST",
|
|
413
|
-
body: { slug, name: opts.name, status: opts.status, summary: opts.summary }
|
|
414
|
+
body: { slug, name: opts.name, status: opts.status, summary: opts.summary, ticket_prefix: opts.ticketPrefix }
|
|
414
415
|
});
|
|
415
416
|
process.stdout.write(JSON.stringify(res) + "\n");
|
|
416
417
|
if (res.error) process.exit(1);
|
|
@@ -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>",
|
|
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,16 +1245,16 @@ 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>", "
|
|
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",
|
|
1249
|
-
body: { title, source: opts.source, content, status: opts.status, priority: opts.priority, appetite: opts.appetite }
|
|
1252
|
+
body: { title, source: opts.source, content, status: opts.status, 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>",
|
|
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,11 +1277,56 @@ 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
|
-
// src/commands/
|
|
1312
|
+
// src/commands/tickets.ts
|
|
1279
1313
|
import { Command as Command15 } from "commander";
|
|
1314
|
+
var ticketsCmd = new Command15("tickets").description("Resolve human-readable ticket IDs");
|
|
1315
|
+
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) => {
|
|
1316
|
+
const res = await apiRequest(`/tickets/${encodeURIComponent(key.trim().toUpperCase())}`);
|
|
1317
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
1318
|
+
if (res.data?.legacy_ambiguous) {
|
|
1319
|
+
const others = res.data.candidates.filter((c) => c.entity_id !== res.data.entity_id);
|
|
1320
|
+
process.stderr.write(`note: ${key.toUpperCase()} was a duplicated legacy label; also matches ${others.map((c) => c.ticket_key ?? c.entity_id).join(", ")}
|
|
1321
|
+
`);
|
|
1322
|
+
}
|
|
1323
|
+
if (res.error) process.exit(1);
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
// src/commands/bet-comments.ts
|
|
1327
|
+
import { Command as Command16 } from "commander";
|
|
1280
1328
|
import { readFileSync as readFileSync11 } from "fs";
|
|
1281
|
-
var betCommentsCmd = new
|
|
1329
|
+
var betCommentsCmd = new Command16("bet-comments").description("Manage bet comments");
|
|
1282
1330
|
betCommentsCmd.command("list <slug> <betId>").description("List comments for a bet (oldest first)").action(async (slug, betId) => {
|
|
1283
1331
|
const res = await apiRequest(`/pebbles/${slug}/bets/${betId}/comments`);
|
|
1284
1332
|
process.stdout.write(JSON.stringify(res) + "\n");
|
|
@@ -1305,8 +1353,9 @@ betCommentsCmd.command("create <slug> <betId> [body]").description("Post a comme
|
|
|
1305
1353
|
});
|
|
1306
1354
|
|
|
1307
1355
|
// src/commands/tasks.ts
|
|
1308
|
-
import { Command as
|
|
1309
|
-
|
|
1356
|
+
import { Command as Command17 } from "commander";
|
|
1357
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
1358
|
+
var tasksCmd = new Command17("tasks").description("Manage tasks");
|
|
1310
1359
|
withListFlags(
|
|
1311
1360
|
tasksCmd.command("list <slug>").description("List tasks for a pebble").option("--status <status>", "Filter by status").option("--bet <bet-id>", "Filter by bet ID")
|
|
1312
1361
|
).action(async (slug, opts) => {
|
|
@@ -1327,10 +1376,10 @@ tasksCmd.command("get <slug> <id>").description("Get a task by ID").action(async
|
|
|
1327
1376
|
process.stdout.write(JSON.stringify(res) + "\n");
|
|
1328
1377
|
if (res.error) process.exit(1);
|
|
1329
1378
|
});
|
|
1330
|
-
tasksCmd.command("create <slug> <title>").description("Create a task").option("--source <source>", "Source agent", "claude-code").option("--description <description>", "Task description").option("--bet <bet-id>", "Parent bet ID").option("--status <status>", "Task status").option("--priority <priority>", "Task priority").action(async (slug, title, opts) => {
|
|
1379
|
+
tasksCmd.command("create <slug> <title>").description("Create a task").option("--source <source>", "Source agent", "claude-code").option("--description <description>", "Task description").option("--bet <bet-id>", "Parent bet ID").option("--status <status>", "Task status").option("--priority <priority>", "Task priority").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) => {
|
|
1331
1380
|
const res = await apiRequest(`/pebbles/${slug}/tasks`, {
|
|
1332
1381
|
method: "POST",
|
|
1333
|
-
body: { title, source: opts.source, description: opts.description, bet_id: opts.bet, status: opts.status, priority: opts.priority }
|
|
1382
|
+
body: { title, source: opts.source, description: opts.description, bet_id: opts.bet, status: opts.status, priority: opts.priority, follow_up_of: opts.followUpOf }
|
|
1334
1383
|
});
|
|
1335
1384
|
process.stdout.write(JSON.stringify(res) + "\n");
|
|
1336
1385
|
if (res.error) process.exit(1);
|
|
@@ -1349,6 +1398,15 @@ tasksCmd.command("update <slug> <id>").description("Update a task").option("--ti
|
|
|
1349
1398
|
process.stdout.write(JSON.stringify(res) + "\n");
|
|
1350
1399
|
if (res.error) process.exit(1);
|
|
1351
1400
|
});
|
|
1401
|
+
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) => {
|
|
1402
|
+
const content = opts.file ? readFileSync12(opts.file, "utf-8") : opts.content;
|
|
1403
|
+
const res = await apiRequest(`/pebbles/${slug}/tasks/${id}/promote`, {
|
|
1404
|
+
method: "POST",
|
|
1405
|
+
body: { content, status: opts.status, priority: opts.priority }
|
|
1406
|
+
});
|
|
1407
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
1408
|
+
if (res.error) process.exit(1);
|
|
1409
|
+
});
|
|
1352
1410
|
tasksCmd.command("delete <slug> <id>").description("Delete a task").action(async (slug, id) => {
|
|
1353
1411
|
const res = await apiRequest(`/pebbles/${slug}/tasks/${id}`, {
|
|
1354
1412
|
method: "DELETE"
|
|
@@ -1358,9 +1416,9 @@ tasksCmd.command("delete <slug> <id>").description("Delete a task").action(async
|
|
|
1358
1416
|
});
|
|
1359
1417
|
|
|
1360
1418
|
// src/commands/roadmaps.ts
|
|
1361
|
-
import { Command as
|
|
1362
|
-
import { readFileSync as
|
|
1363
|
-
var roadmapsCmd = new
|
|
1419
|
+
import { Command as Command18 } from "commander";
|
|
1420
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
1421
|
+
var roadmapsCmd = new Command18("roadmaps").description("Manage roadmaps");
|
|
1364
1422
|
roadmapsCmd.command("list <slug>").description("List roadmaps for a pebble").option("--status <status>", "Filter by status").action(async (slug, opts) => {
|
|
1365
1423
|
const params = {};
|
|
1366
1424
|
if (opts.status) params.status = opts.status;
|
|
@@ -1374,7 +1432,7 @@ roadmapsCmd.command("get <slug> <id>").description("Get a roadmap by ID (include
|
|
|
1374
1432
|
if (res.error) process.exit(1);
|
|
1375
1433
|
});
|
|
1376
1434
|
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").action(async (slug, title, opts) => {
|
|
1377
|
-
const content = opts.file ?
|
|
1435
|
+
const content = opts.file ? readFileSync13(opts.file, "utf-8") : opts.content ?? "";
|
|
1378
1436
|
const res = await apiRequest(`/pebbles/${slug}/roadmaps`, {
|
|
1379
1437
|
method: "POST",
|
|
1380
1438
|
body: { title, source: opts.source, content, status: opts.status }
|
|
@@ -1385,7 +1443,7 @@ roadmapsCmd.command("create <slug> <title>").description("Create a roadmap").opt
|
|
|
1385
1443
|
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").action(async (slug, id, opts) => {
|
|
1386
1444
|
const body = {};
|
|
1387
1445
|
if (opts.title) body.title = opts.title;
|
|
1388
|
-
if (opts.file) body.content =
|
|
1446
|
+
if (opts.file) body.content = readFileSync13(opts.file, "utf-8");
|
|
1389
1447
|
else if (opts.content) body.content = opts.content;
|
|
1390
1448
|
if (opts.status) body.status = opts.status;
|
|
1391
1449
|
const res = await apiRequest(`/pebbles/${slug}/roadmaps/${id}`, {
|
|
@@ -1404,11 +1462,11 @@ roadmapsCmd.command("delete <slug> <id>").description("Delete a roadmap").action
|
|
|
1404
1462
|
});
|
|
1405
1463
|
|
|
1406
1464
|
// src/commands/persona-runs.ts
|
|
1407
|
-
import { Command as
|
|
1408
|
-
import { readFileSync as
|
|
1465
|
+
import { Command as Command19 } from "commander";
|
|
1466
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
1409
1467
|
import { join as join2 } from "path";
|
|
1410
1468
|
import { homedir as homedir2 } from "os";
|
|
1411
|
-
var personaRunsCmd = new
|
|
1469
|
+
var personaRunsCmd = new Command19("persona-runs").description(
|
|
1412
1470
|
"Manage per-persona per-session token usage rows"
|
|
1413
1471
|
);
|
|
1414
1472
|
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) => {
|
|
@@ -1432,7 +1490,7 @@ personaRunsCmd.command("get <slug> <id>").description("Get a persona run by id")
|
|
|
1432
1490
|
if (res.error) process.exit(1);
|
|
1433
1491
|
});
|
|
1434
1492
|
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) => {
|
|
1435
|
-
const body = JSON.parse(
|
|
1493
|
+
const body = JSON.parse(readFileSync14(opts.file, "utf-8"));
|
|
1436
1494
|
const res = await apiRequest(`/pebbles/${slug}/persona-runs`, {
|
|
1437
1495
|
method: "POST",
|
|
1438
1496
|
body
|
|
@@ -1444,7 +1502,7 @@ personaRunsCmd.command("capture").description("Read marker + transcript, compute
|
|
|
1444
1502
|
const markerPath = opts.marker ?? join2(homedir2(), ".claude", "odin-context", `${opts.sessionId}.json`);
|
|
1445
1503
|
let marker = {};
|
|
1446
1504
|
try {
|
|
1447
|
-
marker = JSON.parse(
|
|
1505
|
+
marker = JSON.parse(readFileSync14(markerPath, "utf-8"));
|
|
1448
1506
|
} catch {
|
|
1449
1507
|
process.exit(0);
|
|
1450
1508
|
}
|
|
@@ -1454,7 +1512,7 @@ personaRunsCmd.command("capture").description("Read marker + transcript, compute
|
|
|
1454
1512
|
let input = 0, output = 0, cacheRead = 0, cacheCreate = 0;
|
|
1455
1513
|
let minTs = null, maxTs = null;
|
|
1456
1514
|
try {
|
|
1457
|
-
const raw =
|
|
1515
|
+
const raw = readFileSync14(opts.transcript, "utf-8");
|
|
1458
1516
|
for (const line of raw.split("\n")) {
|
|
1459
1517
|
if (!line.trim()) continue;
|
|
1460
1518
|
let obj;
|
|
@@ -1505,9 +1563,9 @@ personaRunsCmd.command("capture").description("Read marker + transcript, compute
|
|
|
1505
1563
|
});
|
|
1506
1564
|
|
|
1507
1565
|
// src/commands/blog.ts
|
|
1508
|
-
import { Command as
|
|
1509
|
-
import { readFileSync as
|
|
1510
|
-
var blogCmd = new
|
|
1566
|
+
import { Command as Command20 } from "commander";
|
|
1567
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
1568
|
+
var blogCmd = new Command20("blog").description("Manage blog posts");
|
|
1511
1569
|
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) => {
|
|
1512
1570
|
const params = {};
|
|
1513
1571
|
if (opts.status) params.status = opts.status;
|
|
@@ -1526,7 +1584,7 @@ blogCmd.command("get <slug> <idOrSlug>").description("Get a blog post by UUID or
|
|
|
1526
1584
|
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) => {
|
|
1527
1585
|
let blocks = [];
|
|
1528
1586
|
if (opts.file) {
|
|
1529
|
-
blocks = JSON.parse(
|
|
1587
|
+
blocks = JSON.parse(readFileSync15(opts.file, "utf-8"));
|
|
1530
1588
|
} else if (opts.blocks) {
|
|
1531
1589
|
blocks = JSON.parse(opts.blocks);
|
|
1532
1590
|
}
|
|
@@ -1550,7 +1608,7 @@ blogCmd.command("update <slug> <id>").description("Update a blog post").option("
|
|
|
1550
1608
|
const body = {};
|
|
1551
1609
|
if (opts.title) body.title = opts.title;
|
|
1552
1610
|
if (opts.postSlug) body.slug = opts.postSlug;
|
|
1553
|
-
if (opts.file) body.blocks = JSON.parse(
|
|
1611
|
+
if (opts.file) body.blocks = JSON.parse(readFileSync15(opts.file, "utf-8"));
|
|
1554
1612
|
else if (opts.blocks) body.blocks = JSON.parse(opts.blocks);
|
|
1555
1613
|
if (opts.status) body.status = opts.status;
|
|
1556
1614
|
if (opts.scheduledPublishAt !== void 0)
|
|
@@ -1572,8 +1630,8 @@ blogCmd.command("delete <slug> <id>").description("Delete a blog post").action(a
|
|
|
1572
1630
|
});
|
|
1573
1631
|
|
|
1574
1632
|
// src/commands/api-keys.ts
|
|
1575
|
-
import { Command as
|
|
1576
|
-
var apiKeysCmd = new
|
|
1633
|
+
import { Command as Command21 } from "commander";
|
|
1634
|
+
var apiKeysCmd = new Command21("api-keys").description("Manage API keys for server-to-server access");
|
|
1577
1635
|
apiKeysCmd.command("list <slug>").description("List API keys for a pebble").action(async (slug) => {
|
|
1578
1636
|
const res = await apiRequest(`/pebbles/${slug}/api-keys`);
|
|
1579
1637
|
process.stdout.write(JSON.stringify(res) + "\n");
|
|
@@ -1615,7 +1673,7 @@ apiKeysCmd.command("delete <slug> <id>").description("Permanently delete an API
|
|
|
1615
1673
|
});
|
|
1616
1674
|
|
|
1617
1675
|
// src/commands/pebble/index.ts
|
|
1618
|
-
import { Command as
|
|
1676
|
+
import { Command as Command22 } from "commander";
|
|
1619
1677
|
|
|
1620
1678
|
// src/commands/pebble/provisioner.ts
|
|
1621
1679
|
async function runProvisioner(ctx, steps) {
|
|
@@ -2028,7 +2086,7 @@ Hand off: /odin:eng-manager bet:${bet.data.id}
|
|
|
2028
2086
|
}
|
|
2029
2087
|
|
|
2030
2088
|
// src/commands/pebble/index.ts
|
|
2031
|
-
var pebbleCmd = new
|
|
2089
|
+
var pebbleCmd = new Command22("pebble").description(
|
|
2032
2090
|
"Provision and initialize pebbles (eng-infra, run-once)"
|
|
2033
2091
|
);
|
|
2034
2092
|
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) => {
|
|
@@ -2049,7 +2107,7 @@ pebbleCmd.command("provision <provider> <slug>").description("Run one provider s
|
|
|
2049
2107
|
});
|
|
2050
2108
|
|
|
2051
2109
|
// src/commands/create/index.ts
|
|
2052
|
-
import { Command as
|
|
2110
|
+
import { Command as Command23 } from "commander";
|
|
2053
2111
|
|
|
2054
2112
|
// src/commands/create/topology.ts
|
|
2055
2113
|
var ENVS = ["prd", "stg", "dev"];
|
|
@@ -2514,22 +2572,22 @@ function assembleInputs(ctx) {
|
|
|
2514
2572
|
}
|
|
2515
2573
|
|
|
2516
2574
|
// src/commands/create/index.ts
|
|
2517
|
-
var createCmd = new
|
|
2575
|
+
var createCmd = new Command23("create").description(
|
|
2518
2576
|
"Create and provision a new project end to end"
|
|
2519
2577
|
);
|
|
2520
2578
|
createCmd.command("project [name]").description("Guided self-serve project + parallel-env provisioning").option("--dry-run", "Print the plan; mutate nothing").action(
|
|
2521
2579
|
async (name, opts) => process.exit(await runCreate(name, { dryRun: opts.dryRun }))
|
|
2522
2580
|
);
|
|
2523
|
-
var setupCmd = new
|
|
2581
|
+
var setupCmd = new Command23("setup").description("Alias for `create project`").arguments("[name]").option("--dry-run", "Print the plan; mutate nothing").action(
|
|
2524
2582
|
async (name, opts) => process.exit(await runCreate(name, { dryRun: opts.dryRun }))
|
|
2525
2583
|
);
|
|
2526
2584
|
|
|
2527
2585
|
// src/index.ts
|
|
2528
|
-
import { readFileSync as
|
|
2586
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
2529
2587
|
import { fileURLToPath } from "url";
|
|
2530
2588
|
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
2531
2589
|
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
2532
|
-
var pkg = JSON.parse(
|
|
2590
|
+
var pkg = JSON.parse(readFileSync16(resolve2(__dirname, "../package.json"), "utf-8"));
|
|
2533
2591
|
var GOLD = "\x1B[33m";
|
|
2534
2592
|
var DIM = "\x1B[2m";
|
|
2535
2593
|
var RESET = "\x1B[0m";
|
|
@@ -2547,7 +2605,7 @@ ${RESET}
|
|
|
2547
2605
|
${DIM}${cwd}${RESET}
|
|
2548
2606
|
`);
|
|
2549
2607
|
}
|
|
2550
|
-
var program = new
|
|
2608
|
+
var program = new Command24();
|
|
2551
2609
|
program.name("odin").description("CLI for Odin \u2014 the knowledge backbone for Pebble House").version(VERSION);
|
|
2552
2610
|
program.command("login").description("Authenticate with Odin via browser").option("--device", "Use the device-code flow (for headless or remote machines)").action(async (options) => {
|
|
2553
2611
|
printBanner();
|
|
@@ -2576,6 +2634,7 @@ program.addCommand(rulesCmd);
|
|
|
2576
2634
|
program.addCommand(briefsCmd);
|
|
2577
2635
|
program.addCommand(blueprintsCmd);
|
|
2578
2636
|
program.addCommand(betsCmd);
|
|
2637
|
+
program.addCommand(ticketsCmd);
|
|
2579
2638
|
program.addCommand(betCommentsCmd);
|
|
2580
2639
|
program.addCommand(tasksCmd);
|
|
2581
2640
|
program.addCommand(roadmapsCmd);
|