@pebblehouse/odin-cli 0.4.0 → 0.5.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.
Files changed (2) hide show
  1. package/dist/index.js +117 -202
  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 Command14 } from "commander";
4
+ import { Command as Command10 } from "commander";
5
5
 
6
6
  // src/auth/login.ts
7
7
  import { createServer } from "http";
@@ -102,8 +102,13 @@ import { Command } from "commander";
102
102
 
103
103
  // src/auth/refresh.ts
104
104
  import { createClient } from "@supabase/supabase-js";
105
+ import { existsSync as existsSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, readFileSync as readFileSync2, mkdirSync as mkdirSync2 } from "fs";
106
+ import { dirname } from "path";
105
107
  var SUPABASE_URL = process.env.ODIN_SUPABASE_URL ?? "https://vdiwtiiksdyhlibqrngw.supabase.co";
106
108
  var SUPABASE_ANON_KEY = process.env.ODIN_SUPABASE_ANON_KEY ?? "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZkaXd0aWlrc2R5aGxpYnFybmd3Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzM2ODM4NDksImV4cCI6MjA4OTI1OTg0OX0.gAccODqqhBnL_npqG7H42EvaT2CWR1P2pqo5NVjKFas";
109
+ var REFRESH_THRESHOLD_SECONDS = 900;
110
+ var LOCK_PATH = CREDENTIALS_PATH + ".lock";
111
+ var LOCK_STALE_MS = 1e4;
107
112
  function createNodeClient() {
108
113
  return createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
109
114
  auth: {
@@ -112,15 +117,46 @@ function createNodeClient() {
112
117
  }
113
118
  });
114
119
  }
