@pebblehouse/odin-cli 0.12.0 → 0.13.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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +443 -8
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -82,7 +82,7 @@ All commands output JSON to stdout. Errors go to stderr with non-zero exit codes
82
82
  |----------|---------|-------------|
83
83
  | `ODIN_API_URL` | `https://www.odin.mu` | API base URL |
84
84
  | `ODIN_SUPABASE_URL` | — | Supabase URL (for token refresh) |
85
- | `ODIN_SUPABASE_ANON_KEY` | — | Supabase anon key (for token refresh) |
85
+ | `ODIN_SUPABASE_PUBLISHABLE_KEY` | — | Supabase publishable key (for token refresh) |
86
86
 
87
87
  ## License
88
88
 
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 Command20 } from "commander";
4
+ import { Command as Command21 } from "commander";
5
5
 
6
6
  // src/auth/login.ts
7
7
  import { createServer } from "http";
@@ -37,7 +37,7 @@ function clearCredentials() {
37
37
  // src/auth/login.ts
38
38
  var API_URL = process.env.ODIN_API_URL ?? "https://www.odin.mu";
39
39
  async function login() {
40
- return new Promise((resolve2, reject) => {
40
+ return new Promise((resolve3, reject) => {
41
41
  const server = createServer(async (req, res) => {
42
42
  if (req.method === "POST" && req.url?.startsWith("/callback")) {
43
43
  let body = "";
@@ -70,7 +70,7 @@ async function login() {
70
70
  </div>
71
71
  </body></html>`);
72
72
  server.close();
73
- resolve2();
73
+ resolve3();
74
74
  } else {
75
75
  res.writeHead(404);
76
76
  res.end();
@@ -105,12 +105,12 @@ import { createClient } from "@supabase/supabase-js";
105
105
  import { existsSync as existsSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, readFileSync as readFileSync2, mkdirSync as mkdirSync2 } from "fs";
106
106
  import { dirname } from "path";
107
107
  var SUPABASE_URL = process.env.ODIN_SUPABASE_URL ?? "https://vdiwtiiksdyhlibqrngw.supabase.co";
108
- var SUPABASE_ANON_KEY = process.env.ODIN_SUPABASE_ANON_KEY ?? "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZkaXd0aWlrc2R5aGxpYnFybmd3Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzM2ODM4NDksImV4cCI6MjA4OTI1OTg0OX0.gAccODqqhBnL_npqG7H42EvaT2CWR1P2pqo5NVjKFas";
108
+ var SUPABASE_PUBLISHABLE_KEY = process.env.ODIN_SUPABASE_PUBLISHABLE_KEY ?? "sb_publishable_qTgDHReuOlNcsxm_r7PIrg_K-EV3F7a";
109
109
  var REFRESH_THRESHOLD_SECONDS = 900;
110
110
  var LOCK_PATH = CREDENTIALS_PATH + ".lock";
111
111
  var LOCK_STALE_MS = 1e4;
112
112
  function createNodeClient() {
113
- return createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
113
+ return createClient(SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, {
114
114
  auth: {
115
115
  autoRefreshToken: false,
116
116
  persistSession: false
@@ -1274,12 +1274,446 @@ apiKeysCmd.command("delete <slug> <id>").description("Permanently delete an API
1274
1274
  if (res.error) process.exit(1);
1275
1275
  });
1276
1276
 
1277
+ // src/commands/pebble/index.ts
1278
+ import { Command as Command20 } from "commander";
1279
+
1280
+ // src/commands/pebble/provisioner.ts
1281
+ async function runProvisioner(ctx, steps) {
1282
+ const report = { steps: [], captured: ctx.captured };
1283
+ for (const step of steps) {
1284
+ let result;
1285
+ const checked = await step.check(ctx);
1286
+ if (checked && checked.status === "satisfied") result = checked;
1287
+ else if (checked && checked.status === "blocked") result = checked;
1288
+ else result = await step.apply(ctx);
1289
+ if (result.data) {
1290
+ Object.assign(ctx.captured, result.data);
1291
+ }
1292
+ report.steps.push({
1293
+ id: step.id,
1294
+ label: step.label,
1295
+ status: result.status,
1296
+ detail: result.detail
1297
+ });
1298
+ if (result.status === "blocked") return { report, halted: true };
1299
+ if (result.status === "failed") return { report, halted: true };
1300
+ }
1301
+ return { report, halted: false };
1302
+ }
1303
+
1304
+ // src/commands/pebble/exec.ts
1305
+ import { execFile } from "child_process";
1306
+ function run(cmd, args, opts = {}) {
1307
+ return new Promise((resolve3) => {
1308
+ execFile(cmd, args, { cwd: opts.cwd, encoding: "utf-8" }, (err, stdout, stderr) => {
1309
+ const code = err && typeof err.code === "number" ? err.code : err ? 1 : 0;
1310
+ resolve3({ ok: code === 0, stdout: stdout ?? "", stderr: stderr ?? "", code });
1311
+ });
1312
+ });
1313
+ }
1314
+
1315
+ // src/commands/pebble/steps/doppler.ts
1316
+ var dopplerStep = {
1317
+ id: "doppler",
1318
+ label: "Doppler project + dev/prd configs",
1319
+ async check(ctx) {
1320
+ const cli = await run("doppler", ["--version"]);
1321
+ if (!cli.ok) {
1322
+ return { status: "blocked", detail: "Doppler CLI not found. Install + run `doppler login`." };
1323
+ }
1324
+ const got = await run("doppler", ["projects", "get", ctx.slug]);
1325
+ return got.ok ? { status: "satisfied", detail: `Project ${ctx.slug} exists` } : null;
1326
+ },
1327
+ async apply(ctx) {
1328
+ if (ctx.dryRun) {
1329
+ return {
1330
+ status: "applied",
1331
+ detail: `[dry-run] would create Doppler project ${ctx.slug} + dev/prd configs`
1332
+ };
1333
+ }
1334
+ const c = await run("doppler", ["projects", "create", ctx.slug]);
1335
+ if (!c.ok) return { status: "failed", detail: `doppler projects create failed: ${c.stderr}` };
1336
+ await run("doppler", ["configs", "create", "dev", "-p", ctx.slug]);
1337
+ await run("doppler", ["configs", "create", "prd", "-p", ctx.slug]);
1338
+ return { status: "applied", detail: `Created Doppler project ${ctx.slug} + dev/prd configs` };
1339
+ }
1340
+ };
1341
+
1342
+ // src/commands/pebble/steps/github.ts
1343
+ import { spawn } from "child_process";
1344
+ import { resolve } from "path";
1345
+ var ORG = "pebblehouse";
1346
+ var TEMPLATE = "pebblehouse/pebble-template";
1347
+ var githubRepoStep = {
1348
+ id: "github",
1349
+ label: "GitHub repo from pebble-template",
1350
+ async check(ctx) {
1351
+ const cli = await run("gh", ["--version"]);
1352
+ if (!cli.ok) {
1353
+ return { status: "blocked", detail: "GitHub CLI not found. Install + run `gh auth login`." };
1354
+ }
1355
+ const view = await run("gh", ["repo", "view", `${ORG}/${ctx.slug}`]);
1356
+ if (view.ok) {
1357
+ const repoDir = ctx.repoDir ?? resolve(ctx.repoDir ?? ".", ctx.slug);
1358
+ return {
1359
+ status: "satisfied",
1360
+ detail: `Repo ${ORG}/${ctx.slug} exists`,
1361
+ data: { repoDir }
1362
+ };
1363
+ }
1364
+ return null;
1365
+ },
1366
+ async apply(ctx) {
1367
+ if (ctx.dryRun) {
1368
+ return {
1369
+ status: "applied",
1370
+ detail: `[dry-run] would create ${ORG}/${ctx.slug} from ${TEMPLATE}`
1371
+ };
1372
+ }
1373
+ const parent = ctx.repoDir ?? ".";
1374
+ const c = await run(
1375
+ "gh",
1376
+ [
1377
+ "repo",
1378
+ "create",
1379
+ `${ORG}/${ctx.slug}`,
1380
+ "--template",
1381
+ TEMPLATE,
1382
+ "--private",
1383
+ "--clone"
1384
+ ],
1385
+ { cwd: parent }
1386
+ );
1387
+ if (!c.ok) return { status: "failed", detail: `gh repo create failed: ${c.stderr}` };
1388
+ const repoDir = resolve(parent, ctx.slug);
1389
+ return {
1390
+ status: "applied",
1391
+ detail: `Created ${ORG}/${ctx.slug} and cloned to ${repoDir}`,
1392
+ data: { repoDir }
1393
+ };
1394
+ }
1395
+ };
1396
+ var githubSecretStep = {
1397
+ id: "github-secret",
1398
+ label: "GitHub Actions secret DOPPLER_TOKEN_PRD",
1399
+ async check(ctx) {
1400
+ const cli = await run("gh", ["--version"]);
1401
+ if (!cli.ok) {
1402
+ return { status: "blocked", detail: "GitHub CLI not found. Install + run `gh auth login`." };
1403
+ }
1404
+ const list = await run("gh", ["secret", "list", "-R", `${ORG}/${ctx.slug}`]);
1405
+ if (list.ok && /DOPPLER_TOKEN_PRD/.test(list.stdout)) {
1406
+ return { status: "satisfied", detail: "DOPPLER_TOKEN_PRD already set" };
1407
+ }
1408
+ return null;
1409
+ },
1410
+ async apply(ctx) {
1411
+ if (ctx.dryRun) {
1412
+ return {
1413
+ status: "applied",
1414
+ detail: "[dry-run] would mint scoped prd token and set DOPPLER_TOKEN_PRD"
1415
+ };
1416
+ }
1417
+ const token = await run("doppler", [
1418
+ "configs",
1419
+ "tokens",
1420
+ "create",
1421
+ "ci",
1422
+ "-p",
1423
+ ctx.slug,
1424
+ "-c",
1425
+ "prd",
1426
+ "--plain"
1427
+ ]);
1428
+ if (!token.ok) {
1429
+ return { status: "failed", detail: `doppler token mint failed: ${token.stderr}` };
1430
+ }
1431
+ const set = await setSecretViaStdin(
1432
+ ["secret", "set", "DOPPLER_TOKEN_PRD", "-R", `${ORG}/${ctx.slug}`],
1433
+ token.stdout.trim()
1434
+ );
1435
+ if (!set.ok) {
1436
+ return { status: "failed", detail: `gh secret set failed: ${set.stderr}` };
1437
+ }
1438
+ return { status: "applied", detail: "Set DOPPLER_TOKEN_PRD (read-only prd token)" };
1439
+ }
1440
+ };
1441
+ function setSecretViaStdin(args, value) {
1442
+ return new Promise((resolvePromise) => {
1443
+ const child = spawn("gh", args, { stdio: ["pipe", "ignore", "pipe"] });
1444
+ let stderr = "";
1445
+ child.stderr.on("data", (d) => {
1446
+ stderr += d.toString();
1447
+ });
1448
+ child.on("error", (e) => resolvePromise({ ok: false, stderr: e.message }));
1449
+ child.on("close", (code) => resolvePromise({ ok: code === 0, stderr }));
1450
+ child.stdin.write(value);
1451
+ child.stdin.end();
1452
+ });
1453
+ }
1454
+
1455
+ // src/commands/pebble/steps/vercel.ts
1456
+ var vercelStep = {
1457
+ id: "vercel",
1458
+ label: "Vercel project",
1459
+ async check(ctx) {
1460
+ const cli = await run("vercel", ["--version"]);
1461
+ if (!cli.ok) {
1462
+ return { status: "blocked", detail: "Vercel CLI not found. Install + run `vercel login`." };
1463
+ }
1464
+ const list = await run("vercel", ["project", "ls"]);
1465
+ if (list.ok && new RegExp(`(^|\\s)${ctx.slug}(\\s|$)`, "m").test(list.stdout)) {
1466
+ return { status: "satisfied", detail: `Vercel project ${ctx.slug} exists` };
1467
+ }
1468
+ return null;
1469
+ },
1470
+ async apply(ctx) {
1471
+ if (ctx.dryRun) {
1472
+ return { status: "applied", detail: `[dry-run] would add Vercel project ${ctx.slug}` };
1473
+ }
1474
+ const c = await run("vercel", ["project", "add", ctx.slug]);
1475
+ if (!c.ok) return { status: "failed", detail: `vercel project add failed: ${c.stderr}` };
1476
+ return { status: "applied", detail: `Added Vercel project ${ctx.slug}` };
1477
+ }
1478
+ };
1479
+
1480
+ // src/commands/pebble/steps/r2.ts
1481
+ var bucketName = (slug) => `${slug}-tfstate`;
1482
+ var r2Step = {
1483
+ id: "r2",
1484
+ label: "Cloudflare R2 tfstate bucket",
1485
+ async check(ctx) {
1486
+ const cli = await run("wrangler", ["--version"]);
1487
+ if (!cli.ok) {
1488
+ return { status: "blocked", detail: "Wrangler CLI not found. Install + run `wrangler login`." };
1489
+ }
1490
+ const list = await run("wrangler", ["r2", "bucket", "list"]);
1491
+ if (list.ok && list.stdout.includes(bucketName(ctx.slug))) {
1492
+ return { status: "satisfied", detail: `Bucket ${bucketName(ctx.slug)} exists` };
1493
+ }
1494
+ return null;
1495
+ },
1496
+ async apply(ctx) {
1497
+ if (ctx.dryRun) {
1498
+ return {
1499
+ status: "applied",
1500
+ detail: `[dry-run] would create R2 bucket ${bucketName(ctx.slug)} + store S3 creds to Doppler prd`
1501
+ };
1502
+ }
1503
+ const c = await run("wrangler", ["r2", "bucket", "create", bucketName(ctx.slug)]);
1504
+ if (!c.ok) return { status: "failed", detail: `r2 bucket create failed: ${c.stderr}` };
1505
+ return {
1506
+ status: "applied",
1507
+ detail: `Created R2 bucket ${bucketName(ctx.slug)}`,
1508
+ data: { tfstate_bucket: bucketName(ctx.slug) }
1509
+ };
1510
+ }
1511
+ };
1512
+
1513
+ // src/commands/pebble/steps/supabase.ts
1514
+ var supabaseStep = {
1515
+ id: "supabase",
1516
+ label: "Supabase project",
1517
+ async check(ctx) {
1518
+ const cli = await run("supabase", ["--version"]);
1519
+ if (!cli.ok) {
1520
+ return { status: "blocked", detail: "Supabase CLI not found. Install + run `supabase login`." };
1521
+ }
1522
+ const list = await run("supabase", ["projects", "list"]);
1523
+ if (list.ok && new RegExp(`(^|\\s)${ctx.slug}(\\s|$)`, "m").test(list.stdout)) {
1524
+ return { status: "satisfied", detail: `Supabase project ${ctx.slug} exists` };
1525
+ }
1526
+ return null;
1527
+ },
1528
+ async apply(ctx) {
1529
+ if (ctx.dryRun) {
1530
+ return { status: "applied", detail: `[dry-run] would create Supabase project ${ctx.slug}` };
1531
+ }
1532
+ const c = await run("supabase", ["projects", "create", ctx.slug]);
1533
+ if (!c.ok) return { status: "failed", detail: `supabase projects create failed: ${c.stderr}` };
1534
+ const ref = parseRef(c.stdout);
1535
+ return {
1536
+ status: "applied",
1537
+ detail: `Created Supabase project ${ctx.slug}${ref ? ` (${ref})` : ""}`,
1538
+ data: ref ? { supabase_ref: ref } : void 0
1539
+ };
1540
+ }
1541
+ };
1542
+ function parseRef(stdout) {
1543
+ const m = stdout.match(/[a-z]{20}/);
1544
+ return m ? m[0] : void 0;
1545
+ }
1546
+
1547
+ // src/commands/pebble/steps/resend.ts
1548
+ var resendStep = {
1549
+ id: "resend",
1550
+ label: "Resend sending domain (send-only)",
1551
+ async check(ctx) {
1552
+ const cli = await run("resend", ["--version"]);
1553
+ if (!cli.ok) {
1554
+ return {
1555
+ status: "blocked",
1556
+ detail: "Resend CLI not found / not configured. Set RESEND_API_KEY and install the CLI."
1557
+ };
1558
+ }
1559
+ const list = await run("resend", ["domains", "list"]);
1560
+ if (list.ok && list.stdout.includes(ctx.slug)) {
1561
+ return { status: "satisfied", detail: `Resend sending domain for ${ctx.slug} exists` };
1562
+ }
1563
+ return null;
1564
+ },
1565
+ async apply(ctx) {
1566
+ if (ctx.dryRun) {
1567
+ return {
1568
+ status: "applied",
1569
+ detail: `[dry-run] would create Resend sending domain for ${ctx.slug} (send-only)`
1570
+ };
1571
+ }
1572
+ const c = await run("resend", ["domains", "create", ctx.slug]);
1573
+ if (!c.ok) return { status: "failed", detail: `resend domains create failed: ${c.stderr}` };
1574
+ return { status: "applied", detail: `Created Resend sending domain for ${ctx.slug} (send-only)` };
1575
+ }
1576
+ };
1577
+
1578
+ // src/commands/pebble/steps/stripe.ts
1579
+ var stripeStep = {
1580
+ id: "stripe",
1581
+ label: "Stripe account (conditional)",
1582
+ async check() {
1583
+ return { status: "satisfied", detail: "conditional \u2014 evaluate in init bet" };
1584
+ },
1585
+ async apply() {
1586
+ return { status: "satisfied", detail: "conditional \u2014 evaluate in init bet" };
1587
+ }
1588
+ };
1589
+
1590
+ // src/commands/pebble/steps/index.ts
1591
+ var CORE_STEPS = [
1592
+ dopplerStep,
1593
+ githubRepoStep,
1594
+ vercelStep,
1595
+ r2Step,
1596
+ supabaseStep,
1597
+ resendStep,
1598
+ githubSecretStep
1599
+ ];
1600
+ var STEP_REGISTRY = {
1601
+ doppler: dopplerStep,
1602
+ github: githubRepoStep,
1603
+ vercel: vercelStep,
1604
+ r2: r2Step,
1605
+ supabase: supabaseStep,
1606
+ resend: resendStep,
1607
+ stripe: stripeStep
1608
+ };
1609
+
1610
+ // src/commands/pebble/init-bet.ts
1611
+ function renderInitBetBody(slug, report) {
1612
+ const rows = report.steps.map((s) => `| ${s.id} | ${s.status} | ${s.detail} |`).join("\n");
1613
+ return `# Initialize ${slug}
1614
+
1615
+ ## Problem
1616
+ \`${slug}\` is genesis'd from \`pebble-template\` with the full IaC spine (PLACEHOLDER_* values), but its real stack is unknown. \`infra-apply\` is OFF until the spine is tailored to the actual stack.
1617
+
1618
+ ## Solution
1619
+ 1. Interview the actual stack (which providers/workflows this pebble needs).
1620
+ 2. Trim the template's tofu modules + workflow set to match; remove dangling refs in \`main.tf\`/\`variables.tf\`.
1621
+ 3. Provision any conditional providers via \`odin pebble provision <provider> ${slug}\`.
1622
+ 4. Worktree eval + run the test suite.
1623
+ 5. Flip the deploy-ready signal so \`infra-apply\` runs on merge.
1624
+
1625
+ ## Constraints
1626
+ - Brownfield-safe; never blow away live resources.
1627
+ - No plaintext secrets or tfstate in git.
1628
+ - Reuse the codified IaC standard (blueprint 36839408 + pebble-template); no parallel layout.
1629
+
1630
+ ## Success Looks Like
1631
+ - Clean no-op \`tofu plan\`.
1632
+ - \`infra-apply\` flipped on and green on merge.
1633
+ - Test suite passes.
1634
+
1635
+ ### Provisioned day-one
1636
+ | Provider | Status | Detail |
1637
+ |---|---|---|
1638
+ ${rows}
1639
+
1640
+ ### Conditional providers to evaluate
1641
+ - [ ] stripe (only if this pebble takes payments)
1642
+ - [ ] Resend inbound / email-routing (only if this pebble receives mail)
1643
+ - [ ] any tail provider specific to the stack
1644
+ `;
1645
+ }
1646
+
1647
+ // src/commands/pebble/new.ts
1648
+ async function runNew(name, opts) {
1649
+ const slug = name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
1650
+ const created = await apiRequest("/pebbles", {
1651
+ method: "POST",
1652
+ body: { slug, name }
1653
+ });
1654
+ if (created.error && !/already exists/i.test(created.error)) {
1655
+ console.error(created.error);
1656
+ return 1;
1657
+ }
1658
+ const ctx = {
1659
+ slug,
1660
+ name,
1661
+ repoDir: opts.dir ?? null,
1662
+ dryRun: !!opts.dryRun,
1663
+ captured: {}
1664
+ };
1665
+ const { report, halted } = await runProvisioner(ctx, CORE_STEPS);
1666
+ const last = report.steps[report.steps.length - 1];
1667
+ process.stdout.write(JSON.stringify({ slug, report }) + "\n");
1668
+ if (halted && last?.status === "failed") return 1;
1669
+ if (halted && last?.status === "blocked") {
1670
+ process.stderr.write(`
1671
+ Paused: ${last.detail}
1672
+ Re-run \`odin pebble new ${name}\` after acting.
1673
+ `);
1674
+ return 0;
1675
+ }
1676
+ const body = renderInitBetBody(slug, report);
1677
+ const bet = await apiRequest(`/pebbles/${slug}/bets`, {
1678
+ method: "POST",
1679
+ body: { title: `Initialize ${slug}`, source: "odin-cli", content: body, status: "shaped" }
1680
+ });
1681
+ process.stdout.write(JSON.stringify(bet) + "\n");
1682
+ if (opts.run && bet.data?.id) {
1683
+ process.stderr.write(`
1684
+ Hand off: /odin:eng-manager bet:${bet.data.id}
1685
+ `);
1686
+ }
1687
+ return 0;
1688
+ }
1689
+
1690
+ // src/commands/pebble/index.ts
1691
+ var pebbleCmd = new Command20("pebble").description(
1692
+ "Provision and initialize pebbles (eng-infra, run-once)"
1693
+ );
1694
+ pebbleCmd.command("new <name>").description("Provision a new pebble from pebble-template (day-one)").option("--run", "Hand off to eng-manager on the init bet").option("--dir <path>", "Parent dir to clone into").option("--dry-run", "Print the plan; mutate nothing").action(async (name, opts) => {
1695
+ process.exit(await runNew(name, { run: opts.run, dir: opts.dir, dryRun: opts.dryRun }));
1696
+ });
1697
+ pebbleCmd.command("provision <provider> <slug>").description("Run one provider step against an existing pebble").option("--dry-run", "Print; mutate nothing").action(async (provider, slug, opts) => {
1698
+ const step = STEP_REGISTRY[provider];
1699
+ if (!step) {
1700
+ console.error(`Unknown provider: ${provider}`);
1701
+ process.exit(1);
1702
+ }
1703
+ const { report, halted } = await runProvisioner(
1704
+ { slug, name: slug, repoDir: null, dryRun: !!opts.dryRun, captured: {} },
1705
+ [step]
1706
+ );
1707
+ process.stdout.write(JSON.stringify(report) + "\n");
1708
+ process.exit(halted && report.steps[0]?.status === "failed" ? 1 : 0);
1709
+ });
1710
+
1277
1711
  // src/index.ts
1278
1712
  import { readFileSync as readFileSync14 } from "fs";
1279
1713
  import { fileURLToPath } from "url";
1280
- import { dirname as dirname2, resolve } from "path";
1714
+ import { dirname as dirname2, resolve as resolve2 } from "path";
1281
1715
  var __dirname = dirname2(fileURLToPath(import.meta.url));
1282
- var pkg = JSON.parse(readFileSync14(resolve(__dirname, "../package.json"), "utf-8"));
1716
+ var pkg = JSON.parse(readFileSync14(resolve2(__dirname, "../package.json"), "utf-8"));
1283
1717
  var GOLD = "\x1B[33m";
1284
1718
  var DIM = "\x1B[2m";
1285
1719
  var RESET = "\x1B[0m";
@@ -1297,7 +1731,7 @@ ${RESET}
1297
1731
  ${DIM}${cwd}${RESET}
1298
1732
  `);
1299
1733
  }
1300
- var program = new Command20();
1734
+ var program = new Command21();
1301
1735
  program.name("odin").description("CLI for Odin \u2014 the knowledge backbone for Pebble House").version(VERSION);
1302
1736
  program.command("login").description("Authenticate with Odin via browser").action(async () => {
1303
1737
  printBanner();
@@ -1331,6 +1765,7 @@ program.addCommand(roadmapsCmd);
1331
1765
  program.addCommand(personaRunsCmd);
1332
1766
  program.addCommand(blogCmd);
1333
1767
  program.addCommand(apiKeysCmd);
1768
+ program.addCommand(pebbleCmd);
1334
1769
  process.on("exit", () => {
1335
1770
  process.stderr.write("\n\n\n\n\n");
1336
1771
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pebblehouse/odin-cli",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "description": "CLI for Odin — the knowledge backbone for Pebble House",
6
6
  "bin": {
@@ -34,7 +34,7 @@
34
34
  "node": ">=18"
35
35
  },
36
36
  "dependencies": {
37
- "@supabase/supabase-js": "^2.47.10",
37
+ "@supabase/supabase-js": "^2.105.0",
38
38
  "commander": "^12.0.0",
39
39
  "open": "^10.0.0"
40
40
  },