@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.
- package/dist/index.js +147 -10
- 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
|
|
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>
|
|
285
|
-
|
|
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|
|
|
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|
|
|
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").
|
|
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/
|
|
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(
|
|
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
|
|
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
|
});
|