@pebblehouse/odin-cli 0.10.0 → 0.10.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 +296 -6
  2. package/package.json +1 -1
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 Command13 } from "commander";
4
+ import { Command as Command18 } from "commander";
5
5
 
6
6
  // src/auth/login.ts
7
7
  import { createServer } from "http";
@@ -464,11 +464,23 @@ plansCmd.command("create <slug> <title>").description("Create a plan").option("-
464
464
  process.stdout.write(JSON.stringify(res) + "\n");
465
465
  if (res.error) process.exit(1);
466
466
  });
467
- 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|completed|archived)").action(async (slug, id, opts) => {
467
+ 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) => {
468
+ if (opts.betId && opts.clearBet) {
469
+ process.stderr.write("Cannot combine --bet-id with --clear-bet\n");
470
+ process.exit(1);
471
+ }
472
+ if (opts.taskId && opts.clearTask) {
473
+ process.stderr.write("Cannot combine --task-id with --clear-task\n");
474
+ process.exit(1);
475
+ }
468
476
  const body = {};
469
477
  if (opts.title) body.title = opts.title;
470
478
  if (opts.description) body.description = opts.description;
471
479
  if (opts.status) body.status = opts.status;
480
+ if (opts.betId) body.bet_id = opts.betId;
481
+ else if (opts.clearBet) body.bet_id = null;
482
+ if (opts.taskId) body.task_id = opts.taskId;
483
+ else if (opts.clearTask) body.task_id = null;
472
484
  const res = await apiRequest(`/pebbles/${slug}/plans/${id}`, {
473
485
  method: "PUT",
474
486
  body
@@ -495,7 +507,7 @@ plansCmd.command("add-phase <slug> <planId> <title>").description("Add a phase t
495
507
  process.stdout.write(JSON.stringify(res) + "\n");
496
508
  if (res.error) process.exit(1);
497
509
  });
498
- plansCmd.command("update-phase <slug> <planId> <phaseId>").description("Update a phase").option("--title <title>", "Phase title").option("--status <status>", "Phase status (pending|in_progress|completed|skipped)").option("--notes <notes>", "Phase notes").option("--description <description>", "Phase description").action(async (slug, planId, phaseId, opts) => {
510
+ 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) => {
499
511
  const body = {};
500
512
  if (opts.title) body.title = opts.title;
501
513
  if (opts.status) body.status = opts.status;
@@ -865,12 +877,286 @@ blueprintsCmd.command("assets <id>").description("List assets for a blueprint").
865
877
  if (res.error) process.exit(1);
866
878
  });
867
879
 
868
- // src/index.ts
880
+ // src/commands/bets.ts
881
+ import { Command as Command14 } from "commander";
869
882
  import { readFileSync as readFileSync10 } from "fs";
883
+
884
+ // src/commands/bet-dependencies.ts
885
+ import { Command as Command13 } from "commander";
886
+ var depsCmd = new Command13("deps").description("Manage bet dependencies (blocked-by graph)");
887
+ depsCmd.command("list <slug> <bet-id>").description("List dependencies for a bet (blocked_by + blocking)").action(async (slug, betId) => {
888
+ const res = await apiRequest(
889
+ `/pebbles/${slug}/bets/${betId}/dependencies`
890
+ );
891
+ process.stdout.write(JSON.stringify(res) + "\n");
892
+ if (res.error) process.exit(1);
893
+ });
894
+ depsCmd.command("add <slug> <bet-id>").description("Add a blocked-by dependency").requiredOption("--blocked-by <id>", "Bet ID that blocks this bet").action(async (slug, betId, opts) => {
895
+ const res = await apiRequest(
896
+ `/pebbles/${slug}/bets/${betId}/dependencies`,
897
+ {
898
+ method: "POST",
899
+ body: { blocked_by_bet_id: opts.blockedBy }
900
+ }
901
+ );
902
+ process.stdout.write(JSON.stringify(res) + "\n");
903
+ if (res.error) process.exit(1);
904
+ });
905
+ depsCmd.command("remove <slug> <bet-id>").description("Remove a blocked-by dependency").requiredOption("--blocked-by <id>", "Bet ID to unlink").action(async (slug, betId, opts) => {
906
+ const res = await apiRequest(
907
+ `/pebbles/${slug}/bets/${betId}/dependencies/${opts.blockedBy}`,
908
+ { method: "DELETE" }
909
+ );
910
+ process.stdout.write(JSON.stringify(res) + "\n");
911
+ if (res.error) process.exit(1);
912
+ });
913
+
914
+ // src/commands/bets.ts
915
+ var betsCmd = new Command14("bets").description("Manage bets");
916
+ betsCmd.command("list <slug>").description("List bets for a pebble").option("--status <status>", "Filter by status").action(async (slug, opts) => {
917
+ const params = {};
918
+ if (opts.status) params.status = opts.status;
919
+ const res = await apiRequest(`/pebbles/${slug}/bets`, { params });
920
+ process.stdout.write(JSON.stringify(res) + "\n");
921
+ if (res.error) process.exit(1);
922
+ });
923
+ betsCmd.command("get <slug> <id>").description("Get a bet by ID").action(async (slug, id) => {
924
+ const res = await apiRequest(`/pebbles/${slug}/bets/${id}`);
925
+ process.stdout.write(JSON.stringify(res) + "\n");
926
+ if (res.error) process.exit(1);
927
+ });
928
+ 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").option("--priority <priority>", "Bet priority").option("--appetite <appetite>", "Bet appetite").action(async (slug, title, opts) => {
929
+ const content = opts.file ? readFileSync10(opts.file, "utf-8") : opts.content ?? "";
930
+ const res = await apiRequest(`/pebbles/${slug}/bets`, {
931
+ method: "POST",
932
+ body: { title, source: opts.source, content, status: opts.status, priority: opts.priority, appetite: opts.appetite }
933
+ });
934
+ process.stdout.write(JSON.stringify(res) + "\n");
935
+ if (res.error) process.exit(1);
936
+ });
937
+ 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").option("--priority <priority>", "New priority").option("--appetite <appetite>", "New appetite").action(async (slug, id, opts) => {
938
+ const body = {};
939
+ if (opts.title) body.title = opts.title;
940
+ if (opts.file) body.content = readFileSync10(opts.file, "utf-8");
941
+ else if (opts.content) body.content = opts.content;
942
+ if (opts.status) body.status = opts.status;
943
+ if (opts.priority) body.priority = opts.priority;
944
+ if (opts.appetite) body.appetite = opts.appetite;
945
+ const res = await apiRequest(`/pebbles/${slug}/bets/${id}`, {
946
+ method: "PUT",
947
+ body
948
+ });
949
+ process.stdout.write(JSON.stringify(res) + "\n");
950
+ if (res.error) process.exit(1);
951
+ });
952
+ betsCmd.command("delete <slug> <id>").description("Delete a bet").action(async (slug, id) => {
953
+ const res = await apiRequest(`/pebbles/${slug}/bets/${id}`, {
954
+ method: "DELETE"
955
+ });
956
+ process.stdout.write(JSON.stringify(res) + "\n");
957
+ if (res.error) process.exit(1);
958
+ });
959
+ betsCmd.addCommand(depsCmd);
960
+
961
+ // src/commands/tasks.ts
962
+ import { Command as Command15 } from "commander";
963
+ var tasksCmd = new Command15("tasks").description("Manage tasks");
964
+ tasksCmd.command("list <slug>").description("List tasks for a pebble").option("--status <status>", "Filter by status").option("--bet <bet-id>", "Filter by bet ID").action(async (slug, opts) => {
965
+ const params = {};
966
+ if (opts.status) params.status = opts.status;
967
+ if (opts.bet) params.bet_id = opts.bet;
968
+ const res = await apiRequest(`/pebbles/${slug}/tasks`, { params });
969
+ process.stdout.write(JSON.stringify(res) + "\n");
970
+ if (res.error) process.exit(1);
971
+ });
972
+ tasksCmd.command("get <slug> <id>").description("Get a task by ID").action(async (slug, id) => {
973
+ const res = await apiRequest(`/pebbles/${slug}/tasks/${id}`);
974
+ process.stdout.write(JSON.stringify(res) + "\n");
975
+ if (res.error) process.exit(1);
976
+ });
977
+ 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) => {
978
+ const res = await apiRequest(`/pebbles/${slug}/tasks`, {
979
+ method: "POST",
980
+ body: { title, source: opts.source, description: opts.description, bet_id: opts.bet, status: opts.status, priority: opts.priority }
981
+ });
982
+ process.stdout.write(JSON.stringify(res) + "\n");
983
+ if (res.error) process.exit(1);
984
+ });
985
+ tasksCmd.command("update <slug> <id>").description("Update a task").option("--title <title>", "New title").option("--description <description>", "New description").option("--status <status>", "New status").option("--priority <priority>", "New priority").option("--bet <bet-id>", "New parent bet ID").action(async (slug, id, opts) => {
986
+ const body = {};
987
+ if (opts.title) body.title = opts.title;
988
+ if (opts.description) body.description = opts.description;
989
+ if (opts.status) body.status = opts.status;
990
+ if (opts.priority) body.priority = opts.priority;
991
+ if (opts.bet) body.bet_id = opts.bet;
992
+ const res = await apiRequest(`/pebbles/${slug}/tasks/${id}`, {
993
+ method: "PUT",
994
+ body
995
+ });
996
+ process.stdout.write(JSON.stringify(res) + "\n");
997
+ if (res.error) process.exit(1);
998
+ });
999
+ tasksCmd.command("delete <slug> <id>").description("Delete a task").action(async (slug, id) => {
1000
+ const res = await apiRequest(`/pebbles/${slug}/tasks/${id}`, {
1001
+ method: "DELETE"
1002
+ });
1003
+ process.stdout.write(JSON.stringify(res) + "\n");
1004
+ if (res.error) process.exit(1);
1005
+ });
1006
+
1007
+ // src/commands/roadmaps.ts
1008
+ import { Command as Command16 } from "commander";
1009
+ import { readFileSync as readFileSync11 } from "fs";
1010
+ var roadmapsCmd = new Command16("roadmaps").description("Manage roadmaps");
1011
+ roadmapsCmd.command("list <slug>").description("List roadmaps for a pebble").option("--status <status>", "Filter by status").action(async (slug, opts) => {
1012
+ const params = {};
1013
+ if (opts.status) params.status = opts.status;
1014
+ const res = await apiRequest(`/pebbles/${slug}/roadmaps`, { params });
1015
+ process.stdout.write(JSON.stringify(res) + "\n");
1016
+ if (res.error) process.exit(1);
1017
+ });
1018
+ roadmapsCmd.command("get <slug> <id>").description("Get a roadmap by ID (includes bets)").action(async (slug, id) => {
1019
+ const res = await apiRequest(`/pebbles/${slug}/roadmaps/${id}`);
1020
+ process.stdout.write(JSON.stringify(res) + "\n");
1021
+ if (res.error) process.exit(1);
1022
+ });
1023
+ 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) => {
1024
+ const content = opts.file ? readFileSync11(opts.file, "utf-8") : opts.content ?? "";
1025
+ const res = await apiRequest(`/pebbles/${slug}/roadmaps`, {
1026
+ method: "POST",
1027
+ body: { title, source: opts.source, content, status: opts.status }
1028
+ });
1029
+ process.stdout.write(JSON.stringify(res) + "\n");
1030
+ if (res.error) process.exit(1);
1031
+ });
1032
+ 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) => {
1033
+ const body = {};
1034
+ if (opts.title) body.title = opts.title;
1035
+ if (opts.file) body.content = readFileSync11(opts.file, "utf-8");
1036
+ else if (opts.content) body.content = opts.content;
1037
+ if (opts.status) body.status = opts.status;
1038
+ const res = await apiRequest(`/pebbles/${slug}/roadmaps/${id}`, {
1039
+ method: "PUT",
1040
+ body
1041
+ });
1042
+ process.stdout.write(JSON.stringify(res) + "\n");
1043
+ if (res.error) process.exit(1);
1044
+ });
1045
+ roadmapsCmd.command("delete <slug> <id>").description("Delete a roadmap").action(async (slug, id) => {
1046
+ const res = await apiRequest(`/pebbles/${slug}/roadmaps/${id}`, {
1047
+ method: "DELETE"
1048
+ });
1049
+ process.stdout.write(JSON.stringify(res) + "\n");
1050
+ if (res.error) process.exit(1);
1051
+ });
1052
+
1053
+ // src/commands/persona-runs.ts
1054
+ import { Command as Command17 } from "commander";
1055
+ import { readFileSync as readFileSync12 } from "fs";
1056
+ import { join as join2 } from "path";
1057
+ import { homedir as homedir2 } from "os";
1058
+ var personaRunsCmd = new Command17("persona-runs").description(
1059
+ "Manage per-persona per-session token usage rows"
1060
+ );
1061
+ 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) => {
1062
+ const params = {};
1063
+ if (opts.betId) params.bet_id = opts.betId;
1064
+ if (opts.planId) params.plan_id = opts.planId;
1065
+ if (opts.persona) params.persona = opts.persona;
1066
+ if (opts.limit) params.limit = String(opts.limit);
1067
+ const res = await apiRequest(
1068
+ `/pebbles/${slug}/persona-runs`,
1069
+ { params }
1070
+ );
1071
+ process.stdout.write(JSON.stringify(res) + "\n");
1072
+ if (res.error) process.exit(1);
1073
+ });
1074
+ personaRunsCmd.command("get <slug> <id>").description("Get a persona run by id").action(async (slug, id) => {
1075
+ const res = await apiRequest(
1076
+ `/pebbles/${slug}/persona-runs/${id}`
1077
+ );
1078
+ process.stdout.write(JSON.stringify(res) + "\n");
1079
+ if (res.error) process.exit(1);
1080
+ });
1081
+ 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) => {
1082
+ const body = JSON.parse(readFileSync12(opts.file, "utf-8"));
1083
+ const res = await apiRequest(`/pebbles/${slug}/persona-runs`, {
1084
+ method: "POST",
1085
+ body
1086
+ });
1087
+ process.stdout.write(JSON.stringify(res) + "\n");
1088
+ if (res.error) process.exit(1);
1089
+ });
1090
+ 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) => {
1091
+ const markerPath = opts.marker ?? join2(homedir2(), ".claude", "odin-context", `${opts.sessionId}.json`);
1092
+ let marker = {};
1093
+ try {
1094
+ marker = JSON.parse(readFileSync12(markerPath, "utf-8"));
1095
+ } catch {
1096
+ process.exit(0);
1097
+ }
1098
+ if (!marker.pebble_slug || !marker.persona) {
1099
+ process.exit(0);
1100
+ }
1101
+ let input = 0, output = 0, cacheRead = 0, cacheCreate = 0;
1102
+ let minTs = null, maxTs = null;
1103
+ try {
1104
+ const raw = readFileSync12(opts.transcript, "utf-8");
1105
+ for (const line of raw.split("\n")) {
1106
+ if (!line.trim()) continue;
1107
+ let obj;
1108
+ try {
1109
+ obj = JSON.parse(line);
1110
+ } catch {
1111
+ continue;
1112
+ }
1113
+ if (obj.type !== "assistant") continue;
1114
+ const u = obj.message?.usage;
1115
+ if (!u) continue;
1116
+ input += Number(u.input_tokens ?? 0);
1117
+ output += Number(u.output_tokens ?? 0);
1118
+ cacheRead += Number(u.cache_read_input_tokens ?? 0);
1119
+ cacheCreate += Number(u.cache_creation_input_tokens ?? 0);
1120
+ const ts = obj.timestamp;
1121
+ if (ts) {
1122
+ if (!minTs || ts < minTs) minTs = ts;
1123
+ if (!maxTs || ts > maxTs) maxTs = ts;
1124
+ }
1125
+ }
1126
+ } catch {
1127
+ process.exit(0);
1128
+ }
1129
+ const payload = {
1130
+ persona: marker.persona,
1131
+ session_id: opts.sessionId,
1132
+ input_tokens: input,
1133
+ output_tokens: output,
1134
+ cache_read_tokens: cacheRead,
1135
+ cache_creation_tokens: cacheCreate,
1136
+ bet_id: marker.bet_id ?? null,
1137
+ plan_id: marker.plan_id ?? null,
1138
+ phase_id: marker.phase_id ?? null,
1139
+ started_at: minTs,
1140
+ ended_at: maxTs
1141
+ };
1142
+ const res = await apiRequest(
1143
+ `/pebbles/${marker.pebble_slug}/persona-runs`,
1144
+ { method: "POST", body: payload }
1145
+ );
1146
+ if (res.error) {
1147
+ process.stderr.write(`odin persona-runs capture: ${res.error}
1148
+ `);
1149
+ process.exit(1);
1150
+ }
1151
+ process.stdout.write(JSON.stringify(res.data) + "\n");
1152
+ });
1153
+
1154
+ // src/index.ts
1155
+ import { readFileSync as readFileSync13 } from "fs";
870
1156
  import { fileURLToPath } from "url";
