@pebblehouse/odin-cli 0.10.2 → 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 +133 -8
  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");
@@ -858,12 +865,21 @@ blueprintsCmd.command("deprecate <id>").description("Deprecate a blueprint").act
858
865
  process.stdout.write(JSON.stringify(res) + "\n");
859
866
  if (res.error) process.exit(1);
860
867
  });
861
- 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
+ }
862
877
  const res = await apiRequest(`/blueprints/${id}/assets`, {
863
878
  method: "POST",
864
879
  body: {
865
880
  title: opts.title,
866
- asset_url: opts.url,
881
+ asset_url: opts.url || null,
882
+ content: content || null,
867
883
  type: opts.type,
868
884
  description: opts.description ?? ""
869
885
  }
@@ -1151,12 +1167,119 @@ personaRunsCmd.command("capture").description("Read marker + transcript, compute
1151
1167
  process.stdout.write(JSON.stringify(res.data) + "\n");
1152
1168
  });
1153
1169
 
1154
- // src/index.ts
1170
+ // src/commands/blog.ts
1171
+ import { Command as Command18 } from "commander";
1155
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";
1156
1279
  import { fileURLToPath } from "url";
1157
1280
  import { dirname as dirname2, resolve } from "path";
1158
1281
  var __dirname = dirname2(fileURLToPath(import.meta.url));
1159
- var pkg = JSON.parse(readFileSync13(resolve(__dirname, "../package.json"), "utf-8"));
1282
+ var pkg = JSON.parse(readFileSync14(resolve(__dirname, "../package.json"), "utf-8"));
1160
1283
  var GOLD = "\x1B[33m";
1161
1284
  var DIM = "\x1B[2m";
1162
1285
  var RESET = "\x1B[0m";
@@ -1174,7 +1297,7 @@ ${RESET}
1174
1297
  ${DIM}${cwd}${RESET}
1175
1298
  `);
1176
1299
  }
1177
- var program = new Command18();
1300
+ var program = new Command20();
1178
1301
  program.name("odin").description("CLI for Odin \u2014 the knowledge backbone for Pebble House").version(VERSION);
1179
1302
  program.command("login").description("Authenticate with Odin via browser").action(async () => {
1180
1303
  printBanner();
@@ -1206,6 +1329,8 @@ program.addCommand(betsCmd);
1206
1329
  program.addCommand(tasksCmd);
1207
1330
  program.addCommand(roadmapsCmd);
1208
1331
  program.addCommand(personaRunsCmd);
1332
+ program.addCommand(blogCmd);
1333
+ program.addCommand(apiKeysCmd);
1209
1334
  process.on("exit", () => {
1210
1335
  process.stderr.write("\n\n\n\n\n");
1211
1336
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pebblehouse/odin-cli",
3
- "version": "0.10.2",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
5
  "description": "CLI for Odin — the knowledge backbone for Pebble House",
6
6
  "bin": {