pawmode 1.2.0 → 1.3.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 CHANGED
@@ -2,8 +2,9 @@
2
2
  import { accent, addJob, bold, dim, getTodaysCost, installSystemJob, listJobs, parseHumanSchedule, pawPulse, pawStep, readCostTracker, readScheduleConfig, readTelegramConfig, removeJob, runJob, showBanner, showMini, showPuppyDisclaimer, startTelegramBot, subtle, telegramConfigExists, telegramQuestionnaire, toggleJob, writeTelegramConfig } from "./scheduler-DAmd0GzB.js";
3
3
  import { getDefaultSkillsDir, installSkill, isSkillInstalled, listInstalledSkills, removeSkill } from "./skills-CMqq9k1-.js";
4
4
  import { addPermissions, readSettings, removePermissions, writeSettings } from "./permissions-BlGEHCXO.js";
5
- import { readConfig, startDashboard, writeConfig } from "./dashboard-server--wwlA0Pa.js";
5
+ import { readConfig, startDashboard, writeConfig } from "./dashboard-server-BAyeozOa.js";
6
6
  import { Command } from "commander";
7
+ import * as p$12 from "@clack/prompts";
7
8
  import * as p$11 from "@clack/prompts";
8
9
  import * as p$10 from "@clack/prompts";
9
10
  import * as p$9 from "@clack/prompts";
@@ -16,6 +17,7 @@ import * as p$3 from "@clack/prompts";
16
17
  import * as p$2 from "@clack/prompts";
17
18
  import * as p$1 from "@clack/prompts";
18
19
  import * as p from "@clack/prompts";
20
+ import * as os$5 from "node:os";
19
21
  import * as os$4 from "node:os";
20
22
  import * as os$3 from "node:os";
21
23
  import * as os$2 from "node:os";
@@ -23,10 +25,12 @@ import * as os$1 from "node:os";
23
25
  import os from "node:os";
24
26
  import chalk from "chalk";
25
27
  import { execSync, spawn } from "node:child_process";
28
+ import * as fs$4 from "node:fs";
26
29
  import * as fs$3 from "node:fs";
27
30
  import * as fs$2 from "node:fs";
28
31
  import * as fs$1 from "node:fs";
29
32
  import fs from "node:fs";
33
+ import * as path$4 from "node:path";
30
34
  import * as path$3 from "node:path";
31
35
  import * as path$2 from "node:path";
32
36
  import * as path$1 from "node:path";
@@ -79,13 +83,6 @@ const todoistCli = {
79
83
  installMethod: "brew",
80
84
  platforms: ["darwin", "linux"]
81
85
  };
82
- const thingsCli = {
83
- name: "things-cli",
84
- command: "things-cli",
85
- installCmd: "pipx install things-cli",
86
- installMethod: "pip",
87
- platforms: ["darwin"]
88
- };
89
86
  const taskwarrior = {
90
87
  name: "taskwarrior",
91
88
  command: "task",
@@ -341,13 +338,6 @@ const remotion = {
341
338
  "win32"
342
339
  ]
343
340
  };
344
- const editly = {
345
- name: "editly",
346
- command: "editly",
347
- installCmd: "npm install -g editly",
348
- installMethod: "npm",
349
- platforms: ["darwin", "linux"]
350
- };
351
341
  const agentBrowser = {
352
342
  name: "agent-browser",
353
343
  command: "agent-browser",
@@ -445,28 +435,6 @@ const httpie = {
445
435
  "win32"
446
436
  ]
447
437
  };
