pawmode 1.0.1 → 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
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { getDefaultSkillsDir, installSkill, isSkillInstalled, listInstalledSkills, removeSkill } from "./skills-CJ_pyPlv.js";
3
- import { addPermissions, readSettings, removePermissions, writeSettings } from "./permissions-BHOAvP8i.js";
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
+ import { getDefaultSkillsDir, installSkill, isSkillInstalled, listInstalledSkills, removeSkill } from "./skills-CMqq9k1-.js";
4
+ import { addPermissions, readSettings, removePermissions, writeSettings } from "./permissions-BlGEHCXO.js";
5
+ import { readConfig, startDashboard, writeConfig } from "./dashboard-server-BAyeozOa.js";
4
6
  import { Command } from "commander";
7
+ import * as p$12 from "@clack/prompts";
5
8
  import * as p$11 from "@clack/prompts";
6
9
  import * as p$10 from "@clack/prompts";
7
10
  import * as p$9 from "@clack/prompts";
@@ -14,7 +17,6 @@ import * as p$3 from "@clack/prompts";
14
17
  import * as p$2 from "@clack/prompts";
15
18
  import * as p$1 from "@clack/prompts";
16
19
  import * as p from "@clack/prompts";
17
- import * as os$6 from "node:os";
18
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";
@@ -33,9 +35,6 @@ import * as path$3 from "node:path";
33
35
  import * as path$2 from "node:path";
34
36
  import * as path$1 from "node:path";
35
37
  import path from "node:path";
36
- import { Bot } from "grammy";
37
- import { hydrate } from "@grammyjs/hydrate";
38
- import { query } from "@anthropic-ai/claude-agent-sdk";
39
38
 
40
39
  //#region src/catalog/index.ts
