@pebblehouse/odin-cli 0.14.0 → 0.15.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 +145 -19
- 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
|
}
|
|
@@ -307,11 +307,116 @@ docsCmd.command("update <slug> <type>").description("Update document content").r
|
|
|
307
307
|
|
|
308
308
|
// src/commands/decisions.ts
|
|
309
309
|
import { Command as Command3 } from "commander";
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
310
|
+
|
|
311
|
+
// src/list-flags.ts
|
|
312
|
+
var LIST_DEFAULT_LIMIT = 50;
|
|
313
|
+
var LIST_MAX_LIMIT = 100;
|
|
314
|
+
function withListFlags(cmd) {
|
|
315
|
+
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");
|
|
316
|
+
}
|
|
317
|
+
function parseInteger(raw) {
|
|
318
|
+
if (raw === void 0 || raw === "") return null;
|
|
319
|
+
const n = Number(raw);
|
|
320
|
+
if (!Number.isFinite(n)) return null;
|
|
321
|
+
return Math.trunc(n);
|
|
322
|
+
}
|
|
323
|
+
function resolveListParams(opts) {
|
|
324
|
+
const notices = [];
|
|
325
|
+
const params = {};
|
|
326
|
+
if (opts.page !== void 0 && opts.offset !== void 0) {
|
|
327
|
+
return {
|
|
328
|
+
params: {},
|
|
329
|
+
limit: LIST_DEFAULT_LIMIT,
|
|
330
|
+
offset: 0,
|
|
331
|
+
notices: [],
|
|
332
|
+
fatal: "--page and --offset are mutually exclusive \u2014 use one"
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
let effectiveLimit = LIST_DEFAULT_LIMIT;
|
|
336
|
+
if (opts.limit !== void 0) {
|
|
337
|
+
const raw = parseInteger(opts.limit);
|
|
338
|
+
if (raw === null) {
|
|
339
|
+
notices.push(`--limit "${opts.limit}" is not a number \u2014 ignoring`);
|
|
340
|
+
} else if (raw > LIST_MAX_LIMIT) {
|
|
341
|
+
notices.push(`limit ${raw} exceeds the server maximum of ${LIST_MAX_LIMIT} \u2014 using ${LIST_MAX_LIMIT}`);
|
|
342
|
+
effectiveLimit = LIST_MAX_LIMIT;
|
|
343
|
+
params.limit = String(LIST_MAX_LIMIT);
|
|
344
|
+
} else if (raw < 1) {
|
|
345
|
+
notices.push(`--limit must be 1 or greater \u2014 using 1`);
|
|
346
|
+
effectiveLimit = 1;
|
|
347
|
+
params.limit = "1";
|
|
348
|
+
} else {
|
|
349
|
+
effectiveLimit = raw;
|
|
350
|
+
params.limit = String(raw);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
let effectiveOffset = 0;
|
|
354
|
+
if (opts.offset !== void 0) {
|
|
355
|
+
const raw = parseInteger(opts.offset);
|
|
356
|
+
if (raw === null) {
|
|
357
|
+
notices.push(`--offset "${opts.offset}" is not a number \u2014 ignoring`);
|
|
358
|
+
} else if (raw < 0) {
|
|
359
|
+
notices.push(`--offset must be 0 or greater \u2014 using 0`);
|
|
360
|
+
params.offset = "0";
|
|
361
|
+
} else {
|
|
362
|
+
effectiveOffset = raw;
|
|
363
|
+
params.offset = String(raw);
|
|
364
|
+
}
|
|
365
|
+
} else if (opts.page !== void 0) {
|
|
366
|
+
const raw = parseInteger(opts.page);
|
|
367
|
+
if (raw === null) {
|
|
368
|
+
notices.push(`--page "${opts.page}" is not a number \u2014 ignoring`);
|
|
369
|
+
} else {
|
|
370
|
+
const page = raw < 1 ? 1 : raw;
|
|
371
|
+
if (raw < 1) notices.push(`--page must be 1 or greater \u2014 using page 1`);
|
|
372
|
+
effectiveOffset = (page - 1) * effectiveLimit;
|
|
373
|
+
params.offset = String(effectiveOffset);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return { params, limit: effectiveLimit, offset: effectiveOffset, notices, fatal: null };
|
|
377
|
+
}
|
|
378
|
+
function renderListFooter(noun, rowCount, total, offset, limit) {
|
|
379
|
+
if (total === void 0) {
|
|
380
|
+
return `\u2014 showing ${rowCount} ${noun} (total unavailable)`;
|
|
381
|
+
}
|
|
382
|
+
if (rowCount === 0) {
|
|
383
|
+
return offset > 0 ? `\u2014 no ${noun} in this window (${total} total) \xB7 try --offset 0` : `\u2014 no ${noun}`;
|
|
384
|
+
}
|
|
385
|
+
const first = offset + 1;
|
|
386
|
+
const last = offset + rowCount;
|
|
387
|
+
if (offset === 0 && rowCount >= total) {
|
|
388
|
+
return `\u2014 showing all ${rowCount} ${noun}`;
|
|
389
|
+
}
|
|
390
|
+
const nextOffset = offset + limit;
|
|
391
|
+
const more = last < total ? ` \xB7 more: --offset ${nextOffset}` : "";
|
|
392
|
+
return `\u2014 showing ${rowCount} of ${total} ${noun} (rows ${first}\u2013${last})${more}`;
|
|
393
|
+
}
|
|
394
|
+
function emitListResult(res, noun, resolved) {
|
|
313
395
|
process.stdout.write(JSON.stringify(res) + "\n");
|
|
396
|
+
for (const notice of resolved.notices) {
|
|
397
|
+
process.stderr.write(notice + "\n");
|
|
398
|
+
}
|
|
399
|
+
const rowCount = Array.isArray(res.data) ? res.data.length : 0;
|
|
400
|
+
const footer = renderListFooter(noun, rowCount, res.total, resolved.offset, resolved.limit);
|
|
401
|
+
if (footer) process.stderr.write(footer + "\n");
|
|
314
402
|
if (res.error) process.exit(1);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// src/commands/decisions.ts
|
|
406
|
+
var decisionsCmd = new Command3("decisions").description("Manage decisions");
|
|
407
|
+
withListFlags(
|
|
408
|
+
decisionsCmd.command("list <slug>").description("List decisions for a pebble")
|
|
409
|
+
).action(async (slug, opts) => {
|
|
410
|
+
const resolved = resolveListParams(opts);
|
|
411
|
+
if (resolved.fatal) {
|
|
412
|
+
process.stderr.write(resolved.fatal + "\n");
|
|
413
|
+
process.exit(1);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const res = await apiRequest(`/pebbles/${slug}/decisions`, {
|
|
417
|
+
params: resolved.params
|
|
418
|
+
});
|
|
419
|
+
emitListResult(res, "decisions", resolved);
|
|
315
420
|
});
|
|
316
421
|
decisionsCmd.command("get <slug> <id>").description("Get a decision by ID").action(async (slug, id) => {
|
|
317
422
|
const res = await apiRequest(`/pebbles/${slug}/decisions/${id}`);
|
|
@@ -379,7 +484,7 @@ decisionsCmd.command("log <slug> <title>").description("Log a decision").option(
|
|
|
379
484
|
const threeDaysAgo = /* @__PURE__ */ new Date();
|
|
380
485
|
threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);
|
|
381
486
|
const recentRes = await apiRequest(`/pebbles/${slug}/decisions`, {
|
|
382
|
-
params: { limit:
|
|
487
|
+
params: { limit: String(LIST_MAX_LIMIT) }
|
|
383
488
|
});
|
|
384
489
|
if (recentRes.data) {
|
|
385
490
|
const recentActive = recentRes.data.filter(
|
|
@@ -558,12 +663,19 @@ plansCmd.command("remove-phase-link <slug> <planId> <phaseId> <linkId>").descrip
|
|
|
558
663
|
import { Command as Command7 } from "commander";
|
|
559
664
|
import { readFileSync as readFileSync5 } from "fs";
|
|
560
665
|
var artifactsCmd = new Command7("artifacts").description("Manage artifacts");
|
|
561
|
-
|
|
562
|
-
|
|
666
|
+
withListFlags(
|
|
667
|
+
artifactsCmd.command("list <slug>").description("List artifacts for a pebble").option("--source <source>", "Filter by source (claude-code, superpowers, etc.)")
|
|
668
|
+
).action(async (slug, opts) => {
|
|
669
|
+
const resolved = resolveListParams(opts);
|
|
670
|
+
if (resolved.fatal) {
|
|
671
|
+
process.stderr.write(resolved.fatal + "\n");
|
|
672
|
+
process.exit(1);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
const params = { ...resolved.params };
|
|
563
676
|
if (opts.source) params.source = opts.source;
|
|
564
677
|
const res = await apiRequest(`/pebbles/${slug}/artifacts`, { params });
|
|
565
|
-
|
|
566
|
-
if (res.error) process.exit(1);
|
|
678
|
+
emitListResult(res, "artifacts", resolved);
|
|
567
679
|
});
|
|
568
680
|
artifactsCmd.command("get <slug> <id>").description("Get an artifact by ID").action(async (slug, id) => {
|
|
569
681
|
const res = await apiRequest(`/pebbles/${slug}/artifacts/${id}`);
|
|
@@ -929,19 +1041,26 @@ depsCmd.command("remove <slug> <bet-id>").description("Remove a blocked-by depen
|
|
|
929
1041
|
|
|
930
1042
|
// src/commands/bets.ts
|
|
931
1043
|
var betsCmd = new Command14("bets").description("Manage bets");
|
|
932
|
-
|
|
933
|
-
|
|
1044
|
+
withListFlags(
|
|
1045
|
+
betsCmd.command("list <slug>").description("List bets for a pebble").option("--status <status>", "Filter by status (pitch, shaped, betting, active, blocked, building, done, archived)")
|
|
1046
|
+
).action(async (slug, opts) => {
|
|
1047
|
+
const resolved = resolveListParams(opts);
|
|
1048
|
+
if (resolved.fatal) {
|
|
1049
|
+
process.stderr.write(resolved.fatal + "\n");
|
|
1050
|
+
process.exit(1);
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
const params = { ...resolved.params };
|
|
934
1054
|
if (opts.status) params.status = opts.status;
|
|
935
1055
|
const res = await apiRequest(`/pebbles/${slug}/bets`, { params });
|
|
936
|
-
|
|
937
|
-
if (res.error) process.exit(1);
|
|
1056
|
+
emitListResult(res, "bets", resolved);
|
|
938
1057
|
});
|
|
939
1058
|
betsCmd.command("get <slug> <id>").description("Get a bet by ID").action(async (slug, id) => {
|
|
940
1059
|
const res = await apiRequest(`/pebbles/${slug}/bets/${id}`);
|
|
941
1060
|
process.stdout.write(JSON.stringify(res) + "\n");
|
|
942
1061
|
if (res.error) process.exit(1);
|
|
943
1062
|
});
|
|
944
|
-
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) => {
|
|
1063
|
+
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 (pitch, shaped, betting, active, blocked, building, done, archived)").option("--priority <priority>", "Bet priority").option("--appetite <appetite>", "Bet appetite").action(async (slug, title, opts) => {
|
|
945
1064
|
const content = opts.file ? readFileSync10(opts.file, "utf-8") : opts.content ?? "";
|
|
946
1065
|
const res = await apiRequest(`/pebbles/${slug}/bets`, {
|
|
947
1066
|
method: "POST",
|
|
@@ -950,7 +1069,7 @@ betsCmd.command("create <slug> <title>").description("Create a bet").option("--s
|
|
|
950
1069
|
process.stdout.write(JSON.stringify(res) + "\n");
|
|
951
1070
|
if (res.error) process.exit(1);
|
|
952
1071
|
});
|
|
953
|
-
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) => {
|
|
1072
|
+
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 (pitch, shaped, betting, active, blocked, building, done, archived)").option("--priority <priority>", "New priority").option("--appetite <appetite>", "New appetite").action(async (slug, id, opts) => {
|
|
954
1073
|
const body = {};
|
|
955
1074
|
if (opts.title) body.title = opts.title;
|
|
956
1075
|
if (opts.file) body.content = readFileSync10(opts.file, "utf-8");
|
|
@@ -1006,13 +1125,20 @@ betCommentsCmd.command("create <slug> <betId> [body]").description("Post a comme
|
|
|
1006
1125
|
// src/commands/tasks.ts
|
|
1007
1126
|
import { Command as Command16 } from "commander";
|
|
1008
1127
|
var tasksCmd = new Command16("tasks").description("Manage tasks");
|
|
1009
|
-
|
|
1010
|
-
|
|
1128
|
+
withListFlags(
|
|
1129
|
+
tasksCmd.command("list <slug>").description("List tasks for a pebble").option("--status <status>", "Filter by status").option("--bet <bet-id>", "Filter by bet ID")
|
|
1130
|
+
).action(async (slug, opts) => {
|
|
1131
|
+
const resolved = resolveListParams(opts);
|
|
1132
|
+
if (resolved.fatal) {
|
|
1133
|
+
process.stderr.write(resolved.fatal + "\n");
|
|
1134
|
+
process.exit(1);
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
const params = { ...resolved.params };
|
|
1011
1138
|
if (opts.status) params.status = opts.status;
|
|
1012
1139
|
if (opts.bet) params.bet_id = opts.bet;
|
|
1013
1140
|
const res = await apiRequest(`/pebbles/${slug}/tasks`, { params });
|
|
1014
|
-
|
|
1015
|
-
if (res.error) process.exit(1);
|
|
1141
|
+
emitListResult(res, "tasks", resolved);
|
|
1016
1142
|
});
|
|
1017
1143
|
tasksCmd.command("get <slug> <id>").description("Get a task by ID").action(async (slug, id) => {
|
|
1018
1144
|
const res = await apiRequest(`/pebbles/${slug}/tasks/${id}`);
|