@pebblehouse/odin-cli 0.14.1 → 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.
Files changed (2) hide show
  1. package/dist/index.js +143 -17
  2. 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
- var decisionsCmd = new Command3("decisions").description("Manage decisions");
311
- decisionsCmd.command("list <slug>").description("List decisions for a pebble").action(async (slug) => {
312
- const res = await apiRequest(`/pebbles/${slug}/decisions`);
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: "100" }
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
- artifactsCmd.command("list <slug>").description("List artifacts for a pebble").option("--source <source>", "Filter by source (claude-code, superpowers, etc.)").action(async (slug, opts) => {
562
- const params = {};
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
- process.stdout.write(JSON.stringify(res) + "\n");
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,12 +1041,19 @@ 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
- betsCmd.command("list <slug>").description("List bets for a pebble").option("--status <status>", "Filter by status (pitch, shaped, betting, active, blocked, building, done, archived)").action(async (slug, opts) => {
933
- const params = {};
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
- process.stdout.write(JSON.stringify(res) + "\n");
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}`);
@@ -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
- 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) => {
1010
- const params = {};
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
- process.stdout.write(JSON.stringify(res) + "\n");
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}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pebblehouse/odin-cli",
3
- "version": "0.14.1",
3
+ "version": "0.15.0",
4
4
  "type": "module",
5
5
  "description": "CLI for Odin — the knowledge backbone for Pebble House",
6
6
  "bin": {