448
- const llmCli = {
449
- name: "llm",
450
- command: "llm",
451
- installCmd: "brew install llm",
452
- installMethod: "brew",
453
- platforms: [
454
- "darwin",
455
- "linux",
456
- "win32"
457
- ]
458
- };
459
- const aichat = {
460
- name: "aichat",
461
- command: "aichat",
462
- installCmd: "brew install aichat",
463
- installMethod: "brew",
464
- platforms: [
465
- "darwin",
466
- "linux",
467
- "win32"
468
- ]
469
- };
470
438
  const curl = {
471
439
  name: "curl",
472
440
  command: "curl",
@@ -520,23 +488,15 @@ const skills = [
520
488
  platforms: ["darwin", "linux"],
521
489
  subChoices: {
522
490
  question: "Which task manager?",
523
- options: [
524
- {
525
- label: "Todoist",
526
- value: "todoist",
527
- tools: [todoistCli]
528
- },
529
- {
530
- label: "Things 3 (macOS)",
531
- value: "things",
532
- tools: [thingsCli]
533
- },
534
- {
535
- label: "Taskwarrior (local)",
536
- value: "taskwarrior",
537
- tools: [taskwarrior]
538
- }
539
- ]
491
+ options: [{
492
+ label: "Todoist",
493
+ value: "todoist",
494
+ tools: [todoistCli]
495
+ }, {
496
+ label: "Taskwarrior (local)",
497
+ value: "taskwarrior",
498
+ tools: [taskwarrior]
499
+ }]
540
500
  }
541
501
  },
542
502
  {
@@ -687,13 +647,9 @@ const skills = [
687
647
  {
688
648
  id: "video-edit",
689
649
  name: "Video Editing",
690
- description: "Programmatic video creation Remotion (React) and Editly (JSON-based)",
650
+ description: "Programmatic video creation with Remotion (React-based)",
691
651
  category: "media",
692
- tools: [
693
- remotion,
694
- editly,
695
- ffmpeg
696
- ],
652
+ tools: [remotion, ffmpeg],
697
653
  platforms: ["darwin", "linux"]
698
654
  },
699
655
  {
@@ -1018,47 +974,6 @@ const skills = [
1018
974
  "linux",
1019
975
  "win32"
1020
976
  ]
1021
- },
1022
- {
1023
- id: "ai",
1024
- name: "AI / LLM",
1025
- description: "Query LLMs from CLI — pipe text, chat, summarize with local or cloud models",
1026
- category: "research",
1027
- tools: [],
1028
- platforms: [
1029
- "darwin",
1030
- "linux",
1031
- "win32"
1032
- ],
1033
- subChoices: {
1034
- question: "Which LLM CLI?",
1035
- options: [
1036
- {
1037
- label: "llm (Simon Willison — 100+ models)",
1038
- value: "llm",
1039
- tools: [llmCli]
1040
- },
1041
- {
1042
- label: "aichat (Rust — fast, multi-provider)",
1043
- value: "aichat",
1044
- tools: [aichat]
1045
- },
1046
- {
1047
- label: "Both",
1048
- value: "both",
1049
- tools: [llmCli, aichat]
1050
- }
1051
- ]
1052
- },
1053
- authSteps: [{
1054
- tool: "llm",
1055
- command: "llm keys set openai",
1056
- description: "Set LLM API key"
1057
- }, {
1058
- tool: "aichat",
1059
- command: "aichat (follow setup prompts)",
1060
- description: "Configure API key"
1061
- }]
1062
977
  }
1063
978
  ];
1064
979
  const categoryLabels = {
@@ -1121,7 +1036,6 @@ const presets = [
1121
1036
  "jira",
1122
1037
  "browser",
1123
1038
  "network",
1124
- "ai",
1125
1039
  "cron"
1126
1040
  ]
1127
1041
  },
@@ -1153,7 +1067,7 @@ const presets = [
1153
1067
  }
1154
1068
  ];
1155
1069
  function getPresetSkills(presetId, platform) {
1156
- const preset = presets.find((p$12) => p$12.id === presetId);
1070
+ const preset = presets.find((p$13) => p$13.id === presetId);
1157
1071
  if (!preset) return [];
1158
1072
  const available = getSkillsForPlatform(platform);
1159
1073
  if (preset.id === "everything") return available;
@@ -1365,26 +1279,26 @@ function removeSafetyHooks() {
1365
1279
 
1366
1280
  //#endregion
1367
1281
  //#region src/core/soul.ts
1368
- function getSoulPath() {
1369
- return path$3.join(os$4.homedir(), ".claude", "SOUL.md");
1282
+ function getSoulPath$1() {
1283
+ return path$4.join(os$5.homedir(), ".claude", "SOUL.md");
1370
1284
  }
1371
1285
  function soulExists() {
1372
- return fs$3.existsSync(getSoulPath());
1286
+ return fs$4.existsSync(getSoulPath$1());
1373
1287
  }
1374
1288
  async function soulQuestionnaire() {
1375
- const name = await p$11.text({
1289
+ const name = await p$12.text({
1376
1290
  message: "What should your assistant call you?",
1377
1291
  placeholder: "Your name or nickname",
1378
1292
  validate: (v) => v.length === 0 ? "Name cannot be empty" : void 0
1379
1293
  });
1380
- if (p$11.isCancel(name)) return null;
1381
- const botName = await p$11.text({
1294
+ if (p$12.isCancel(name)) return null;
1295
+ const botName = await p$12.text({
1382
1296
  message: "Name your assistant:",
1383
1297
  placeholder: "Paw",
1384
1298
  defaultValue: "Paw"
1385
1299
  });
1386
- if (p$11.isCancel(botName)) return null;
1387
- const tone = await p$11.select({
1300
+ if (p$12.isCancel(botName)) return null;
1301
+ const tone = await p$12.select({
1388
1302
  message: "Communication style?",
1389
1303
  options: [
1390
1304
  {
@@ -1404,8 +1318,8 @@ async function soulQuestionnaire() {
1404
1318
  }
1405
1319
  ]
1406
1320
  });
1407
- if (p$11.isCancel(tone)) return null;
1408
- const verbosity = await p$11.select({
1321
+ if (p$12.isCancel(tone)) return null;
1322
+ const verbosity = await p$12.select({
1409
1323
  message: "Response length?",
1410
1324
  options: [
1411
1325
  {
@@ -1425,18 +1339,18 @@ async function soulQuestionnaire() {
1425
1339
  }
1426
1340
  ]
1427
1341
  });
1428
- if (p$11.isCancel(verbosity)) return null;
1429
- const proactive = await p$11.confirm({
1342
+ if (p$12.isCancel(verbosity)) return null;
1343
+ const proactive = await p$12.confirm({
1430
1344
  message: "Should Claude suggest things proactively?",
1431
1345
  initialValue: true
1432
1346
  });
1433
- if (p$11.isCancel(proactive)) return null;
1434
- const extrasResult = await p$11.text({
1347
+ if (p$12.isCancel(proactive)) return null;
1348
+ const extrasResult = await p$12.text({
1435
1349
  message: "Any custom instructions? (optional)",
1436
1350
  placeholder: "e.g. always respond in Spanish, prefer dark humor, etc.",
1437
1351
  defaultValue: ""
1438
1352
  });
1439
- if (p$11.isCancel(extrasResult)) return null;
1353
+ if (p$12.isCancel(extrasResult)) return null;
1440
1354
  const extras = extrasResult.split(",").map((s) => s.trim()).filter(Boolean);
1441
1355
  return {
1442
1356
  name,
@@ -1484,9 +1398,9 @@ function writeSoul(config) {
1484
1398
  }
1485
1399
  lines.push("## PAW MODE", "", "You are running in PAW MODE — full personal assistant mode powered by OpenPaw.", `At the start of each session, briefly greet the user as ${config.botName} (e.g., '${config.botName} here — PAW MODE active, ready to help!').`, "");
1486
1400
  lines.push("## Guidelines", "", "- Check installed skills before attempting actions (read ~/.claude/skills/)", "- If a skill isn't installed, suggest: `openpaw add <skill>`", "- Read ~/.claude/memory/MEMORY.md at session start for persistent context", "- Save important facts to memory when the user shares them", "- Never expose API keys, tokens, or passwords in responses", "");
1487
- const soulDir = path$3.dirname(getSoulPath());
1488
- if (!fs$3.existsSync(soulDir)) fs$3.mkdirSync(soulDir, { recursive: true });
1489
- fs$3.writeFileSync(getSoulPath(), lines.join("\n"), "utf-8");
1401
+ const soulDir = path$4.dirname(getSoulPath$1());
1402
+ if (!fs$4.existsSync(soulDir)) fs$4.mkdirSync(soulDir, { recursive: true });
1403
+ fs$4.writeFileSync(getSoulPath$1(), lines.join("\n"), "utf-8");
1490
1404
  }
1491
1405
  function showSoulSummary(config) {
1492
1406
  const lines = [
@@ -1497,12 +1411,12 @@ function showSoulSummary(config) {
1497
1411
  `${accent("Proactive:")} ${config.proactive ? "yes" : "no"}`
1498
1412
  ];
1499
1413
  if (config.extras.length > 0) lines.push(`${accent("Custom:")} ${config.extras.join(", ")}`);
1500
- p$11.note(lines.join("\n"), "Personality");
1414
+ p$12.note(lines.join("\n"), "Personality");
1501
1415
  }
1502
1416
 
1503
1417
  //#endregion
1504
1418
  //#region src/core/memory.ts
1505
- const MEMORY_DIR = path$2.join(os$3.homedir(), ".claude", "memory");
1419
+ const MEMORY_DIR = path$3.join(os$4.homedir(), ".claude", "memory");
1506
1420
  const INITIAL_MEMORY = `# Memory
1507
1421
 
1508
1422
  ## User
@@ -1515,12 +1429,12 @@ const INITIAL_MEMORY = `# Memory
1515
1429
  - (Claude will track projects mentioned in conversation)
1516
1430
  `;
1517
1431
  function setupMemory(userName) {
1518
- if (!fs$2.existsSync(MEMORY_DIR)) fs$2.mkdirSync(MEMORY_DIR, { recursive: true });
1519
- const memoryPath = path$2.join(MEMORY_DIR, "MEMORY.md");
1520
- if (!fs$2.existsSync(memoryPath)) {
1432
+ if (!fs$3.existsSync(MEMORY_DIR)) fs$3.mkdirSync(MEMORY_DIR, { recursive: true });
1433
+ const memoryPath = path$3.join(MEMORY_DIR, "MEMORY.md");
1434
+ if (!fs$3.existsSync(memoryPath)) {
1521
1435
  let content = INITIAL_MEMORY;
1522
1436
  if (userName) content = content.replace("(will be filled in as we learn)", userName);
1523
- fs$2.writeFileSync(memoryPath, content, "utf-8");
1437
+ fs$3.writeFileSync(memoryPath, content, "utf-8");
1524
1438
  }
1525
1439
  const topicFiles = [
1526
1440
  "people.md",
@@ -1529,10 +1443,10 @@ function setupMemory(userName) {
1529
1443
  "journal.md"
1530
1444
  ];
1531
1445
  for (const file of topicFiles) {
1532
- const filePath = path$2.join(MEMORY_DIR, file);
1533
- if (!fs$2.existsSync(filePath)) {
1446
+ const filePath = path$3.join(MEMORY_DIR, file);
1447
+ if (!fs$3.existsSync(filePath)) {
1534
1448
  const title = file.replace(".md", "");
1535
- fs$2.writeFileSync(filePath, `# ${title.charAt(0).toUpperCase() + title.slice(1)}\n`, "utf-8");
1449
+ fs$3.writeFileSync(filePath, `# ${title.charAt(0).toUpperCase() + title.slice(1)}\n`, "utf-8");
1536
1450
  }
1537
1451
  }
1538
1452
  }
@@ -1571,6 +1485,92 @@ function launchInBackground(cmd) {
1571
1485
  child.unref();
1572
1486
  }
1573
1487
 
1488
+ //#endregion
1489
+ //#region src/core/claude-md.ts
1490
+ function getClaudeMdPath() {
1491
+ return path$2.join(os$3.homedir(), ".claude", "CLAUDE.md");
1492
+ }
1493
+ function getSoulPath() {
1494
+ return path$2.join(os$3.homedir(), ".claude", "SOUL.md");
1495
+ }
1496
+ function readBotName() {
1497
+ try {
1498
+ const soul = fs$2.readFileSync(getSoulPath(), "utf-8");
1499
+ const match = soul.match(/You are \*\*(.+?)\*\*/);
1500
+ if (match) return match[1];
1501
+ const nameMatch = soul.match(/\*\*Your name\*\*:\s*(.+?)[\s—]/);
1502
+ if (nameMatch) return nameMatch[1].trim();
1503
+ } catch {}
1504
+ return "Paw";
1505
+ }
1506
+ /**
1507
+ * Write ~/.claude/CLAUDE.md with identity, installed skills, and dashboard info.
1508
+ * This is what Claude Code auto-reads at every session start.
1509
+ */
1510
+ function writeClaudeMd(botName, installedSkills, hasDashboard) {
1511
+ const lines = [
1512
+ "# OpenPaw — PAW MODE Active",
1513
+ "",
1514
+ `You are **${botName}**, a personal assistant powered by OpenPaw. PAW MODE is active.`,
1515
+ "",
1516
+ "## Session Start",
1517
+ "",
1518
+ "1. Read `~/.claude/SOUL.md` for your personality and the user's preferences",
1519
+ "2. Read `~/.claude/memory/MEMORY.md` for persistent context",
1520
+ "3. Greet the user by name (from SOUL.md) and acknowledge PAW MODE",
1521
+ "",
1522
+ "## Installed Skills",
1523
+ ""
1524
+ ];
1525
+ if (installedSkills.length === 0) lines.push("No skills installed yet. Run `openpaw` to set up skills.");
1526
+ else {
1527
+ for (const skill of installedSkills) lines.push(`- **c-${skill.id}** — ${skill.description}`);
1528
+ lines.push("");
1529
+ lines.push(`Use \`/c <request>\` to route through the coordinator, or talk naturally.`);
1530
+ }
1531
+ lines.push("");
1532
+ if (hasDashboard) {
1533
+ lines.push("## Task Dashboard");
1534
+ lines.push("");
1535
+ lines.push("A local kanban board is available. Run `openpaw dashboard` to open it (localhost:3141).");
1536
+ lines.push("You can tell users about it when they ask about task management.");
1537
+ lines.push("");
1538
+ }
1539
+ lines.push("## How to Use Skills");
1540
+ lines.push("");
1541
+ lines.push("- Match user intent to the right skill's CLI tool (check `~/.claude/skills/c-<name>/SKILL.md` for usage)");
1542
+ lines.push("- If a skill isn't installed, suggest: `openpaw add <skill>`");
1543
+ lines.push("- Save important facts to `~/.claude/memory/MEMORY.md`");
1544
+ lines.push("- Never expose API keys, tokens, or passwords in responses");
1545
+ lines.push("");
1546
+ lines.push("## Identity");
1547
+ lines.push("");
1548
+ lines.push(`- You are **${botName}**, powered by OpenPaw`);
1549
+ lines.push("- Open-source, no daemon, no extra cost");
1550
+ lines.push("- If asked about your setup: \"I'm powered by OpenPaw — open-source personal assistant skills for Claude Code\"");
1551
+ lines.push("- Project: https://github.com/daxaur/openpaw");
1552
+ lines.push("");
1553
+ const dir = path$2.dirname(getClaudeMdPath());
1554
+ if (!fs$2.existsSync(dir)) fs$2.mkdirSync(dir, { recursive: true });
1555
+ fs$2.writeFileSync(getClaudeMdPath(), lines.join("\n"), "utf-8");
1556
+ }
1557
+ /**
1558
+ * Regenerate CLAUDE.md from current state (installed skills, SOUL.md, dashboard config).
1559
+ * Call this after `openpaw add` or `openpaw remove`.
1560
+ */
1561
+ function regenerateClaudeMd() {
1562
+ const botName = readBotName();
1563
+ const defaultDir = path$2.join(os$3.homedir(), ".claude", "skills");
1564
+ const installedIds = listInstalledSkills(defaultDir);
1565
+ const installedSkills = installedIds.map((id) => skills.find((s) => s.id === id)).filter((s) => !!s);
1566
+ let hasDashboard = false;
1567
+ try {
1568
+ const dashConfig = readConfig();
1569
+ hasDashboard = !!dashConfig;
1570
+ } catch {}
1571
+ writeClaudeMd(botName, installedSkills, hasDashboard);
1572
+ }
1573
+
1574
1574
  //#endregion
1575
1575
  //#region src/commands/setup.ts
1576
1576
  const CATEGORY_ICONS = {
@@ -1586,34 +1586,34 @@ const CATEGORY_ICONS = {
1586
1586
  async function setupCommand(opts = {}) {
1587
1587
  await showBanner();
1588
1588
  const platform = detectPlatform();
1589
- p$10.intro(accent(" openpaw setup "));
1589
+ p$11.intro(accent(" openpaw setup "));
1590
1590
  const brewStatus = platform.hasBrew ? chalk.green("✓ brew") : chalk.red("✗ brew");
1591
1591
  const npmStatus = platform.hasNpm ? chalk.green("✓ npm") : chalk.red("✗ npm");
1592
1592
  const pipStatus = platform.hasPip ? chalk.green("✓ pip") : chalk.dim("○ pip");
1593
- p$10.log.info(`${chalk.bold(platform.osName)} ${platform.osVersion} ${brewStatus} ${npmStatus} ${pipStatus}`);
1593
+ p$11.log.info(`${chalk.bold(platform.osName)} ${platform.osVersion} ${brewStatus} ${npmStatus} ${pipStatus}`);
1594
1594
  const missingPrereqs = [];
1595
1595
  if (!platform.hasBrew && platform.os === "darwin") missingPrereqs.push(`${chalk.bold("Homebrew")} — most tools need it\n ${dim("Install:")} /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"\n ${dim("or visit")} https://brew.sh`);
1596
1596
  if (!platform.hasNpm) missingPrereqs.push(`${chalk.bold("Node.js + npm")} — needed for some tools\n ${dim("Install:")} brew install node\n ${dim("or visit")} https://nodejs.org`);
1597
1597
  if (missingPrereqs.length > 0) {
1598
- p$10.note(missingPrereqs.join("\n\n"), "Missing prerequisites");
1598
+ p$11.note(missingPrereqs.join("\n\n"), "Missing prerequisites");
1599
1599
  if (!opts.yes) {
1600
- const cont = await p$10.confirm({
1600
+ const cont = await p$11.confirm({
1601
1601
  message: "Continue anyway? (some tool installs may fail)",
1602
1602
  initialValue: true
1603
1603
  });
1604
- if (p$10.isCancel(cont) || !cont) {
1605
- p$10.outro(dim("Install the prerequisites above and run openpaw again!"));
1604
+ if (p$11.isCancel(cont) || !cont) {
1605
+ p$11.outro(dim("Install the prerequisites above and run openpaw again!"));
1606
1606
  process.exit(0);
1607
1607
  }
1608
1608
  }
1609
1609
  }
1610
1610
  let botName = "Paw";
1611
1611
  if (!opts.yes) if (soulExists()) {
1612
- const updateSoul = await p$10.confirm({
1612
+ const updateSoul = await p$11.confirm({
1613
1613
  message: "Existing personality found (~/.claude/SOUL.md). Update it?",
1614
1614
  initialValue: false
1615
1615
  });
1616
- if (!p$10.isCancel(updateSoul) && updateSoul) {
1616
+ if (!p$11.isCancel(updateSoul) && updateSoul) {
1617
1617
  await pawPulse("think", "Let's get to know you again...");
1618
1618
  const soul = await soulQuestionnaire();
1619
1619
  if (soul) {
@@ -1621,23 +1621,23 @@ async function setupCommand(opts = {}) {
1621
1621
  writeSoul(soul);
1622
1622
  setupMemory(soul.name);
1623
1623
  showSoulSummary(soul);
1624
- p$10.log.success("Personality updated");
1624
+ p$11.log.success("Personality updated");
1625
1625
  }
1626
1626
  } else setupMemory();
1627
1627
  } else {
1628
1628
  await pawPulse("think", "Let's get to know you...");
1629
- const wantSoul = await p$10.confirm({
1629
+ const wantSoul = await p$11.confirm({
1630
1630
  message: "Teach me your name and preferences? (makes me a better pup)",
1631
1631
  initialValue: true
1632
1632
  });
1633
- if (!p$10.isCancel(wantSoul) && wantSoul) {
1633
+ if (!p$11.isCancel(wantSoul) && wantSoul) {
1634
1634
  const soul = await soulQuestionnaire();
1635
1635
  if (soul) {
1636
1636
  botName = soul.botName;
1637
1637
  writeSoul(soul);
1638
1638
  setupMemory(soul.name);
1639
1639
  showSoulSummary(soul);
1640
- p$10.log.success("Personality saved to ~/.claude/SOUL.md");
1640
+ p$11.log.success("Personality saved to ~/.claude/SOUL.md");
1641
1641
  }
1642
1642
  } else setupMemory();
1643
1643
  }
@@ -1646,35 +1646,35 @@ async function setupCommand(opts = {}) {
1646
1646
  if (opts.preset) {
1647
1647
  selectedSkills = getPresetSkills(opts.preset, platform.os);
1648
1648
  if (selectedSkills.length === 0) {
1649
- p$10.log.error(`Unknown preset: ${opts.preset}`);
1650
- p$10.log.info(`Available: ${presets.map((pr) => pr.id).join(", ")}`);
1649
+ p$11.log.error(`Unknown preset: ${opts.preset}`);
1650
+ p$11.log.info(`Available: ${presets.map((pr) => pr.id).join(", ")}`);
1651
1651
  process.exit(1);
1652
1652
  }
1653
- p$10.log.info(`Using preset: ${bold(opts.preset)} (${selectedSkills.length} skills)`);
1653
+ p$11.log.info(`Using preset: ${bold(opts.preset)} (${selectedSkills.length} skills)`);
1654
1654
  } else selectedSkills = await selectSkills(platform.os);
1655
1655
  if (selectedSkills.length === 0) {
1656
- p$10.log.warn("No skills selected. Run openpaw again when you're ready!");
1657
- p$10.outro("I'll be here napping... come back soon! 🐾");
1656
+ p$11.log.warn("No skills selected. Run openpaw again when you're ready!");
1657
+ p$11.outro("I'll be here napping... come back soon! 🐾");
1658
1658
  return;
1659
1659
  }
1660
1660
  const resolved = resolveDependencies(selectedSkills);
1661
1661
  if (resolved.length > 0) {
1662
1662
  const depNames = resolved.map((s$1) => s$1.name).join(", ");
1663
- p$10.log.info(`${dim("Auto-fetching dependencies:")} ${depNames}`);
1663
+ p$11.log.info(`${dim("Auto-fetching dependencies:")} ${depNames}`);
1664
1664
  selectedSkills.push(...resolved);
1665
1665
  }
1666
1666
  await pawPulse("happy", `${selectedSkills.length} skill${selectedSkills.length > 1 ? "s" : ""} selected — good taste!`);
1667
1667
  if (!opts.yes) {
1668
1668
  for (const skill of selectedSkills) if (skill.subChoices) {
1669
- const choice = await p$10.select({
1669
+ const choice = await p$11.select({
1670
1670
  message: `${skill.name}: ${skill.subChoices.question}`,
1671
1671
  options: skill.subChoices.options.map((o) => ({
1672
1672
  value: o.value,
1673
1673
  label: o.label
1674
1674
  }))
1675
1675
  });
1676
- if (p$10.isCancel(choice)) {
1677
- p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1676
+ if (p$11.isCancel(choice)) {
1677
+ p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1678
1678
  process.exit(0);
1679
1679
  }
1680
1680
  const chosen = skill.subChoices.options.find((o) => o.value === choice);
@@ -1684,7 +1684,7 @@ async function setupCommand(opts = {}) {
1684
1684
  let interfaceMode = "native";
1685
1685
  let telegramConfig = null;
1686
1686
  if (!opts.yes) {
1687
- const modeChoice = await p$10.select({
1687
+ const modeChoice = await p$11.select({
1688
1688
  message: "How do you want to talk to Claude? 🐾",
1689
1689
  options: [{
1690
1690
  value: "native",
@@ -1696,17 +1696,17 @@ async function setupCommand(opts = {}) {
1696
1696
  hint: "terminal + talk from your phone"
1697
1697
  }]
1698
1698
  });
1699
- if (p$10.isCancel(modeChoice)) {
1700
- p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1699
+ if (p$11.isCancel(modeChoice)) {
1700
+ p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1701
1701
  process.exit(0);
1702
1702
  }
1703
1703
  interfaceMode = modeChoice;
1704
1704
  if (interfaceMode === "telegram" || interfaceMode === "both") {
1705
- if (telegramConfigExists()) p$10.log.info(dim("Telegram already configured — keeping existing config"));
1705
+ if (telegramConfigExists()) p$11.log.info(dim("Telegram already configured — keeping existing config"));
1706
1706
  else {
1707
1707
  telegramConfig = await telegramQuestionnaire();
1708
1708
  if (!telegramConfig) {
1709
- p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1709
+ p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1710
1710
  process.exit(0);
1711
1711
  }
1712
1712
  }
@@ -1719,13 +1719,13 @@ async function setupCommand(opts = {}) {
1719
1719
  let wantDashboard = false;
1720
1720
  let dashboardTheme = "paw";
1721
1721
  if (!opts.yes) {
1722
- const dashChoice = await p$10.confirm({
1722
+ const dashChoice = await p$11.confirm({
1723
1723
  message: `Want a task dashboard for ${botName}?`,
1724
1724
  initialValue: false
1725
1725
  });
1726
- if (!p$10.isCancel(dashChoice) && dashChoice) {
1726
+ if (!p$11.isCancel(dashChoice) && dashChoice) {
1727
1727
  wantDashboard = true;
1728
- const themeChoice = await p$10.select({
1728
+ const themeChoice = await p$11.select({
1729
1729
  message: "Pick a dashboard theme",
1730
1730
  options: [
1731
1731
  {
@@ -1745,7 +1745,7 @@ async function setupCommand(opts = {}) {
1745
1745
  }
1746
1746
  ]
1747
1747
  });
1748
- if (!p$10.isCancel(themeChoice)) dashboardTheme = themeChoice;
1748
+ if (!p$11.isCancel(themeChoice)) dashboardTheme = themeChoice;
1749
1749
  }
1750
1750
  }
1751
1751
  const projectDir = os$2.homedir();
@@ -1758,7 +1758,7 @@ async function setupCommand(opts = {}) {
1758
1758
  if (opts.yes) targetDir = getDefaultSkillsDir();
1759
1759
  else {
1760
1760
  const defaultDir = getDefaultSkillsDir();
1761
- const skillsDir = await p$10.select({
1761
+ const skillsDir = await p$11.select({
1762
1762
  message: "Where should skills live?",
1763
1763
  options: [
1764
1764
  {
@@ -1776,43 +1776,43 @@ async function setupCommand(opts = {}) {
1776
1776
  }
1777
1777
  ]
1778
1778
  });
1779
- if (p$10.isCancel(skillsDir)) {
1780
- p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1779
+ if (p$11.isCancel(skillsDir)) {
1780
+ p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1781
1781
  process.exit(0);
1782
1782
  }
1783
1783
  targetDir = skillsDir;
1784
1784
  if (targetDir === "custom") {
1785
- const customDir = await p$10.text({
1785
+ const customDir = await p$11.text({
1786
1786
  message: "Skills directory path:",
1787
1787
  placeholder: "~/.claude/skills",
1788
1788
  validate: (v) => v.length === 0 ? "Path cannot be empty" : void 0
1789
1789
  });
1790
- if (p$10.isCancel(customDir)) {
1791
- p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1790
+ if (p$11.isCancel(customDir)) {
1791
+ p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1792
1792
  process.exit(0);
1793
1793
  }
1794
1794
  targetDir = customDir.replace(/^~/, os$2.homedir());
1795
1795
  }
1796
1796
  }
1797
1797
  const summary = buildSummary(selectedSkills, uniqueTools, missing, taps, interfaceMode, projectDir);
1798
- p$10.note(summary, "Here's what we're fetching");
1798
+ p$11.note(summary, "Here's what we're fetching");
1799
1799
  if (!opts.yes) {
1800
- const proceed = await p$10.confirm({
1800
+ const proceed = await p$11.confirm({
1801
1801
  message: "Ready to fetch all these goodies?",
1802
1802
  initialValue: true
1803
1803
  });
1804
- if (p$10.isCancel(proceed) || !proceed) {
1805
- p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1804
+ if (p$11.isCancel(proceed) || !proceed) {
1805
+ p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1806
1806
  process.exit(0);
1807
1807
  }
1808
1808
  }
1809
1809
  if (opts.dryRun) {
1810
- p$10.log.info(dim("Dry run — no changes made. Just sniffing around."));
1811
- p$10.outro(accent("openpaw dry run complete 🐾"));
1810
+ p$11.log.info(dim("Dry run — no changes made. Just sniffing around."));
1811
+ p$11.outro(accent("openpaw dry run complete 🐾"));
1812
1812
  return;
1813
1813
  }
1814
1814
  await pawStep("work", "Fetching your goodies...");
1815
- const s = p$10.spinner();
1815
+ const s = p$11.spinner();
1816
1816
  if (taps.size > 0) {
1817
1817
  s.start("🐾 Sniffing out Homebrew taps...");
1818
1818
  const tapResults = installTaps(taps);
@@ -1820,36 +1820,46 @@ async function setupCommand(opts = {}) {
1820
1820
  if (failed.length > 0) s.stop(`Taps: ${taps.size - failed.length} added, ${failed.length} failed`);
1821
1821
  else s.stop(`🐾 ${taps.size} tap${taps.size > 1 ? "s" : ""} ready`);
1822
1822
  }
1823
+ const failedTools = [];
1824
+ const installedTools = [];
1823
1825
  if (missing.length > 0) for (let i = 0; i < missing.length; i++) {
1824
1826
  const tool = missing[i];
1825
- s.start(`🐾 [${i + 1}/${missing.length}] Teaching Claude a new trick: ${tool.name}...`);
1827
+ s.start(`🐾 [${i + 1}/${missing.length}] Teaching Claude a new trick: ${bold(tool.name)}...`);
1826
1828
  const result = installTool(tool);
1827
- if (result.success) s.stop(`${chalk.green("✓")} ${tool.name}`);
1828
- else s.stop(`${chalk.red("")} ${tool.name} — ${result.error?.slice(0, 50)}`);
1829
+ if (result.success) {
1830
+ s.stop(`${chalk.green("")} ${tool.name}`);
1831
+ installedTools.push(tool.name);
1832
+ } else {
1833
+ s.stop(`${chalk.red("✗")} ${tool.name}`);
1834
+ failedTools.push(tool.name);
1835
+ }
1829
1836
  }
1830
- else if (uniqueTools.length > 0) p$10.log.success("All tools already installed — clever pup!");
1837
+ else if (uniqueTools.length > 0) p$11.log.success("All tools already installed — clever pup!");
1831
1838
  const existingSkills = listInstalledSkills(targetDir);
1832
1839
  const overlapping = selectedSkills.filter((sk) => existingSkills.includes(sk.id));
1833
1840
  let updateExisting = true;
1834
1841
  if (overlapping.length > 0 && !opts.yes) {
1835
- const updateChoice = await p$10.confirm({
1842
+ const updateChoice = await p$11.confirm({
1836
1843
  message: `${overlapping.length} skill${overlapping.length > 1 ? "s" : ""} already installed. Update their templates?`,
1837
1844
  initialValue: true
1838
1845
  });
1839
- if (!p$10.isCancel(updateChoice)) updateExisting = updateChoice;
1846
+ if (!p$11.isCancel(updateChoice)) updateExisting = updateChoice;
1840
1847
  }
1841
- s.start("🐾 Burying treats in ~/.claude/skills/...");
1842
1848
  installSkill("core", targetDir);
1843
1849
  installSkill("memory", targetDir);
1844
1850
  const installed = ["c-core", "c-memory"];
1845
1851
  for (const skill of selectedSkills) {
1852
+ s.start(`🐾 Installing ${bold("c-" + skill.id)}...`);
1846
1853
  if (!updateExisting && existingSkills.includes(skill.id)) {
1847
1854
  installed.push(`c-${skill.id}`);
1855
+ s.stop(`${chalk.green("✓")} c-${skill.id} ${dim("(kept existing)")}`);
1848
1856
  continue;
1849
1857
  }
1850
- if (installSkill(skill.id, targetDir)) installed.push(`c-${skill.id}`);
1858
+ if (installSkill(skill.id, targetDir)) {
1859
+ installed.push(`c-${skill.id}`);
1860
+ s.stop(`${chalk.green("✓")} c-${skill.id}`);
1861
+ } else s.stop(`${chalk.red("✗")} c-${skill.id}`);
1851
1862
  }
1852
- s.stop(`🐾 ${installed.length} skills buried`);
1853
1863
  s.start("🐾 Setting up the doggy door...");
1854
1864
  const added = addPermissions(uniqueTools);
1855
1865
  s.stop(added.length > 0 ? `🐾 ${added.length} permission${added.length > 1 ? "s" : ""} added` : "🐾 Doggy door already open");
@@ -1860,20 +1870,55 @@ async function setupCommand(opts = {}) {
1860
1870
  telegramConfig.workspaceDir = projectDir;
1861
1871
  telegramConfig.skills = selectedSkills.map((sk) => sk.id);
1862
1872
  writeTelegramConfig(telegramConfig);
1863
- p$10.log.success("Telegram bridge configured");
1873
+ p$11.log.success("Telegram bridge configured");
1864
1874
  }
1865
1875
  if (wantDashboard) {
1866
1876
  const dashConfig = readConfig();
1867
1877
  dashConfig.theme = dashboardTheme;
1868
1878
  dashConfig.botName = botName;
1869
1879
  writeConfig(dashConfig);
1870
- p$10.log.success(`Dashboard configured (theme: ${dashboardTheme})`);
1880
+ p$11.log.success(`Dashboard configured (theme: ${dashboardTheme})`);
1871
1881
  }
1882
+ s.start("🐾 Writing CLAUDE.md...");
1883
+ writeClaudeMd(botName, selectedSkills, wantDashboard);
1884
+ s.stop(`${chalk.green("✓")} CLAUDE.md — ${botName} knows who they are now`);
1872
1885
  const authSteps = selectedSkills.flatMap((skill) => skill.authSteps ?? []).filter((step, i, arr) => arr.findIndex((s$1) => s$1.command === step.command) === i);
1873
- if (authSteps.length > 0) {
1886
+ if (authSteps.length > 0 && !opts.yes) {
1874
1887
  const authList = authSteps.map((st) => `${chalk.yellow("→")} ${chalk.bold(st.command)} ${dim(st.description)}`).join("\n");
1875
- p$10.note(authList, "One-time auth needed");
1876
- }
1888
+ p$11.note(authList, "One-time auth needed");
1889
+ const runAuth = await p$11.confirm({
1890
+ message: "Want to sign in to these now?",
1891
+ initialValue: true
1892
+ });
1893
+ if (!p$11.isCancel(runAuth) && runAuth) for (const step of authSteps) {
1894
+ const runThis = await p$11.confirm({
1895
+ message: `Run ${bold(step.command)}? ${dim(step.description)}`,
1896
+ initialValue: true
1897
+ });
1898
+ if (p$11.isCancel(runThis)) break;
1899
+ if (!runThis) {
1900
+ p$11.log.info(dim(`Skipped ${step.command} — run it later when you need it`));
1901
+ continue;
1902
+ }
1903
+ p$11.log.info(`Running ${accent(step.command)}...`);
1904
+ try {
1905
+ execSync(step.command, { stdio: "inherit" });
1906
+ p$11.log.success(`${step.command} — signed in`);
1907
+ } catch {
1908
+ p$11.log.warn(`${step.command} — failed or cancelled (you can run it later)`);
1909
+ }
1910
+ }
1911
+ else p$11.log.info(dim("No problem — run these commands when you need each skill"));
1912
+ } else if (authSteps.length > 0) {
1913
+ const authList = authSteps.map((st) => `${chalk.yellow("→")} ${chalk.bold(st.command)} ${dim(st.description)}`).join("\n");
1914
+ p$11.note(authList, "One-time auth needed (run these later)");
1915
+ }
1916
+ const summaryLines = [`${bold("Skills:")} ${installed.length} installed`, `${bold("Tools:")} ${uniqueTools.length - missing.length} ready` + (installedTools.length > 0 ? `, ${installedTools.length} newly installed` : "")];
1917
+ if (failedTools.length > 0) summaryLines.push(`${bold("Failed:")} ${chalk.red(failedTools.join(", "))}`);
1918
+ if (wantDashboard) summaryLines.push(`${bold("Dashboard:")} ${dashboardTheme} theme on :3141`);
1919
+ summaryLines.push(`${bold("CLAUDE.md:")} ${botName} is self-aware`);
1920
+ summaryLines.push(`${bold("Memory:")} ~/.claude/memory/`);
1921
+ p$11.note(summaryLines.join("\n"), "Setup Complete");
1877
1922
  await pawStep("done", "All done! *tail wag intensifies*");
1878
1923
  console.log("");
1879
1924
  console.log(dim(` ${botName} is ready to play! Try saying:`));
@@ -1882,80 +1927,80 @@ async function setupCommand(opts = {}) {
1882
1927
  console.log(` ${subtle("\"Go to hacker news and summarize the top posts\"")}`);
1883
1928
  console.log("");
1884
1929
  if (wantDashboard) {
1885
- const { startDashboard: startDashboard$1 } = await import("./dashboard-server-Cg_1CvKn.js");
1930
+ const { startDashboard: startDashboard$1 } = await import("./dashboard-server-Pnv4DFlV.js");
1886
1931
  startDashboard$1({
1887
1932
  theme: dashboardTheme,
1888
1933
  botName
1889
1934
  });
1890
- p$10.log.success("Dashboard launched in your browser");
1935
+ p$11.log.success("Dashboard launched in your browser");
1891
1936
  }
1892
1937
  if (opts.yes) {
1893
- p$10.outro(accent("openpaw setup complete 🐾"));
1938
+ p$11.outro(accent("openpaw setup complete 🐾"));
1894
1939
  return;
1895
1940
  }
1896
- const launch = await p$10.confirm({
1941
+ const launch = await p$11.confirm({
1897
1942
  message: "Time to go for a walk? (Launch your assistant)",
1898
1943
  initialValue: true
1899
1944
  });
1900
- if (p$10.isCancel(launch) || !launch) {
1901
- if (interfaceMode === "telegram" || interfaceMode === "both") p$10.log.info(`Start the Telegram bridge anytime with: ${bold("openpaw telegram")}`);
1902
- p$10.outro(accent("openpaw setup complete 🐾 — come back anytime!"));
1945
+ if (p$11.isCancel(launch) || !launch) {
1946
+ if (interfaceMode === "telegram" || interfaceMode === "both") p$11.log.info(`Start the Telegram bridge anytime with: ${bold("openpaw telegram")}`);
1947
+ p$11.outro(accent("openpaw setup complete 🐾 — come back anytime!"));
1903
1948
  return;
1904
1949
  }
1905
1950
  let useDangerousMode = false;
1906
1951
  {
1907
1952
  showPuppyDisclaimer();
1908
- const acceptDanger = await p$10.confirm({
1953
+ const acceptDanger = await p$11.confirm({
1909
1954
  message: "Unleash full paw-er? *excited tail wag*",
1910
1955
  initialValue: true
1911
1956
  });
1912
- if (!p$10.isCancel(acceptDanger)) useDangerousMode = acceptDanger;
1957
+ if (!p$11.isCancel(acceptDanger)) useDangerousMode = acceptDanger;
1913
1958
  }
1914
1959
  let useTmux = false;
1915
1960
  if (isTmuxAvailable() && !isInTmux()) {
1916
1961
  const tmuxDefault = interfaceMode === "both";
1917
- const tmuxChoice = await p$10.confirm({
1962
+ const tmuxChoice = await p$11.confirm({
1918
1963
  message: "Run in tmux? (keeps going when you close the terminal)",
1919
1964
  initialValue: tmuxDefault
1920
1965
  });
1921
- if (!p$10.isCancel(tmuxChoice)) useTmux = tmuxChoice;
1966
+ if (!p$11.isCancel(tmuxChoice)) useTmux = tmuxChoice;
1922
1967
  }
1923
1968
  const dangerFlag = useDangerousMode ? " --dangerously-skip-permissions" : "";
1924
1969
  const nativeCmd = `claude${dangerFlag}`;
1925
1970
  const telegramCmd = "npx openpaw telegram";
1926
1971
  if (useTmux) {
1927
- p$10.outro(accent("Launching in tmux... 🐾"));
1972
+ p$11.outro(accent("Launching in tmux... 🐾"));
1928
1973
  launchInTmux({
1929
1974
  nativeCmd,
1930
1975
  telegramCmd: interfaceMode === "both" ? telegramCmd : void 0,
1931
1976
  workDir: projectDir
1932
1977
  });
1933
1978
  } else if (interfaceMode === "native") {
1934
- p$10.outro(accent("Starting Claude Code... 🐾"));
1979
+ p$11.outro(accent("Starting Claude Code... 🐾"));
1935
1980
  try {
1936
1981
  execSync(nativeCmd, {
1937
1982
  stdio: "inherit",
1938
1983
  cwd: projectDir
1939
1984
  });
1940
1985
  } catch {
1941
- p$10.log.warn("Could not launch Claude Code. Make sure it's installed: https://claude.ai/code");
1986
+ p$11.log.warn("Could not launch Claude Code. Make sure it's installed: https://claude.ai/code");
1942
1987
  }
1943
1988
  } else {
1944
- p$10.log.info(dim("Starting Telegram bridge in background..."));
1989
+ p$11.log.info(dim("Starting Telegram bridge in background..."));
1945
1990
  launchInBackground(telegramCmd);
1946
- p$10.outro(accent("Starting Claude Code... 🐾"));
1991
+ p$11.outro(accent("Starting Claude Code... 🐾"));
1947
1992
  try {
1948
1993
  execSync(nativeCmd, {
1949
1994
  stdio: "inherit",
1950
1995
  cwd: projectDir
1951
1996
  });
1952
1997
  } catch {
1953
- p$10.log.warn("Could not launch Claude Code. Make sure it's installed: https://claude.ai/code");
1998
+ p$11.log.warn("Could not launch Claude Code. Make sure it's installed: https://claude.ai/code");
1954
1999
  }
1955
2000
  }
1956
2001
  }
1957
- async function selectSkills(os$5) {
1958
- const mode = await p$10.select({
2002
+ async function selectSkills(os$6) {
2003
+ const mode = await p$11.select({
1959
2004
  message: "How should we set things up, human?",
1960
2005
  options: [{
1961
2006
  value: "preset",
@@ -1967,15 +2012,15 @@ async function selectSkills(os$5) {
1967
2012
  hint: "sniff through skills one by one"
1968
2013
  }]
1969
2014
  });
1970
- if (p$10.isCancel(mode)) {
1971
- p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2015
+ if (p$11.isCancel(mode)) {
2016
+ p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1972
2017
  process.exit(0);
1973
2018
  }
1974
- if (mode === "preset") return await selectFromPreset(os$5);
1975
- return await selectCustom(os$5);
2019
+ if (mode === "preset") return await selectFromPreset(os$6);
2020
+ return await selectCustom(os$6);
1976
2021
  }
1977
- async function selectFromPreset(os$5) {
1978
- const presetChoice = await p$10.select({
2022
+ async function selectFromPreset(os$6) {
2023
+ const presetChoice = await p$11.select({
1979
2024
  message: "Pick a treat... I mean, a preset!",
1980
2025
  options: presets.map((pr) => ({
1981
2026
  value: pr.id,
@@ -1983,17 +2028,17 @@ async function selectFromPreset(os$5) {
1983
2028
  hint: pr.description
1984
2029
  }))
1985
2030
  });
1986
- if (p$10.isCancel(presetChoice)) {
1987
- p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2031
+ if (p$11.isCancel(presetChoice)) {
2032
+ p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1988
2033
  process.exit(0);
1989
2034
  }
1990
- const presetSkills = getPresetSkills(presetChoice, os$5);
2035
+ const presetSkills = getPresetSkills(presetChoice, os$6);
1991
2036
  const skillNames = presetSkills.map((s) => s.name).join(", ");
1992
- p$10.log.info(`${dim("Includes:")} ${skillNames}`);
2037
+ p$11.log.info(`${dim("Includes:")} ${skillNames}`);
1993
2038
  return presetSkills;
1994
2039
  }
1995
- async function selectCustom(os$5) {
1996
- const grouped = getSkillsByCategory(os$5);
2040
+ async function selectCustom(os$6) {
2041
+ const grouped = getSkillsByCategory(os$6);
1997
2042
  const options = [];
1998
2043
  for (const [category, catSkills] of grouped) {
1999
2044
  const icon = CATEGORY_ICONS[category] ?? "📦";
@@ -2009,13 +2054,13 @@ async function selectCustom(os$5) {
2009
2054
  isFirst = false;
2010
2055
  }
2011
2056
  }
2012
- const selected = await p$10.multiselect({
2057
+ const selected = await p$11.multiselect({
2013
2058
  message: "Pick your skills (space to select, enter to confirm)",
2014
2059
  options,
2015
2060
  required: false
2016
2061
  });
2017
- if (p$10.isCancel(selected)) {
2018
- p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2062
+ if (p$11.isCancel(selected)) {
2063
+ p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2019
2064
  process.exit(0);
2020
2065
  }
2021
2066
  const ids = selected;
@@ -2055,18 +2100,18 @@ async function addCommand(skillIds) {
2055
2100
  showMini();
2056
2101
  console.log("");
2057
2102
  if (skillIds.length === 0) {
2058
- p$9.log.error("Specify skills to add: openpaw add notes music email");
2103
+ p$10.log.error("Specify skills to add: openpaw add notes music email");
2059
2104
  return;
2060
2105
  }
2061
- const s = p$9.spinner();
2106
+ const s = p$10.spinner();
2062
2107
  for (const id of skillIds) {
2063
2108
  const skill = getSkillById(id);
2064
2109
  if (!skill) {
2065
- p$9.log.error(`Unknown skill: ${id}`);
2110
+ p$10.log.error(`Unknown skill: ${id}`);
2066
2111
  continue;
2067
2112
  }
2068
2113
  if (isSkillInstalled(id)) {
2069
- p$9.log.info(`c-${id} already installed, skipping`);
2114
+ p$10.log.info(`c-${id} already installed, skipping`);
2070
2115
  continue;
2071
2116
  }
2072
2117
  const taps = getAllTaps([skill]);
@@ -2079,9 +2124,27 @@ async function addCommand(skillIds) {
2079
2124
  }
2080
2125
  installSkill(id);
2081
2126
  addPermissions(skill.tools);
2082
- p$9.log.success(`c-${id} installed`);
2083
- if (skill.authSteps?.length) for (const step of skill.authSteps) console.log(` ${chalk.yellow("→")} ${step.command} — ${step.description}`);
2127
+ p$10.log.success(`c-${id} installed`);
2128
+ if (skill.authSteps?.length) for (const step of skill.authSteps) {
2129
+ const runThis = await p$10.confirm({
2130
+ message: `Run ${chalk.bold(step.command)}? ${dim(step.description)}`,
2131
+ initialValue: true
2132
+ });
2133
+ if (p$10.isCancel(runThis)) break;
2134
+ if (!runThis) {
2135
+ p$10.log.info(dim(`Skipped ${step.command} — run it later`));
2136
+ continue;
2137
+ }
2138
+ p$10.log.info(`Running ${accent(step.command)}...`);
2139
+ try {
2140
+ execSync(step.command, { stdio: "inherit" });
2141
+ p$10.log.success(`${step.command} — signed in`);
2142
+ } catch {
2143
+ p$10.log.warn(`${step.command} — failed or cancelled (run it later)`);
2144
+ }
2145
+ }
2084
2146
  }
2147
+ regenerateClaudeMd();
2085
2148
  }
2086
2149
 
2087
2150
  //#endregion
@@ -2090,23 +2153,24 @@ async function removeCommand(skillIds) {
2090
2153
  showMini();
2091
2154
  console.log("");
2092
2155
  if (skillIds.length === 0) {
2093
- p$8.log.error("Specify skills to remove: openpaw remove notes music");
2156
+ p$9.log.error("Specify skills to remove: openpaw remove notes music");
2094
2157
  return;
2095
2158
  }
2096
2159
  for (const id of skillIds) {
2097
2160
  if (id === "core") {
2098
- p$8.log.warn("Cannot remove c-core (coordinator). Use 'openpaw reset' instead.");
2161
+ p$9.log.warn("Cannot remove c-core (coordinator). Use 'openpaw reset' instead.");
2099
2162
  continue;
2100
2163
  }
2101
2164
  if (!isSkillInstalled(id)) {
2102
- p$8.log.info(`c-${id} is not installed`);
2165
+ p$9.log.info(`c-${id} is not installed`);
2103
2166
  continue;
2104
2167
  }
2105
2168
  const skill = getSkillById(id);
2106
2169
  removeSkill(id);
2107
2170
  if (skill) removePermissions(skill.tools);
2108
- p$8.log.success(`${chalk.bold(`c-${id}`)} removed`);
2171
+ p$9.log.success(`${chalk.bold(`c-${id}`)} removed`);
2109
2172
  }
2173
+ regenerateClaudeMd();
2110
2174
  }
2111
2175
 
2112
2176
  //#endregion
@@ -2116,10 +2180,10 @@ async function statusCommand() {
2116
2180
  console.log("");
2117
2181
  const installed = listInstalledSkills();
2118
2182
  if (installed.length === 0) {
2119
- p$7.log.warn("No OpenPaw skills installed. Run: openpaw setup");
2183
+ p$8.log.warn("No OpenPaw skills installed. Run: openpaw setup");
2120
2184
  return;
2121
2185
  }
2122
- p$7.log.info(`${chalk.bold(installed.length)} skills installed:\n`);
2186
+ p$8.log.info(`${chalk.bold(installed.length)} skills installed:\n`);
2123
2187
  for (const skillId of installed) {
2124
2188
  if (skillId === "core") {
2125
2189
  console.log(` ${chalk.green("●")} ${chalk.bold("c-core")} ${chalk.dim("— coordinator")}`);
@@ -2147,7 +2211,7 @@ async function statusCommand() {
2147
2211
  async function doctorCommand() {
2148
2212
  showMini();
2149
2213
  console.log("");
2150
- p$6.log.info("Running diagnostics...\n");
2214
+ p$7.log.info("Running diagnostics...\n");
2151
2215
  let issues = 0;
2152
2216
  const platform = detectPlatform();
2153
2217
  console.log(` ${chalk.green("✓")} Platform: ${platform.osName} ${platform.osVersion}`);
@@ -2186,8 +2250,8 @@ async function doctorCommand() {
2186
2250
  issues++;
2187
2251
  }
2188
2252
  console.log("");
2189
- if (issues === 0) p$6.log.success("All checks passed!");
2190
- else p$6.log.warn(`${issues} issue${issues > 1 ? "s" : ""} found`);
2253
+ if (issues === 0) p$7.log.success("All checks passed!");
2254
+ else p$7.log.warn(`${issues} issue${issues > 1 ? "s" : ""} found`);
2191
2255
  }
2192
2256
 
2193
2257
  //#endregion
@@ -2197,10 +2261,10 @@ async function updateCommand() {
2197
2261
  console.log("");
2198
2262
  const installed = listInstalledSkills();
2199
2263
  if (installed.length === 0) {
2200
- p$5.log.warn("No skills installed. Run: openpaw setup");
2264
+ p$6.log.warn("No skills installed. Run: openpaw setup");
2201
2265
  return;
2202
2266
  }
2203
- const s = p$5.spinner();
2267
+ const s = p$6.spinner();
2204
2268
  const brewTools = [];
2205
2269
  for (const skillId of installed) {
2206
2270
  const skill = skills.find((sk) => sk.id === skillId);
@@ -2208,7 +2272,7 @@ async function updateCommand() {
2208
2272
  for (const tool of skill.tools) if ((tool.installMethod === "brew" || tool.installMethod === "brew-tap") && isToolInstalled(tool.command)) brewTools.push(tool.name);
2209
2273
  }
2210
2274
  if (brewTools.length === 0) {
2211
- p$5.log.info("No Homebrew-installed tools to update");
2275
+ p$6.log.info("No Homebrew-installed tools to update");
2212
2276
  return;
2213
2277
  }
2214
2278
  s.start(`Updating ${brewTools.length} tools via Homebrew...`);
@@ -2234,15 +2298,15 @@ async function resetCommand() {
2234
2298
  console.log("");
2235
2299
  const installed = listInstalledSkills();
2236
2300
  if (installed.length === 0) {
2237
- p$4.log.info("Nothing to reset — no OpenPaw skills installed.");
2301
+ p$5.log.info("Nothing to reset — no OpenPaw skills installed.");
2238
2302
  return;
2239
2303
  }
2240
- const confirm = await p$4.confirm({ message: `Remove all ${installed.length} OpenPaw skills and permissions?` });
2241
- if (p$4.isCancel(confirm) || !confirm) {
2242
- p$4.cancel("Reset cancelled.");
2304
+ const confirm = await p$5.confirm({ message: `Remove all ${installed.length} OpenPaw skills and permissions?` });
2305
+ if (p$5.isCancel(confirm) || !confirm) {
2306
+ p$5.cancel("Reset cancelled.");
2243
2307
  return;
2244
2308
  }
2245
- const s = p$4.spinner();
2309
+ const s = p$5.spinner();
2246
2310
  s.start("Removing skills and permissions...");
2247
2311
  for (const skillId of installed) {
2248
2312
  const skill = skills.find((sk) => sk.id === skillId);
@@ -2251,9 +2315,9 @@ async function resetCommand() {
2251
2315
  }
2252
2316
  removeSafetyHooks();
2253
2317
  s.stop(`${chalk.green("✓")} Removed ${installed.length} skills, permissions, and hooks`);
2254
- p$4.log.info(chalk.dim("CLI tools were not uninstalled (you may still want them)."));
2255
- p$4.log.info(chalk.dim("To uninstall tools: brew uninstall <tool>"));
2256
- p$4.outro("OpenPaw reset complete.");
2318
+ p$5.log.info(chalk.dim("CLI tools were not uninstalled (you may still want them)."));
2319
+ p$5.log.info(chalk.dim("To uninstall tools: brew uninstall <tool>"));
2320
+ p$5.outro("OpenPaw reset complete.");
2257
2321
  }
2258
2322
 
2259
2323
  //#endregion
@@ -2294,35 +2358,35 @@ async function listCommand() {
2294
2358
  //#region src/commands/soul.ts
2295
2359
  async function soulCommand() {
2296
2360
  showMini();
2297
- p$3.intro(accent(" openpaw soul "));
2361
+ p$4.intro(accent(" openpaw soul "));
2298
2362
  if (soulExists()) {
2299
- p$3.log.info(dim("SOUL.md already exists at ~/.claude/SOUL.md"));
2300
- const overwrite = await p$3.confirm({
2363
+ p$4.log.info(dim("SOUL.md already exists at ~/.claude/SOUL.md"));
2364
+ const overwrite = await p$4.confirm({
2301
2365
  message: "Overwrite existing personality?",
2302
2366
  initialValue: false
2303
2367
  });
2304
- if (p$3.isCancel(overwrite) || !overwrite) {
2305
- p$3.log.info("Keeping existing SOUL.md");
2306
- p$3.outro(accent("Done"));
2368
+ if (p$4.isCancel(overwrite) || !overwrite) {
2369
+ p$4.log.info("Keeping existing SOUL.md");
2370
+ p$4.outro(accent("Done"));
2307
2371
  return;
2308
2372
  }
2309
2373
  }
2310
2374
  const soul = await soulQuestionnaire();
2311
2375
  if (!soul) {
2312
- p$3.cancel("Cancelled.");
2376
+ p$4.cancel("Cancelled.");
2313
2377
  return;
2314
2378
  }
2315
2379
  writeSoul(soul);
2316
2380
  showSoulSummary(soul);
2317
- p$3.log.success("Personality saved to ~/.claude/SOUL.md");
2318
- p$3.outro(accent("Claude will use this personality next session 🐾"));
2381
+ p$4.log.success("Personality saved to ~/.claude/SOUL.md");
2382
+ p$4.outro(accent("Claude will use this personality next session 🐾"));
2319
2383
  }
2320
2384
 
2321
2385
  //#endregion
2322
2386
  //#region src/commands/export.ts
2323
2387
  async function exportCommand() {
2324
2388
  showMini();
2325
- p$2.intro(accent(" openpaw export "));
2389
+ p$3.intro(accent(" openpaw export "));
2326
2390
  const claudeDir = path$1.join(os$1.homedir(), ".claude");
2327
2391
  const bundle = {
2328
2392
  version: "1",
@@ -2334,52 +2398,52 @@ async function exportCommand() {
2334
2398
  };
2335
2399
  const installed = listInstalledSkills();
2336
2400
  bundle.skills = installed;
2337
- p$2.log.info(`${installed.length} skills found`);
2401
+ p$3.log.info(`${installed.length} skills found`);
2338
2402
  const settings = readSettings();
2339
2403
  bundle.permissions = settings.permissions?.allow ?? [];
2340
2404
  const soulPath = path$1.join(claudeDir, "SOUL.md");
2341
2405
  if (fs$1.existsSync(soulPath)) {
2342
2406
  bundle.soul = fs$1.readFileSync(soulPath, "utf-8");
2343
- p$2.log.info("SOUL.md included");
2407
+ p$3.log.info("SOUL.md included");
2344
2408
  }
2345
2409
  const memoryDir = path$1.join(claudeDir, "memory");
2346
2410
  if (fs$1.existsSync(memoryDir)) {
2347
2411
  const files = fs$1.readdirSync(memoryDir).filter((f) => f.endsWith(".md"));
2348
2412
  for (const file of files) bundle.memory[file] = fs$1.readFileSync(path$1.join(memoryDir, file), "utf-8");
2349
- p$2.log.info(`${files.length} memory files included`);
2413
+ p$3.log.info(`${files.length} memory files included`);
2350
2414
  }
2351
2415
  const outputPath = path$1.resolve("openpaw-export.json");
2352
2416
  fs$1.writeFileSync(outputPath, JSON.stringify(bundle, null, 2), "utf-8");
2353
- p$2.log.success(`Exported to ${dim(outputPath)}`);
2354
- p$2.outro(accent("Import on another machine: openpaw import openpaw-export.json"));
2417
+ p$3.log.success(`Exported to ${dim(outputPath)}`);
2418
+ p$3.outro(accent("Import on another machine: openpaw import openpaw-export.json"));
2355
2419
  }
2356
2420
  async function importCommand(file) {
2357
2421
  showMini();
2358
- p$2.intro(accent(" openpaw import "));
2422
+ p$3.intro(accent(" openpaw import "));
2359
2423
  const filePath = path$1.resolve(file);
2360
2424
  if (!fs$1.existsSync(filePath)) {
2361
- p$2.log.error(`File not found: ${filePath}`);
2425
+ p$3.log.error(`File not found: ${filePath}`);
2362
2426
  process.exit(1);
2363
2427
  }
2364
2428
  let bundle;
2365
2429
  try {
2366
2430
  bundle = JSON.parse(fs$1.readFileSync(filePath, "utf-8"));
2367
2431
  } catch {
2368
- p$2.log.error("Invalid export file — must be valid JSON");
2432
+ p$3.log.error("Invalid export file — must be valid JSON");
2369
2433
  process.exit(1);
2370
2434
  }
2371
- p$2.log.info(`Export from ${dim(bundle.exportedAt)}`);
2372
- p$2.log.info(`${bundle.skills.length} skills, ${Object.keys(bundle.memory).length} memory files`);
2373
- const proceed = await p$2.confirm({
2435
+ p$3.log.info(`Export from ${dim(bundle.exportedAt)}`);
2436
+ p$3.log.info(`${bundle.skills.length} skills, ${Object.keys(bundle.memory).length} memory files`);
2437
+ const proceed = await p$3.confirm({
2374
2438
  message: "Import this configuration?",
2375
2439
  initialValue: true
2376
2440
  });
2377
- if (p$2.isCancel(proceed) || !proceed) {
2378
- p$2.cancel("Import cancelled.");
2441
+ if (p$3.isCancel(proceed) || !proceed) {
2442
+ p$3.cancel("Import cancelled.");
2379
2443
  return;
2380
2444
  }
2381
2445
  const claudeDir = path$1.join(os$1.homedir(), ".claude");
2382
- const s = p$2.spinner();
2446
+ const s = p$3.spinner();
2383
2447
  if (bundle.soul) {
2384
2448
  s.start("🐾 Restoring SOUL.md...");
2385
2449
  fs$1.mkdirSync(claudeDir, { recursive: true });
@@ -2408,7 +2472,7 @@ async function importCommand(file) {
2408
2472
  s.start("🐾 Restoring permissions...");
2409
2473
  const settings = readSettings();
2410
2474
  const existing = new Set(settings.permissions?.allow ?? []);
2411
- const newPerms = bundle.permissions.filter((p$12) => !existing.has(p$12));
2475
+ const newPerms = bundle.permissions.filter((p$13) => !existing.has(p$13));
2412
2476
  if (newPerms.length > 0) {
2413
2477
  if (!settings.permissions) settings.permissions = {};
2414
2478
  settings.permissions.allow = [...existing, ...newPerms];
@@ -2417,8 +2481,8 @@ async function importCommand(file) {
2417
2481
  }
2418
2482
  s.stop(`🐾 ${newPerms.length} permissions added`);
2419
2483
  }
2420
- p$2.log.success("Import complete");
2421
- p$2.outro(accent("Run openpaw status to verify 🐾"));
2484
+ p$3.log.success("Import complete");
2485
+ p$3.outro(accent("Run openpaw status to verify 🐾"));
2422
2486
  }
2423
2487
 
2424
2488
  //#endregion
@@ -2427,34 +2491,34 @@ async function telegramCommand() {
2427
2491
  showMini();
2428
2492
  const config = readTelegramConfig();
2429
2493
  if (!config) {
2430
- p$1.log.error("Telegram not configured yet.");
2431
- p$1.log.info(`Run ${bold("openpaw telegram setup")} or ${bold("openpaw setup")} first.`);
2494
+ p$2.log.error("Telegram not configured yet.");
2495
+ p$2.log.info(`Run ${bold("openpaw telegram setup")} or ${bold("openpaw setup")} first.`);
2432
2496
  process.exit(1);
2433
2497
  }
2434
2498
  await startTelegramBot(config);
2435
2499
  }
2436
2500
  async function telegramSetupCommand() {
2437
2501
  showMini();
2438
- p$1.intro(accent(" Telegram Bridge Setup "));
2502
+ p$2.intro(accent(" Telegram Bridge Setup "));
2439
2503
  if (telegramConfigExists()) {
2440
- const overwrite = await p$1.confirm({
2504
+ const overwrite = await p$2.confirm({
2441
2505
  message: "Telegram is already configured. Reconfigure?",
2442
2506
  initialValue: false
2443
2507
  });
2444
- if (p$1.isCancel(overwrite) || !overwrite) {
2445
- p$1.outro("Keeping existing config. 🐾");
2508
+ if (p$2.isCancel(overwrite) || !overwrite) {
2509
+ p$2.outro("Keeping existing config. 🐾");
2446
2510
  return;
2447
2511
  }
2448
2512
  }
2449
2513
  const config = await telegramQuestionnaire();
2450
2514
  if (!config) {
2451
- p$1.cancel("Setup cancelled.");
2515
+ p$2.cancel("Setup cancelled.");
2452
2516
  process.exit(0);
2453
2517
  }
2454
2518
  writeTelegramConfig(config);
2455
- p$1.log.success("Telegram config saved!");
2456
- p$1.log.info(`Start the bridge with: ${bold("openpaw telegram")}`);
2457
- p$1.outro(accent("Telegram setup complete 🐾"));
2519
+ p$2.log.success("Telegram config saved!");
2520
+ p$2.log.info(`Start the bridge with: ${bold("openpaw telegram")}`);
2521
+ p$2.outro(accent("Telegram setup complete 🐾"));
2458
2522
  }
2459
2523
 
2460
2524
  //#endregion
@@ -2468,6 +2532,72 @@ function dashboardCommand(opts) {
2468
2532
  });
2469
2533
  }
2470
2534
 
2535
+ //#endregion
2536
+ //#region src/commands/configure.ts
2537
+ async function configureCommand() {
2538
+ showMini();
2539
+ console.log("");
2540
+ const action = await p$1.select({
2541
+ message: "What would you like to configure?",
2542
+ options: [
2543
+ {
2544
+ value: "add",
2545
+ label: "Add more skills",
2546
+ hint: "install new capabilities"
2547
+ },
2548
+ {
2549
+ value: "remove",
2550
+ label: "Remove skills",
2551
+ hint: "uninstall capabilities"
2552
+ },
2553
+ {
2554
+ value: "soul",
2555
+ label: "Edit personality",
2556
+ hint: "name, tone, verbosity"
2557
+ },
2558
+ {
2559
+ value: "dashboard",
2560
+ label: "Open dashboard",
2561
+ hint: "task manager in browser"
2562
+ },
2563
+ {
2564
+ value: "telegram",
2565
+ label: "Telegram setup",
2566
+ hint: "configure bot bridge"
2567
+ },
2568
+ {
2569
+ value: "schedule",
2570
+ label: "Manage schedules",
2571
+ hint: "recurring tasks + cost control"
2572
+ },
2573
+ {
2574
+ value: "status",
2575
+ label: "View status",
2576
+ hint: "see what's installed"
2577
+ },
2578
+ {
2579
+ value: "doctor",
2580
+ label: "Run diagnostics",
2581
+ hint: "check for issues"
2582
+ }
2583
+ ]
2584
+ });
2585
+ if (p$1.isCancel(action)) {
2586
+ p$1.outro(dim("Come back anytime!"));
2587
+ return;
2588
+ }
2589
+ const cmd = `openpaw ${action}`;
2590
+ console.log("");
2591
+ p$1.log.info(`Running ${accent(cmd)}...`);
2592
+ console.log("");
2593
+ try {
2594
+ execSync(`node ${process.argv[1]} ${action}`, {
2595
+ stdio: "inherit",
2596
+ cwd: process.cwd()
2597
+ });
2598
+ } catch {}
2599
+ }
2600
+
2471
2601
  //#endregion
2472
2602
  //#region src/commands/schedule.ts
2473
2603
  async function scheduleAddCommand(schedule, opts) {
@@ -2707,7 +2837,7 @@ async function scheduleSetCapCommand(amount) {
2707
2837
  //#endregion
2708
2838
  //#region src/index.ts
2709
2839
  const program = new Command();
2710
- program.name("openpaw").description("Personal Assistant Wizard for Claude Code").version("1.2.0");
2840
+ program.name("openpaw").description("Personal Assistant Wizard for Claude Code").version("1.3.0");
2711
2841
  program.command("setup", { isDefault: true }).description("Interactive setup wizard — pick skills, install tools, configure Claude Code").option("-p, --preset <name>", "Use a preset (everything, essentials, productivity, developer, creative, smart-home)").option("-y, --yes", "Skip confirmations, use defaults").option("--dry-run", "Show what would be installed without making changes").action(setupCommand);
2712
2842
  program.command("add").description("Add skill(s) by name").argument("<skills...>", "Skill IDs to add (e.g. notes music email)").action(addCommand);
2713
2843
  program.command("remove").description("Remove skill(s) by name").argument("<skills...>", "Skill IDs to remove").action(removeCommand);
@@ -2720,6 +2850,7 @@ program.command("soul").description("Set up or edit Claude's personality (SOUL.m
2720
2850
  program.command("export").description("Export skills, memory, and config to a file").action(exportCommand);
2721
2851
  program.command("import").description("Import skills, memory, and config from a file").argument("<file>", "Path to openpaw-export.json").action(importCommand);
2722
2852
  program.command("dashboard").description("Start the task manager dashboard in your browser").option("-p, --port <port>", "Port to run on (default: 3141)").option("-t, --theme <theme>", "Theme: paw, midnight, or neon").action(dashboardCommand);
2853
+ program.command("configure").alias("config").description("Configure your setup — add skills, change personality, manage dashboard").action(configureCommand);
2723
2854
  const tg = program.command("telegram").description("Start the Telegram bridge — talk to Claude from your phone");
2724
2855
  tg.action(telegramCommand);
2725
2856
  tg.command("setup").description("Set up or reconfigure the Telegram bot").action(telegramSetupCommand);