@pebblehouse/odin-cli 0.18.3 → 0.19.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 +67 -16
- 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
|
@@ -1416,7 +1416,28 @@ tasksCmd.command("delete <slug> <id>").description("Delete a task").action(async
|
|
|
1416
1416
|
|
|
1417
1417
|
// src/commands/roadmaps.ts
|
|
1418
1418
|
import { Command as Command18 } from "commander";
|
|
1419
|
-
import { readFileSync as
|
|
1419
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
1420
|
+
|
|
1421
|
+
// src/resolve-pebble.ts
|
|
1422
|
+
import { existsSync as existsSync3, readFileSync as readFileSync13 } from "fs";
|
|
1423
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
1424
|
+
var PEBBLE_LINE = /^pebble:\s*([a-z0-9][a-z0-9-]*)\s*$/m;
|
|
1425
|
+
function resolvePebbleSlug(explicit, cwd = process.cwd()) {
|
|
1426
|
+
if (explicit) return explicit;
|
|
1427
|
+
let dir = cwd;
|
|
1428
|
+
for (; ; ) {
|
|
1429
|
+
const file = join2(dir, "CLAUDE.md");
|
|
1430
|
+
if (existsSync3(file)) {
|
|
1431
|
+
const m = readFileSync13(file, "utf-8").match(PEBBLE_LINE);
|
|
1432
|
+
if (m) return m[1];
|
|
1433
|
+
}
|
|
1434
|
+
const parent = dirname2(dir);
|
|
1435
|
+
if (parent === dir) return null;
|
|
1436
|
+
dir = parent;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// src/commands/roadmaps.ts
|
|
1420
1441
|
var roadmapsCmd = new Command18("roadmaps").description("Manage roadmaps");
|
|
1421
1442
|
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
1443
|
const params = { nesting: opts.all ? "all" : "top" };
|
|
@@ -1431,7 +1452,7 @@ roadmapsCmd.command("get <slug> <id>").description("Get a roadmap by ID (include
|
|
|
1431
1452
|
if (res.error) process.exit(1);
|
|
1432
1453
|
});
|
|
1433
1454
|
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 ?
|
|
1455
|
+
const content = opts.file ? readFileSync14(opts.file, "utf-8") : opts.content ?? "";
|
|
1435
1456
|
const res = await apiRequest(`/pebbles/${slug}/roadmaps`, {
|
|
1436
1457
|
method: "POST",
|
|
1437
1458
|
body: {
|
|
@@ -1450,7 +1471,7 @@ roadmapsCmd.command("create <slug> <title>").description("Create a roadmap").opt
|
|
|
1450
1471
|
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
1472
|
const body = {};
|
|
1452
1473
|
if (opts.title) body.title = opts.title;
|
|
1453
|
-
if (opts.file) body.content =
|
|
1474
|
+
if (opts.file) body.content = readFileSync14(opts.file, "utf-8");
|
|
1454
1475
|
else if (opts.content) body.content = opts.content;
|
|
1455
1476
|
if (opts.status) body.status = opts.status;
|
|
1456
1477
|
const nullable = (v) => v === void 0 ? void 0 : v === "null" ? null : v;
|
|
@@ -1471,11 +1492,41 @@ roadmapsCmd.command("delete <slug> <id>").description("Delete a roadmap").action
|
|
|
1471
1492
|
process.stdout.write(JSON.stringify(res) + "\n");
|
|
1472
1493
|
if (res.error) process.exit(1);
|
|
1473
1494
|
});
|
|
1495
|
+
function emit(res, failed) {
|
|
1496
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
1497
|
+
if (failed) process.exit(1);
|
|
1498
|
+
}
|
|
1499
|
+
function draftPath(pebble, roadmapId) {
|
|
1500
|
+
const slug = resolvePebbleSlug(pebble);
|
|
1501
|
+
return slug ? `/pebbles/${slug}/roadmaps/${roadmapId}/client-page/draft` : null;
|
|
1502
|
+
}
|
|
1503
|
+
var clientDraftCmd = roadmapsCmd.command("client-draft").description("Read or save the client-page draft of a master roadmap (Kenny's session only; draft only)");
|
|
1504
|
+
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) => {
|
|
1505
|
+
const path = draftPath(opts.pebble, roadmapId);
|
|
1506
|
+
if (!path) return emit({ data: null, error: "pebble_unresolved" }, true);
|
|
1507
|
+
const res = await apiRequest(path);
|
|
1508
|
+
emit(res, Boolean(res.error));
|
|
1509
|
+
});
|
|
1510
|
+
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) => {
|
|
1511
|
+
const path = draftPath(opts.pebble, roadmapId);
|
|
1512
|
+
if (!path) return emit({ data: null, error: "pebble_unresolved" }, true);
|
|
1513
|
+
if (!/^\d+$/.test(opts.expectedRevision) || !Number.isSafeInteger(Number(opts.expectedRevision))) {
|
|
1514
|
+
return emit({ data: null, error: "invalid_payload", issues: [{ path: "expectedRevision", message: "must be a non-negative integer" }] }, true);
|
|
1515
|
+
}
|
|
1516
|
+
let payload;
|
|
1517
|
+
try {
|
|
1518
|
+
payload = JSON.parse(readFileSync14(opts.file, "utf-8"));
|
|
1519
|
+
} catch (e) {
|
|
1520
|
+
return emit({ data: null, error: "invalid_payload", issues: [{ path: "(root)", message: `payload file is not valid JSON: ${e.message}` }] }, true);
|
|
1521
|
+
}
|
|
1522
|
+
const res = await apiRequest(path, { method: "PUT", body: { expectedRevision: Number(opts.expectedRevision), payload } });
|
|
1523
|
+
emit(res, Boolean(res.error));
|
|
1524
|
+
});
|
|
1474
1525
|
|
|
1475
1526
|
// src/commands/persona-runs.ts
|
|
1476
1527
|
import { Command as Command19 } from "commander";
|
|
1477
|
-
import { readFileSync as
|
|
1478
|
-
import { join as
|
|
1528
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
1529
|
+
import { join as join3 } from "path";
|
|
1479
1530
|
import { homedir as homedir2 } from "os";
|
|
1480
1531
|
var personaRunsCmd = new Command19("persona-runs").description(
|
|
1481
1532
|
"Manage per-persona per-session token usage rows"
|
|
@@ -1501,7 +1552,7 @@ personaRunsCmd.command("get <slug> <id>").description("Get a persona run by id")
|
|
|
1501
1552
|
if (res.error) process.exit(1);
|
|
1502
1553
|
});
|
|
1503
1554
|
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(
|
|
1555
|
+
const body = JSON.parse(readFileSync15(opts.file, "utf-8"));
|
|
1505
1556
|
const res = await apiRequest(`/pebbles/${slug}/persona-runs`, {
|
|
1506
1557
|
method: "POST",
|
|
1507
1558
|
body
|
|
@@ -1510,10 +1561,10 @@ personaRunsCmd.command("create <slug>").description("Create/upsert a persona run
|
|
|
1510
1561
|
if (res.error) process.exit(1);
|
|
1511
1562
|
});
|
|
1512
1563
|
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 ??
|
|
1564
|
+
const markerPath = opts.marker ?? join3(homedir2(), ".claude", "odin-context", `${opts.sessionId}.json`);
|
|
1514
1565
|
let marker = {};
|
|
1515
1566
|
try {
|
|
1516
|
-
marker = JSON.parse(
|
|
1567
|
+
marker = JSON.parse(readFileSync15(markerPath, "utf-8"));
|
|
1517
1568
|
} catch {
|
|
1518
1569
|
process.exit(0);
|
|
1519
1570
|
}
|
|
@@ -1523,7 +1574,7 @@ personaRunsCmd.command("capture").description("Read marker + transcript, compute
|
|
|
1523
1574
|
let input = 0, output = 0, cacheRead = 0, cacheCreate = 0;
|
|
1524
1575
|
let minTs = null, maxTs = null;
|
|
1525
1576
|
try {
|
|
1526
|
-
const raw =
|
|
1577
|
+
const raw = readFileSync15(opts.transcript, "utf-8");
|
|
1527
1578
|
for (const line of raw.split("\n")) {
|
|
1528
1579
|
if (!line.trim()) continue;
|
|
1529
1580
|
let obj;
|
|
@@ -1575,7 +1626,7 @@ personaRunsCmd.command("capture").description("Read marker + transcript, compute
|
|
|
1575
1626
|
|
|
1576
1627
|
// src/commands/blog.ts
|
|
1577
1628
|
import { Command as Command20 } from "commander";
|
|
1578
|
-
import { readFileSync as
|
|
1629
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
1579
1630
|
var blogCmd = new Command20("blog").description("Manage blog posts");
|
|
1580
1631
|
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
1632
|
const params = {};
|
|
@@ -1595,7 +1646,7 @@ blogCmd.command("get <slug> <idOrSlug>").description("Get a blog post by UUID or
|
|
|
1595
1646
|
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
1647
|
let blocks = [];
|
|
1597
1648
|
if (opts.file) {
|
|
1598
|
-
blocks = JSON.parse(
|
|
1649
|
+
blocks = JSON.parse(readFileSync16(opts.file, "utf-8"));
|
|
1599
1650
|
} else if (opts.blocks) {
|
|
1600
1651
|
blocks = JSON.parse(opts.blocks);
|
|
1601
1652
|
}
|
|
@@ -1619,7 +1670,7 @@ blogCmd.command("update <slug> <id>").description("Update a blog post").option("
|
|
|
1619
1670
|
const body = {};
|
|
1620
1671
|
if (opts.title) body.title = opts.title;
|
|
1621
1672
|
if (opts.postSlug) body.slug = opts.postSlug;
|
|
1622
|
-
if (opts.file) body.blocks = JSON.parse(
|
|
1673
|
+
if (opts.file) body.blocks = JSON.parse(readFileSync16(opts.file, "utf-8"));
|
|
1623
1674
|
else if (opts.blocks) body.blocks = JSON.parse(opts.blocks);
|
|
1624
1675
|
if (opts.status) body.status = opts.status;
|
|
1625
1676
|
if (opts.scheduledPublishAt !== void 0)
|
|
@@ -2594,11 +2645,11 @@ var setupCmd = new Command23("setup").description("Alias for `create project`").
|
|
|
2594
2645
|
);
|
|
2595
2646
|
|
|
2596
2647
|
// src/index.ts
|
|
2597
|
-
import { readFileSync as
|
|
2648
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
2598
2649
|
import { fileURLToPath } from "url";
|
|
2599
|
-
import { dirname as
|
|
2600
|
-
var __dirname =
|
|
2601
|
-
var pkg = JSON.parse(
|
|
2650
|
+
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
2651
|
+
var __dirname = dirname3(fileURLToPath(import.meta.url));
|
|
2652
|
+
var pkg = JSON.parse(readFileSync17(resolve2(__dirname, "../package.json"), "utf-8"));
|
|
2602
2653
|
var GOLD = "\x1B[33m";
|
|
2603
2654
|
var DIM = "\x1B[2m";
|
|
2604
2655
|
var RESET = "\x1B[0m";
|