@pebblehouse/odin-cli 0.14.1 → 0.16.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 +159 -21
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -208,7 +208,7 @@ async function apiRequest(path, options = {}) {
|
|
|
208
208
|
const { method = "GET", body, params } = options;
|
|
209
209
|
const token = await getValidToken();
|
|
210
210
|
let url = `${BASE_URL}/api${path}`;
|
|
211
|
-
if (params) {
|
|
211
|
+
if (params && Object.keys(params).length > 0) {
|
|
212
212
|
const searchParams = new URLSearchParams(params);
|
|
213
213
|
url += `?${searchParams.toString()}`;
|
|
214
214
|
}
|
|
@@ -262,8 +262,13 @@ pebblesCmd.command("update <slug>").description("Update a pebble").option("--slu
|
|
|
262
262
|
import { Command as Command2 } from "commander";
|
|
263
263
|
import { readFileSync as readFileSync3 } from "fs";
|
|
264
264
|
var docsCmd = new Command2("docs").description("Manage documents");
|
|
265
|
-
docsCmd.command("list <slug>").description("List documents for a pebble").
|
|
266
|
-
|
|
265
|
+
docsCmd.command("list <slug>").description("List documents for a pebble").option(
|
|
266
|
+
"--doc-type <type>",
|
|
267
|
+
"Filter by document type (architecture, product, api, readme, spec, research, marketing, other, reference, org_chart, design, schema)"
|
|
268
|
+
).action(async (slug, opts) => {
|
|
269
|
+
const params = {};
|
|
270
|
+
if (opts.docType) params.doc_type = opts.docType;
|
|
271
|
+
const res = await apiRequest(`/pebbles/${slug}/documents`, { params });
|
|
267
272
|
process.stdout.write(JSON.stringify(res) + "\n");
|
|
268
273
|
if (res.error) process.exit(1);
|
|
269
274
|
});
|
|
@@ -307,11 +312,119 @@ docsCmd.command("update <slug> <type>").description("Update document content").r
|
|
|
307
312
|
|
|
308
313
|
// src/commands/decisions.ts
|
|
309
314
|
import { Command as Command3 } from "commander";
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
315
|
+
|
|
316
|
+
// src/list-flags.ts
|
|
317
|
+
var LIST_DEFAULT_LIMIT = 50;
|
|
318
|
+
var LIST_MAX_LIMIT = 100;
|
|
319
|
+
function withListFlags(cmd) {
|
|
320
|
+
return cmd.option("--limit <n>", `Rows per request (max ${LIST_MAX_LIMIT})`).option("--offset <n>", "Rows to skip").option("--page <n>", "1-based page number; shorthand for --offset (page - 1) * limit");
|
|
321
|
+
}
|
|
322
|
+
function parseInteger(raw) {
|
|
323
|
+
if (raw === void 0 || raw === "") return null;
|
|
324
|
+
const n = Number(raw);
|
|
325
|
+
if (!Number.isFinite(n)) return null;
|
|
326
|
+
return Math.trunc(n);
|
|
327
|
+
}
|
|
328
|
+
function resolveListParams(opts) {
|
|
329
|
+
const notices = [];
|
|
330
|
+
const params = {};
|
|
331
|
+
if (opts.page !== void 0 && opts.offset !== void 0) {
|
|
332
|
+
return {
|
|
333
|
+
params: {},
|
|
334
|
+
limit: LIST_DEFAULT_LIMIT,
|
|
335
|
+
offset: 0,
|
|
336
|
+
notices: [],
|
|
337
|
+
fatal: "--page and --offset are mutually exclusive \u2014 use one"
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
let effectiveLimit = LIST_DEFAULT_LIMIT;
|
|
341
|
+
if (opts.limit !== void 0) {
|
|
342
|
+
const raw = parseInteger(opts.limit);
|
|
343
|
+
if (raw === null) {
|
|
344
|
+
notices.push(`--limit "${opts.limit}" is not a number \u2014 ignoring`);
|
|
345
|
+
} else if (raw > LIST_MAX_LIMIT) {
|
|
346
|
+
notices.push(`limit ${raw} exceeds the server maximum of ${LIST_MAX_LIMIT} \u2014 using ${LIST_MAX_LIMIT}`);
|
|
347
|
+
effectiveLimit = LIST_MAX_LIMIT;
|
|
348
|
+
params.limit = String(LIST_MAX_LIMIT);
|
|
349
|
+
} else if (raw < 1) {
|
|
350
|
+
notices.push(`--limit must be 1 or greater \u2014 using 1`);
|
|
351
|
+
effectiveLimit = 1;
|
|
352
|
+
params.limit = "1";
|
|
353
|
+
} else {
|
|
354
|
+
effectiveLimit = raw;
|
|
355
|
+
params.limit = String(raw);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
let effectiveOffset = 0;
|
|
359
|
+
if (opts.offset !== void 0) {
|
|
360
|
+
const raw = parseInteger(opts.offset);
|
|
361
|
+
if (raw === null) {
|
|
362
|
+
notices.push(`--offset "${opts.offset}" is not a number \u2014 ignoring`);
|
|
363
|
+
} else if (raw < 0) {
|
|
364
|
+
notices.push(`--offset must be 0 or greater \u2014 using 0`);
|
|
365
|
+
params.offset = "0";
|
|
366
|
+
} else {
|
|
367
|
+
effectiveOffset = raw;
|
|
368
|
+
params.offset = String(raw);
|
|
369
|
+
}
|
|
370
|
+
} else if (opts.page !== void 0) {
|
|
371
|
+
const raw = parseInteger(opts.page);
|
|
372
|
+
if (raw === null) {
|
|
373
|
+
notices.push(`--page "${opts.page}" is not a number \u2014 ignoring`);
|
|
374
|
+
} else {
|
|
375
|
+
const page = raw < 1 ? 1 : raw;
|
|
376
|
+
if (raw < 1) notices.push(`--page must be 1 or greater \u2014 using page 1`);
|
|
377
|
+
effectiveOffset = (page - 1) * effectiveLimit;
|
|
378
|
+
params.offset = String(effectiveOffset);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return { params, limit: effectiveLimit, offset: effectiveOffset, notices, fatal: null };
|
|
382
|
+
}
|
|
383
|
+
function renderListFooter(noun, rowCount, total, offset, limit) {
|
|
384
|
+
if (total === void 0) {
|
|
385
|
+
return `\u2014 showing ${rowCount} ${noun} (total unavailable)`;
|
|
386
|
+
}
|
|
387
|
+
if (rowCount === 0) {
|
|
388
|
+
return offset > 0 ? `\u2014 no ${noun} in this window (${total} total) \xB7 try --offset 0` : `\u2014 no ${noun}`;
|
|
389
|
+
}
|
|
390
|
+
const first = offset + 1;
|
|
391
|
+
const last = offset + rowCount;
|
|
392
|
+
if (offset === 0 && rowCount >= total) {
|
|
393
|
+
return `\u2014 showing all ${rowCount} ${noun}`;
|
|
394
|
+
}
|
|
395
|
+
const nextOffset = offset + limit;
|
|
396
|
+
const more = last < total ? ` \xB7 more: --offset ${nextOffset}` : "";
|
|
397
|
+
return `\u2014 showing ${rowCount} of ${total} ${noun} (rows ${first}\u2013${last})${more}`;
|
|
398
|
+
}
|
|
399
|
+
function emitListResult(res, noun, resolved) {
|
|
313
400
|
process.stdout.write(JSON.stringify(res) + "\n");
|
|
401
|
+
for (const notice of resolved.notices) {
|
|
402
|
+
process.stderr.write(notice + "\n");
|
|
403
|
+
}
|
|
404
|
+
const rowCount = Array.isArray(res.data) ? res.data.length : 0;
|
|
405
|
+
const footer = renderListFooter(noun, rowCount, res.total, resolved.offset, resolved.limit);
|
|
406
|
+
if (footer) process.stderr.write(footer + "\n");
|
|
314
407
|
if (res.error) process.exit(1);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// src/commands/decisions.ts
|
|
411
|
+
var decisionsCmd = new Command3("decisions").description("Manage decisions");
|
|
412
|
+
withListFlags(
|
|
413
|
+
decisionsCmd.command("list <slug>").description("List decisions for a pebble").option(
|
|
414
|
+
"--status <status>",
|
|
415
|
+
"Filter by status (active, superseded, all). Defaults to active on the server."
|
|
416
|
+
)
|
|
417
|
+
).action(async (slug, opts) => {
|
|
418
|
+
const resolved = resolveListParams(opts);
|
|
419
|
+
if (resolved.fatal) {
|
|
420
|
+
process.stderr.write(resolved.fatal + "\n");
|
|
421
|
+
process.exit(1);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const params = { ...resolved.params };
|
|
425
|
+
if (opts.status) params.status = opts.status;
|
|
426
|
+
const res = await apiRequest(`/pebbles/${slug}/decisions`, { params });
|
|
427
|
+
emitListResult(res, "decisions", resolved);
|
|
315
428
|
});
|
|
316
429
|
decisionsCmd.command("get <slug> <id>").description("Get a decision by ID").action(async (slug, id) => {
|
|
317
430
|
const res = await apiRequest(`/pebbles/${slug}/decisions/${id}`);
|
|
@@ -379,7 +492,7 @@ decisionsCmd.command("log <slug> <title>").description("Log a decision").option(
|
|
|
379
492
|
const threeDaysAgo = /* @__PURE__ */ new Date();
|
|
380
493
|
threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);
|
|
381
494
|
const recentRes = await apiRequest(`/pebbles/${slug}/decisions`, {
|
|
382
|
-
params: { limit:
|
|
495
|
+
params: { limit: String(LIST_MAX_LIMIT) }
|
|
383
496
|
});
|
|
384
497
|
if (recentRes.data) {
|
|
385
498
|
const recentActive = recentRes.data.filter(
|
|
@@ -444,8 +557,12 @@ var searchCmd = new Command5("search").description("Full-text search across Odin
|
|
|
444
557
|
import { Command as Command6 } from "commander";
|
|
445
558
|
import { readFileSync as readFileSync4 } from "fs";
|
|
446
559
|
var plansCmd = new Command6("plans").description("Manage plans");
|
|
447
|
-
plansCmd.command("list <slug>").description("List plans for a pebble").action(async (slug) => {
|
|
448
|
-
const
|
|
560
|
+
plansCmd.command("list <slug>").description("List plans for a pebble").option("--status <status>", "Filter by status (active, done, archived)").option("--bet <id>", "Filter to plans linked to this bet").option("--task <id>", "Filter to plans linked to this task").action(async (slug, opts) => {
|
|
561
|
+
const params = {};
|
|
562
|
+
if (opts.status) params.status = opts.status;
|
|
563
|
+
if (opts.bet) params.bet_id = opts.bet;
|
|
564
|
+
if (opts.task) params.task_id = opts.task;
|
|
565
|
+
const res = await apiRequest(`/pebbles/${slug}/plans`, { params });
|
|
449
566
|
process.stdout.write(JSON.stringify(res) + "\n");
|
|
450
567
|
if (res.error) process.exit(1);
|
|
451
568
|
});
|
|
@@ -558,12 +675,19 @@ plansCmd.command("remove-phase-link <slug> <planId> <phaseId> <linkId>").descrip
|
|
|
558
675
|
import { Command as Command7 } from "commander";
|
|
559
676
|
import { readFileSync as readFileSync5 } from "fs";
|
|
560
677
|
var artifactsCmd = new Command7("artifacts").description("Manage artifacts");
|
|
561
|
-
|
|
562
|
-
|
|
678
|
+
withListFlags(
|
|
679
|
+
artifactsCmd.command("list <slug>").description("List artifacts for a pebble").option("--source <source>", "Filter by source (claude-code, superpowers, etc.)")
|
|
680
|
+
).action(async (slug, opts) => {
|
|
681
|
+
const resolved = resolveListParams(opts);
|
|
682
|
+
if (resolved.fatal) {
|
|
683
|
+
process.stderr.write(resolved.fatal + "\n");
|
|
684
|
+
process.exit(1);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
const params = { ...resolved.params };
|
|
563
688
|
if (opts.source) params.source = opts.source;
|
|
564
689
|
const res = await apiRequest(`/pebbles/${slug}/artifacts`, { params });
|
|
565
|
-
|
|
566
|
-
if (res.error) process.exit(1);
|
|
690
|
+
emitListResult(res, "artifacts", resolved);
|
|
567
691
|
});
|
|
568
692
|
artifactsCmd.command("get <slug> <id>").description("Get an artifact by ID").action(async (slug, id) => {
|
|
569
693
|
const res = await apiRequest(`/pebbles/${slug}/artifacts/${id}`);
|
|
@@ -929,12 +1053,19 @@ depsCmd.command("remove <slug> <bet-id>").description("Remove a blocked-by depen
|
|
|
929
1053
|
|
|
930
1054
|
// src/commands/bets.ts
|
|
931
1055
|
var betsCmd = new Command14("bets").description("Manage bets");
|
|
932
|
-
|
|
933
|
-
|
|
1056
|
+
withListFlags(
|
|
1057
|
+
betsCmd.command("list <slug>").description("List bets for a pebble").option("--status <status>", "Filter by status (pitch, shaped, betting, active, blocked, building, done, archived)")
|
|
1058
|
+
).action(async (slug, opts) => {
|
|
1059
|
+
const resolved = resolveListParams(opts);
|
|
1060
|
+
if (resolved.fatal) {
|
|
1061
|
+
process.stderr.write(resolved.fatal + "\n");
|
|
1062
|
+
process.exit(1);
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
const params = { ...resolved.params };
|
|
934
1066
|
if (opts.status) params.status = opts.status;
|
|
935
1067
|
const res = await apiRequest(`/pebbles/${slug}/bets`, { params });
|
|
936
|
-
|
|
937
|
-
if (res.error) process.exit(1);
|
|
1068
|
+
emitListResult(res, "bets", resolved);
|
|
938
1069
|
});
|
|
939
1070
|
betsCmd.command("get <slug> <id>").description("Get a bet by ID").action(async (slug, id) => {
|
|
940
1071
|
const res = await apiRequest(`/pebbles/${slug}/bets/${id}`);
|
|
@@ -1006,13 +1137,20 @@ betCommentsCmd.command("create <slug> <betId> [body]").description("Post a comme
|
|
|
1006
1137
|
// src/commands/tasks.ts
|
|
1007
1138
|
import { Command as Command16 } from "commander";
|
|
1008
1139
|
var tasksCmd = new Command16("tasks").description("Manage tasks");
|
|
1009
|
-
|
|
1010
|
-
|
|
1140
|
+
withListFlags(
|
|
1141
|
+
tasksCmd.command("list <slug>").description("List tasks for a pebble").option("--status <status>", "Filter by status").option("--bet <bet-id>", "Filter by bet ID")
|
|
1142
|
+
).action(async (slug, opts) => {
|
|
1143
|
+
const resolved = resolveListParams(opts);
|
|
1144
|
+
if (resolved.fatal) {
|
|
1145
|
+
process.stderr.write(resolved.fatal + "\n");
|
|
1146
|
+
process.exit(1);
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
const params = { ...resolved.params };
|
|
1011
1150
|
if (opts.status) params.status = opts.status;
|
|
1012
1151
|
if (opts.bet) params.bet_id = opts.bet;
|
|
1013
1152
|
const res = await apiRequest(`/pebbles/${slug}/tasks`, { params });
|
|
1014
|
-
|
|
1015
|
-
if (res.error) process.exit(1);
|
|
1153
|
+
emitListResult(res, "tasks", resolved);
|
|
1016
1154
|
});
|
|
1017
1155
|
tasksCmd.command("get <slug> <id>").description("Get a task by ID").action(async (slug, id) => {
|
|
1018
1156
|
const res = await apiRequest(`/pebbles/${slug}/tasks/${id}`);
|