@pebblehouse/odin-cli 0.10.1 → 0.12.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 +147 -10
  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 Command18 } from "commander";
4
+ import { Command as Command20 } from "commander";
5
5
 
6
6
  // src/auth/login.ts
7
7
  import { createServer } from "http";
@@ -281,8 +281,15 @@ docsCmd.command("create <slug> <type>").description("Create a document").require
281
281
  process.stdout.write(JSON.stringify(res) + "\n");
282
282
  if (res.error) process.exit(1);
283
283
  });
284
- docsCmd.command("delete <slug> <type>").description("Delete a document").action(async (slug, type) => {
285
- const res = await apiRequest(`/pebbles/${slug}/documents/${type}`, {
284
+ docsCmd.command("delete <slug> [type]").description("Delete a document by type, or by --id (unambiguous)").option("--id <id>", "Delete a specific document by its UUID").action(async (slug, type, opts) => {
285
+ if (!opts.id && !type) {
286
+ process.stdout.write(
287
+ JSON.stringify({ data: null, error: "Provide a <type> or --id <uuid> to delete" }) + "\n"
288
+ );
289
+ process.exit(1);
290
+ }
291
+ const path = opts.id ? `/pebbles/${slug}/documents/id/${opts.id}` : `/pebbles/${slug}/documents/${type}`;
292
+ const res = await apiRequest(path, {
286
293
  method: "DELETE"
287
294
  });
288
295
  process.stdout.write(JSON.stringify(res) + "\n");
@@ -464,11 +471,23 @@ plansCmd.command("create <slug> <title>").description("Create a plan").option("-
464
471
  process.stdout.write(JSON.stringify(res) + "\n");
465
472
  if (res.error) process.exit(1);
466
473
  });
467
- plansCmd.command("update <slug> <id>").description("Update a plan").option("--title <title>", "New title").option("--description <description>", "New description").option("--status <status>", "Plan status (active|completed|archived)").action(async (slug, id, opts) => {
474
+ plansCmd.command("update <slug> <id>").description("Update a plan").option("--title <title>", "New title").option("--description <description>", "New description").option("--status <status>", "Plan status (active|done|archived)").option("--bet-id <uuid>", "Link plan to a bet").option("--task-id <uuid>", "Link plan to a task").option("--clear-bet", "Clear the bet link").option("--clear-task", "Clear the task link").action(async (slug, id, opts) => {
475
+ if (opts.betId && opts.clearBet) {
476
+ process.stderr.write("Cannot combine --bet-id with --clear-bet\n");
477
+ process.exit(1);
478
+ }
479
+ if (opts.taskId && opts.clearTask) {
480
+ process.stderr.write("Cannot combine --task-id with --clear-task\n");
481
+ process.exit(1);
482
+ }
468
483
  const body = {};
469
484
  if (opts.title) body.title = opts.title;
470
485
  if (opts.description) body.description = opts.description;
471
486
  if (opts.status) body.status = opts.status;
487
+ if (opts.betId) body.bet_id = opts.betId;
488
+ else if (opts.clearBet) body.bet_id = null;
489
+ if (opts.taskId) body.task_id = opts.taskId;
490
+ else if (opts.clearTask) body.task_id = null;
472
491
  const res = await apiRequest(`/pebbles/${slug}/plans/${id}`, {
473
492
  method: "PUT",
474
493
  body
@@ -495,7 +514,7 @@ plansCmd.command("add-phase <slug> <planId> <title>").description("Add a phase t
495
514
  process.stdout.write(JSON.stringify(res) + "\n");
496
515
  if (res.error) process.exit(1);
497
516
  });
498
- 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) => {
517
+ plansCmd.command("update-phase <slug> <planId> <phaseId>").description("Update a phase").option("--title <title>", "Phase title").option("--status <status>", "Phase status (pending|in_progress|done|skipped)").option("--notes <notes>", "Phase notes").option("--description <description>", "Phase description").action(async (slug, planId, phaseId, opts) => {
499
518
  const body = {};
500
519
  if (opts.title) body.title = opts.title;
501
520
  if (opts.status) body.status = opts.status;
@@ -846,12 +865,21 @@ blueprintsCmd.command("deprecate <id>").description("Deprecate a blueprint").act
846
865
  process.stdout.write(JSON.stringify(res) + "\n");
847
866
  if (res.error) process.exit(1);
848
867
  });
849
- blueprintsCmd.command("add-asset <id>").description("Add an asset reference to a blueprint").requiredOption("--title <title>", "Asset title").requiredOption("--url <url>", "Asset URL").option("--type <type>", "Asset type (screenshot|diagram|code-snippet|figma-link|github-link|raw-text|other)", "other").option("--description <description>", "Asset description").action(async (id, opts) => {
868
+ blueprintsCmd.command("add-asset <id>").description("Add an asset reference or inline content to a blueprint").requiredOption("--title <title>", "Asset title").option("--url <url>", "Asset URL (optional if content provided)").option("--content <content>", "Inline content string").option("--content-file <path>", "Read content from file").option("--type <type>", "Asset type (screenshot|diagram|code-snippet|figma-link|github-link|raw-text|visual-spec|other)", "other").option("--description <description>", "Asset description").action(async (id, opts) => {
869
+ let content = opts.content;
870
+ if (opts.contentFile) {
871
+ content = readFileSync9(opts.contentFile, "utf-8");
872
+ }
873
+ if (!opts.url && !content) {
874
+ process.stderr.write("Error: at least one of --url, --content, or --content-file is required\n");
875
+ process.exit(1);
876
+ }
850
877
  const res = await apiRequest(`/blueprints/${id}/assets`, {
851
878
  method: "POST",
852
879
  body: {
853
880
  title: opts.title,
854
- asset_url: opts.url,
881
+ asset_url: opts.url || null,
882
+ content: content || null,
855
883
  type: opts.type,
856
884
  description: opts.description ?? ""
857
885
  }
@@ -1139,12 +1167,119 @@ personaRunsCmd.command("capture").description("Read marker + transcript, compute
1139
1167
  process.stdout.write(JSON.stringify(res.data) + "\n");
1140
1168
  });
1141
1169
 
1142
- // src/index.ts
1170
+ // src/commands/blog.ts
1171
+ import { Command as Command18 } from "commander";
1143
1172
  import { readFileSync as readFileSync13 } from "fs";
1173
+ var blogCmd = new Command18("blog").description("Manage blog posts");
1174
+ blogCmd.command("list <slug>").description("List blog posts for a pebble").option("--status <status>", "Filter by status (draft, review, published, archived)").option("--tag <tag>", "Filter by tag").option("--page <page>", "Page number", "1").option("--per-page <perPage>", "Results per page", "20").action(async (slug, opts) => {
1175
+ const params = {};
1176
+ if (opts.status) params.status = opts.status;
1177
+ if (opts.tag) params.tag = opts.tag;
1178
+ params.page = opts.page;
1179
+ params.per_page = opts.perPage;
1180
+ const res = await apiRequest(`/pebbles/${slug}/blog`, { params });
1181
+ process.stdout.write(JSON.stringify(res) + "\n");
1182
+ if (res.error) process.exit(1);
1183
+ });
1184
+ blogCmd.command("get <slug> <idOrSlug>").description("Get a blog post by UUID or slug").action(async (slug, idOrSlug) => {
1185
+ const res = await apiRequest(`/pebbles/${slug}/blog/${idOrSlug}`);
1186
+ process.stdout.write(JSON.stringify(res) + "\n");
1187
+ if (res.error) process.exit(1);
1188
+ });
1189
+ blogCmd.command("create <slug> <title>").description("Create a blog post").requiredOption("--post-slug <postSlug>", "URL slug for the post").option("--blocks <blocks>", "Content blocks as JSON string").option("--file <path>", "Read content blocks from JSON file").option("--status <status>", "Initial status (draft, review)", "draft").option("--metadata <metadata>", "Post metadata as JSON string").option("--campaign-id <campaignId>", "Link to a marketing campaign").action(async (slug, title, opts) => {
1190
+ let blocks = [];
1191
+ if (opts.file) {
1192
+ blocks = JSON.parse(readFileSync13(opts.file, "utf-8"));
1193
+ } else if (opts.blocks) {
1194
+ blocks = JSON.parse(opts.blocks);
1195
+ }
1196
+ const body = {
1197
+ title,
1198
+ slug: opts.postSlug,
1199
+ blocks,
1200
+ status: opts.status
1201
+ };
1202
+ if (opts.metadata) body.metadata = JSON.parse(opts.metadata);
1203
+ if (opts.campaignId) body.campaign_id = opts.campaignId;
1204
+ const res = await apiRequest(`/pebbles/${slug}/blog`, {
1205
+ method: "POST",
1206
+ body
1207
+ });
1208
+ process.stdout.write(JSON.stringify(res) + "\n");
1209
+ if (res.error) process.exit(1);
1210
+ });
1211
+ blogCmd.command("update <slug> <id>").description("Update a blog post").option("--title <title>", "New title").option("--post-slug <postSlug>", "New URL slug").option("--blocks <blocks>", "Content blocks as JSON string").option("--file <path>", "Read content blocks from JSON file").option("--status <status>", "New status (draft, review, published, archived)").option("--metadata <metadata>", "Post metadata as JSON string").action(async (slug, id, opts) => {
1212
+ const body = {};
1213
+ if (opts.title) body.title = opts.title;
1214
+ if (opts.postSlug) body.slug = opts.postSlug;
1215
+ if (opts.file) body.blocks = JSON.parse(readFileSync13(opts.file, "utf-8"));
1216
+ else if (opts.blocks) body.blocks = JSON.parse(opts.blocks);
1217
+ if (opts.status) body.status = opts.status;
1218
+ if (opts.metadata) body.metadata = JSON.parse(opts.metadata);
1219
+ const res = await apiRequest(`/pebbles/${slug}/blog/${id}`, {
1220
+ method: "PUT",
1221
+ body
1222
+ });
1223
+ process.stdout.write(JSON.stringify(res) + "\n");
1224
+ if (res.error) process.exit(1);
1225
+ });
1226
+ blogCmd.command("delete <slug> <id>").description("Delete a blog post").action(async (slug, id) => {
1227
+ const res = await apiRequest(`/pebbles/${slug}/blog/${id}`, {
1228
+ method: "DELETE"
1229
+ });
1230
+ process.stdout.write(JSON.stringify(res) + "\n");
1231
+ if (res.error) process.exit(1);
1232
+ });
1233
+
1234
+ // src/commands/api-keys.ts
1235
+ import { Command as Command19 } from "commander";
1236
+ var apiKeysCmd = new Command19("api-keys").description("Manage API keys for server-to-server access");
1237
+ apiKeysCmd.command("list <slug>").description("List API keys for a pebble").action(async (slug) => {
1238
+ const res = await apiRequest(`/pebbles/${slug}/api-keys`);
1239
+ process.stdout.write(JSON.stringify(res) + "\n");
1240
+ if (res.error) process.exit(1);
1241
+ });
1242
+ apiKeysCmd.command("create <slug>").description("Create a new API key").requiredOption("--name <name>", "Human-readable key name").requiredOption("--scopes <scopes>", "Comma-separated scopes (e.g. blog:read,marketing_content:read)").option("--expires-at <expiresAt>", "Expiration timestamp (ISO 8601)").action(async (slug, opts) => {
1243
+ const scopes = opts.scopes.split(",").map((s) => s.trim());
1244
+ const body = { name: opts.name, scopes };
1245
+ if (opts.expiresAt) body.expires_at = opts.expiresAt;
1246
+ const res = await apiRequest(`/pebbles/${slug}/api-keys`, {
1247
+ method: "POST",
1248
+ body
1249
+ });
1250
+ process.stdout.write(JSON.stringify(res) + "\n");
1251
+ if (res.error) process.exit(1);
1252
+ if (res.data?.key) {
1253
+ process.stderr.write(
1254
+ `
1255
+ Store this key securely -- it cannot be retrieved again:
1256
+ ${res.data.key}
1257
+
1258
+ `
1259
+ );
1260
+ }
1261
+ });
1262
+ apiKeysCmd.command("revoke <slug> <id>").description("Revoke an API key").action(async (slug, id) => {
1263
+ const res = await apiRequest(`/pebbles/${slug}/api-keys/${id}/revoke`, {
1264
+ method: "POST"
1265
+ });
1266
+ process.stdout.write(JSON.stringify(res) + "\n");
1267
+ if (res.error) process.exit(1);
1268
+ });
1269
+ apiKeysCmd.command("delete <slug> <id>").description("Permanently delete an API key").action(async (slug, id) => {
1270
+ const res = await apiRequest(`/pebbles/${slug}/api-keys/${id}`, {
1271
+ method: "DELETE"
1272
+ });
1273
+ process.stdout.write(JSON.stringify(res) + "\n");
1274
+ if (res.error) process.exit(1);
1275
+ });
1276
+
1277
+ // src/index.ts
1278
+ import { readFileSync as readFileSync14 } from "fs";
1144
1279
  import { fileURLToPath } from "url";
1145
1280
  import { dirname as dirname2, resolve } from "path";
1146
1281
  var __dirname = dirname2(fileURLToPath(import.meta.url));
1147
- var pkg = JSON.parse(readFileSync13(resolve(__dirname, "../package.json"), "utf-8"));
1282
+ var pkg = JSON.parse(readFileSync14(resolve(__dirname, "../package.json"), "utf-8"));
1148
1283
  var GOLD = "\x1B[33m";
1149
1284
  var DIM = "\x1B[2m";
1150
1285
  var RESET = "\x1B[0m";
@@ -1162,7 +1297,7 @@ ${RESET}
1162
1297
  ${DIM}${cwd}${RESET}
1163
1298
  `);
1164
1299
  }
1165
- var program = new Command18();
1300
+ var program = new Command20();
1166
1301
  program.name("odin").description("CLI for Odin \u2014 the knowledge backbone for Pebble House").version(VERSION);
1167
1302
  program.command("login").description("Authenticate with Odin via browser").action(async () => {
1168
1303
  printBanner();
@@ -1194,6 +1329,8 @@ program.addCommand(betsCmd);
1194
1329
  program.addCommand(tasksCmd);
1195
1330
  program.addCommand(roadmapsCmd);
1196
1331
  program.addCommand(personaRunsCmd);
1332
+ program.addCommand(blogCmd);
1333
+ program.addCommand(apiKeysCmd);
1197
1334
  process.on("exit", () => {
1198
1335
  process.stderr.write("\n\n\n\n\n");
1199
1336
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pebblehouse/odin-cli",
3
- "version": "0.10.1",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
5
  "description": "CLI for Odin — the knowledge backbone for Pebble House",
6
6
  "bin": {