115
- async function getValidToken() {
116
- const creds = getCredentials();
117
- if (!creds) {
118
- throw new Error("Not logged in. Run 'odin login' first.");
120
+ function acquireLock() {
121
+ try {
122
+ if (existsSync2(LOCK_PATH)) {
123
+ try {
124
+ const stat = JSON.parse(readFileSync2(LOCK_PATH, "utf-8"));
125
+ if (Date.now() - stat.pid_time > LOCK_STALE_MS) {
126
+ unlinkSync2(LOCK_PATH);
127
+ } else {
128
+ return false;
129
+ }
130
+ } catch {
131
+ try {
132
+ unlinkSync2(LOCK_PATH);
133
+ } catch {
134
+ }
135
+ }
136
+ }
137
+ const dir = dirname(LOCK_PATH);
138
+ if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
139
+ writeFileSync2(LOCK_PATH, JSON.stringify({ pid: process.pid, pid_time: Date.now() }), { flag: "wx" });
140
+ return true;
141
+ } catch {
142
+ return false;
119
143
  }
120
- const now = Math.floor(Date.now() / 1e3);
121
- if (creds.expires_at > now + 60) {
122
- return creds.access_token;
144
+ }
145
+ function releaseLock() {
146
+ try {
147
+ unlinkSync2(LOCK_PATH);
148
+ } catch {
123
149
  }
150
+ }
151
+ async function waitForLock(timeoutMs = 5e3) {
152
+ const start = Date.now();
153
+ while (Date.now() - start < timeoutMs) {
154
+ if (acquireLock()) return true;
155
+ await new Promise((r) => setTimeout(r, 50));
156
+ }
157
+ return false;
158
+ }
159
+ async function refreshTokens(creds) {
124
160
  const supabase = createNodeClient();
125
161
  const { data, error } = await supabase.auth.refreshSession({
126
162
  refresh_token: creds.refresh_token
@@ -128,13 +164,42 @@ async function getValidToken() {
128
164
  if (error || !data.session) {
129
165
  throw new Error("Session expired. Run 'odin login' to re-authenticate.");
130
166
  }
167
+ const now = Math.floor(Date.now() / 1e3);
131
168
  const newCreds = {
132
169
  access_token: data.session.access_token,
133
170
  refresh_token: data.session.refresh_token,
134
171
  expires_at: data.session.expires_at ?? now + 3600
135
172
  };
136
173
  saveCredentials(newCreds);
137
- return newCreds.access_token;
174
+ return newCreds;
175
+ }
176
+ async function getValidToken() {
177
+ const creds = getCredentials();
178
+ if (!creds) {
179
+ throw new Error("Not logged in. Run 'odin login' first.");
180
+ }
181
+ const now = Math.floor(Date.now() / 1e3);
182
+ if (creds.expires_at > now + REFRESH_THRESHOLD_SECONDS) {
183
+ return creds.access_token;
184
+ }
185
+ const locked = await waitForLock();
186
+ try {
187
+ const freshCreds = getCredentials();
188
+ if (freshCreds && freshCreds.expires_at > now + REFRESH_THRESHOLD_SECONDS) {
189
+ return freshCreds.access_token;
190
+ }
191
+ const useForRefresh = freshCreds ?? creds;
192
+ const newCreds = await refreshTokens(useForRefresh);
193
+ return newCreds.access_token;
194
+ } catch (err) {
195
+ const retryCreds = getCredentials();
196
+ if (retryCreds && retryCreds.expires_at > now + REFRESH_THRESHOLD_SECONDS) {
197
+ return retryCreds.access_token;
198
+ }
199
+ throw err;
200
+ } finally {
201
+ if (locked) releaseLock();
202
+ }
138
203
  }
139
204
 
140
205
  // src/api-client.ts
@@ -195,7 +260,7 @@ pebblesCmd.command("update <slug>").description("Update a pebble").option("--slu
195
260
 
196
261
  // src/commands/documents.ts
197
262
  import { Command as Command2 } from "commander";
198
- import { readFileSync as readFileSync2 } from "fs";
263
+ import { readFileSync as readFileSync3 } from "fs";
199
264
  var docsCmd = new Command2("docs").description("Manage documents");
200
265
  docsCmd.command("list <slug>").description("List documents for a pebble").action(async (slug) => {
201
266
  const res = await apiRequest(`/pebbles/${slug}/documents`);
@@ -208,7 +273,7 @@ docsCmd.command("get <slug> <type>").description("Get document by type").action(
208
273
  if (res.error) process.exit(1);
209
274
  });
210
275
  docsCmd.command("create <slug> <type>").description("Create a document").requiredOption("--title <title>", "Document title").option("--file <path>", "Read content from file").option("--source <source>", "Source", "manual").action(async (slug, type, opts) => {
211
- const content = opts.file ? readFileSync2(opts.file, "utf-8") : "";
276
+ const content = opts.file ? readFileSync3(opts.file, "utf-8") : "";
212
277
  const res = await apiRequest(`/pebbles/${slug}/documents`, {
213
278
  method: "POST",
214
279
  body: { doc_type: type, title: opts.title, content, source: opts.source }
@@ -224,7 +289,7 @@ docsCmd.command("delete <slug> <type>").description("Delete a document").action(
224
289
  if (res.error) process.exit(1);
225
290
  });
226
291
  docsCmd.command("update <slug> <type>").description("Update document content").requiredOption("--file <path>", "Read content from file").option("--source <source>", "Source", "manual").action(async (slug, type, opts) => {
227
- const content = readFileSync2(opts.file, "utf-8");
292
+ const content = readFileSync3(opts.file, "utf-8");
228
293
  const res = await apiRequest(`/pebbles/${slug}/documents/${type}`, {
229
294
  method: "PUT",
230
295
  body: { content, source: opts.source }
@@ -262,42 +327,18 @@ decisionsCmd.command("log <slug> <title>").description("Log a decision").option(
262
327
 
263
328
  // src/commands/context.ts
264
329
  import { Command as Command4 } from "commander";
265
- var contextCmd = new Command4("context").description("Get Tier 1 context for a pebble").argument("<slug>", "Pebble slug").option("--limit <n>", "Number of context entries", "50").action(async (slug, opts) => {
266
- const res = await apiRequest(`/pebbles/${slug}/context`, {
267
- params: { limit: opts.limit }
268
- });
269
- process.stdout.write(JSON.stringify(res) + "\n");
270
- if (res.error) process.exit(1);
271
- });
272
-
273
- // src/commands/sessions.ts
274
- import { Command as Command5 } from "commander";
275
- var sessionsCmd = new Command5("sessions").description("Manage sessions");
276
- sessionsCmd.command("start <slug>").description("Start a session").option("--source <source>", "Session source", "claude-code").action(async (slug, opts) => {
277
- const res = await apiRequest("/sessions", {
278
- method: "POST",
279
- body: { pebble_slug: slug, source: opts.source }
280
- });
281
- process.stdout.write(JSON.stringify(res) + "\n");
282
- if (res.error) process.exit(1);
283
- });
284
- sessionsCmd.command("list <slug>").description("List sessions for a pebble").action(async (slug) => {
285
- const res = await apiRequest(`/sessions/by-pebble/${slug}`);
286
- process.stdout.write(JSON.stringify(res) + "\n");
287
- if (res.error) process.exit(1);
288
- });
289
- sessionsCmd.command("end <id>").description("End a session").option("--summary <summary>", "Session summary").option("--status <status>", "Final status", "completed").action(async (id, opts) => {
290
- const res = await apiRequest(`/sessions/${id}`, {
291
- method: "PUT",
292
- body: { summary: opts.summary, status: opts.status }
293
- });
294
- process.stdout.write(JSON.stringify(res) + "\n");
295
- if (res.error) process.exit(1);
330
+ var contextCmd = new Command4("context").description("Get Tier 1 context for a pebble").argument("<slug>", "Pebble slug").action(async (slug) => {
331
+ console.error(`[odin context] T1 context endpoint is being rebuilt (Phase 5).`);
332
+ console.error(`In the meantime, use:`);
333
+ console.error(` odin guidelines list`);
334
+ console.error(` odin docs get ${slug} <type>`);
335
+ console.error(` odin decisions list ${slug}`);
336
+ process.exit(0);
296
337
  });
297
338
 
298
339
  // src/commands/search.ts
299
- import { Command as Command6 } from "commander";
300
- var searchCmd = new Command6("search").description("Full-text search across Odin").argument("<query>", "Search query").option("--pebble <slug>", "Scope to a pebble").action(async (query, opts) => {
340
+ import { Command as Command5 } from "commander";
341
+ var searchCmd = new Command5("search").description("Full-text search across Odin").argument("<query>", "Search query").option("--pebble <slug>", "Scope to a pebble").action(async (query, opts) => {
301
342
  const params = { q: query };
302
343
  if (opts.pebble) params.pebble = opts.pebble;
303
344
  const res = await apiRequest("/search", { params });
@@ -306,9 +347,9 @@ var searchCmd = new Command6("search").description("Full-text search across Odin
306
347
  });
307
348
 
308
349
  // src/commands/plans.ts
309
- import { Command as Command7 } from "commander";
310
- import { readFileSync as readFileSync3 } from "fs";
311
- var plansCmd = new Command7("plans").description("Manage plans");
350
+ import { Command as Command6 } from "commander";
351
+ import { readFileSync as readFileSync4 } from "fs";
352
+ var plansCmd = new Command6("plans").description("Manage plans");
312
353
  plansCmd.command("list <slug>").description("List plans for a pebble").action(async (slug) => {
313
354
  const res = await apiRequest(`/pebbles/${slug}/plans`);
314
355
  process.stdout.write(JSON.stringify(res) + "\n");
@@ -328,7 +369,7 @@ plansCmd.command("get <slug> <id>").description("Get plan with phases").action(a
328
369
  process.stdout.write(JSON.stringify(result) + "\n");
329
370
  });
330
371
  plansCmd.command("create <slug> <title>").description("Create a plan").option("--description <description>", "Plan description").option("--file <path>", "Read description from file").option("--source <source>", "Source", "claude-code").action(async (slug, title, opts) => {
331
- const description = opts.file ? readFileSync3(opts.file, "utf-8") : opts.description ?? null;
372
+ const description = opts.file ? readFileSync4(opts.file, "utf-8") : opts.description ?? null;
332
373
  const res = await apiRequest(`/pebbles/${slug}/plans`, {
333
374
  method: "POST",
334
375
  body: { title, description, source: opts.source }
@@ -355,28 +396,24 @@ plansCmd.command("delete <slug> <id>").description("Delete a plan").action(async
355
396
  process.stdout.write(JSON.stringify(res) + "\n");
356
397
  if (res.error) process.exit(1);
357
398
  });
358
- plansCmd.command("add-phase <slug> <planId> <title>").description("Add a phase to a plan").requiredOption("--phase-number <number>", "Phase number", parseInt).option("--description <description>", "Phase description").option("--detail-type <type>", "Detail type (e.g. agent_spec, document)").option("--detail-id <id>", "Detail ID (UUID of linked entity)").action(async (slug, planId, title, opts) => {
399
+ plansCmd.command("add-phase <slug> <planId> <title>").description("Add a phase to a plan").requiredOption("--phase-number <number>", "Phase number", parseInt).option("--description <description>", "Phase description").action(async (slug, planId, title, opts) => {
359
400
  const res = await apiRequest(`/pebbles/${slug}/plans/${planId}/phases`, {
360
401
  method: "POST",
361
402
  body: {
362
403
  phase_number: opts.phaseNumber,
363
404
  title,
364
- description: opts.description ?? null,
365
- detail_type: opts.detailType ?? null,
366
- detail_id: opts.detailId ?? null
405
+ description: opts.description ?? null
367
406
  }
368
407
  });
369
408
  process.stdout.write(JSON.stringify(res) + "\n");
370
409
  if (res.error) process.exit(1);
371
410
  });
372
- 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").option("--detail-type <type>", "Detail type (e.g. agent_spec, document)").option("--detail-id <id>", "Detail ID (UUID of linked entity)").action(async (slug, planId, phaseId, opts) => {
411
+ 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) => {
373
412
  const body = {};
374
413
  if (opts.title) body.title = opts.title;
375
414
  if (opts.status) body.status = opts.status;
376
415
  if (opts.notes) body.notes = opts.notes;
377
416
  if (opts.description) body.description = opts.description;
378
- if (opts.detailType) body.detail_type = opts.detailType;
379
- if (opts.detailId) body.detail_id = opts.detailId;
380
417
  const res = await apiRequest(`/pebbles/${slug}/plans/${planId}/phases/${phaseId}`, {
381
418
  method: "PUT",
382
419
  body
@@ -391,54 +428,20 @@ plansCmd.command("delete-phase <slug> <planId> <phaseId>").description("Delete a
391
428
  process.stdout.write(JSON.stringify(res) + "\n");
392
429
  if (res.error) process.exit(1);
393
430
  });
394
-
395
- // src/commands/conversations.ts
396
- import { Command as Command8 } from "commander";
397
- function collect(value, previous) {
398
- return previous.concat([value]);
399
- }
400
- var conversationsCmd = new Command8("conversations").description("Manage conversations");
401
- conversationsCmd.command("list <slug>").description("List conversations for a pebble").action(async (slug) => {
402
- const res = await apiRequest(`/pebbles/${slug}/conversations`);
403
- process.stdout.write(JSON.stringify(res) + "\n");
404
- if (res.error) process.exit(1);
405
- });
406
- conversationsCmd.command("get <slug> <id>").description("Get a conversation by ID").action(async (slug, id) => {
407
- const res = await apiRequest(`/pebbles/${slug}/conversations/${id}`);
408
- process.stdout.write(JSON.stringify(res) + "\n");
409
- if (res.error) process.exit(1);
410
- });
411
- conversationsCmd.command("create <slug> <title>").description("Create a conversation").option("--summary <summary>", "Conversation summary").option("--key-point <point>", "Key point (repeatable)", collect, []).option("--decision <decision>", "Decision made (repeatable)", collect, []).option("--source-url <url>", "Source URL").option("--session-id <id>", "Link to active session").action(async (slug, title, opts) => {
412
- const res = await apiRequest(`/pebbles/${slug}/conversations`, {
431
+ plansCmd.command("add-phase-link <slug> <planId> <phaseId>").description("Add a link from a phase to a spec or document").requiredOption("--link-type <type>", "Link type (spec|plan_doc)").requiredOption("--target-type <type>", "Target type (agent_spec|document)").requiredOption("--target-id <id>", "Target entity UUID").action(async (slug, planId, phaseId, opts) => {
432
+ const res = await apiRequest(`/pebbles/${slug}/plans/${planId}/phases/${phaseId}/links`, {
413
433
  method: "POST",
414
434
  body: {
415
- title,
416
- summary: opts.summary ?? null,
417
- key_points: opts.keyPoint,
418
- decisions_made: opts.decision,
419
- source_url: opts.sourceUrl ?? null,
420
- session_id: opts.sessionId ?? null
435
+ link_type: opts.linkType,
436
+ target_type: opts.targetType,
437
+ target_id: opts.targetId
421
438
  }
422
439
  });
423
440
  process.stdout.write(JSON.stringify(res) + "\n");
424
441
  if (res.error) process.exit(1);
425
442
  });
426
- conversationsCmd.command("update <slug> <id>").description("Update a conversation").option("--title <title>", "New title").option("--summary <summary>", "New summary").option("--key-point <point>", "Key point (repeatable, replaces existing)", collect, []).option("--decision <decision>", "Decision made (repeatable, replaces existing)", collect, []).option("--source-url <url>", "New source URL").action(async (slug, id, opts) => {
427
- const body = {};
428
- if (opts.title) body.title = opts.title;
429
- if (opts.summary) body.summary = opts.summary;
430
- if (opts.keyPoint.length > 0) body.key_points = opts.keyPoint;
431
- if (opts.decision.length > 0) body.decisions_made = opts.decision;
432
- if (opts.sourceUrl) body.source_url = opts.sourceUrl;
433
- const res = await apiRequest(`/pebbles/${slug}/conversations/${id}`, {
434
- method: "PUT",
435
- body
436
- });
437
- process.stdout.write(JSON.stringify(res) + "\n");
438
- if (res.error) process.exit(1);
439
- });
440
- conversationsCmd.command("delete <slug> <id>").description("Delete a conversation").action(async (slug, id) => {
441
- const res = await apiRequest(`/pebbles/${slug}/conversations/${id}`, {
443
+ plansCmd.command("remove-phase-link <slug> <planId> <phaseId> <linkId>").description("Remove a link from a phase").action(async (slug, planId, phaseId, linkId) => {
444
+ const res = await apiRequest(`/pebbles/${slug}/plans/${planId}/phases/${phaseId}/links/${linkId}`, {
442
445
  method: "DELETE"
443
446
  });
444
447
  process.stdout.write(JSON.stringify(res) + "\n");
@@ -446,9 +449,9 @@ conversationsCmd.command("delete <slug> <id>").description("Delete a conversatio
446
449
  });
447
450
 
448
451
  // src/commands/specs.ts
449
- import { Command as Command9 } from "commander";
450
- import { readFileSync as readFileSync4 } from "fs";
451
- var specsCmd = new Command9("specs").description("Manage agent specs");
452
+ import { Command as Command7 } from "commander";
453
+ import { readFileSync as readFileSync5 } from "fs";
454
+ var specsCmd = new Command7("specs").description("Manage agent specs");
452
455
  specsCmd.command("list <slug>").description("List specs for a pebble").option("--source <source>", "Filter by source (claude-code, superpowers, etc.)").action(async (slug, opts) => {
453
456
  const params = {};
454
457
  if (opts.source) params.source = opts.source;
@@ -462,7 +465,7 @@ specsCmd.command("get <slug> <id>").description("Get a spec by ID").action(async
462
465
  if (res.error) process.exit(1);
463
466
  });
464
467
  specsCmd.command("create <slug> <title>").description("Create a spec").requiredOption("--spec-type <type>", "Spec type (e.g. brainstorm, plan, research)").option("--source <source>", "Source agent", "claude-code").option("--content <content>", "Spec content").option("--file <path>", "Read content from file").option("--session-id <id>", "Link to active session").action(async (slug, title, opts) => {
465
- const content = opts.file ? readFileSync4(opts.file, "utf-8") : opts.content ?? "";
468
+ const content = opts.file ? readFileSync5(opts.file, "utf-8") : opts.content ?? "";
466
469
  const res = await apiRequest(`/pebbles/${slug}/agent-specs`, {
467
470
  method: "POST",
468
471
  body: {
@@ -481,7 +484,7 @@ specsCmd.command("update <slug> <id>").description("Update a spec").option("--ti
481
484
  if (opts.title) body.title = opts.title;
482
485
  if (opts.specType) body.spec_type = opts.specType;
483
486
  if (opts.source) body.source = opts.source;
484
- if (opts.file) body.content = readFileSync4(opts.file, "utf-8");
487
+ if (opts.file) body.content = readFileSync5(opts.file, "utf-8");
485
488
  else if (opts.content) body.content = opts.content;
486
489
  const res = await apiRequest(`/pebbles/${slug}/agent-specs/${id}`, {
487
490
  method: "PUT",
@@ -498,48 +501,9 @@ specsCmd.command("delete <slug> <id>").description("Delete a spec").action(async
498
501
  if (res.error) process.exit(1);
499
502
  });
500
503
 
501
- // src/commands/executions.ts
502
- import { Command as Command10 } from "commander";
503
- var executionsCmd = new Command10("executions").description("View Claude Code executions");
504
- executionsCmd.command("create <slug>").description("Create an execution record").requiredOption("--tool-name <name>", "Tool name").option("--session-id <id>", "Session ID").option("--summary-text <text>", "Summary text").option("--execution-type <type>", "Execution type (task|discovery|system|plan)").option("--raw-input <json>", "Raw input JSON").option("--raw-output <json>", "Raw output JSON").option("--files-read <files...>", "Files read").option("--files-modified <files...>", "Files modified").option("--prompt-number <n>", "Prompt number").option("--content-hash <hash>", "Content hash for dedup").action(async (slug, opts) => {
505
- const body = {
506
- tool_name: opts.toolName
507
- };
508
- if (opts.sessionId) body.session_id = opts.sessionId;
509
- if (opts.summaryText) body.summary_text = opts.summaryText;
510
- if (opts.executionType) body.execution_type = opts.executionType;
511
- if (opts.rawInput) body.raw_input = opts.rawInput;
512
- if (opts.rawOutput) body.raw_output = opts.rawOutput;
513
- if (opts.filesRead) body.files_read = opts.filesRead;
514
- if (opts.filesModified) body.files_modified = opts.filesModified;
515
- if (opts.promptNumber) body.prompt_number = parseInt(opts.promptNumber, 10);
516
- if (opts.contentHash) body.content_hash = opts.contentHash;
517
- const res = await apiRequest(`/pebbles/${slug}/executions`, {
518
- method: "POST",
519
- body
520
- });
521
- process.stdout.write(JSON.stringify(res) + "\n");
522
- if (res.error) process.exit(1);
523
- });
524
- executionsCmd.command("list <slug>").description("List executions for a pebble").option("--session-id <id>", "Filter by session ID").option("--limit <n>", "Max results", "50").option("--offset <n>", "Offset for pagination", "0").action(async (slug, opts) => {
525
- const params = {
526
- limit: opts.limit,
527
- offset: opts.offset
528
- };
529
- if (opts.sessionId) params.session_id = opts.sessionId;
530
- const res = await apiRequest(`/pebbles/${slug}/executions`, { params });
531
- process.stdout.write(JSON.stringify(res) + "\n");
532
- if (res.error) process.exit(1);
533
- });
534
- executionsCmd.command("get <slug> <id>").description("Get a single execution").action(async (slug, id) => {
535
- const res = await apiRequest(`/pebbles/${slug}/executions/${id}`);
536
- process.stdout.write(JSON.stringify(res) + "\n");
537
- if (res.error) process.exit(1);
538
- });
539
-
540
504
  // src/commands/versions.ts
541
- import { Command as Command11 } from "commander";
542
- var versionsCmd = new Command11("versions").description("View document version history");
505
+ import { Command as Command8 } from "commander";
506
+ var versionsCmd = new Command8("versions").description("View document version history");
543
507
  versionsCmd.command("list <slug> <docType>").description("List versions for a document").action(async (slug, docType) => {
544
508
  const res = await apiRequest(`/pebbles/${slug}/documents/${docType}/versions`);
545
509
  process.stdout.write(JSON.stringify(res) + "\n");
@@ -552,9 +516,9 @@ versionsCmd.command("get <slug> <docType> <versionId>").description("Get a speci
552
516
  });
553
517
 
554
518
  // src/commands/guidelines.ts
555
- import { Command as Command12 } from "commander";
556
- import { readFileSync as readFileSync5 } from "fs";
557
- var guidelinesCmd = new Command12("guidelines").description("Manage global guidelines");
519
+ import { Command as Command9 } from "commander";
520
+ import { readFileSync as readFileSync6 } from "fs";
521
+ var guidelinesCmd = new Command9("guidelines").description("Manage global guidelines");
558
522
  guidelinesCmd.command("list").description("List guidelines").option("--category <category>", "Filter by category").option("--status <status>", "Filter by status (default: active)").option("--all", "Include deprecated guidelines").action(async (opts) => {
559
523
  const params = new URLSearchParams();
560
524
  if (opts.category) params.set("category", opts.category);
@@ -571,7 +535,7 @@ guidelinesCmd.command("get <id>").description("Get a guideline by ID").action(as
571
535
  if (res.error) process.exit(1);
572
536
  });
573
537
  guidelinesCmd.command("create <title>").description("Create a guideline").option("--category <category>", "Category", "other").option("--content <content>", "Guideline content").option("--file <path>", "Read content from file").option("--source <source>", "Source", "manual").action(async (title, opts) => {
574
- const content = opts.file ? readFileSync5(opts.file, "utf-8") : opts.content ?? "";
538
+ const content = opts.file ? readFileSync6(opts.file, "utf-8") : opts.content ?? "";
575
539
  const res = await apiRequest("/guidelines", {
576
540
  method: "POST",
577
541
  body: { category: opts.category, title, content, source: opts.source }
@@ -582,7 +546,7 @@ guidelinesCmd.command("create <title>").description("Create a guideline").option
582
546
  guidelinesCmd.command("update <id>").description("Update a guideline").option("--title <title>", "New title").option("--content <content>", "New content").option("--file <path>", "Read content from file").option("--category <category>", "New category").option("--status <status>", "New status (active|deprecated)").option("--source <source>", "Source").action(async (id, opts) => {
583
547
  const body = {};
584
548
  if (opts.title) body.title = opts.title;
585
- if (opts.file) body.content = readFileSync5(opts.file, "utf-8");
549
+ if (opts.file) body.content = readFileSync6(opts.file, "utf-8");
586
550
  else if (opts.content) body.content = opts.content;
587
551
  if (opts.category) body.category = opts.category;
588
552
  if (opts.status) body.status = opts.status;
@@ -615,57 +579,12 @@ guidelinesCmd.command("deprecate <id>").description("Deprecate a guideline").act
615
579
  if (res.error) process.exit(1);
616
580
  });
617
581
 
618
- // src/commands/observations.ts
619
- import { Command as Command13 } from "commander";
620
- var observationsCmd = new Command13("observations").description(
621
- "View and synthesize observations"
622
- );
623
- observationsCmd.command("list <slug>").description("List observations for a pebble").option("--limit <n>", "Max results", "50").option("--offset <n>", "Offset for pagination", "0").action(async (slug, opts) => {
624
- const res = await apiRequest(
625
- `/pebbles/${slug}/observations`,
626
- {
627
- params: { limit: opts.limit, offset: opts.offset }
628
- }
629
- );
630
- process.stdout.write(JSON.stringify(res) + "\n");
631
- if (res.error) process.exit(1);
632
- });
633
- observationsCmd.command("synthesize [slug]").description("Trigger observation synthesis (requires running worker)").action(async (slug) => {
634
- try {
635
- const body = {};
636
- if (slug) body.slug = slug;
637
- const res = await fetch("http://127.0.0.1:7433/synthesize", {
638
- method: "POST",
639
- headers: { "Content-Type": "application/json" },
640
- body: JSON.stringify(body)
641
- });
642
- if (!res.ok) {
643
- const text = await res.text();
644
- console.error(`Worker returned ${res.status}: ${text}`);
645
- process.exit(1);
646
- }
647
- const data = await res.json();
648
- process.stdout.write(JSON.stringify(data) + "\n");
649
- } catch (err) {
650
- if (err instanceof Error && (err.message.includes("ECONNREFUSED") || err.message.includes("fetch failed"))) {
651
- console.error(
652
- "Worker not running. Start a Claude Code session to launch the worker."
653
- );
654
- } else {
655
- console.error(
656
- `Failed: ${err instanceof Error ? err.message : err}`
657
- );
658
- }
659
- process.exit(1);
660
- }
661
- });
662
-
663
582
  // src/index.ts
664
- import { readFileSync as readFileSync6 } from "fs";
583
+ import { readFileSync as readFileSync7 } from "fs";
665
584
  import { fileURLToPath } from "url";
666
- import { dirname, resolve } from "path";
667
- var __dirname = dirname(fileURLToPath(import.meta.url));
668
- var pkg = JSON.parse(readFileSync6(resolve(__dirname, "../package.json"), "utf-8"));
585
+ import { dirname as dirname2, resolve } from "path";
586
+ var __dirname = dirname2(fileURLToPath(import.meta.url));
587
+ var pkg = JSON.parse(readFileSync7(resolve(__dirname, "../package.json"), "utf-8"));
669
588
  var GOLD = "\x1B[33m";
670
589
  var DIM = "\x1B[2m";
671
590
  var RESET = "\x1B[0m";
@@ -683,7 +602,7 @@ ${RESET}
683
602
  ${DIM}${cwd}${RESET}
684
603
  `);
685
604
  }
686
- var program = new Command14();
605
+ var program = new Command10();
687
606
  program.name("odin").description("CLI for Odin \u2014 the knowledge backbone for Pebble House").version(VERSION);
688
607
  program.command("login").description("Authenticate with Odin via browser").action(async () => {
689
608
  printBanner();
@@ -703,15 +622,11 @@ program.addCommand(pebblesCmd);
703
622
  program.addCommand(docsCmd);
704
623
  program.addCommand(decisionsCmd);
705
624
  program.addCommand(contextCmd);
706
- program.addCommand(sessionsCmd);
707
625
  program.addCommand(searchCmd);
708
626
  program.addCommand(plansCmd);
709
- program.addCommand(conversationsCmd);
710
627
  program.addCommand(specsCmd);
711
- program.addCommand(executionsCmd);
712
628
  program.addCommand(versionsCmd);
713
629
  program.addCommand(guidelinesCmd);
714
- program.addCommand(observationsCmd);
715
630
  process.on("exit", () => {
716
631
  process.stderr.write("\n\n\n\n\n");
717
632
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pebblehouse/odin-cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "description": "CLI for Odin — the knowledge backbone for Pebble House",
6
6
  "bin": {