871
1157
  import { dirname as dirname2, resolve } from "path";
872
1158
  var __dirname = dirname2(fileURLToPath(import.meta.url));
873
- var pkg = JSON.parse(readFileSync10(resolve(__dirname, "../package.json"), "utf-8"));
1159
+ var pkg = JSON.parse(readFileSync13(resolve(__dirname, "../package.json"), "utf-8"));
874
1160
  var GOLD = "\x1B[33m";
875
1161
  var DIM = "\x1B[2m";
876
1162
  var RESET = "\x1B[0m";
@@ -888,7 +1174,7 @@ ${RESET}
888
1174
  ${DIM}${cwd}${RESET}
889
1175
  `);
890
1176
  }
891
- var program = new Command13();
1177
+ var program = new Command18();
892
1178
  program.name("odin").description("CLI for Odin \u2014 the knowledge backbone for Pebble House").version(VERSION);
893
1179
  program.command("login").description("Authenticate with Odin via browser").action(async () => {
894
1180
  printBanner();
@@ -916,6 +1202,10 @@ program.addCommand(guidelinesCmd);
916
1202
  program.addCommand(rulesCmd);
917
1203
  program.addCommand(briefsCmd);
918
1204
  program.addCommand(blueprintsCmd);
1205
+ program.addCommand(betsCmd);
1206
+ program.addCommand(tasksCmd);
1207
+ program.addCommand(roadmapsCmd);
1208
+ program.addCommand(personaRunsCmd);
919
1209
  process.on("exit", () => {
920
1210
  process.stderr.write("\n\n\n\n\n");
921
1211
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pebblehouse/odin-cli",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "type": "module",
5
5
  "description": "CLI for Odin — the knowledge backbone for Pebble House",
6
6
  "bin": {