41
40
  const memo = {
@@ -84,13 +83,6 @@ const todoistCli = {
84
83
  installMethod: "brew",
85
84
  platforms: ["darwin", "linux"]
86
85
  };
87
- const thingsCli = {
88
- name: "things-cli",
89
- command: "things-cli",
90
- installCmd: "pipx install things-cli",
91
- installMethod: "pip",
92
- platforms: ["darwin"]
93
- };
94
86
  const taskwarrior = {
95
87
  name: "taskwarrior",
96
88
  command: "task",
@@ -335,6 +327,17 @@ const jiraCli = {
335
327
  installMethod: "brew",
336
328
  platforms: ["darwin", "linux"]
337
329
  };
330
+ const remotion = {
331
+ name: "remotion",
332
+ command: "remotion",
333
+ installCmd: "npm install -g remotion @remotion/cli",
334
+ installMethod: "npm",
335
+ platforms: [
336
+ "darwin",
337
+ "linux",
338
+ "win32"
339
+ ]
340
+ };
338
341
  const agentBrowser = {
339
342
  name: "agent-browser",
340
343
  command: "agent-browser",
@@ -432,22 +435,11 @@ const httpie = {
432
435
  "win32"
433
436
  ]
434
437
  };
435
- const llmCli = {
436
- name: "llm",
437
- command: "llm",
438
- installCmd: "brew install llm",
439
- installMethod: "brew",
440
- platforms: [
441
- "darwin",
442
- "linux",
443
- "win32"
444
- ]
445
- };
446
- const aichat = {
447
- name: "aichat",
448
- command: "aichat",
449
- installCmd: "brew install aichat",
450
- installMethod: "brew",
438
+ const curl = {
439
+ name: "curl",
440
+ command: "curl",
441
+ installCmd: "brew install curl",
442
+ installMethod: "builtin",
451
443
  platforms: [
452
444
  "darwin",
453
445
  "linux",
@@ -496,23 +488,15 @@ const skills = [
496
488
  platforms: ["darwin", "linux"],
497
489
  subChoices: {
498
490
  question: "Which task manager?",
499
- options: [
500
- {
501
- label: "Todoist",
502
- value: "todoist",
503
- tools: [todoistCli]
504
- },
505
- {
506
- label: "Things 3 (macOS)",
507
- value: "things",
508
- tools: [thingsCli]
509
- },
510
- {
511
- label: "Taskwarrior (local)",
512
- value: "taskwarrior",
513
- tools: [taskwarrior]
514
- }
515
- ]
491
+ options: [{
492
+ label: "Todoist",
493
+ value: "todoist",
494
+ tools: [todoistCli]
495
+ }, {
496
+ label: "Taskwarrior (local)",
497
+ value: "taskwarrior",
498
+ tools: [taskwarrior]
499
+ }]
516
500
  }
517
501
  },
518
502
  {
@@ -660,6 +644,14 @@ const skills = [
660
644
  "win32"
661
645
  ]
662
646
  },
647
+ {
648
+ id: "video-edit",
649
+ name: "Video Editing",
650
+ description: "Programmatic video creation with Remotion (React-based)",
651
+ category: "media",
652
+ tools: [remotion, ffmpeg],
653
+ platforms: ["darwin", "linux"]
654
+ },
663
655
  {
664
656
  id: "screen",
665
657
  name: "Screen & Vision",
@@ -713,6 +705,18 @@ const skills = [
713
705
  tools: [blucli],
714
706
  platforms: ["darwin"]
715
707
  },
708
+ {
709
+ id: "weather",
710
+ name: "Weather",
711
+ description: "Forecasts and conditions — current, hourly, multi-day via wttr.in",
712
+ category: "research",
713
+ tools: [curl],
714
+ platforms: [
715
+ "darwin",
716
+ "linux",
717
+ "win32"
718
+ ]
719
+ },
716
720
  {
717
721
  id: "research",
718
722
  name: "Web Research",
@@ -825,6 +829,14 @@ const skills = [
825
829
  description: "Configure Jira instance"
826
830
  }]
827
831
  },
832
+ {
833
+ id: "schedule",
834
+ name: "Smart Scheduling",
835
+ description: "Automate recurring Claude tasks with built-in cost control and Telegram delivery",
836
+ category: "automation",
837
+ tools: [],
838
+ platforms: ["darwin", "linux"]
839
+ },
828
840
  {
829
841
  id: "briefing",
830
842
  name: "Daily Briefing",
@@ -878,6 +890,30 @@ const skills = [
878
890
  tools: [lunchyGo],
879
891
  platforms: ["darwin"]
880
892
  },
893
+ {
894
+ id: "clipboard",
895
+ name: "Clipboard",
896
+ description: "Copy, paste, and transform clipboard content",
897
+ category: "system",
898
+ tools: [],
899
+ platforms: ["darwin", "linux"]
900
+ },
901
+ {
902
+ id: "contacts",
903
+ name: "Contacts",
904
+ description: "Search and look up contacts from macOS Address Book",
905
+ category: "system",
906
+ tools: [],
907
+ platforms: ["darwin"]
908
+ },
909
+ {
910
+ id: "timer",
911
+ name: "Timer & Pomodoro",
912
+ description: "Countdown timers, alarms, and pomodoro with native notifications",
913
+ category: "system",
914
+ tools: [terminalNotifier],
915
+ platforms: ["darwin"]
916
+ },
881
917
  {
882
918
  id: "system",
883
919
  name: "System Control",
@@ -938,47 +974,6 @@ const skills = [
938
974
  "linux",
939
975
  "win32"
940
976
  ]
941
- },
942
- {
943
- id: "ai",
944
- name: "AI / LLM",
945
- description: "Query LLMs from CLI — pipe text, chat, summarize with local or cloud models",
946
- category: "research",
947
- tools: [],
948
- platforms: [
949
- "darwin",
950
- "linux",
951
- "win32"
952
- ],
953
- subChoices: {
954
- question: "Which LLM CLI?",
955
- options: [
956
- {
957
- label: "llm (Simon Willison — 100+ models)",
958
- value: "llm",
959
- tools: [llmCli]
960
- },
961
- {
962
- label: "aichat (Rust — fast, multi-provider)",
963
- value: "aichat",
964
- tools: [aichat]
965
- },
966
- {
967
- label: "Both",
968
- value: "both",
969
- tools: [llmCli, aichat]
970
- }
971
- ]
972
- },
973
- authSteps: [{
974
- tool: "llm",
975
- command: "llm keys set openai",
976
- description: "Set LLM API key"
977
- }, {
978
- tool: "aichat",
979
- command: "aichat (follow setup prompts)",
980
- description: "Configure API key"
981
- }]
982
977
  }
983
978
  ];
984
979
  const categoryLabels = {
@@ -1001,12 +996,16 @@ const presets = [
1001
996
  {
1002
997
  id: "essentials",
1003
998
  name: "Essentials",
1004
- description: "Email, calendar, notes, music, browser, system",
999
+ description: "Email, calendar, notes, music, weather, clipboard, browser, system",
1005
1000
  skillIds: [
1006
1001
  "email",
1007
1002
  "calendar",
1008
1003
  "notes",
1009
1004
  "music",
1005
+ "weather",
1006
+ "clipboard",
1007
+ "contacts",
1008
+ "timer",
1010
1009
  "browser",
1011
1010
  "system",
1012
1011
  "notify"
@@ -1037,7 +1036,6 @@ const presets = [
1037
1036
  "jira",
1038
1037
  "browser",
1039
1038
  "network",
1040
- "ai",
1041
1039
  "cron"
1042
1040
  ]
1043
1041
  },
@@ -1069,7 +1067,7 @@ const presets = [
1069
1067
  }
1070
1068
  ];
1071
1069
  function getPresetSkills(presetId, platform) {
1072
- const preset = presets.find((p$12) => p$12.id === presetId);
1070
+ const preset = presets.find((p$13) => p$13.id === presetId);
1073
1071
  if (!preset) return [];
1074
1072
  const available = getSkillsForPlatform(platform);
1075
1073
  if (preset.id === "everything") return available;
@@ -1149,163 +1147,6 @@ function isToolInstalled(command) {
1149
1147
  return commandExists(command);
1150
1148
  }
1151
1149
 
1152
- //#endregion
1153
- //#region src/core/branding.ts
1154
- const accent = chalk.hex("#b4783c");
1155
- const subtle = chalk.hex("#8a5a2a");
1156
- const dim = chalk.dim;
1157
- const bold = chalk.bold;
1158
- const pawClr = chalk.hex("#b4783c");
1159
- const PAW_ART = [
1160
- " ▃▅",
1161
- " ▁██▁ ▄█▁",
1162
- " ▁▁▂▆▇██▃ ▅█▆",
1163
- " ▅▆█▇▆▄███▆▂ ▁▂▆▇██▇▅▁",
1164
- " ▁▃█▆▁ ▄███▄▁ ▆▇▇▅▄▄███▇▃▁",
1165
- " ▁▃█▄ ▁████▂ ▁▅█▃ ▁▃████▂",
1166
- " ▄█▇▁ ▂████▄▁ ▁▃█▇ ▁████▅▂",
1167
- " ▅█▆ ▂█████▁ ▄█▇▁ ▂████▃",
1168
- " ▂▁ ▆█▆ ▂█████▁ ▄█▇ ▁▂████▂",
1169
- " ▁▅█▂ ▆█▆▁ ▁▃▇████▆▁ ▄█▇ ▂▄█████▂",
1170
- " ▁██▄▂ ▂██▇▆▃▄▇██████ ▄██▄▇▇▃▃▆█████▁",
1171
- " ▂█████▇▅▂▁ ▁▄▇█████████▆▂ ▃▇████████████▁ ▁▂▁",
1172
- " ▄█▇▃ ▁███▆▁ ▁▃▆▇▇▇▇▃▄▂ ▂▆█████████▅ ▁▇█▂",
1173
- " ▃▆▄▁ ▅███▆▃ ▄▄▅▅▅▅▂▁ ▂▄▃▄██▂",
1174
- " ▆█▄ ▃████▄ ▁▄▄██████▄▃▃▂ ▁▂▆▇▇▆████▇▁",
1175
- " ▆█▄ ▂████▄ ▂▅▇▇▅▅▁▃▅█▇████▆▂ ▅▇▇▅▄▁ ▂▆████▂",
1176
- " ▇█▄ ▅████▄ ▇█▄ ▁▃▇▆████▇▂ ▂▃█▇▃ ▁ ▃███▂",
1177
- " ▇██▅ ▂▆████▄▁ ▆█▄▁ ▁ ▅▄▅█████▅ ▆█▆▂ ▃▆███▁",
1178
- " ▁████████████▃ ▂▇▆▂ ▄█████▆ ▃██▂ ▂▆███▆",
1179
- " ▃█████████▅▂ ██▂ ▄█████▆ ▅██▁ ▁████▇",
1180
- " ▂▄▄▄▄▄▄ ▇█▃▁ ▁▅█████▅ ▃██▄▇▆▁▁▄▇████▆",
1181
- " ▁▁▁▇█▂▁ ▁ ▄██████▆▂ ▇███████████▅▁",
1182
- " ▁▁▄▅▆██▅▂ ▁▁ ▄███████▄ ▄▅██████▆▅▂",
1183
- " ▄██▆▅▄▂▁ ▁ ▃▆██████▇▄▂ ▁▁▁▁▁▁▁",
1184
- " ▂██▄▁▁ ▁▄▅██████▇▆▂▁",
1185
- " ▃▇▇▂ ▁▁ ▁ ▃▇██████▇▃▁",
1186
- " ▁▄█▆ ▁ ▁▆█▇█████▆▂",
1187
- " ▄██▇ ▃▅███████▅▁",
1188
- " ▄██▇▁ ▂▂▂▁▁▂▆▇▇▇▇▇▃▁ ▄▆▄▂▇█████▃",
1189
- " ▃▇██▇▃ ▁▂▆▆██▆▇█████████▆▄▁▁ ▁▁▁▄▇██████▃",
1190
- " ▄█████▆▆▇███████████████████▆▃▁ ▁▄▇███████▃",
1191
- " ▁▅█████████████████████████████▅█████████▄▁",
1192
- " ▁▂▆█████████████████████████████████████▂",
1193
- " ▁▂▆▆▆▆▆▆▃▂▂▂▂▂▂▂▂▂▂▂▂▂▅▆███████████▇▆▁",
1194
- " ▃▃▇▇▇▇▇▇▇▃▃"
1195
- ];
1196
- const PAW_ROWS = PAW_ART.length;
1197
- function sleep(ms) {
1198
- return new Promise((r) => setTimeout(r, ms));
1199
- }
1200
- function renderBox(title, subtitle) {
1201
- const boxW = 48;
1202
- const center = (s, w) => {
1203
- const pad = w - s.length;
1204
- const left = Math.floor(pad / 2);
1205
- return " ".repeat(left) + s + " ".repeat(pad - left);
1206
- };
1207
- const margin = " ";
1208
- const lines = [
1209
- pawClr(margin + "┌" + "─".repeat(boxW) + "┐"),
1210
- pawClr(margin + "│" + " ".repeat(boxW) + "│"),
1211
- pawClr(margin + "│" + center(title, boxW) + "│"),
1212
- dim(margin + "│" + center(subtitle, boxW) + "│"),
1213
- pawClr(margin + "│" + " ".repeat(boxW) + "│"),
1214
- pawClr(margin + "└" + "─".repeat(boxW) + "┘")
1215
- ];
1216
- return lines.join("\n");
1217
- }
1218
- const MOOD_HEX = {
1219
- wave: "#b4783c",
1220
- think: "#b4783c",
1221
- happy: "#b4783c",
1222
- work: "#9a6832",
1223
- done: "#c88a48",
1224
- warn: "#dca03c"
1225
- };
1226
- function pawColor(mood) {
1227
- return chalk.hex(MOOD_HEX[mood]);
1228
- }
1229
- function renderPaw(color) {
1230
- return PAW_ART.map((line) => color(line)).join("\n");
1231
- }
1232
- /**
1233
- * Animated banner: fade in paw → pulse → title box.
1234
- */
1235
- async function showBanner() {
1236
- process.stdout.write("\x1B[?25l");
1237
- process.stdout.write(renderPaw(chalk.hex("#3d2810")) + "\n");
1238
- await sleep(60);
1239
- process.stdout.write(`\x1B[${PAW_ROWS}A\x1B[J`);
1240
- process.stdout.write(renderPaw(chalk.hex("#7a501e")) + "\n");
1241
- await sleep(60);
1242
- process.stdout.write(`\x1B[${PAW_ROWS}A\x1B[J`);
1243
- process.stdout.write(renderPaw(pawClr) + "\n");
1244
- await sleep(60);
1245
- process.stdout.write(`\x1B[${PAW_ROWS}A\x1B[J`);
1246
- process.stdout.write(renderPaw(chalk.hex("#d4984c")) + "\n");
1247
- await sleep(80);
1248
- process.stdout.write(`\x1B[${PAW_ROWS}A\x1B[J`);
1249
- process.stdout.write(renderPaw(pawClr) + "\n");
1250
- process.stdout.write("\x1B[?25h");
1251
- console.log("");
1252
- console.log(renderBox("O P E N P A W", "Personal Assistant Wizard for Claude Code"));
1253
- console.log("");
1254
- }
1255
- /**
1256
- * Show paw between wizard steps — mood-colored, brief flash, then clears.
1257
- */
1258
- async function pawStep(mood, message) {
1259
- const color = pawColor(mood);
1260
- process.stdout.write(renderPaw(color) + "\n");
1261
- if (message) console.log(` ${accent(message)}`);
1262
- await sleep(300);
1263
- const lines = PAW_ROWS + (message ? 1 : 0);
1264
- process.stdout.write(`\x1B[${lines}A\x1B[J`);
1265
- }
1266
- /**
1267
- * Inline pulse indicator for quick transitions.
1268
- */
1269
- async function pawPulse(mood, message) {
1270
- if (!message) return;
1271
- const line = ` ${accent("◉")} ${subtle(message)}`;
1272
- for (let i = 0; i < 3; i++) {
1273
- if (i > 0) process.stdout.write("\x1B[1A");
1274
- const s = i % 2 === 0 ? bold : dim;
1275
- process.stdout.write(`\x1B[2K${s(line)}\n`);
1276
- await sleep(80);
1277
- }
1278
- process.stdout.write(`\x1B[1A\x1B[2K${line}\n`);
1279
- }
1280
- /**
1281
- * Mini one-liner.
1282
- */
1283
- function showMini() {
1284
- console.log(accent(" ◉ openpaw") + dim(" — Personal Assistant Wizard for Claude Code"));
1285
- }
1286
- /**
1287
- * Puppy disclaimer about --dangerously-skip-permissions.
1288
- */
1289
- function showPuppyDisclaimer() {
1290
- console.log("");
1291
- console.log(pawClr(" /\\_/\\"));
1292
- console.log(pawClr(" ( o.o )") + ` ${bold("WOOF! One important sniff...")}`);
1293
- console.log(pawClr(" > ^ <"));
1294
- console.log("");
1295
- console.log(` ${accent("You're about to let Claude off the leash!")}`);
1296
- console.log(dim(" (--dangerously-skip-permissions)"));
1297
- console.log("");
1298
- console.log(" This lets Claude run commands without asking each time.");
1299
- console.log(" It's how your assistant actually gets things done —");
1300
- console.log(" checking email, playing music, managing files.");
1301
- console.log("");
1302
- console.log(dim(" OpenPaw's safety hooks still block the dangerous stuff"));
1303
- console.log(dim(" (mass deletes, credential leaks, etc)."));
1304
- console.log("");
1305
- console.log(dim(" You can always run 'claude' normally without this."));
1306
- console.log("");
1307
- }
1308
-
1309
1150
  //#endregion
1310
1151
  //#region src/core/installer.ts
1311
1152
  function getMissingTools(tools) {
@@ -1436,142 +1277,28 @@ function removeSafetyHooks() {
1436
1277
  }
1437
1278
  }
1438
1279
 
1439
- //#endregion
1440
- //#region src/core/mcp.ts
1441
- const mcpServers = [
1442
- {
1443
- id: "filesystem",
1444
- name: "Filesystem",
1445
- description: "Read, write, search, and manage files with advanced operations",
1446
- command: "npx",
1447
- args: [
1448
- "-y",
1449
- "@modelcontextprotocol/server-filesystem",
1450
- os$6.homedir()
1451
- ],
1452
- category: "system"
1453
- },
1454
- {
1455
- id: "fetch",
1456
- name: "Fetch",
1457
- description: "Fetch and convert web content to markdown for analysis",
1458
- command: "npx",
1459
- args: ["-y", "@modelcontextprotocol/server-fetch"],
1460
- category: "research"
1461
- },
1462
- {
1463
- id: "memory",
1464
- name: "Memory (KG)",
1465
- description: "Persistent knowledge graph memory — entities, relations, observations",
1466
- command: "npx",
1467
- args: ["-y", "@modelcontextprotocol/server-memory"],
1468
- category: "productivity"
1469
- },
1470
- {
1471
- id: "github",
1472
- name: "GitHub",
1473
- description: "Repos, PRs, issues, branches, file operations via GitHub API",
1474
- command: "npx",
1475
- args: ["-y", "@modelcontextprotocol/server-github"],
1476
- env: { GITHUB_PERSONAL_ACCESS_TOKEN: "" },
1477
- envPlaceholders: { GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_your_token_here" },
1478
- category: "developer"
1479
- },
1480
- {
1481
- id: "slack",
1482
- name: "Slack",
1483
- description: "Read/send Slack messages, manage channels, users, reactions",
1484
- command: "npx",
1485
- args: ["-y", "@modelcontextprotocol/server-slack"],
1486
- env: {
1487
- SLACK_BOT_TOKEN: "",
1488
- SLACK_TEAM_ID: ""
1489
- },
1490
- envPlaceholders: {
1491
- SLACK_BOT_TOKEN: "xoxb-your-token",
1492
- SLACK_TEAM_ID: "T00000000"
1493
- },
1494
- category: "communication"
1495
- },
1496
- {
1497
- id: "google-drive",
1498
- name: "Google Drive",
1499
- description: "Search and read Google Drive files, Docs, Sheets",
1500
- command: "npx",
1501
- args: ["-y", "@modelcontextprotocol/server-gdrive"],
1502
- category: "productivity"
1503
- },
1504
- {
1505
- id: "postgres",
1506
- name: "PostgreSQL",
1507
- description: "Query PostgreSQL databases with read-only access",
1508
- command: "npx",
1509
- args: ["-y", "@modelcontextprotocol/server-postgres"],
1510
- env: { POSTGRES_CONNECTION_STRING: "" },
1511
- envPlaceholders: { POSTGRES_CONNECTION_STRING: "postgresql://user:pass@localhost/db" },
1512
- category: "developer"
1513
- },
1514
- {
1515
- id: "brave-search",
1516
- name: "Brave Search",
1517
- description: "Web and local search using Brave Search API",
1518
- command: "npx",
1519
- args: ["-y", "@modelcontextprotocol/server-brave-search"],
1520
- env: { BRAVE_API_KEY: "" },
1521
- envPlaceholders: { BRAVE_API_KEY: "your_api_key" },
1522
- category: "research"
1523
- },
1524
- {
1525
- id: "puppeteer",
1526
- name: "Puppeteer",
1527
- description: "Browser automation — navigate, screenshot, interact with web pages",
1528
- command: "npx",
1529
- args: ["-y", "@modelcontextprotocol/server-puppeteer"],
1530
- category: "automation"
1531
- },
1532
- {
1533
- id: "sequential-thinking",
1534
- name: "Sequential Thinking",
1535
- description: "Step-by-step reasoning and problem-solving tool",
1536
- command: "npx",
1537
- args: ["-y", "@modelcontextprotocol/server-sequential-thinking"],
1538
- category: "research"
1539
- }
1540
- ];
1541
- function installMcpServer(server, envValues) {
1542
- const settings = readSettings();
1543
- if (!settings.mcpServers) settings.mcpServers = {};
1544
- const mcpSection = settings.mcpServers;
1545
- const config = {
1546
- command: server.command,
1547
- args: server.args
1548
- };
1549
- if (server.env) {
1550
- const env = {};
1551
- for (const [key, defaultVal] of Object.entries(server.env)) env[key] = envValues?.[key] ?? defaultVal;
1552
- config.env = env;
1553
- }
1554
- mcpSection[server.id] = config;
1555
- writeSettings(settings);
1556
- return true;
1557
- }
1558
-
1559
1280
  //#endregion
1560
1281
  //#region src/core/soul.ts
1561
- function getSoulPath() {
1282
+ function getSoulPath$1() {
1562
1283
  return path$4.join(os$5.homedir(), ".claude", "SOUL.md");
1563
1284
  }
1564
1285
  function soulExists() {
1565
- return fs$4.existsSync(getSoulPath());
1286
+ return fs$4.existsSync(getSoulPath$1());
1566
1287
  }
1567
1288
  async function soulQuestionnaire() {
1568
- const name = await p$11.text({
1569
- message: "What should Claude call you?",
1289
+ const name = await p$12.text({
1290
+ message: "What should your assistant call you?",
1570
1291
  placeholder: "Your name or nickname",
1571
1292
  validate: (v) => v.length === 0 ? "Name cannot be empty" : void 0
1572
1293
  });
1573
- if (p$11.isCancel(name)) return null;
1574
- const tone = await p$11.select({
1294
+ if (p$12.isCancel(name)) return null;
1295
+ const botName = await p$12.text({
1296
+ message: "Name your assistant:",
1297
+ placeholder: "Paw",
1298
+ defaultValue: "Paw"
1299
+ });
1300
+ if (p$12.isCancel(botName)) return null;
1301
+ const tone = await p$12.select({
1575
1302
  message: "Communication style?",
1576
1303
  options: [
1577
1304
  {
@@ -1591,8 +1318,8 @@ async function soulQuestionnaire() {
1591
1318
  }
1592
1319
  ]
1593
1320
  });
1594
- if (p$11.isCancel(tone)) return null;
1595
- const verbosity = await p$11.select({
1321
+ if (p$12.isCancel(tone)) return null;
1322
+ const verbosity = await p$12.select({
1596
1323
  message: "Response length?",
1597
1324
  options: [
1598
1325
  {
@@ -1612,21 +1339,22 @@ async function soulQuestionnaire() {
1612
1339
  }
1613
1340
  ]
1614
1341
  });
1615
- if (p$11.isCancel(verbosity)) return null;
1616
- const proactive = await p$11.confirm({
1342
+ if (p$12.isCancel(verbosity)) return null;
1343
+ const proactive = await p$12.confirm({
1617
1344
  message: "Should Claude suggest things proactively?",
1618
1345
  initialValue: true
1619
1346
  });
1620
- if (p$11.isCancel(proactive)) return null;
1621
- const extrasResult = await p$11.text({
1347
+ if (p$12.isCancel(proactive)) return null;
1348
+ const extrasResult = await p$12.text({
1622
1349
  message: "Any custom instructions? (optional)",
1623
1350
  placeholder: "e.g. always respond in Spanish, prefer dark humor, etc.",
1624
1351
  defaultValue: ""
1625
1352
  });
1626
- if (p$11.isCancel(extrasResult)) return null;
1353
+ if (p$12.isCancel(extrasResult)) return null;
1627
1354
  const extras = extrasResult.split(",").map((s) => s.trim()).filter(Boolean);
1628
1355
  return {
1629
1356
  name,
1357
+ botName: botName || "Paw",
1630
1358
  tone,
1631
1359
  verbosity,
1632
1360
  proactive,
@@ -1647,11 +1375,12 @@ function writeSoul(config) {
1647
1375
  const lines = [
1648
1376
  "# SOUL.md — OpenPaw Personality",
1649
1377
  "",
1650
- `You are ${config.name}'s personal assistant, powered by OpenPaw.`,
1378
+ `You are **${config.botName}**, ${config.name}'s personal assistant, powered by OpenPaw.`,
1651
1379
  "",
1652
1380
  "## Identity",
1653
1381
  "",
1654
- `- **Name**: Call the user "${config.name}"`,
1382
+ `- **Your name**: ${config.botName} — use this when introducing yourself or signing off`,
1383
+ `- **User's name**: Call the user "${config.name}"`,
1655
1384
  `- **Role**: Personal assistant with access to system tools, apps, and services`,
1656
1385
  "- **Source**: Configured by OpenPaw (open-source, no daemon, free forever)",
1657
1386
  "",
@@ -1667,21 +1396,22 @@ function writeSoul(config) {
1667
1396
  for (const extra of config.extras) lines.push(`- ${extra}`);
1668
1397
  lines.push("");
1669
1398
  }
1670
- lines.push("## PAW MODE", "", "You are running in PAW MODE — full personal assistant mode powered by OpenPaw.", "At the start of each session, briefly acknowledge this (e.g., 'PAW MODE active, ready to help!').", "");
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!').`, "");
1671
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", "");
1672
- const soulDir = path$4.dirname(getSoulPath());
1401
+ const soulDir = path$4.dirname(getSoulPath$1());
1673
1402
  if (!fs$4.existsSync(soulDir)) fs$4.mkdirSync(soulDir, { recursive: true });
1674
- fs$4.writeFileSync(getSoulPath(), lines.join("\n"), "utf-8");
1403
+ fs$4.writeFileSync(getSoulPath$1(), lines.join("\n"), "utf-8");
1675
1404
  }
1676
1405
  function showSoulSummary(config) {
1677
1406
  const lines = [
1678
- `${accent("Name:")} ${config.name}`,
1407
+ `${accent("You:")} ${config.name}`,
1408
+ `${accent("Assistant:")} ${config.botName}`,
1679
1409
  `${accent("Tone:")} ${config.tone}`,
1680
1410
  `${accent("Verbosity:")} ${config.verbosity}`,
1681
1411
  `${accent("Proactive:")} ${config.proactive ? "yes" : "no"}`
1682
1412
  ];
1683
1413
  if (config.extras.length > 0) lines.push(`${accent("Custom:")} ${config.extras.join(", ")}`);
1684
- p$11.note(lines.join("\n"), "Personality");
1414
+ p$12.note(lines.join("\n"), "Personality");
1685
1415
  }
1686
1416
 
1687
1417
  //#endregion
@@ -1721,260 +1451,6 @@ function setupMemory(userName) {
1721
1451
  }
1722
1452
  }
1723
1453
 
1724
- //#endregion
1725
- //#region src/core/telegram.ts
1726
- const CONFIG_DIR = path$2.join(os$3.homedir(), ".config", "openpaw");
1727
- const CONFIG_PATH = path$2.join(CONFIG_DIR, "telegram.json");
1728
- function writeTelegramConfig(config) {
1729
- fs$2.mkdirSync(CONFIG_DIR, { recursive: true });
1730
- fs$2.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
1731
- fs$2.chmodSync(CONFIG_PATH, 384);
1732
- }
1733
- function readTelegramConfig() {
1734
- try {
1735
- const raw = fs$2.readFileSync(CONFIG_PATH, "utf-8");
1736
- return JSON.parse(raw);
1737
- } catch {
1738
- return null;
1739
- }
1740
- }
1741
- function telegramConfigExists() {
1742
- return fs$2.existsSync(CONFIG_PATH);
1743
- }
1744
- async function telegramQuestionnaire() {
1745
- p$10.log.info(dim("Let's set up your Telegram bot! You'll need:"));
1746
- p$10.log.info(` ${accent("1.")} Message ${bold("@BotFather")} on Telegram → /newbot`);
1747
- p$10.log.info(` ${accent("2.")} Message ${bold("@userinfobot")} to get your user ID`);
1748
- console.log("");
1749
- const botToken = await p$10.text({
1750
- message: "Paste your bot token (from @BotFather):",
1751
- placeholder: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
1752
- validate: (v) => {
1753
- if (v.length === 0) return "Bot token is required";
1754
- if (!v.includes(":")) return "That doesn't look like a bot token (should contain ':')";
1755
- return void 0;
1756
- }
1757
- });
1758
- if (p$10.isCancel(botToken)) return null;
1759
- const userId = await p$10.text({
1760
- message: "Your Telegram user ID (from @userinfobot):",
1761
- placeholder: "123456789",
1762
- validate: (v) => {
1763
- if (v.length === 0) return "User ID is required";
1764
- if (!/^\d+$/.test(v)) return "User ID should be a number";
1765
- return void 0;
1766
- }
1767
- });
1768
- if (p$10.isCancel(userId)) return null;
1769
- return {
1770
- botToken,
1771
- allowedUserIds: [userId.trim()],
1772
- workspaceDir: os$3.homedir(),
1773
- model: "sonnet",
1774
- skills: []
1775
- };
1776
- }
1777
- const sessions = new Map();
1778
- const MODEL_MAP = {
1779
- sonnet: "claude-sonnet-4-5-20250514",
1780
- opus: "claude-opus-4-6",
1781
- haiku: "claude-haiku-4-5-20251001"
1782
- };
1783
- function getModelId(shortName) {
1784
- return MODEL_MAP[shortName] || MODEL_MAP.sonnet;
1785
- }
1786
- async function startTelegramBot(config) {
1787
- const bot = new Bot(config.botToken);
1788
- bot.use(hydrate());
1789
- const allowedIds = new Set(config.allowedUserIds.map(Number));
1790
- let currentModel = config.model || "sonnet";
1791
- bot.use(async (ctx, next) => {
1792
- if (!ctx.from || !allowedIds.has(ctx.from.id)) {
1793
- await ctx.reply("Woof! I don't know you. Unauthorized. 🐾");
1794
- return;
1795
- }
1796
- await next();
1797
- });
1798
- const installedSkills = listInstalledSkills();
1799
- const skillCommands = installedSkills.filter((id) => id !== "core" && id !== "memory").map((id) => ({
1800
- command: id,
1801
- description: `Use the ${id} skill`
1802
- }));
1803
- const allCommands = [
1804
- {
1805
- command: "start",
1806
- description: "Start the bot"
1807
- },
1808
- {
1809
- command: "model",
1810
- description: "Switch Claude model (sonnet/opus/haiku)"
1811
- },
1812
- {
1813
- command: "skills",
1814
- description: "List installed skills"
1815
- },
1816
- {
1817
- command: "stop",
1818
- description: "Cancel current operation"
1819
- },
1820
- {
1821
- command: "clear",
1822
- description: "Reset conversation"
1823
- },
1824
- ...skillCommands
1825
- ];
1826
- try {
1827
- await bot.api.setMyCommands(allCommands);
1828
- } catch {}
1829
- bot.command("start", async (ctx) => {
1830
- const skills$1 = installedSkills.filter((id) => id !== "core" && id !== "memory");
1831
- await ctx.reply(`*PAW MODE active* 🐾
1832
-
1833
- I'm your personal assistant, powered by OpenPaw.
1834
- Model: \`${currentModel}\`\nSkills: ${skills$1.length > 0 ? skills$1.map((s) => `/${s}`).join(", ") : "none"}\n\nJust send me a message or use a /command!`, { parse_mode: "Markdown" });
1835
- });
1836
- bot.command("model", async (ctx) => {
1837
- const arg = ctx.match?.trim().toLowerCase();
1838
- if (!arg || ![
1839
- "sonnet",
1840
- "opus",
1841
- "haiku"
1842
- ].includes(arg)) {
1843
- await ctx.reply(`Current model: \`${currentModel}\`\n\nSwitch with:\n/model sonnet\n/model opus\n/model haiku`, { parse_mode: "Markdown" });
1844
- return;
1845
- }
1846
- currentModel = arg;
1847
- config.model = arg;
1848
- writeTelegramConfig(config);
1849
- await ctx.reply(`Model switched to \`${currentModel}\` 🐾`, { parse_mode: "Markdown" });
1850
- });
1851
- bot.command("skills", async (ctx) => {
1852
- const skills$1 = installedSkills.filter((id) => id !== "core" && id !== "memory");
1853
- if (skills$1.length === 0) {
1854
- await ctx.reply("No skills installed yet. Run `openpaw setup` first! 🐾");
1855
- return;
1856
- }
1857
- const list = skills$1.map((s) => `• /${s}`).join("\n");
1858
- await ctx.reply(`*Installed skills:*\n\n${list}`, { parse_mode: "Markdown" });
1859
- });
1860
- bot.command("stop", async (ctx) => {
1861
- const userId = ctx.from.id;
1862
- const session = sessions.get(userId);
1863
- if (session?.controller) {
1864
- session.controller.abort();
1865
- sessions.delete(userId);
1866
- await ctx.reply("Operation cancelled. 🐾");
1867
- } else await ctx.reply("Nothing running right now. 🐾");
1868
- });
1869
- bot.command("clear", async (ctx) => {
1870
- const userId = ctx.from.id;
1871
- sessions.delete(userId);
1872
- await ctx.reply("Conversation cleared! Fresh start. 🐾");
1873
- });
1874
- for (const skillId of installedSkills) {
1875
- if (skillId === "core" || skillId === "memory") continue;
1876
- bot.command(skillId, async (ctx) => {
1877
- const args = ctx.match || "";
1878
- const prompt = args ? `Use the c-${skillId} skill: ${args}` : `What can the c-${skillId} skill do? Give a brief overview.`;
1879
- await handleClaudeMessage(ctx, prompt, currentModel, config);
1880
- });
1881
- }
1882
- bot.on("message:text", async (ctx) => {
1883
- await handleClaudeMessage(ctx, ctx.msg.text, currentModel, config);
1884
- });
1885
- bot.catch((err) => {
1886
- console.error("Bot error:", err.message || err);
1887
- });
1888
- process.on("SIGINT", () => {
1889
- console.log("\nShutting down gracefully... 🐾");
1890
- bot.stop();
1891
- process.exit(0);
1892
- });
1893
- process.on("SIGTERM", () => {
1894
- bot.stop();
1895
- process.exit(0);
1896
- });
1897
- console.log("");
1898
- console.log(` 🐾 ${bold("OpenPaw Telegram Bridge")}`);
1899
- console.log(` Model: ${accent(currentModel)}`);
1900
- console.log(` Skills: ${accent(String(installedSkills.length))}`);
1901
- console.log(` Workspace: ${dim(config.workspaceDir)}`);
1902
- console.log(` Allowed users: ${dim(config.allowedUserIds.join(", "))}`);
1903
- console.log("");
1904
- console.log(dim(" Listening for messages... (Ctrl+C to stop)"));
1905
- console.log("");
1906
- await bot.start();
1907
- }
1908
- async function handleClaudeMessage(ctx, prompt, model, config) {
1909
- const userId = ctx.from.id;
1910
- const existing = sessions.get(userId);
1911
- if (existing?.controller) existing.controller.abort();
1912
- const controller = new AbortController();
1913
- const session = sessions.get(userId) || {};
1914
- session.controller = controller;
1915
- sessions.set(userId, session);
1916
- const statusMsg = await ctx.reply("Thinking... 🐾");
1917
- let fullText = "";
1918
- let lastEditTime = 0;
1919
- const EDIT_INTERVAL = 1500;
1920
- try {
1921
- const q = query({
1922
- prompt,
1923
- options: {
1924
- model: getModelId(model),
1925
- permissionMode: "bypassPermissions",
1926
- allowDangerouslySkipPermissions: true,
1927
- cwd: config.workspaceDir,
1928
- abortController: controller,
1929
- maxTurns: 25,
1930
- ...session.sessionId ? { resume: session.sessionId } : {}
1931
- }
1932
- });
1933
- for await (const message of q) {
1934
- if (controller.signal.aborted) break;
1935
- if (message.type === "system" && "session_id" in message) session.sessionId = message.session_id;
1936
- if (message.type === "assistant") {
1937
- const msgContent = message.message;
1938
- const text = msgContent.content.filter((block) => block.type === "text").map((block) => block.text || "").join("");
1939
- if (text) {
1940
- fullText = text;
1941
- const now = Date.now();
1942
- if (now - lastEditTime > EDIT_INTERVAL) {
1943
- lastEditTime = now;
1944
- const truncated = fullText.length > 4e3 ? `${fullText.slice(0, 4e3)}...` : fullText;
1945
- try {
1946
- await statusMsg.editText(truncated);
1947
- } catch {}
1948
- }
1949
- }
1950
- }
1951
- if (message.type === "result") {
1952
- const result = message.result;
1953
- if (result) fullText = result;
1954
- }
1955
- }
1956
- if (fullText) {
1957
- const truncated = fullText.length > 4e3 ? `${fullText.slice(0, 4e3)}...` : fullText;
1958
- try {
1959
- await statusMsg.editText(truncated);
1960
- } catch {
1961
- await ctx.reply(truncated);
1962
- }
1963
- } else await statusMsg.editText("Done! (no text output) 🐾");
1964
- } catch (err) {
1965
- const errorMsg = err instanceof Error ? err.message : "Unknown error";
1966
- if (errorMsg.includes("abort") || controller.signal.aborted) return;
1967
- try {
1968
- await statusMsg.editText(`Woof, something went wrong: ${errorMsg.slice(0, 200)} 🐾`);
1969
- } catch {
1970
- await ctx.reply(`Woof, something went wrong: ${errorMsg.slice(0, 200)} 🐾`);
1971
- }
1972
- } finally {
1973
- session.controller = void 0;
1974
- sessions.set(userId, session);
1975
- }
1976
- }
1977
-
1978
1454
  //#endregion
1979
1455
  //#region src/core/tmux.ts
1980
1456
  function isTmuxAvailable() {
@@ -2009,6 +1485,92 @@ function launchInBackground(cmd) {
2009
1485
  child.unref();
2010
1486
  }
2011
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
+
2012
1574
  //#endregion
2013
1575
  //#region src/commands/setup.ts
2014
1576
  const CATEGORY_ICONS = {
@@ -2024,60 +1586,95 @@ const CATEGORY_ICONS = {
2024
1586
  async function setupCommand(opts = {}) {
2025
1587
  await showBanner();
2026
1588
  const platform = detectPlatform();
2027
- p$9.intro(accent(" openpaw setup "));
1589
+ p$11.intro(accent(" openpaw setup "));
2028
1590
  const brewStatus = platform.hasBrew ? chalk.green("✓ brew") : chalk.red("✗ brew");
2029
1591
  const npmStatus = platform.hasNpm ? chalk.green("✓ npm") : chalk.red("✗ npm");
2030
- p$9.log.info(`${chalk.bold(platform.osName)} ${platform.osVersion} ${brewStatus} ${npmStatus}`);
2031
- if (!platform.hasBrew && platform.os === "darwin") p$9.log.warn("Homebrew is required for most tools → https://brew.sh");
2032
- if (!opts.yes && !soulExists()) {
1592
+ const pipStatus = platform.hasPip ? chalk.green("✓ pip") : chalk.dim("○ pip");
1593
+ p$11.log.info(`${chalk.bold(platform.osName)} ${platform.osVersion} ${brewStatus} ${npmStatus} ${pipStatus}`);
1594
+ const missingPrereqs = [];
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
+ 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
+ if (missingPrereqs.length > 0) {
1598
+ p$11.note(missingPrereqs.join("\n\n"), "Missing prerequisites");
1599
+ if (!opts.yes) {
1600
+ const cont = await p$11.confirm({
1601
+ message: "Continue anyway? (some tool installs may fail)",
1602
+ initialValue: true
1603
+ });
1604
+ if (p$11.isCancel(cont) || !cont) {
1605
+ p$11.outro(dim("Install the prerequisites above and run openpaw again!"));
1606
+ process.exit(0);
1607
+ }
1608
+ }
1609
+ }
1610
+ let botName = "Paw";
1611
+ if (!opts.yes) if (soulExists()) {
1612
+ const updateSoul = await p$11.confirm({
1613
+ message: "Existing personality found (~/.claude/SOUL.md). Update it?",
1614
+ initialValue: false
1615
+ });
1616
+ if (!p$11.isCancel(updateSoul) && updateSoul) {
1617
+ await pawPulse("think", "Let's get to know you again...");
1618
+ const soul = await soulQuestionnaire();
1619
+ if (soul) {
1620
+ botName = soul.botName;
1621
+ writeSoul(soul);
1622
+ setupMemory(soul.name);
1623
+ showSoulSummary(soul);
1624
+ p$11.log.success("Personality updated");
1625
+ }
1626
+ } else setupMemory();
1627
+ } else {
2033
1628
  await pawPulse("think", "Let's get to know you...");
2034
- const wantSoul = await p$9.confirm({
1629
+ const wantSoul = await p$11.confirm({
2035
1630
  message: "Teach me your name and preferences? (makes me a better pup)",
2036
1631
  initialValue: true
2037
1632
  });
2038
- if (!p$9.isCancel(wantSoul) && wantSoul) {
1633
+ if (!p$11.isCancel(wantSoul) && wantSoul) {
2039
1634
  const soul = await soulQuestionnaire();
2040
1635
  if (soul) {
1636
+ botName = soul.botName;
2041
1637
  writeSoul(soul);
2042
1638
  setupMemory(soul.name);
2043
1639
  showSoulSummary(soul);
2044
- p$9.log.success("Personality saved to ~/.claude/SOUL.md");
1640
+ p$11.log.success("Personality saved to ~/.claude/SOUL.md");
2045
1641
  }
2046
1642
  } else setupMemory();
2047
- } else if (opts.yes) setupMemory();
1643
+ }
1644
+ else setupMemory();
2048
1645
  let selectedSkills;
2049
1646
  if (opts.preset) {
2050
1647
  selectedSkills = getPresetSkills(opts.preset, platform.os);
2051
1648
  if (selectedSkills.length === 0) {
2052
- p$9.log.error(`Unknown preset: ${opts.preset}`);
2053
- p$9.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(", ")}`);
2054
1651
  process.exit(1);
2055
1652
  }
2056
- p$9.log.info(`Using preset: ${bold(opts.preset)} (${selectedSkills.length} skills)`);
1653
+ p$11.log.info(`Using preset: ${bold(opts.preset)} (${selectedSkills.length} skills)`);
2057
1654
  } else selectedSkills = await selectSkills(platform.os);
2058
1655
  if (selectedSkills.length === 0) {
2059
- p$9.log.warn("No skills selected. Run openpaw again when you're ready!");
2060
- p$9.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! 🐾");
2061
1658
  return;
2062
1659
  }
2063
1660
  const resolved = resolveDependencies(selectedSkills);
2064
1661
  if (resolved.length > 0) {
2065
1662
  const depNames = resolved.map((s$1) => s$1.name).join(", ");
2066
- p$9.log.info(`${dim("Auto-fetching dependencies:")} ${depNames}`);
1663
+ p$11.log.info(`${dim("Auto-fetching dependencies:")} ${depNames}`);
2067
1664
  selectedSkills.push(...resolved);
2068
1665
  }
2069
1666
  await pawPulse("happy", `${selectedSkills.length} skill${selectedSkills.length > 1 ? "s" : ""} selected — good taste!`);
2070
1667
  if (!opts.yes) {
2071
1668
  for (const skill of selectedSkills) if (skill.subChoices) {
2072
- const choice = await p$9.select({
1669
+ const choice = await p$11.select({
2073
1670
  message: `${skill.name}: ${skill.subChoices.question}`,
2074
1671
  options: skill.subChoices.options.map((o) => ({
2075
1672
  value: o.value,
2076
1673
  label: o.label
2077
1674
  }))
2078
1675
  });
2079
- if (p$9.isCancel(choice)) {
2080
- p$9.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*");
2081
1678
  process.exit(0);
2082
1679
  }
2083
1680
  const chosen = skill.subChoices.options.find((o) => o.value === choice);
@@ -2087,37 +1684,29 @@ async function setupCommand(opts = {}) {
2087
1684
  let interfaceMode = "native";
2088
1685
  let telegramConfig = null;
2089
1686
  if (!opts.yes) {
2090
- const modeChoice = await p$9.select({
1687
+ const modeChoice = await p$11.select({
2091
1688
  message: "How do you want to talk to Claude? 🐾",
2092
- options: [
2093
- {
2094
- value: "native",
2095
- label: "🖥 Terminal only",
2096
- hint: "Claude Code in your terminal"
2097
- },
2098
- {
2099
- value: "telegram",
2100
- label: "📱 Telegram",
2101
- hint: "talk to Claude from your phone"
2102
- },
2103
- {
2104
- value: "both",
2105
- label: "🖥📱 Both",
2106
- hint: "terminal + Telegram"
2107
- }
2108
- ]
1689
+ options: [{
1690
+ value: "native",
1691
+ label: "🖥 Terminal only",
1692
+ hint: "Claude Code in your terminal"
1693
+ }, {
1694
+ value: "both",
1695
+ label: "🖥📱 Terminal + Telegram",
1696
+ hint: "terminal + talk from your phone"
1697
+ }]
2109
1698
  });
2110
- if (p$9.isCancel(modeChoice)) {
2111
- p$9.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*");
2112
1701
  process.exit(0);
2113
1702
  }
2114
1703
  interfaceMode = modeChoice;
2115
1704
  if (interfaceMode === "telegram" || interfaceMode === "both") {
2116
- if (telegramConfigExists()) p$9.log.info(dim("Telegram already configured — keeping existing config"));
1705
+ if (telegramConfigExists()) p$11.log.info(dim("Telegram already configured — keeping existing config"));
2117
1706
  else {
2118
1707
  telegramConfig = await telegramQuestionnaire();
2119
1708
  if (!telegramConfig) {
2120
- p$9.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*");
2121
1710
  process.exit(0);
2122
1711
  }
2123
1712
  }
@@ -2127,37 +1716,39 @@ async function setupCommand(opts = {}) {
2127
1716
  }
2128
1717
  }
2129
1718
  }
2130
- let projectDir = os$2.homedir();
1719
+ let wantDashboard = false;
1720
+ let dashboardTheme = "paw";
2131
1721
  if (!opts.yes) {
2132
- const workChoice = await p$9.select({
2133
- message: "Where should Claude work? 🐾",
2134
- options: [{
2135
- value: "home",
2136
- label: `Home directory ${dim("~")}`,
2137
- hint: "recommended for general assistant"
2138
- }, {
2139
- value: "custom",
2140
- label: "Pick a project directory",
2141
- hint: "for project-focused work"
2142
- }]
1722
+ const dashChoice = await p$11.confirm({
1723
+ message: `Want a task dashboard for ${botName}?`,
1724
+ initialValue: false
2143
1725
  });
2144
- if (p$9.isCancel(workChoice)) {
2145
- p$9.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2146
- process.exit(0);
2147
- }
2148
- if (workChoice === "custom") {
2149
- const customDir = await p$9.text({
2150
- message: "Project directory path:",
2151
- placeholder: "~/projects/my-app",
2152
- validate: (v) => v.length === 0 ? "Path cannot be empty" : void 0
1726
+ if (!p$11.isCancel(dashChoice) && dashChoice) {
1727
+ wantDashboard = true;
1728
+ const themeChoice = await p$11.select({
1729
+ message: "Pick a dashboard theme",
1730
+ options: [
1731
+ {
1732
+ value: "paw",
1733
+ label: "Paw",
1734
+ hint: "warm brown"
1735
+ },
1736
+ {
1737
+ value: "midnight",
1738
+ label: "Midnight",
1739
+ hint: "cool dark blue"
1740
+ },
1741
+ {
1742
+ value: "neon",
1743
+ label: "Neon",
1744
+ hint: "cyber green"
1745
+ }
1746
+ ]
2153
1747
  });
2154
- if (p$9.isCancel(customDir)) {
2155
- p$9.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2156
- process.exit(0);
2157
- }
2158
- projectDir = customDir.replace(/^~/, os$2.homedir());
1748
+ if (!p$11.isCancel(themeChoice)) dashboardTheme = themeChoice;
2159
1749
  }
2160
1750
  }
1751
+ const projectDir = os$2.homedir();
2161
1752
  const allTools = [];
2162
1753
  for (const skill of selectedSkills) allTools.push(...skill.tools);
2163
1754
  const uniqueTools = [...new Map(allTools.map((t) => [t.command, t])).values()];
@@ -2165,26 +1756,63 @@ async function setupCommand(opts = {}) {
2165
1756
  const missing = getMissingTools(uniqueTools);
2166
1757
  let targetDir;
2167
1758
  if (opts.yes) targetDir = getDefaultSkillsDir();
2168
- else targetDir = await selectInstallLocation();
1759
+ else {
1760
+ const defaultDir = getDefaultSkillsDir();
1761
+ const skillsDir = await p$11.select({
1762
+ message: "Where should skills live?",
1763
+ options: [
1764
+ {
1765
+ value: defaultDir,
1766
+ label: `Global ${dim("~/.claude/skills/")}`,
1767
+ hint: "recommended"
1768
+ },
1769
+ {
1770
+ value: ".claude/skills",
1771
+ label: `Project ${dim(".claude/skills/")}`
1772
+ },
1773
+ {
1774
+ value: "custom",
1775
+ label: "Custom path"
1776
+ }
1777
+ ]
1778
+ });
1779
+ if (p$11.isCancel(skillsDir)) {
1780
+ p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1781
+ process.exit(0);
1782
+ }
1783
+ targetDir = skillsDir;
1784
+ if (targetDir === "custom") {
1785
+ const customDir = await p$11.text({
1786
+ message: "Skills directory path:",
1787
+ placeholder: "~/.claude/skills",
1788
+ validate: (v) => v.length === 0 ? "Path cannot be empty" : void 0
1789
+ });
1790
+ if (p$11.isCancel(customDir)) {
1791
+ p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1792
+ process.exit(0);
1793
+ }
1794
+ targetDir = customDir.replace(/^~/, os$2.homedir());
1795
+ }
1796
+ }
2169
1797
  const summary = buildSummary(selectedSkills, uniqueTools, missing, taps, interfaceMode, projectDir);
2170
- p$9.note(summary, "Here's what we're fetching");
1798
+ p$11.note(summary, "Here's what we're fetching");
2171
1799
  if (!opts.yes) {
2172
- const proceed = await p$9.confirm({
1800
+ const proceed = await p$11.confirm({
2173
1801
  message: "Ready to fetch all these goodies?",
2174
1802
  initialValue: true
2175
1803
  });
2176
- if (p$9.isCancel(proceed) || !proceed) {
2177
- p$9.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*");
2178
1806
  process.exit(0);
2179
1807
  }
2180
1808
  }
2181
1809
  if (opts.dryRun) {
2182
- p$9.log.info(dim("Dry run — no changes made. Just sniffing around."));
2183
- p$9.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 🐾"));
2184
1812
  return;
2185
1813
  }
2186
1814
  await pawStep("work", "Fetching your goodies...");
2187
- const s = p$9.spinner();
1815
+ const s = p$11.spinner();
2188
1816
  if (taps.size > 0) {
2189
1817
  s.start("🐾 Sniffing out Homebrew taps...");
2190
1818
  const tapResults = installTaps(taps);
@@ -2192,20 +1820,46 @@ async function setupCommand(opts = {}) {
2192
1820
  if (failed.length > 0) s.stop(`Taps: ${taps.size - failed.length} added, ${failed.length} failed`);
2193
1821
  else s.stop(`🐾 ${taps.size} tap${taps.size > 1 ? "s" : ""} ready`);
2194
1822
  }
1823
+ const failedTools = [];
1824
+ const installedTools = [];
2195
1825
  if (missing.length > 0) for (let i = 0; i < missing.length; i++) {
2196
1826
  const tool = missing[i];
2197
- 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)}...`);
2198
1828
  const result = installTool(tool);
2199
- if (result.success) s.stop(`${chalk.green("✓")} ${tool.name}`);
2200
- 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
+ }
1836
+ }
1837
+ else if (uniqueTools.length > 0) p$11.log.success("All tools already installed — clever pup!");
1838
+ const existingSkills = listInstalledSkills(targetDir);
1839
+ const overlapping = selectedSkills.filter((sk) => existingSkills.includes(sk.id));
1840
+ let updateExisting = true;
1841
+ if (overlapping.length > 0 && !opts.yes) {
1842
+ const updateChoice = await p$11.confirm({
1843
+ message: `${overlapping.length} skill${overlapping.length > 1 ? "s" : ""} already installed. Update their templates?`,
1844
+ initialValue: true
1845
+ });
1846
+ if (!p$11.isCancel(updateChoice)) updateExisting = updateChoice;
2201
1847
  }
2202
- else if (uniqueTools.length > 0) p$9.log.success("All tools already installed — clever pup!");
2203
- s.start("🐾 Burying treats in ~/.claude/skills/...");
2204
1848
  installSkill("core", targetDir);
2205
1849
  installSkill("memory", targetDir);
2206
1850
  const installed = ["c-core", "c-memory"];
2207
- for (const skill of selectedSkills) if (installSkill(skill.id, targetDir)) installed.push(`c-${skill.id}`);
2208
- s.stop(`🐾 ${installed.length} skills buried`);
1851
+ for (const skill of selectedSkills) {
1852
+ s.start(`🐾 Installing ${bold("c-" + skill.id)}...`);
1853
+ if (!updateExisting && existingSkills.includes(skill.id)) {
1854
+ installed.push(`c-${skill.id}`);
1855
+ s.stop(`${chalk.green("✓")} c-${skill.id} ${dim("(kept existing)")}`);
1856
+ continue;
1857
+ }
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}`);
1862
+ }
2209
1863
  s.start("🐾 Setting up the doggy door...");
2210
1864
  const added = addPermissions(uniqueTools);
2211
1865
  s.stop(added.length > 0 ? `🐾 ${added.length} permission${added.length > 1 ? "s" : ""} added` : "🐾 Doggy door already open");
@@ -2216,128 +1870,137 @@ async function setupCommand(opts = {}) {
2216
1870
  telegramConfig.workspaceDir = projectDir;
2217
1871
  telegramConfig.skills = selectedSkills.map((sk) => sk.id);
2218
1872
  writeTelegramConfig(telegramConfig);
2219
- p$9.log.success("Telegram bridge configured");
2220
- }
2221
- if (!opts.yes) {
2222
- const wantMcp = await p$9.confirm({
2223
- message: "Sniff out some MCP servers? (optional — search, memory, browser tools)",
2224
- initialValue: false
1873
+ p$11.log.success("Telegram bridge configured");
1874
+ }
1875
+ if (wantDashboard) {
1876
+ const dashConfig = readConfig();
1877
+ dashConfig.theme = dashboardTheme;
1878
+ dashConfig.botName = botName;
1879
+ writeConfig(dashConfig);
1880
+ p$11.log.success(`Dashboard configured (theme: ${dashboardTheme})`);
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`);
1885
+ const authSteps = selectedSkills.flatMap((skill) => skill.authSteps ?? []).filter((step, i, arr) => arr.findIndex((s$1) => s$1.command === step.command) === i);
1886
+ if (authSteps.length > 0 && !opts.yes) {
1887
+ const authList = authSteps.map((st) => `${chalk.yellow("→")} ${chalk.bold(st.command)} ${dim(st.description)}`).join("\n");
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
2225
1892
  });
2226
- if (!p$9.isCancel(wantMcp) && wantMcp) {
2227
- const mcpChoices = await p$9.multiselect({
2228
- message: "🔌 MCP Servers",
2229
- options: mcpServers.map((srv) => ({
2230
- value: srv.id,
2231
- label: srv.name,
2232
- hint: srv.description
2233
- })),
2234
- required: false
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
2235
1897
  });
2236
- if (!p$9.isCancel(mcpChoices)) {
2237
- const chosen = mcpChoices;
2238
- if (chosen.length > 0) {
2239
- s.start("🐾 Configuring MCP servers...");
2240
- let mcpCount = 0;
2241
- for (const id of chosen) {
2242
- const srv = mcpServers.find((m) => m.id === id);
2243
- if (srv && installMcpServer(srv)) mcpCount++;
2244
- }
2245
- s.stop(`🐾 ${mcpCount} MCP server${mcpCount > 1 ? "s" : ""} configured`);
2246
- const needsEnv = chosen.map((id) => mcpServers.find((m) => m.id === id)).filter((srv) => !!srv?.envPlaceholders);
2247
- if (needsEnv.length > 0) {
2248
- const envList = needsEnv.flatMap((srv) => Object.entries(srv.envPlaceholders).map(([key, _placeholder]) => `${chalk.yellow("→")} ${bold(srv.name)}: Set ${dim(key)} in ~/.claude/settings.json`)).join("\n");
2249
- p$9.note(envList, "MCP servers need API keys");
2250
- }
2251
- }
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)`);
2252
1909
  }
2253
1910
  }
2254
- }
2255
- const authSteps = selectedSkills.flatMap((skill) => skill.authSteps ?? []).filter((step, i, arr) => arr.findIndex((s$1) => s$1.command === step.command) === i);
2256
- if (authSteps.length > 0) {
1911
+ else p$11.log.info(dim("No problem — run these commands when you need each skill"));
1912
+ } else if (authSteps.length > 0) {
2257
1913
  const authList = authSteps.map((st) => `${chalk.yellow("→")} ${chalk.bold(st.command)} ${dim(st.description)}`).join("\n");
2258
- p$9.note(authList, "One-time auth needed");
2259
- }
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");
2260
1922
  await pawStep("done", "All done! *tail wag intensifies*");
2261
1923
  console.log("");
2262
- console.log(dim(" Your pup is ready to play! Try saying:"));
1924
+ console.log(dim(` ${botName} is ready to play! Try saying:`));
2263
1925
  console.log(` ${subtle("\"What are my latest emails?\"")}`);
2264
1926
  console.log(` ${subtle("\"Play some jazz on Spotify\"")}`);
2265
1927
  console.log(` ${subtle("\"Go to hacker news and summarize the top posts\"")}`);
2266
1928
  console.log("");
1929
+ if (wantDashboard) {
1930
+ const { startDashboard: startDashboard$1 } = await import("./dashboard-server-Pnv4DFlV.js");
1931
+ startDashboard$1({
1932
+ theme: dashboardTheme,
1933
+ botName
1934
+ });
1935
+ p$11.log.success("Dashboard launched in your browser");
1936
+ }
2267
1937
  if (opts.yes) {
2268
- p$9.outro(accent("openpaw setup complete 🐾"));
1938
+ p$11.outro(accent("openpaw setup complete 🐾"));
2269
1939
  return;
2270
1940
  }
2271
- const launch = await p$9.confirm({
1941
+ const launch = await p$11.confirm({
2272
1942
  message: "Time to go for a walk? (Launch your assistant)",
2273
1943
  initialValue: true
2274
1944
  });
2275
- if (p$9.isCancel(launch) || !launch) {
2276
- if (interfaceMode === "telegram" || interfaceMode === "both") p$9.log.info(`Start the Telegram bridge anytime with: ${bold("openpaw telegram")}`);
2277
- p$9.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!"));
2278
1948
  return;
2279
1949
  }
2280
1950
  let useDangerousMode = false;
2281
- if (interfaceMode === "native" || interfaceMode === "both") {
1951
+ {
2282
1952
  showPuppyDisclaimer();
2283
- const acceptDanger = await p$9.confirm({
1953
+ const acceptDanger = await p$11.confirm({
2284
1954
  message: "Unleash full paw-er? *excited tail wag*",
2285
1955
  initialValue: true
2286
1956
  });
2287
- if (!p$9.isCancel(acceptDanger)) useDangerousMode = acceptDanger;
1957
+ if (!p$11.isCancel(acceptDanger)) useDangerousMode = acceptDanger;
2288
1958
  }
2289
1959
  let useTmux = false;
2290
1960
  if (isTmuxAvailable() && !isInTmux()) {
2291
1961
  const tmuxDefault = interfaceMode === "both";
2292
- const tmuxChoice = await p$9.confirm({
1962
+ const tmuxChoice = await p$11.confirm({
2293
1963
  message: "Run in tmux? (keeps going when you close the terminal)",
2294
1964
  initialValue: tmuxDefault
2295
1965
  });
2296
- if (!p$9.isCancel(tmuxChoice)) useTmux = tmuxChoice;
1966
+ if (!p$11.isCancel(tmuxChoice)) useTmux = tmuxChoice;
2297
1967
  }
2298
1968
  const dangerFlag = useDangerousMode ? " --dangerously-skip-permissions" : "";
2299
1969
  const nativeCmd = `claude${dangerFlag}`;
2300
1970
  const telegramCmd = "npx openpaw telegram";
2301
1971
  if (useTmux) {
2302
- p$9.outro(accent("Launching in tmux... 🐾"));
1972
+ p$11.outro(accent("Launching in tmux... 🐾"));
2303
1973
  launchInTmux({
2304
- nativeCmd: interfaceMode === "native" || interfaceMode === "both" ? nativeCmd : void 0,
2305
- telegramCmd: interfaceMode === "telegram" || interfaceMode === "both" ? telegramCmd : void 0,
1974
+ nativeCmd,
1975
+ telegramCmd: interfaceMode === "both" ? telegramCmd : void 0,
2306
1976
  workDir: projectDir
2307
1977
  });
2308
1978
  } else if (interfaceMode === "native") {
2309
- p$9.outro(accent("Starting Claude Code... 🐾"));
1979
+ p$11.outro(accent("Starting Claude Code... 🐾"));
2310
1980
  try {
2311
1981
  execSync(nativeCmd, {
2312
1982
  stdio: "inherit",
2313
1983
  cwd: projectDir
2314
1984
  });
2315
1985
  } catch {
2316
- p$9.log.warn("Could not launch Claude Code. Make sure it's installed: https://claude.ai/code");
2317
- }
2318
- } else if (interfaceMode === "telegram") {
2319
- p$9.outro(accent("Starting Telegram bridge... 🐾"));
2320
- try {
2321
- execSync(telegramCmd, { stdio: "inherit" });
2322
- } catch {
2323
- p$9.log.warn("Telegram bridge failed to start.");
1986
+ p$11.log.warn("Could not launch Claude Code. Make sure it's installed: https://claude.ai/code");
2324
1987
  }
2325
1988
  } else {
2326
- p$9.log.info(dim("Starting Telegram bridge in background..."));
1989
+ p$11.log.info(dim("Starting Telegram bridge in background..."));
2327
1990
  launchInBackground(telegramCmd);
2328
- p$9.outro(accent("Starting Claude Code... 🐾"));
1991
+ p$11.outro(accent("Starting Claude Code... 🐾"));
2329
1992
  try {
2330
1993
  execSync(nativeCmd, {
2331
1994
  stdio: "inherit",
2332
1995
  cwd: projectDir
2333
1996
  });
2334
1997
  } catch {
2335
- p$9.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");
2336
1999
  }
2337
2000
  }
2338
2001
  }
2339
- async function selectSkills(os$7) {
2340
- const mode = await p$9.select({
2002
+ async function selectSkills(os$6) {
2003
+ const mode = await p$11.select({
2341
2004
  message: "How should we set things up, human?",
2342
2005
  options: [{
2343
2006
  value: "preset",
@@ -2349,15 +2012,15 @@ async function selectSkills(os$7) {
2349
2012
  hint: "sniff through skills one by one"
2350
2013
  }]
2351
2014
  });
2352
- if (p$9.isCancel(mode)) {
2353
- p$9.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*");
2354
2017
  process.exit(0);
2355
2018
  }
2356
- if (mode === "preset") return await selectFromPreset(os$7);
2357
- return await selectCustom(os$7);
2019
+ if (mode === "preset") return await selectFromPreset(os$6);
2020
+ return await selectCustom(os$6);
2358
2021
  }
2359
- async function selectFromPreset(os$7) {
2360
- const presetChoice = await p$9.select({
2022
+ async function selectFromPreset(os$6) {
2023
+ const presetChoice = await p$11.select({
2361
2024
  message: "Pick a treat... I mean, a preset!",
2362
2025
  options: presets.map((pr) => ({
2363
2026
  value: pr.id,
@@ -2365,88 +2028,50 @@ async function selectFromPreset(os$7) {
2365
2028
  hint: pr.description
2366
2029
  }))
2367
2030
  });
2368
- if (p$9.isCancel(presetChoice)) {
2369
- p$9.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*");
2370
2033
  process.exit(0);
2371
2034
  }
2372
- const presetSkills = getPresetSkills(presetChoice, os$7);
2035
+ const presetSkills = getPresetSkills(presetChoice, os$6);
2373
2036
  const skillNames = presetSkills.map((s) => s.name).join(", ");
2374
- p$9.log.info(`${dim("Includes:")} ${skillNames}`);
2037
+ p$11.log.info(`${dim("Includes:")} ${skillNames}`);
2375
2038
  return presetSkills;
2376
2039
  }
2377
- async function selectCustom(os$7) {
2378
- const grouped = getSkillsByCategory(os$7);
2379
- const allSelected = [];
2380
- for (const [category, categorySkills] of grouped) {
2381
- const label = categoryLabels[category] ?? category;
2040
+ async function selectCustom(os$6) {
2041
+ const grouped = getSkillsByCategory(os$6);
2042
+ const options = [];
2043
+ for (const [category, catSkills] of grouped) {
2382
2044
  const icon = CATEGORY_ICONS[category] ?? "📦";
2383
- const selected = await p$9.multiselect({
2384
- message: `${icon} ${label}`,
2385
- options: categorySkills.map((skill) => ({
2045
+ const catLabel = categoryLabels[category] ?? category;
2046
+ let isFirst = true;
2047
+ for (const skill of catSkills) {
2048
+ if (skill.id === "telegram") continue;
2049
+ options.push({
2386
2050
  value: skill.id,
2387
- label: skill.name,
2388
- hint: skill.description
2389
- })),
2390
- required: false
2391
- });
2392
- if (p$9.isCancel(selected)) {
2393
- p$9.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2394
- process.exit(0);
2395
- }
2396
- const ids = selected;
2397
- for (const id of ids) {
2398
- const skill = skills.find((s) => s.id === id);
2399
- if (skill) allSelected.push(skill);
2051
+ label: `${icon} ${skill.name}`,
2052
+ hint: isFirst ? `── ${catLabel} ── ${skill.description}` : skill.description
2053
+ });
2054
+ isFirst = false;
2400
2055
  }
2401
- if (ids.length > 0) p$9.log.step(`${ids.length} selected from ${label}`);
2402
2056
  }
2403
- return allSelected;
2404
- }
2405
- async function selectInstallLocation() {
2406
- const defaultDir = getDefaultSkillsDir();
2407
- const skillsDir = await p$9.select({
2408
- message: "Where should skills live?",
2409
- options: [
2410
- {
2411
- value: defaultDir,
2412
- label: `Global ${dim("~/.claude/skills/")}`,
2413
- hint: "recommended"
2414
- },
2415
- {
2416
- value: ".claude/skills",
2417
- label: `Project ${dim(".claude/skills/")}`
2418
- },
2419
- {
2420
- value: "custom",
2421
- label: "Custom path"
2422
- }
2423
- ]
2057
+ const selected = await p$11.multiselect({
2058
+ message: "Pick your skills (space to select, enter to confirm)",
2059
+ options,
2060
+ required: false
2424
2061
  });
2425
- if (p$9.isCancel(skillsDir)) {
2426
- p$9.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*");
2427
2064
  process.exit(0);
2428
2065
  }
2429
- let targetDir = skillsDir;
2430
- if (targetDir === "custom") {
2431
- const customDir = await p$9.text({
2432
- message: "Skills directory path:",
2433
- placeholder: "~/.claude/skills",
2434
- validate: (v) => v.length === 0 ? "Path cannot be empty" : void 0
2435
- });
2436
- if (p$9.isCancel(customDir)) {
2437
- p$9.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2438
- process.exit(0);
2439
- }
2440
- targetDir = customDir;
2441
- }
2442
- return targetDir;
2066
+ const ids = selected;
2067
+ return ids.map((id) => skills.find((s) => s.id === id)).filter((s) => !!s);
2443
2068
  }
2444
2069
  function buildSummary(selectedSkills, uniqueTools, missing, taps, interfaceMode, projectDir) {
2445
2070
  const lines = [];
2446
2071
  lines.push(`${bold("Skills:")} ${selectedSkills.map((s) => s.name).join(", ")}`);
2447
2072
  lines.push(`${bold("Tools:")} ${uniqueTools.length} total, ${missing.length} to install`);
2448
2073
  if (taps.size > 0) lines.push(`${bold("Taps:")} ${[...taps].join(", ")}`);
2449
- const modeLabel = interfaceMode === "native" ? "Terminal" : interfaceMode === "telegram" ? "Telegram" : "Terminal + Telegram";
2074
+ const modeLabel = interfaceMode === "native" ? "Terminal" : "Terminal + Telegram";
2450
2075
  lines.push(`${bold("Interface:")} ${modeLabel}`);
2451
2076
  lines.push(`${bold("Workspace:")} ${projectDir.replace(os$2.homedir(), "~")}`);
2452
2077
  lines.push(`${bold("Memory:")} ~/.claude/memory/`);
@@ -2475,18 +2100,18 @@ async function addCommand(skillIds) {
2475
2100
  showMini();
2476
2101
  console.log("");
2477
2102
  if (skillIds.length === 0) {
2478
- p$8.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");
2479
2104
  return;
2480
2105
  }
2481
- const s = p$8.spinner();
2106
+ const s = p$10.spinner();
2482
2107
  for (const id of skillIds) {
2483
2108
  const skill = getSkillById(id);
2484
2109
  if (!skill) {
2485
- p$8.log.error(`Unknown skill: ${id}`);
2110
+ p$10.log.error(`Unknown skill: ${id}`);
2486
2111
  continue;
2487
2112
  }
2488
2113
  if (isSkillInstalled(id)) {
2489
- p$8.log.info(`c-${id} already installed, skipping`);
2114
+ p$10.log.info(`c-${id} already installed, skipping`);
2490
2115
  continue;
2491
2116
  }
2492
2117
  const taps = getAllTaps([skill]);
@@ -2499,9 +2124,27 @@ async function addCommand(skillIds) {
2499
2124
  }
2500
2125
  installSkill(id);
2501
2126
  addPermissions(skill.tools);
2502
- p$8.log.success(`c-${id} installed`);
2503
- 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
+ }
2504
2146
  }
2147
+ regenerateClaudeMd();
2505
2148
  }
2506
2149
 
2507
2150
  //#endregion
@@ -2510,23 +2153,24 @@ async function removeCommand(skillIds) {
2510
2153
  showMini();
2511
2154
  console.log("");
2512
2155
  if (skillIds.length === 0) {
2513
- p$7.log.error("Specify skills to remove: openpaw remove notes music");
2156
+ p$9.log.error("Specify skills to remove: openpaw remove notes music");
2514
2157
  return;
2515
2158
  }
2516
2159
  for (const id of skillIds) {
2517
2160
  if (id === "core") {
2518
- p$7.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.");
2519
2162
  continue;
2520
2163
  }
2521
2164
  if (!isSkillInstalled(id)) {
2522
- p$7.log.info(`c-${id} is not installed`);
2165
+ p$9.log.info(`c-${id} is not installed`);
2523
2166
  continue;
2524
2167
  }
2525
2168
  const skill = getSkillById(id);
2526
2169
  removeSkill(id);
2527
2170
  if (skill) removePermissions(skill.tools);
2528
- p$7.log.success(`${chalk.bold(`c-${id}`)} removed`);
2171
+ p$9.log.success(`${chalk.bold(`c-${id}`)} removed`);
2529
2172
  }
2173
+ regenerateClaudeMd();
2530
2174
  }
2531
2175
 
2532
2176
  //#endregion
@@ -2536,10 +2180,10 @@ async function statusCommand() {
2536
2180
  console.log("");
2537
2181
  const installed = listInstalledSkills();
2538
2182
  if (installed.length === 0) {
2539
- p$6.log.warn("No OpenPaw skills installed. Run: openpaw setup");
2183
+ p$8.log.warn("No OpenPaw skills installed. Run: openpaw setup");
2540
2184
  return;
2541
2185
  }
2542
- p$6.log.info(`${chalk.bold(installed.length)} skills installed:\n`);
2186
+ p$8.log.info(`${chalk.bold(installed.length)} skills installed:\n`);
2543
2187
  for (const skillId of installed) {
2544
2188
  if (skillId === "core") {
2545
2189
  console.log(` ${chalk.green("●")} ${chalk.bold("c-core")} ${chalk.dim("— coordinator")}`);
@@ -2567,7 +2211,7 @@ async function statusCommand() {
2567
2211
  async function doctorCommand() {
2568
2212
  showMini();
2569
2213
  console.log("");
2570
- p$5.log.info("Running diagnostics...\n");
2214
+ p$7.log.info("Running diagnostics...\n");
2571
2215
  let issues = 0;
2572
2216
  const platform = detectPlatform();
2573
2217
  console.log(` ${chalk.green("✓")} Platform: ${platform.osName} ${platform.osVersion}`);
@@ -2606,8 +2250,8 @@ async function doctorCommand() {
2606
2250
  issues++;
2607
2251
  }
2608
2252
  console.log("");
2609
- if (issues === 0) p$5.log.success("All checks passed!");
2610
- else p$5.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`);
2611
2255
  }
2612
2256
 
2613
2257
  //#endregion
@@ -2617,10 +2261,10 @@ async function updateCommand() {
2617
2261
  console.log("");
2618
2262
  const installed = listInstalledSkills();
2619
2263
  if (installed.length === 0) {
2620
- p$4.log.warn("No skills installed. Run: openpaw setup");
2264
+ p$6.log.warn("No skills installed. Run: openpaw setup");
2621
2265
  return;
2622
2266
  }
2623
- const s = p$4.spinner();
2267
+ const s = p$6.spinner();
2624
2268
  const brewTools = [];
2625
2269
  for (const skillId of installed) {
2626
2270
  const skill = skills.find((sk) => sk.id === skillId);
@@ -2628,7 +2272,7 @@ async function updateCommand() {
2628
2272
  for (const tool of skill.tools) if ((tool.installMethod === "brew" || tool.installMethod === "brew-tap") && isToolInstalled(tool.command)) brewTools.push(tool.name);
2629
2273
  }
2630
2274
  if (brewTools.length === 0) {
2631
- p$4.log.info("No Homebrew-installed tools to update");
2275
+ p$6.log.info("No Homebrew-installed tools to update");
2632
2276
  return;
2633
2277
  }
2634
2278
  s.start(`Updating ${brewTools.length} tools via Homebrew...`);
@@ -2654,15 +2298,15 @@ async function resetCommand() {
2654
2298
  console.log("");
2655
2299
  const installed = listInstalledSkills();
2656
2300
  if (installed.length === 0) {
2657
- p$3.log.info("Nothing to reset — no OpenPaw skills installed.");
2301
+ p$5.log.info("Nothing to reset — no OpenPaw skills installed.");
2658
2302
  return;
2659
2303
  }
2660
- const confirm = await p$3.confirm({ message: `Remove all ${installed.length} OpenPaw skills and permissions?` });
2661
- if (p$3.isCancel(confirm) || !confirm) {
2662
- p$3.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.");
2663
2307
  return;
2664
2308
  }
2665
- const s = p$3.spinner();
2309
+ const s = p$5.spinner();
2666
2310
  s.start("Removing skills and permissions...");
2667
2311
  for (const skillId of installed) {
2668
2312
  const skill = skills.find((sk) => sk.id === skillId);
@@ -2671,9 +2315,9 @@ async function resetCommand() {
2671
2315
  }
2672
2316
  removeSafetyHooks();
2673
2317
  s.stop(`${chalk.green("✓")} Removed ${installed.length} skills, permissions, and hooks`);
2674
- p$3.log.info(chalk.dim("CLI tools were not uninstalled (you may still want them)."));
2675
- p$3.log.info(chalk.dim("To uninstall tools: brew uninstall <tool>"));
2676
- p$3.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.");
2677
2321
  }
2678
2322
 
2679
2323
  //#endregion
@@ -2714,35 +2358,35 @@ async function listCommand() {
2714
2358
  //#region src/commands/soul.ts
2715
2359
  async function soulCommand() {
2716
2360
  showMini();
2717
- p$2.intro(accent(" openpaw soul "));
2361
+ p$4.intro(accent(" openpaw soul "));
2718
2362
  if (soulExists()) {
2719
- p$2.log.info(dim("SOUL.md already exists at ~/.claude/SOUL.md"));
2720
- const overwrite = await p$2.confirm({
2363
+ p$4.log.info(dim("SOUL.md already exists at ~/.claude/SOUL.md"));
2364
+ const overwrite = await p$4.confirm({
2721
2365
  message: "Overwrite existing personality?",
2722
2366
  initialValue: false
2723
2367
  });
2724
- if (p$2.isCancel(overwrite) || !overwrite) {
2725
- p$2.log.info("Keeping existing SOUL.md");
2726
- p$2.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"));
2727
2371
  return;
2728
2372
  }
2729
2373
  }
2730
2374
  const soul = await soulQuestionnaire();
2731
2375
  if (!soul) {
2732
- p$2.cancel("Cancelled.");
2376
+ p$4.cancel("Cancelled.");
2733
2377
  return;
2734
2378
  }
2735
2379
  writeSoul(soul);
2736
2380
  showSoulSummary(soul);
2737
- p$2.log.success("Personality saved to ~/.claude/SOUL.md");
2738
- p$2.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 🐾"));
2739
2383
  }
2740
2384
 
2741
2385
  //#endregion
2742
2386
  //#region src/commands/export.ts
2743
2387
  async function exportCommand() {
2744
2388
  showMini();
2745
- p$1.intro(accent(" openpaw export "));
2389
+ p$3.intro(accent(" openpaw export "));
2746
2390
  const claudeDir = path$1.join(os$1.homedir(), ".claude");
2747
2391
  const bundle = {
2748
2392
  version: "1",
@@ -2754,52 +2398,52 @@ async function exportCommand() {
2754
2398
  };
2755
2399
  const installed = listInstalledSkills();
2756
2400
  bundle.skills = installed;
2757
- p$1.log.info(`${installed.length} skills found`);
2401
+ p$3.log.info(`${installed.length} skills found`);
2758
2402
  const settings = readSettings();
2759
2403
  bundle.permissions = settings.permissions?.allow ?? [];
2760
2404
  const soulPath = path$1.join(claudeDir, "SOUL.md");
2761
2405
  if (fs$1.existsSync(soulPath)) {
2762
2406
  bundle.soul = fs$1.readFileSync(soulPath, "utf-8");
2763
- p$1.log.info("SOUL.md included");
2407
+ p$3.log.info("SOUL.md included");
2764
2408
  }
2765
2409
  const memoryDir = path$1.join(claudeDir, "memory");
2766
2410
  if (fs$1.existsSync(memoryDir)) {
2767
2411
  const files = fs$1.readdirSync(memoryDir).filter((f) => f.endsWith(".md"));
2768
2412
  for (const file of files) bundle.memory[file] = fs$1.readFileSync(path$1.join(memoryDir, file), "utf-8");
2769
- p$1.log.info(`${files.length} memory files included`);
2413
+ p$3.log.info(`${files.length} memory files included`);
2770
2414
  }
2771
2415
  const outputPath = path$1.resolve("openpaw-export.json");
2772
2416
  fs$1.writeFileSync(outputPath, JSON.stringify(bundle, null, 2), "utf-8");
2773
- p$1.log.success(`Exported to ${dim(outputPath)}`);
2774
- p$1.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"));
2775
2419
  }
2776
2420
  async function importCommand(file) {
2777
2421
  showMini();
2778
- p$1.intro(accent(" openpaw import "));
2422
+ p$3.intro(accent(" openpaw import "));
2779
2423
  const filePath = path$1.resolve(file);
2780
2424
  if (!fs$1.existsSync(filePath)) {
2781
- p$1.log.error(`File not found: ${filePath}`);
2425
+ p$3.log.error(`File not found: ${filePath}`);
2782
2426
  process.exit(1);
2783
2427
  }
2784
2428
  let bundle;
2785
2429
  try {
2786
2430
  bundle = JSON.parse(fs$1.readFileSync(filePath, "utf-8"));
2787
2431
  } catch {
2788
- p$1.log.error("Invalid export file — must be valid JSON");
2432
+ p$3.log.error("Invalid export file — must be valid JSON");
2789
2433
  process.exit(1);
2790
2434
  }
2791
- p$1.log.info(`Export from ${dim(bundle.exportedAt)}`);
2792
- p$1.log.info(`${bundle.skills.length} skills, ${Object.keys(bundle.memory).length} memory files`);
2793
- const proceed = await p$1.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({
2794
2438
  message: "Import this configuration?",
2795
2439
  initialValue: true
2796
2440
  });
2797
- if (p$1.isCancel(proceed) || !proceed) {
2798
- p$1.cancel("Import cancelled.");
2441
+ if (p$3.isCancel(proceed) || !proceed) {
2442
+ p$3.cancel("Import cancelled.");
2799
2443
  return;
2800
2444
  }
2801
2445
  const claudeDir = path$1.join(os$1.homedir(), ".claude");
2802
- const s = p$1.spinner();
2446
+ const s = p$3.spinner();
2803
2447
  if (bundle.soul) {
2804
2448
  s.start("🐾 Restoring SOUL.md...");
2805
2449
  fs$1.mkdirSync(claudeDir, { recursive: true });
@@ -2814,7 +2458,7 @@ async function importCommand(file) {
2814
2458
  s.stop(`🐾 ${Object.keys(bundle.memory).length} memory files restored`);
2815
2459
  }
2816
2460
  if (bundle.skills.length > 0) {
2817
- const { installSkill: installSkill$1 } = await import("./skills-DwMXaN3R.js");
2461
+ const { installSkill: installSkill$1 } = await import("./skills-CUY0swcW.js");
2818
2462
  s.start("🐾 Reinstalling skills...");
2819
2463
  const targetDir = getDefaultSkillsDir();
2820
2464
  let count = 0;
@@ -2828,17 +2472,17 @@ async function importCommand(file) {
2828
2472
  s.start("🐾 Restoring permissions...");
2829
2473
  const settings = readSettings();
2830
2474
  const existing = new Set(settings.permissions?.allow ?? []);
2831
- const newPerms = bundle.permissions.filter((p$12) => !existing.has(p$12));
2475
+ const newPerms = bundle.permissions.filter((p$13) => !existing.has(p$13));
2832
2476
  if (newPerms.length > 0) {
2833
2477
  if (!settings.permissions) settings.permissions = {};
2834
2478
  settings.permissions.allow = [...existing, ...newPerms];
2835
- const { writeSettings: writeSettings$1 } = await import("./permissions-CoaVX2ZM.js");
2479
+ const { writeSettings: writeSettings$1 } = await import("./permissions-AJXigU7k.js");
2836
2480
  writeSettings$1(settings);
2837
2481
  }
2838
2482
  s.stop(`🐾 ${newPerms.length} permissions added`);
2839
2483
  }
2840
- p$1.log.success("Import complete");
2841
- p$1.outro(accent("Run openpaw status to verify 🐾"));
2484
+ p$3.log.success("Import complete");
2485
+ p$3.outro(accent("Run openpaw status to verify 🐾"));
2842
2486
  }
2843
2487
 
2844
2488
  //#endregion
@@ -2847,40 +2491,353 @@ async function telegramCommand() {
2847
2491
  showMini();
2848
2492
  const config = readTelegramConfig();
2849
2493
  if (!config) {
2850
- p.log.error("Telegram not configured yet.");
2851
- p.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.`);
2852
2496
  process.exit(1);
2853
2497
  }
2854
2498
  await startTelegramBot(config);
2855
2499
  }
2856
2500
  async function telegramSetupCommand() {
2857
2501
  showMini();
2858
- p.intro(accent(" Telegram Bridge Setup "));
2502
+ p$2.intro(accent(" Telegram Bridge Setup "));
2859
2503
  if (telegramConfigExists()) {
2860
- const overwrite = await p.confirm({
2504
+ const overwrite = await p$2.confirm({
2861
2505
  message: "Telegram is already configured. Reconfigure?",
2862
2506
  initialValue: false
2863
2507
  });
2864
- if (p.isCancel(overwrite) || !overwrite) {
2865
- p.outro("Keeping existing config. 🐾");
2508
+ if (p$2.isCancel(overwrite) || !overwrite) {
2509
+ p$2.outro("Keeping existing config. 🐾");
2866
2510
  return;
2867
2511
  }
2868
2512
  }
2869
2513
  const config = await telegramQuestionnaire();
2870
2514
  if (!config) {
2871
- p.cancel("Setup cancelled.");
2515
+ p$2.cancel("Setup cancelled.");
2872
2516
  process.exit(0);
2873
2517
  }
2874
2518
  writeTelegramConfig(config);
2875
- p.log.success("Telegram config saved!");
2876
- p.log.info(`Start the bridge with: ${bold("openpaw telegram")}`);
2877
- p.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 🐾"));
2522
+ }
2523
+
2524
+ //#endregion
2525
+ //#region src/commands/dashboard.ts
2526
+ function dashboardCommand(opts) {
2527
+ const port = opts.port ? Number.parseInt(opts.port, 10) : void 0;
2528
+ const theme = opts.theme && (opts.theme === "paw" || opts.theme === "midnight" || opts.theme === "neon") ? opts.theme : void 0;
2529
+ startDashboard({
2530
+ port,
2531
+ theme
2532
+ });
2533
+ }
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
+
2601
+ //#endregion
2602
+ //#region src/commands/schedule.ts
2603
+ async function scheduleAddCommand(schedule, opts) {
2604
+ showMini();
2605
+ console.log("");
2606
+ let scheduleStr;
2607
+ let prompt;
2608
+ let model;
2609
+ let budgetUsd;
2610
+ let deliveryType;
2611
+ if (opts.run && schedule) {
2612
+ scheduleStr = schedule;
2613
+ prompt = opts.run;
2614
+ model = opts.model || "sonnet";
2615
+ budgetUsd = opts.budget ? Number.parseFloat(opts.budget) : 1;
2616
+ deliveryType = opts.delivery || "file";
2617
+ } else {
2618
+ p.intro(accent("Let's schedule a new job! 🐾"));
2619
+ const schedInput = await p.text({
2620
+ message: "When should this run?",
2621
+ placeholder: "e.g. \"weekdays 8am\", \"daily 9pm\", \"every 30 minutes\"",
2622
+ validate: (v) => v.length === 0 ? "Schedule is required" : void 0
2623
+ });
2624
+ if (p.isCancel(schedInput)) return;
2625
+ scheduleStr = schedInput;
2626
+ const promptInput = await p.text({
2627
+ message: "What should Claude do?",
2628
+ placeholder: "e.g. check my email and summarize the important ones",
2629
+ validate: (v) => v.length === 0 ? "Prompt is required" : void 0
2630
+ });
2631
+ if (p.isCancel(promptInput)) return;
2632
+ prompt = promptInput;
2633
+ const deliveryOptions = [{
2634
+ value: "file",
2635
+ label: "Save to file",
2636
+ hint: "~/.config/openpaw/schedule-results/"
2637
+ }];
2638
+ if (telegramConfigExists()) deliveryOptions.unshift({
2639
+ value: "telegram",
2640
+ label: "Telegram",
2641
+ hint: "send to your phone"
2642
+ });
2643
+ deliveryOptions.push({
2644
+ value: "notify",
2645
+ label: "macOS Notification",
2646
+ hint: "requires terminal-notifier"
2647
+ });
2648
+ const deliveryChoice = await p.select({
2649
+ message: "Where should results be delivered?",
2650
+ options: deliveryOptions
2651
+ });
2652
+ if (p.isCancel(deliveryChoice)) return;
2653
+ deliveryType = deliveryChoice;
2654
+ const modelChoice = await p.select({
2655
+ message: "Which model?",
2656
+ options: [
2657
+ {
2658
+ value: "sonnet",
2659
+ label: "Sonnet",
2660
+ hint: "fast, good for routine tasks ($)"
2661
+ },
2662
+ {
2663
+ value: "haiku",
2664
+ label: "Haiku",
2665
+ hint: "fastest, cheapest (¢)"
2666
+ },
2667
+ {
2668
+ value: "opus",
2669
+ label: "Opus",
2670
+ hint: "most capable, expensive ($$$)"
2671
+ }
2672
+ ]
2673
+ });
2674
+ if (p.isCancel(modelChoice)) return;
2675
+ model = modelChoice;
2676
+ const budgetInput = await p.text({
2677
+ message: "Per-run budget cap (USD)?",
2678
+ placeholder: "1.00",
2679
+ defaultValue: "1.00",
2680
+ validate: (v) => {
2681
+ const n = Number.parseFloat(v);
2682
+ if (Number.isNaN(n) || n <= 0) return "Must be a positive number";
2683
+ return void 0;
2684
+ }
2685
+ });
2686
+ if (p.isCancel(budgetInput)) return;
2687
+ budgetUsd = Number.parseFloat(budgetInput);
2688
+ }
2689
+ const parsed = parseHumanSchedule(scheduleStr);
2690
+ const job = addJob({
2691
+ name: prompt.slice(0, 60),
2692
+ prompt,
2693
+ schedule: parsed.cron,
2694
+ scheduleHuman: parsed.human,
2695
+ enabled: true,
2696
+ model,
2697
+ maxBudgetUsd: budgetUsd,
2698
+ delivery: { type: deliveryType }
2699
+ });
2700
+ const installed = installSystemJob(job);
2701
+ console.log("");
2702
+ p.log.success(`Job created: ${accent(job.id)}`);
2703
+ p.log.info(` Schedule: ${bold(parsed.human)} (${dim(parsed.cron)})`);
2704
+ p.log.info(` Prompt: ${dim(prompt.slice(0, 80))}`);
2705
+ p.log.info(` Model: ${model}`);
2706
+ p.log.info(` Budget: $${budgetUsd.toFixed(2)}/run`);
2707
+ p.log.info(` Delivery: ${deliveryType}`);
2708
+ if (installed) p.log.success(process.platform === "darwin" ? "Registered with launchd (runs even when terminal is closed)" : "Added to crontab");
2709
+ else p.log.warn("Could not register system job. Run manually with: openpaw schedule run " + job.id);
2710
+ console.log("");
2711
+ p.log.info(dim(`Test it now: ${accent("openpaw schedule run " + job.id)}`));
2712
+ }
2713
+ async function scheduleListCommand() {
2714
+ showMini();
2715
+ console.log("");
2716
+ const jobs = listJobs();
2717
+ if (jobs.length === 0) {
2718
+ p.log.info("No scheduled jobs yet. Create one with:");
2719
+ p.log.info(accent(" openpaw schedule add \"weekdays 8am\" --run \"check email\""));
2720
+ return;
2721
+ }
2722
+ const config = readScheduleConfig();
2723
+ const todayCost = getTodaysCost();
2724
+ console.log(` ${bold("Scheduled Jobs")} ${dim(`(daily cap: $${config.dailyCostCapUsd.toFixed(2)}, today: $${todayCost.toFixed(2)})`)}`);
2725
+ console.log("");
2726
+ for (const job of jobs) {
2727
+ const status = job.enabled ? chalk.green("ON ") : chalk.red("OFF");
2728
+ const lastRun = job.lastRunAt ? dim(` last: ${new Date(job.lastRunAt).toLocaleDateString()} ${job.lastRunResult || ""}`) : "";
2729
+ const cost = job.lastRunCostUsd ? dim(` $${job.lastRunCostUsd.toFixed(3)}`) : "";
2730
+ console.log(` ${status} ${accent(job.id)} ${bold(job.scheduleHuman)}`);
2731
+ console.log(` ${dim(job.prompt.slice(0, 70))}${lastRun}${cost}`);
2732
+ console.log(` ${dim(`model: ${job.model} | budget: $${job.maxBudgetUsd.toFixed(2)} | delivery: ${job.delivery.type}`)}`);
2733
+ console.log("");
2734
+ }
2735
+ }
2736
+ async function scheduleRemoveCommand(id) {
2737
+ showMini();
2738
+ console.log("");
2739
+ if (!id) {
2740
+ p.log.error("Usage: openpaw schedule remove <id>");
2741
+ return;
2742
+ }
2743
+ const removed = removeJob(id);
2744
+ if (removed) p.log.success(`Job ${accent(id)} removed and unregistered from system scheduler.`);
2745
+ else p.log.error(`Job ${id} not found.`);
2746
+ }
2747
+ async function scheduleRunCommand(id) {
2748
+ if (!id) {
2749
+ p.log.error("Usage: openpaw schedule run <id>");
2750
+ return;
2751
+ }
2752
+ const isInteractive = process.stdout.isTTY;
2753
+ if (isInteractive) {
2754
+ showMini();
2755
+ console.log("");
2756
+ const s = p.spinner();
2757
+ s.start(`Running job ${accent(id)}...`);
2758
+ const result = await runJob(id);
2759
+ if (result.success) {
2760
+ s.stop(`Job completed! Cost: $${(result.costUsd || 0).toFixed(3)}`);
2761
+ if (result.result) {
2762
+ console.log("");
2763
+ console.log(dim(" ─── Result ───"));
2764
+ console.log("");
2765
+ const lines = result.result.split("\n").slice(0, 20);
2766
+ for (const line of lines) console.log(` ${line}`);
2767
+ if (result.result.split("\n").length > 20) console.log(dim(" ... (truncated)"));
2768
+ }
2769
+ } else s.stop(`Job failed: ${result.error}`);
2770
+ } else {
2771
+ const result = await runJob(id);
2772
+ if (!result.success) {
2773
+ console.error(`[openpaw] Job ${id} failed: ${result.error}`);
2774
+ process.exit(1);
2775
+ }
2776
+ }
2777
+ }
2778
+ async function scheduleToggleCommand(id, enabled) {
2779
+ showMini();
2780
+ console.log("");
2781
+ if (!id) {
2782
+ p.log.error(`Usage: openpaw schedule ${enabled ? "enable" : "disable"} <id>`);
2783
+ return;
2784
+ }
2785
+ const ok = toggleJob(id, enabled);
2786
+ if (ok) p.log.success(`Job ${accent(id)} ${enabled ? "enabled" : "disabled"}.`);
2787
+ else p.log.error(`Job ${id} not found.`);
2788
+ }
2789
+ async function scheduleCostsCommand() {
2790
+ showMini();
2791
+ console.log("");
2792
+ const config = readScheduleConfig();
2793
+ const tracker = readCostTracker();
2794
+ const todayCost = getTodaysCost();
2795
+ const cap = config.dailyCostCapUsd;
2796
+ const pct = cap > 0 ? Math.round(todayCost / cap * 100) : 0;
2797
+ console.log(` ${bold("Cost Tracker")}`);
2798
+ console.log("");
2799
+ console.log(` Today: ${accent(`$${todayCost.toFixed(3)}`)} / $${cap.toFixed(2)} (${pct}%)`);
2800
+ console.log("");
2801
+ const days = Object.entries(tracker.dailyTotals).sort(([a], [b]) => b.localeCompare(a)).slice(0, 7);
2802
+ if (days.length > 0) {
2803
+ console.log(` ${dim("Recent days:")}`);
2804
+ for (const [date, cost] of days) {
2805
+ const bar = "█".repeat(Math.ceil(cost / cap * 20));
2806
+ console.log(` ${dim(date)} $${cost.toFixed(3)} ${accent(bar)}`);
2807
+ }
2808
+ }
2809
+ console.log("");
2810
+ const todayStr = new Date().toISOString().slice(0, 10);
2811
+ const todayEntries = tracker.entries.filter((e) => e.date === todayStr);
2812
+ if (todayEntries.length > 0) {
2813
+ console.log(` ${dim("Today's runs:")}`);
2814
+ for (const entry of todayEntries) {
2815
+ const time = new Date(entry.timestamp).toLocaleTimeString();
2816
+ console.log(` ${dim(time)} ${entry.jobId} $${entry.costUsd.toFixed(3)}`);
2817
+ }
2818
+ }
2819
+ console.log("");
2820
+ p.log.info(dim(`Daily cap: openpaw schedule set-cap <usd>`));
2821
+ }
2822
+ async function scheduleSetCapCommand(amount) {
2823
+ showMini();
2824
+ console.log("");
2825
+ const usd = Number.parseFloat(amount);
2826
+ if (Number.isNaN(usd) || usd <= 0) {
2827
+ p.log.error("Amount must be a positive number (e.g. 5.00)");
2828
+ return;
2829
+ }
2830
+ const config = readScheduleConfig();
2831
+ config.dailyCostCapUsd = usd;
2832
+ const { writeScheduleConfig } = await import("./scheduler-DppXPNqK.js");
2833
+ writeScheduleConfig(config);
2834
+ p.log.success(`Daily cost cap set to ${accent(`$${usd.toFixed(2)}`)}`);
2878
2835
  }
2879
2836
 
2880
2837
  //#endregion
2881
2838
  //#region src/index.ts
2882
2839
  const program = new Command();
2883
- program.name("openpaw").description("Personal Assistant Wizard for Claude Code").version("1.0.0");
2840
+ program.name("openpaw").description("Personal Assistant Wizard for Claude Code").version("1.3.0");
2884
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);
2885
2842
  program.command("add").description("Add skill(s) by name").argument("<skills...>", "Skill IDs to add (e.g. notes music email)").action(addCommand);
2886
2843
  program.command("remove").description("Remove skill(s) by name").argument("<skills...>", "Skill IDs to remove").action(removeCommand);
@@ -2892,9 +2849,20 @@ program.command("reset").description("Remove all OpenPaw skills, permissions, an
2892
2849
  program.command("soul").description("Set up or edit Claude's personality (SOUL.md)").action(soulCommand);
2893
2850
  program.command("export").description("Export skills, memory, and config to a file").action(exportCommand);
2894
2851
  program.command("import").description("Import skills, memory, and config from a file").argument("<file>", "Path to openpaw-export.json").action(importCommand);
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);
2895
2854
  const tg = program.command("telegram").description("Start the Telegram bridge — talk to Claude from your phone");
2896
2855
  tg.action(telegramCommand);
2897
2856
  tg.command("setup").description("Set up or reconfigure the Telegram bot").action(telegramSetupCommand);
2857
+ const sched = program.command("schedule").description("Manage scheduled jobs — automate recurring tasks with cost control");
2858
+ sched.command("add [schedule]").description("Add a scheduled job").option("--run <prompt>", "What Claude should do").option("--model <model>", "Model to use (sonnet/opus/haiku)", "sonnet").option("--budget <usd>", "Per-run budget cap in USD", "1.00").option("--delivery <type>", "Delivery method (telegram/file/notify)", "file").action((schedule, opts) => scheduleAddCommand(schedule, opts));
2859
+ sched.command("list").alias("ls").description("List all scheduled jobs").action(() => scheduleListCommand());
2860
+ sched.command("remove <id>").alias("rm").description("Remove a scheduled job").action((id) => scheduleRemoveCommand(id));
2861
+ sched.command("run <id>").description("Manually trigger a scheduled job").action((id) => scheduleRunCommand(id));
2862
+ sched.command("enable <id>").description("Enable a scheduled job").action((id) => scheduleToggleCommand(id, true));
2863
+ sched.command("disable <id>").description("Disable a scheduled job").action((id) => scheduleToggleCommand(id, false));
2864
+ sched.command("costs").description("Show today's cost usage and daily cap").action(() => scheduleCostsCommand());
2865
+ sched.command("set-cap <usd>").description("Set the daily cost cap in USD").action((usd) => scheduleSetCapCommand(usd));
2898
2866
  program.parse();
2899
2867
 
2900
2868
  //#endregion