@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.
- package/dist/index.js +117 -202
- 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
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
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
|
|
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 ?
|
|
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 =
|
|
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").
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
|
300
|
-
var searchCmd = new
|
|
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
|
|
310
|
-
import { readFileSync as
|
|
311
|
-
var plansCmd = new
|
|
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 ?
|
|
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").
|
|
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").
|
|
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
|
-
|
|
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
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
-
|
|
427
|
-
const
|
|
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
|
|
450
|
-
import { readFileSync as
|
|
451
|
-
var specsCmd = new
|
|
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 ?
|
|
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 =
|
|
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
|
|
542
|
-
var versionsCmd = new
|
|
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
|
|
556
|
-
import { readFileSync as
|
|
557
|
-
var guidelinesCmd = new
|
|
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 ?
|
|
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 =
|
|
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
|
|
583
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
665
584
|
import { fileURLToPath } from "url";
|
|
666
|
-
import { dirname, resolve } from "path";
|
|
667
|
-
var __dirname =
|
|
668
|
-
var pkg = JSON.parse(
|
|
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
|
|
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
|
});
|