pawmode 1.3.0 → 1.5.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,9 +1,10 @@
1
1
  #!/usr/bin/env node
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";
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, writeScheduleConfig, writeTelegramConfig } from "./scheduler-DAmd0GzB.js";
3
3
  import { getDefaultSkillsDir, installSkill, isSkillInstalled, listInstalledSkills, removeSkill } from "./skills-CMqq9k1-.js";
4
4
  import { addPermissions, readSettings, removePermissions, writeSettings } from "./permissions-BlGEHCXO.js";
5
- import { readConfig, startDashboard, writeConfig } from "./dashboard-server-BAyeozOa.js";
5
+ import { readConfig, startDashboard, writeConfig } from "./dashboard-server-BiH4JLbM.js";
6
6
  import { Command } from "commander";
7
+ import * as p$13 from "@clack/prompts";
7
8
  import * as p$12 from "@clack/prompts";
8
9
  import * as p$11 from "@clack/prompts";
9
10
  import * as p$10 from "@clack/prompts";
@@ -17,6 +18,8 @@ import * as p$3 from "@clack/prompts";
17
18
  import * as p$2 from "@clack/prompts";
18
19
  import * as p$1 from "@clack/prompts";
19
20
  import * as p from "@clack/prompts";
21
+ import * as os$7 from "node:os";
22
+ import * as os$6 from "node:os";
20
23
  import * as os$5 from "node:os";
21
24
  import * as os$4 from "node:os";
22
25
  import * as os$3 from "node:os";
@@ -25,11 +28,15 @@ import * as os$1 from "node:os";
25
28
  import os from "node:os";
26
29
  import chalk from "chalk";
27
30
  import { execSync, spawn } from "node:child_process";
31
+ import * as fs$6 from "node:fs";
32
+ import * as fs$5 from "node:fs";
28
33
  import * as fs$4 from "node:fs";
29
34
  import * as fs$3 from "node:fs";
30
35
  import * as fs$2 from "node:fs";
31
36
  import * as fs$1 from "node:fs";
32
37
  import fs from "node:fs";
38
+ import * as path$6 from "node:path";
39
+ import * as path$5 from "node:path";
33
40
  import * as path$4 from "node:path";
34
41
  import * as path$3 from "node:path";
35
42
  import * as path$2 from "node:path";
@@ -1067,7 +1074,7 @@ const presets = [
1067
1074
  }
1068
1075
  ];
1069
1076
  function getPresetSkills(presetId, platform) {
1070
- const preset = presets.find((p$13) => p$13.id === presetId);
1077
+ const preset = presets.find((p$14) => p$14.id === presetId);
1071
1078
  if (!preset) return [];
1072
1079
  const available = getSkillsForPlatform(platform);
1073
1080
  if (preset.id === "everything") return available;
@@ -1280,25 +1287,25 @@ function removeSafetyHooks() {
1280
1287
  //#endregion
1281
1288
  //#region src/core/soul.ts
1282
1289
  function getSoulPath$1() {
1283
- return path$4.join(os$5.homedir(), ".claude", "SOUL.md");
1290
+ return path$6.join(os$7.homedir(), ".claude", "SOUL.md");
1284
1291
  }
1285
1292
  function soulExists() {
1286
- return fs$4.existsSync(getSoulPath$1());
1293
+ return fs$6.existsSync(getSoulPath$1());
1287
1294
  }
1288
1295
  async function soulQuestionnaire() {
1289
- const name = await p$12.text({
1296
+ const name = await p$13.text({
1290
1297
  message: "What should your assistant call you?",
1291
1298
  placeholder: "Your name or nickname",
1292
1299
  validate: (v) => v.length === 0 ? "Name cannot be empty" : void 0
1293
1300
  });
1294
- if (p$12.isCancel(name)) return null;
1295
- const botName = await p$12.text({
1301
+ if (p$13.isCancel(name)) return null;
1302
+ const botName = await p$13.text({
1296
1303
  message: "Name your assistant:",
1297
1304
  placeholder: "Paw",
1298
1305
  defaultValue: "Paw"
1299
1306
  });
1300
- if (p$12.isCancel(botName)) return null;
1301
- const tone = await p$12.select({
1307
+ if (p$13.isCancel(botName)) return null;
1308
+ const tone = await p$13.select({
1302
1309
  message: "Communication style?",
1303
1310
  options: [
1304
1311
  {
@@ -1318,8 +1325,8 @@ async function soulQuestionnaire() {
1318
1325
  }
1319
1326
  ]
1320
1327
  });
1321
- if (p$12.isCancel(tone)) return null;
1322
- const verbosity = await p$12.select({
1328
+ if (p$13.isCancel(tone)) return null;
1329
+ const verbosity = await p$13.select({
1323
1330
  message: "Response length?",
1324
1331
  options: [
1325
1332
  {
@@ -1339,18 +1346,18 @@ async function soulQuestionnaire() {
1339
1346
  }
1340
1347
  ]
1341
1348
  });
1342
- if (p$12.isCancel(verbosity)) return null;
1343
- const proactive = await p$12.confirm({
1349
+ if (p$13.isCancel(verbosity)) return null;
1350
+ const proactive = await p$13.confirm({
1344
1351
  message: "Should Claude suggest things proactively?",
1345
1352
  initialValue: true
1346
1353
  });
1347
- if (p$12.isCancel(proactive)) return null;
1348
- const extrasResult = await p$12.text({
1354
+ if (p$13.isCancel(proactive)) return null;
1355
+ const extrasResult = await p$13.text({
1349
1356
  message: "Any custom instructions? (optional)",
1350
1357
  placeholder: "e.g. always respond in Spanish, prefer dark humor, etc.",
1351
1358
  defaultValue: ""
1352
1359
  });
1353
- if (p$12.isCancel(extrasResult)) return null;
1360
+ if (p$13.isCancel(extrasResult)) return null;
1354
1361
  const extras = extrasResult.split(",").map((s) => s.trim()).filter(Boolean);
1355
1362
  return {
1356
1363
  name,
@@ -1398,9 +1405,9 @@ function writeSoul(config) {
1398
1405
  }
1399
1406
  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!').`, "");
1400
1407
  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", "");
1401
- const soulDir = path$4.dirname(getSoulPath$1());
1402
- if (!fs$4.existsSync(soulDir)) fs$4.mkdirSync(soulDir, { recursive: true });
1403
- fs$4.writeFileSync(getSoulPath$1(), lines.join("\n"), "utf-8");
1408
+ const soulDir = path$6.dirname(getSoulPath$1());
1409
+ if (!fs$6.existsSync(soulDir)) fs$6.mkdirSync(soulDir, { recursive: true });
1410
+ fs$6.writeFileSync(getSoulPath$1(), lines.join("\n"), "utf-8");
1404
1411
  }
1405
1412
  function showSoulSummary(config) {
1406
1413
  const lines = [
@@ -1411,12 +1418,12 @@ function showSoulSummary(config) {
1411
1418
  `${accent("Proactive:")} ${config.proactive ? "yes" : "no"}`
1412
1419
  ];
1413
1420
  if (config.extras.length > 0) lines.push(`${accent("Custom:")} ${config.extras.join(", ")}`);
1414
- p$12.note(lines.join("\n"), "Personality");
1421
+ p$13.note(lines.join("\n"), "Personality");
1415
1422
  }
1416
1423
 
1417
1424
  //#endregion
1418
1425
  //#region src/core/memory.ts
1419
- const MEMORY_DIR = path$3.join(os$4.homedir(), ".claude", "memory");
1426
+ const MEMORY_DIR = path$5.join(os$6.homedir(), ".claude", "memory");
1420
1427
  const INITIAL_MEMORY = `# Memory
1421
1428
 
1422
1429
  ## User
@@ -1429,12 +1436,12 @@ const INITIAL_MEMORY = `# Memory
1429
1436
  - (Claude will track projects mentioned in conversation)
1430
1437
  `;
1431
1438
  function setupMemory(userName) {
1432
- if (!fs$3.existsSync(MEMORY_DIR)) fs$3.mkdirSync(MEMORY_DIR, { recursive: true });
1433
- const memoryPath = path$3.join(MEMORY_DIR, "MEMORY.md");
1434
- if (!fs$3.existsSync(memoryPath)) {
1439
+ if (!fs$5.existsSync(MEMORY_DIR)) fs$5.mkdirSync(MEMORY_DIR, { recursive: true });
1440
+ const memoryPath = path$5.join(MEMORY_DIR, "MEMORY.md");
1441
+ if (!fs$5.existsSync(memoryPath)) {
1435
1442
  let content = INITIAL_MEMORY;
1436
1443
  if (userName) content = content.replace("(will be filled in as we learn)", userName);
1437
- fs$3.writeFileSync(memoryPath, content, "utf-8");
1444
+ fs$5.writeFileSync(memoryPath, content, "utf-8");
1438
1445
  }
1439
1446
  const topicFiles = [
1440
1447
  "people.md",
@@ -1443,10 +1450,10 @@ function setupMemory(userName) {
1443
1450
  "journal.md"
1444
1451
  ];
1445
1452
  for (const file of topicFiles) {
1446
- const filePath = path$3.join(MEMORY_DIR, file);
1447
- if (!fs$3.existsSync(filePath)) {
1453
+ const filePath = path$5.join(MEMORY_DIR, file);
1454
+ if (!fs$5.existsSync(filePath)) {
1448
1455
  const title = file.replace(".md", "");
1449
- fs$3.writeFileSync(filePath, `# ${title.charAt(0).toUpperCase() + title.slice(1)}\n`, "utf-8");
1456
+ fs$5.writeFileSync(filePath, `# ${title.charAt(0).toUpperCase() + title.slice(1)}\n`, "utf-8");
1450
1457
  }
1451
1458
  }
1452
1459
  }
@@ -1485,17 +1492,311 @@ function launchInBackground(cmd) {
1485
1492
  child.unref();
1486
1493
  }
1487
1494
 
1495
+ //#endregion
1496
+ //#region src/core/lockin.ts
1497
+ const CONFIG_DIR = path$4.join(os$5.homedir(), ".config", "openpaw");
1498
+ const LOCKIN_PATH = path$4.join(CONFIG_DIR, "lockin.json");
1499
+ const SESSION_PATH = path$4.join(CONFIG_DIR, "lockin-session.json");
1500
+ function ensureDir() {
1501
+ fs$4.mkdirSync(CONFIG_DIR, { recursive: true });
1502
+ }
1503
+ function readLockInConfig() {
1504
+ try {
1505
+ const raw = fs$4.readFileSync(LOCKIN_PATH, "utf-8");
1506
+ return JSON.parse(raw);
1507
+ } catch {
1508
+ return null;
1509
+ }
1510
+ }
1511
+ function writeLockInConfig(config) {
1512
+ ensureDir();
1513
+ fs$4.writeFileSync(LOCKIN_PATH, JSON.stringify(config, null, 2));
1514
+ fs$4.chmodSync(LOCKIN_PATH, 384);
1515
+ }
1516
+ function lockInConfigExists() {
1517
+ return fs$4.existsSync(LOCKIN_PATH);
1518
+ }
1519
+ function readLockInSession() {
1520
+ try {
1521
+ const raw = fs$4.readFileSync(SESSION_PATH, "utf-8");
1522
+ return JSON.parse(raw);
1523
+ } catch {
1524
+ return null;
1525
+ }
1526
+ }
1527
+ function cmdExists(cmd) {
1528
+ try {
1529
+ execSync(`command -v ${cmd}`, { stdio: "pipe" });
1530
+ return true;
1531
+ } catch {
1532
+ return false;
1533
+ }
1534
+ }
1535
+ function tryExec(cmd) {
1536
+ try {
1537
+ return execSync(cmd, {
1538
+ stdio: "pipe",
1539
+ timeout: 5e3
1540
+ }).toString().trim();
1541
+ } catch {
1542
+ return "";
1543
+ }
1544
+ }
1545
+ function detectCapabilities() {
1546
+ const caps = {
1547
+ hasBluetooth: false,
1548
+ bluetoothDevices: [],
1549
+ hasSpotify: false,
1550
+ hasAppleMusic: false,
1551
+ hasSonos: false,
1552
+ hasYtDlp: false,
1553
+ hasHue: false,
1554
+ hueRooms: [],
1555
+ hasSlack: false,
1556
+ hasObsidian: false,
1557
+ hasTerminalNotifier: false,
1558
+ hasTelegram: false,
1559
+ runningApps: []
1560
+ };
1561
+ if (cmdExists("blu")) {
1562
+ caps.hasBluetooth = true;
1563
+ const out = tryExec("blu list --paired 2>/dev/null");
1564
+ if (out) caps.bluetoothDevices = out.split("\n").map((l) => l.trim()).filter(Boolean).slice(0, 10);
1565
+ } else if (cmdExists("blueutil")) caps.hasBluetooth = true;
1566
+ caps.hasSpotify = cmdExists("spogo");
1567
+ caps.hasAppleMusic = process.platform === "darwin";
1568
+ caps.hasSonos = cmdExists("sonos");
1569
+ caps.hasYtDlp = cmdExists("yt-dlp");
1570
+ if (cmdExists("openhue")) {
1571
+ caps.hasHue = true;
1572
+ const rooms = tryExec("openhue get rooms --json 2>/dev/null");
1573
+ if (rooms) try {
1574
+ const parsed = JSON.parse(rooms);
1575
+ if (Array.isArray(parsed)) caps.hueRooms = parsed.map((r) => r.metadata?.name).filter(Boolean);
1576
+ } catch {}
1577
+ }
1578
+ caps.hasSlack = cmdExists("slack");
1579
+ caps.hasObsidian = cmdExists("obsidian-cli");
1580
+ caps.hasTerminalNotifier = cmdExists("terminal-notifier");
1581
+ try {
1582
+ const tgPath = path$4.join(CONFIG_DIR, "telegram.json");
1583
+ caps.hasTelegram = fs$4.existsSync(tgPath);
1584
+ } catch {}
1585
+ if (process.platform === "darwin") {
1586
+ const out = tryExec(`osascript -e 'tell application "System Events" to get name of every process whose background only is false' 2>/dev/null`);
1587
+ if (out) caps.runningApps = out.split(", ").filter(Boolean);
1588
+ }
1589
+ return caps;
1590
+ }
1591
+ function generateStartScript(opts) {
1592
+ const { config, endsAt, extraSites = [], extraApps = [] } = opts;
1593
+ const durationMin = config.duration;
1594
+ const durationSec = durationMin * 60;
1595
+ const lines = ["#!/bin/bash", ""];
1596
+ const allSites = [...config.blockedSites?.always || [], ...extraSites];
1597
+ if (allSites.length > 0) {
1598
+ const siteList = allSites.map((s) => `"${s}"`).join(",");
1599
+ lines.push("# Site blocking via PAC file");
1600
+ lines.push(`cat > /tmp/lockin-block.pac << 'PACEOF'
1601
+ function FindProxyForURL(url, host) {
1602
+ var blocked = [${siteList}];
1603
+ for (var i = 0; i < blocked.length; i++) {
1604
+ if (dnsDomainIs(host, blocked[i]) || dnsDomainIs(host, "www." + blocked[i])) {
1605
+ return "PROXY 127.0.0.1:1";
1606
+ }
1607
+ }
1608
+ return "DIRECT";
1609
+ }
1610
+ PACEOF`);
1611
+ lines.push(`python3 -m http.server 9777 --directory /tmp &>/dev/null &`);
1612
+ lines.push(`sleep 1`);
1613
+ lines.push(`networksetup -listallnetworkservices | tail -n +2 | while IFS= read -r svc; do networksetup -setautoproxyurl "$svc" "http://127.0.0.1:9777/lockin-block.pac" 2>/dev/null; done`);
1614
+ lines.push(`defaults write com.google.Chrome IncognitoModeAvailability -integer 1`);
1615
+ lines.push("");
1616
+ }
1617
+ const allApps = [...config.quitApps?.always || [], ...extraApps];
1618
+ if (allApps.length > 0) {
1619
+ lines.push("# Quit apps");
1620
+ for (const app of allApps) lines.push(`osascript -e 'quit app "${app}"' 2>/dev/null || true`);
1621
+ lines.push("");
1622
+ }
1623
+ if (config.bluetooth?.device) {
1624
+ lines.push("# Bluetooth");
1625
+ lines.push(`blu connect "${config.bluetooth.device}" 2>/dev/null || true`);
1626
+ lines.push("");
1627
+ }
1628
+ if (config.music) {
1629
+ lines.push("# Music");
1630
+ const q = config.music.query;
1631
+ switch (config.music.source) {
1632
+ case "youtube":
1633
+ lines.push(`open "${q}"`);
1634
+ break;
1635
+ case "spotify":
1636
+ lines.push(`spogo play "${q}" 2>/dev/null || true`);
1637
+ break;
1638
+ case "apple-music":
1639
+ lines.push(`osascript -e 'tell application "Music" to play (first playlist whose name contains "${q}")' 2>/dev/null || true`);
1640
+ break;
1641
+ case "sonos":
1642
+ lines.push(`sonos play "${q}" 2>/dev/null || true`);
1643
+ break;
1644
+ }
1645
+ lines.push("");
1646
+ }
1647
+ if (config.lights) {
1648
+ lines.push("# Lights");
1649
+ let cmd = `openhue set room "${config.lights.room}" --on --brightness ${config.lights.brightness}`;
1650
+ if (config.lights.color) cmd += ` --color "${config.lights.color}"`;
1651
+ lines.push(`${cmd} 2>/dev/null || true`);
1652
+ lines.push("");
1653
+ }
1654
+ if (config.dnd) {
1655
+ lines.push("# Do Not Disturb");
1656
+ lines.push(`shortcuts run "Set Focus" 2>/dev/null || { defaults -currentHost write com.apple.notificationcenterui doNotDisturb -boolean true && killall NotificationCenter 2>/dev/null; } || true`);
1657
+ lines.push("");
1658
+ }
1659
+ if (config.slackDnd) {
1660
+ lines.push("# Slack DND");
1661
+ lines.push(`slack dnd set ${durationMin} 2>/dev/null || true`);
1662
+ lines.push("");
1663
+ }
1664
+ if (config.timer) {
1665
+ lines.push("# Timer notification");
1666
+ lines.push(`(sleep ${durationSec} && terminal-notifier -title "Lock In Complete" -message "Session finished! Time for a break." -sound default) &`);
1667
+ lines.push("");
1668
+ }
1669
+ lines.push("# Window management");
1670
+ lines.push(`FRONT_APP=$(osascript -e 'tell application "System Events" to get name of first application process whose frontmost is true' 2>/dev/null || echo "Terminal")`);
1671
+ lines.push("");
1672
+ const encodedEnds = encodeURIComponent(endsAt);
1673
+ lines.push("# Start dashboard + open timer");
1674
+ lines.push(`curl -s -o /dev/null http://localhost:3141 2>/dev/null || nohup openpaw dashboard --no-open &>/dev/null &`);
1675
+ lines.push(`sleep 1 && open "http://localhost:3141/focus?ends=${encodedEnds}&duration=${durationMin}"`);
1676
+ lines.push("");
1677
+ lines.push("# Position timer top-left");
1678
+ lines.push(`sleep 2 && osascript << 'ASCRIPT'
1679
+ tell application "System Events"
1680
+ set frontApp to name of first application process whose frontmost is true
1681
+ end tell
1682
+ tell application frontApp
1683
+ set bounds of front window to {0, 25, 400, 325}
1684
+ end tell
1685
+ ASCRIPT`);
1686
+ lines.push("");
1687
+ lines.push("# Bring coding app back");
1688
+ lines.push(`osascript -e "tell application \\"$FRONT_APP\\" to activate"`);
1689
+ lines.push("");
1690
+ lines.push("# Minimize everything else");
1691
+ lines.push(`osascript << 'ASCRIPT'
1692
+ tell application "System Events"
1693
+ set frontApp to name of first application process whose frontmost is true
1694
+ repeat with proc in (every process whose visible is true and name is not frontApp and name is not "Finder")
1695
+ try
1696
+ click (first button of (first window of proc) whose subrole is "AXMinimizeButton")
1697
+ end try
1698
+ end repeat
1699
+ end tell
1700
+ ASCRIPT`);
1701
+ lines.push("");
1702
+ const startedAt = new Date().toISOString();
1703
+ const sessionData = {
1704
+ startedAt,
1705
+ endsAt,
1706
+ config,
1707
+ blockedSiteAttempts: 0,
1708
+ gitCommitsBefore: 0
1709
+ };
1710
+ lines.push("# Write session file");
1711
+ lines.push(`GIT_COMMITS=$(git rev-list --count HEAD 2>/dev/null || echo 0)`);
1712
+ const sessionJson = JSON.stringify(sessionData);
1713
+ const sessionWithGit = sessionJson.replace("\"gitCommitsBefore\":0", "\"gitCommitsBefore\":$GIT_COMMITS");
1714
+ lines.push(`echo '${sessionWithGit.replace(/'/g, "'\\''")}' | sed "s/\\$GIT_COMMITS/$GIT_COMMITS/g" > ~/.config/openpaw/lockin-session.json`);
1715
+ lines.push("");
1716
+ lines.push(`echo "Locked in until $(date -j -f '%Y-%m-%dT%H:%M' '${endsAt.slice(0, 16)}' '+%I:%M %p' 2>/dev/null || echo '${endsAt}')"`);
1717
+ return lines.join("\n");
1718
+ }
1719
+ function generateEndScript(config) {
1720
+ const lines = ["#!/bin/bash", ""];
1721
+ lines.push("# Read session data");
1722
+ lines.push(`SESSION_FILE=~/.config/openpaw/lockin-session.json`);
1723
+ lines.push(`if [ -f "$SESSION_FILE" ]; then`);
1724
+ lines.push(` STARTED_AT=$(python3 -c "import json; print(json.load(open('$SESSION_FILE'))['startedAt'])" 2>/dev/null || echo "")`);
1725
+ lines.push(` GIT_BEFORE=$(python3 -c "import json; print(json.load(open('$SESSION_FILE'))['gitCommitsBefore'])" 2>/dev/null || echo "0")`);
1726
+ lines.push(`else`);
1727
+ lines.push(` STARTED_AT=""`);
1728
+ lines.push(` GIT_BEFORE=0`);
1729
+ lines.push(`fi`);
1730
+ lines.push("");
1731
+ if (config.blockedSites && (config.blockedSites.always.length > 0 || config.blockedSites.askEachTime.length > 0)) {
1732
+ lines.push("# Remove site blocking");
1733
+ lines.push(`networksetup -listallnetworkservices | tail -n +2 | while IFS= read -r svc; do networksetup -setautoproxystate "$svc" off 2>/dev/null; done`);
1734
+ lines.push(`pkill -f "python3 -m http.server 9777" 2>/dev/null; rm -f /tmp/lockin-block.pac`);
1735
+ lines.push(`defaults delete com.google.Chrome IncognitoModeAvailability 2>/dev/null || true`);
1736
+ lines.push("");
1737
+ }
1738
+ if (config.dnd) {
1739
+ lines.push("# Disable DND");
1740
+ lines.push(`defaults -currentHost write com.apple.notificationcenterui doNotDisturb -boolean false && killall NotificationCenter 2>/dev/null || true`);
1741
+ lines.push("");
1742
+ }
1743
+ if (config.music) {
1744
+ lines.push("# Stop music");
1745
+ lines.push(`osascript -e 'tell application "Music" to pause' 2>/dev/null; osascript -e 'tell application "Spotify" to pause' 2>/dev/null || true`);
1746
+ lines.push("");
1747
+ }
1748
+ lines.push("# Git receipt");
1749
+ lines.push(`echo "--- Git Activity ---"`);
1750
+ lines.push(`if [ -n "$STARTED_AT" ]; then git log --oneline --after="$STARTED_AT" 2>/dev/null || echo "(no commits)"; fi`);
1751
+ lines.push(`echo ""`);
1752
+ lines.push(`COMMITS_NOW=$(git rev-list --count HEAD 2>/dev/null || echo 0)`);
1753
+ lines.push(`COMMITS_DIFF=$((COMMITS_NOW - GIT_BEFORE))`);
1754
+ lines.push(`if [ "$COMMITS_DIFF" -gt 0 ]; then git diff --stat "HEAD~$COMMITS_DIFF" 2>/dev/null; fi`);
1755
+ lines.push("");
1756
+ lines.push("# Cleanup");
1757
+ lines.push(`rm -f ~/.config/openpaw/lockin-session.json`);
1758
+ lines.push(`rm -f /tmp/lockin-start.sh /tmp/lockin-end.sh`);
1759
+ return lines.join("\n");
1760
+ }
1761
+ const COMMON_BLOCKED_SITES = [
1762
+ "x.com",
1763
+ "reddit.com",
1764
+ "instagram.com",
1765
+ "facebook.com",
1766
+ "tiktok.com",
1767
+ "youtube.com",
1768
+ "linkedin.com",
1769
+ "threads.net",
1770
+ "bsky.app",
1771
+ "discord.com",
1772
+ "twitch.tv"
1773
+ ];
1774
+ const COMMON_QUIT_APPS = [
1775
+ "Messages",
1776
+ "Mail",
1777
+ "Discord",
1778
+ "Slack",
1779
+ "Telegram",
1780
+ "WhatsApp",
1781
+ "Twitter",
1782
+ "Safari",
1783
+ "Chrome",
1784
+ "Firefox"
1785
+ ];
1786
+
1488
1787
  //#endregion
1489
1788
  //#region src/core/claude-md.ts
1789
+ const START_MARKER = "<!-- OPENPAW:START -->";
1790
+ const END_MARKER = "<!-- OPENPAW:END -->";
1490
1791
  function getClaudeMdPath() {
1491
- return path$2.join(os$3.homedir(), ".claude", "CLAUDE.md");
1792
+ return path$3.join(os$4.homedir(), ".claude", "CLAUDE.md");
1492
1793
  }
1493
1794
  function getSoulPath() {
1494
- return path$2.join(os$3.homedir(), ".claude", "SOUL.md");
1795
+ return path$3.join(os$4.homedir(), ".claude", "SOUL.md");
1495
1796
  }
1496
1797
  function readBotName() {
1497
1798
  try {
1498
- const soul = fs$2.readFileSync(getSoulPath(), "utf-8");
1799
+ const soul = fs$3.readFileSync(getSoulPath(), "utf-8");
1499
1800
  const match = soul.match(/You are \*\*(.+?)\*\*/);
1500
1801
  if (match) return match[1];
1501
1802
  const nameMatch = soul.match(/\*\*Your name\*\*:\s*(.+?)[\s—]/);
@@ -1503,12 +1804,10 @@ function readBotName() {
1503
1804
  } catch {}
1504
1805
  return "Paw";
1505
1806
  }
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) {
1807
+ function generateSection(botName, installedSkills, hasDashboard) {
1511
1808
  const lines = [
1809
+ START_MARKER,
1810
+ "",
1512
1811
  "# OpenPaw — PAW MODE Active",
1513
1812
  "",
1514
1813
  `You are **${botName}**, a personal assistant powered by OpenPaw. PAW MODE is active.`,
@@ -1526,7 +1825,7 @@ function writeClaudeMd(botName, installedSkills, hasDashboard) {
1526
1825
  else {
1527
1826
  for (const skill of installedSkills) lines.push(`- **c-${skill.id}** — ${skill.description}`);
1528
1827
  lines.push("");
1529
- lines.push(`Use \`/c <request>\` to route through the coordinator, or talk naturally.`);
1828
+ lines.push("Use `/c <request>` to route through the coordinator, or talk naturally.");
1530
1829
  }
1531
1830
  lines.push("");
1532
1831
  if (hasDashboard) {
@@ -1536,6 +1835,28 @@ function writeClaudeMd(botName, installedSkills, hasDashboard) {
1536
1835
  lines.push("You can tell users about it when they ask about task management.");
1537
1836
  lines.push("");
1538
1837
  }
1838
+ try {
1839
+ const schedConfig = readScheduleConfig();
1840
+ if (schedConfig.jobs.length > 0 || schedConfig.dailyCostCapUsd > 0) {
1841
+ lines.push("## Smart Scheduling");
1842
+ lines.push("");
1843
+ lines.push(`Scheduling is enabled with a $${schedConfig.dailyCostCapUsd}/day cost cap.`);
1844
+ if (schedConfig.jobs.length > 0) lines.push(`${schedConfig.jobs.filter((j) => j.enabled).length} active job(s).`);
1845
+ lines.push("Run `openpaw schedule list` to see jobs, `openpaw schedule costs` to check spending.");
1846
+ lines.push("");
1847
+ }
1848
+ } catch {}
1849
+ try {
1850
+ const lockInConfig = readLockInConfig();
1851
+ if (lockInConfig) {
1852
+ lines.push("## Lock In Mode");
1853
+ lines.push("");
1854
+ lines.push(`Lock In Mode is configured (${lockInConfig.duration} min sessions).`);
1855
+ lines.push("When the user says \"lock in\", \"focus\", or similar, read the c-lockin SKILL.md and follow its instructions.");
1856
+ lines.push("Run `openpaw lockin setup` to reconfigure.");
1857
+ lines.push("");
1858
+ }
1859
+ } catch {}
1539
1860
  lines.push("## How to Use Skills");
1540
1861
  lines.push("");
1541
1862
  lines.push("- Match user intent to the right skill's CLI tool (check `~/.claude/skills/c-<name>/SKILL.md` for usage)");
@@ -1550,17 +1871,47 @@ function writeClaudeMd(botName, installedSkills, hasDashboard) {
1550
1871
  lines.push("- If asked about your setup: \"I'm powered by OpenPaw — open-source personal assistant skills for Claude Code\"");
1551
1872
  lines.push("- Project: https://github.com/daxaur/openpaw");
1552
1873
  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");
1874
+ lines.push(END_MARKER);
1875
+ return lines.join("\n");
1556
1876
  }
1557
1877
  /**
1558
- * Regenerate CLAUDE.md from current state (installed skills, SOUL.md, dashboard config).
1878
+ * Write or update the OpenPaw section in ~/.claude/CLAUDE.md.
1879
+ *
1880
+ * If CLAUDE.md already exists, only the content between
1881
+ * <!-- OPENPAW:START --> and <!-- OPENPAW:END --> is replaced.
1882
+ * Everything else (user's own instructions) is preserved.
1883
+ *
1884
+ * If no markers exist yet, the section is appended to the end.
1885
+ * If the file doesn't exist, it's created with just the OpenPaw section.
1886
+ */
1887
+ function writeClaudeMd(botName, installedSkills, hasDashboard) {
1888
+ const dir = path$3.dirname(getClaudeMdPath());
1889
+ if (!fs$3.existsSync(dir)) fs$3.mkdirSync(dir, { recursive: true });
1890
+ const section = generateSection(botName, installedSkills, hasDashboard);
1891
+ const filePath = getClaudeMdPath();
1892
+ if (!fs$3.existsSync(filePath)) {
1893
+ fs$3.writeFileSync(filePath, section + "\n", "utf-8");
1894
+ return;
1895
+ }
1896
+ const existing = fs$3.readFileSync(filePath, "utf-8");
1897
+ const startIdx = existing.indexOf(START_MARKER);
1898
+ const endIdx = existing.indexOf(END_MARKER);
1899
+ if (startIdx !== -1 && endIdx !== -1) {
1900
+ const before = existing.slice(0, startIdx);
1901
+ const after = existing.slice(endIdx + END_MARKER.length);
1902
+ fs$3.writeFileSync(filePath, before + section + after, "utf-8");
1903
+ } else {
1904
+ const separator = existing.endsWith("\n") ? "\n" : "\n\n";
1905
+ fs$3.writeFileSync(filePath, existing + separator + section + "\n", "utf-8");
1906
+ }
1907
+ }
1908
+ /**
1909
+ * Regenerate the OpenPaw section in CLAUDE.md from current state.
1559
1910
  * Call this after `openpaw add` or `openpaw remove`.
1560
1911
  */
1561
1912
  function regenerateClaudeMd() {
1562
1913
  const botName = readBotName();
1563
- const defaultDir = path$2.join(os$3.homedir(), ".claude", "skills");
1914
+ const defaultDir = path$3.join(os$4.homedir(), ".claude", "skills");
1564
1915
  const installedIds = listInstalledSkills(defaultDir);
1565
1916
  const installedSkills = installedIds.map((id) => skills.find((s) => s.id === id)).filter((s) => !!s);
1566
1917
  let hasDashboard = false;
@@ -1586,34 +1937,34 @@ const CATEGORY_ICONS = {
1586
1937
  async function setupCommand(opts = {}) {
1587
1938
  await showBanner();
1588
1939
  const platform = detectPlatform();
1589
- p$11.intro(accent(" openpaw setup "));
1940
+ p$12.intro(accent(" openpaw setup "));
1590
1941
  const brewStatus = platform.hasBrew ? chalk.green("✓ brew") : chalk.red("✗ brew");
1591
1942
  const npmStatus = platform.hasNpm ? chalk.green("✓ npm") : chalk.red("✗ npm");
1592
1943
  const pipStatus = platform.hasPip ? chalk.green("✓ pip") : chalk.dim("○ pip");
1593
- p$11.log.info(`${chalk.bold(platform.osName)} ${platform.osVersion} ${brewStatus} ${npmStatus} ${pipStatus}`);
1944
+ p$12.log.info(`${chalk.bold(platform.osName)} ${platform.osVersion} ${brewStatus} ${npmStatus} ${pipStatus}`);
1594
1945
  const missingPrereqs = [];
1595
1946
  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
1947
  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
1948
  if (missingPrereqs.length > 0) {
1598
- p$11.note(missingPrereqs.join("\n\n"), "Missing prerequisites");
1949
+ p$12.note(missingPrereqs.join("\n\n"), "Missing prerequisites");
1599
1950
  if (!opts.yes) {
1600
- const cont = await p$11.confirm({
1951
+ const cont = await p$12.confirm({
1601
1952
  message: "Continue anyway? (some tool installs may fail)",
1602
1953
  initialValue: true
1603
1954
  });
1604
- if (p$11.isCancel(cont) || !cont) {
1605
- p$11.outro(dim("Install the prerequisites above and run openpaw again!"));
1955
+ if (p$12.isCancel(cont) || !cont) {
1956
+ p$12.outro(dim("Install the prerequisites above and run openpaw again!"));
1606
1957
  process.exit(0);
1607
1958
  }
1608
1959
  }
1609
1960
  }
1610
1961
  let botName = "Paw";
1611
1962
  if (!opts.yes) if (soulExists()) {
1612
- const updateSoul = await p$11.confirm({
1963
+ const updateSoul = await p$12.confirm({
1613
1964
  message: "Existing personality found (~/.claude/SOUL.md). Update it?",
1614
1965
  initialValue: false
1615
1966
  });
1616
- if (!p$11.isCancel(updateSoul) && updateSoul) {
1967
+ if (!p$12.isCancel(updateSoul) && updateSoul) {
1617
1968
  await pawPulse("think", "Let's get to know you again...");
1618
1969
  const soul = await soulQuestionnaire();
1619
1970
  if (soul) {
@@ -1621,23 +1972,23 @@ async function setupCommand(opts = {}) {
1621
1972
  writeSoul(soul);
1622
1973
  setupMemory(soul.name);
1623
1974
  showSoulSummary(soul);
1624
- p$11.log.success("Personality updated");
1975
+ p$12.log.success("Personality updated");
1625
1976
  }
1626
1977
  } else setupMemory();
1627
1978
  } else {
1628
1979
  await pawPulse("think", "Let's get to know you...");
1629
- const wantSoul = await p$11.confirm({
1980
+ const wantSoul = await p$12.confirm({
1630
1981
  message: "Teach me your name and preferences? (makes me a better pup)",
1631
1982
  initialValue: true
1632
1983
  });
1633
- if (!p$11.isCancel(wantSoul) && wantSoul) {
1984
+ if (!p$12.isCancel(wantSoul) && wantSoul) {
1634
1985
  const soul = await soulQuestionnaire();
1635
1986
  if (soul) {
1636
1987
  botName = soul.botName;
1637
1988
  writeSoul(soul);
1638
1989
  setupMemory(soul.name);
1639
1990
  showSoulSummary(soul);
1640
- p$11.log.success("Personality saved to ~/.claude/SOUL.md");
1991
+ p$12.log.success("Personality saved to ~/.claude/SOUL.md");
1641
1992
  }
1642
1993
  } else setupMemory();
1643
1994
  }
@@ -1646,35 +1997,35 @@ async function setupCommand(opts = {}) {
1646
1997
  if (opts.preset) {
1647
1998
  selectedSkills = getPresetSkills(opts.preset, platform.os);
1648
1999
  if (selectedSkills.length === 0) {
1649
- p$11.log.error(`Unknown preset: ${opts.preset}`);
1650
- p$11.log.info(`Available: ${presets.map((pr) => pr.id).join(", ")}`);
2000
+ p$12.log.error(`Unknown preset: ${opts.preset}`);
2001
+ p$12.log.info(`Available: ${presets.map((pr) => pr.id).join(", ")}`);
1651
2002
  process.exit(1);
1652
2003
  }
1653
- p$11.log.info(`Using preset: ${bold(opts.preset)} (${selectedSkills.length} skills)`);
2004
+ p$12.log.info(`Using preset: ${bold(opts.preset)} (${selectedSkills.length} skills)`);
1654
2005
  } else selectedSkills = await selectSkills(platform.os);
1655
2006
  if (selectedSkills.length === 0) {
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! 🐾");
2007
+ p$12.log.warn("No skills selected. Run openpaw again when you're ready!");
2008
+ p$12.outro("I'll be here napping... come back soon! 🐾");
1658
2009
  return;
1659
2010
  }
1660
2011
  const resolved = resolveDependencies(selectedSkills);
1661
2012
  if (resolved.length > 0) {
1662
2013
  const depNames = resolved.map((s$1) => s$1.name).join(", ");
1663
- p$11.log.info(`${dim("Auto-fetching dependencies:")} ${depNames}`);
2014
+ p$12.log.info(`${dim("Auto-fetching dependencies:")} ${depNames}`);
1664
2015
  selectedSkills.push(...resolved);
1665
2016
  }
1666
2017
  await pawPulse("happy", `${selectedSkills.length} skill${selectedSkills.length > 1 ? "s" : ""} selected — good taste!`);
1667
2018
  if (!opts.yes) {
1668
2019
  for (const skill of selectedSkills) if (skill.subChoices) {
1669
- const choice = await p$11.select({
2020
+ const choice = await p$12.select({
1670
2021
  message: `${skill.name}: ${skill.subChoices.question}`,
1671
2022
  options: skill.subChoices.options.map((o) => ({
1672
2023
  value: o.value,
1673
2024
  label: o.label
1674
2025
  }))
1675
2026
  });
1676
- if (p$11.isCancel(choice)) {
1677
- p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2027
+ if (p$12.isCancel(choice)) {
2028
+ p$12.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1678
2029
  process.exit(0);
1679
2030
  }
1680
2031
  const chosen = skill.subChoices.options.find((o) => o.value === choice);
@@ -1684,7 +2035,7 @@ async function setupCommand(opts = {}) {
1684
2035
  let interfaceMode = "native";
1685
2036
  let telegramConfig = null;
1686
2037
  if (!opts.yes) {
1687
- const modeChoice = await p$11.select({
2038
+ const modeChoice = await p$12.select({
1688
2039
  message: "How do you want to talk to Claude? 🐾",
1689
2040
  options: [{
1690
2041
  value: "native",
@@ -1696,17 +2047,17 @@ async function setupCommand(opts = {}) {
1696
2047
  hint: "terminal + talk from your phone"
1697
2048
  }]
1698
2049
  });
1699
- if (p$11.isCancel(modeChoice)) {
1700
- p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2050
+ if (p$12.isCancel(modeChoice)) {
2051
+ p$12.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1701
2052
  process.exit(0);
1702
2053
  }
1703
2054
  interfaceMode = modeChoice;
1704
2055
  if (interfaceMode === "telegram" || interfaceMode === "both") {
1705
- if (telegramConfigExists()) p$11.log.info(dim("Telegram already configured — keeping existing config"));
2056
+ if (telegramConfigExists()) p$12.log.info(dim("Telegram already configured — keeping existing config"));
1706
2057
  else {
1707
2058
  telegramConfig = await telegramQuestionnaire();
1708
2059
  if (!telegramConfig) {
1709
- p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2060
+ p$12.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1710
2061
  process.exit(0);
1711
2062
  }
1712
2063
  }
@@ -1719,13 +2070,13 @@ async function setupCommand(opts = {}) {
1719
2070
  let wantDashboard = false;
1720
2071
  let dashboardTheme = "paw";
1721
2072
  if (!opts.yes) {
1722
- const dashChoice = await p$11.confirm({
2073
+ const dashChoice = await p$12.confirm({
1723
2074
  message: `Want a task dashboard for ${botName}?`,
1724
2075
  initialValue: false
1725
2076
  });
1726
- if (!p$11.isCancel(dashChoice) && dashChoice) {
2077
+ if (!p$12.isCancel(dashChoice) && dashChoice) {
1727
2078
  wantDashboard = true;
1728
- const themeChoice = await p$11.select({
2079
+ const themeChoice = await p$12.select({
1729
2080
  message: "Pick a dashboard theme",
1730
2081
  options: [
1731
2082
  {
@@ -1745,10 +2096,115 @@ async function setupCommand(opts = {}) {
1745
2096
  }
1746
2097
  ]
1747
2098
  });
1748
- if (!p$11.isCancel(themeChoice)) dashboardTheme = themeChoice;
2099
+ if (!p$12.isCancel(themeChoice)) dashboardTheme = themeChoice;
1749
2100
  }
1750
2101
  }
1751
- const projectDir = os$2.homedir();
2102
+ let wantScheduling = false;
2103
+ let schedulingCap = 5;
2104
+ let schedulingJobCount = 0;
2105
+ if (!opts.yes) {
2106
+ const schedChoice = await p$12.confirm({
2107
+ message: "Enable smart scheduling? (automate recurring tasks with cost control)",
2108
+ initialValue: false
2109
+ });
2110
+ if (!p$12.isCancel(schedChoice) && schedChoice) {
2111
+ wantScheduling = true;
2112
+ const capInput = await p$12.text({
2113
+ message: "Daily cost cap in USD",
2114
+ placeholder: "5.00",
2115
+ initialValue: "5.00",
2116
+ validate: (v) => {
2117
+ const n = Number.parseFloat(v);
2118
+ return Number.isNaN(n) || n <= 0 ? "Enter a valid dollar amount" : void 0;
2119
+ }
2120
+ });
2121
+ if (!p$12.isCancel(capInput)) schedulingCap = Number.parseFloat(capInput);
2122
+ writeScheduleConfig({
2123
+ jobs: [],
2124
+ dailyCostCapUsd: schedulingCap,
2125
+ defaultModel: "sonnet"
2126
+ });
2127
+ const addJobNow = await p$12.confirm({
2128
+ message: "Want to add a recurring job now?",
2129
+ initialValue: false
2130
+ });
2131
+ if (!p$12.isCancel(addJobNow) && addJobNow) {
2132
+ const jobPrompt = await p$12.text({
2133
+ message: "What should Claude do?",
2134
+ placeholder: "check email and summarize anything urgent"
2135
+ });
2136
+ if (!p$12.isCancel(jobPrompt)) {
2137
+ const jobSchedule = await p$12.text({
2138
+ message: "When? (e.g. \"weekdays 8am\", \"daily 6pm\", \"every 30 minutes\")",
2139
+ placeholder: "weekdays 8am"
2140
+ });
2141
+ if (!p$12.isCancel(jobSchedule)) {
2142
+ const jobModel = await p$12.select({
2143
+ message: "Model for this job",
2144
+ options: [
2145
+ {
2146
+ value: "sonnet",
2147
+ label: "Sonnet",
2148
+ hint: "fast + capable (recommended)"
2149
+ },
2150
+ {
2151
+ value: "haiku",
2152
+ label: "Haiku",
2153
+ hint: "cheapest"
2154
+ },
2155
+ {
2156
+ value: "opus",
2157
+ label: "Opus",
2158
+ hint: "most capable"
2159
+ }
2160
+ ]
2161
+ });
2162
+ const jobBudget = await p$12.text({
2163
+ message: "Per-run budget in USD",
2164
+ placeholder: "1.00",
2165
+ initialValue: "1.00"
2166
+ });
2167
+ const deliveryOpts = [{
2168
+ value: "file",
2169
+ label: "File",
2170
+ hint: "save to ~/.config/openpaw/schedule-results/"
2171
+ }, {
2172
+ value: "notify",
2173
+ label: "Notification",
2174
+ hint: "macOS notification"
2175
+ }];
2176
+ if (interfaceMode === "telegram" || interfaceMode === "both") deliveryOpts.unshift({
2177
+ value: "telegram",
2178
+ label: "Telegram",
2179
+ hint: "send to your bot"
2180
+ });
2181
+ const jobDelivery = await p$12.select({
2182
+ message: "Where should results go?",
2183
+ options: deliveryOpts
2184
+ });
2185
+ if (!p$12.isCancel(jobModel) && !p$12.isCancel(jobBudget) && !p$12.isCancel(jobDelivery)) try {
2186
+ const parsed = parseHumanSchedule(jobSchedule);
2187
+ const job = addJob({
2188
+ name: jobPrompt.slice(0, 40),
2189
+ prompt: jobPrompt,
2190
+ schedule: parsed.cron,
2191
+ scheduleHuman: parsed.human,
2192
+ model: jobModel,
2193
+ maxBudgetUsd: Number.parseFloat(jobBudget) || 1,
2194
+ delivery: { type: jobDelivery }
2195
+ });
2196
+ installSystemJob(job);
2197
+ schedulingJobCount = 1;
2198
+ p$12.log.success(`Scheduled: "${parsed.human}" — ${jobModel}`);
2199
+ } catch (e) {
2200
+ p$12.log.warn(`Couldn't parse schedule — run ${bold("openpaw schedule add")} later`);
2201
+ }
2202
+ }
2203
+ }
2204
+ }
2205
+ }
2206
+ }
2207
+ const projectDir = os$3.homedir();
1752
2208
  const allTools = [];
1753
2209
  for (const skill of selectedSkills) allTools.push(...skill.tools);
1754
2210
  const uniqueTools = [...new Map(allTools.map((t) => [t.command, t])).values()];
@@ -1758,7 +2214,7 @@ async function setupCommand(opts = {}) {
1758
2214
  if (opts.yes) targetDir = getDefaultSkillsDir();
1759
2215
  else {
1760
2216
  const defaultDir = getDefaultSkillsDir();
1761
- const skillsDir = await p$11.select({
2217
+ const skillsDir = await p$12.select({
1762
2218
  message: "Where should skills live?",
1763
2219
  options: [
1764
2220
  {
@@ -1776,43 +2232,43 @@ async function setupCommand(opts = {}) {
1776
2232
  }
1777
2233
  ]
1778
2234
  });
1779
- if (p$11.isCancel(skillsDir)) {
1780
- p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2235
+ if (p$12.isCancel(skillsDir)) {
2236
+ p$12.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1781
2237
  process.exit(0);
1782
2238
  }
1783
2239
  targetDir = skillsDir;
1784
2240
  if (targetDir === "custom") {
1785
- const customDir = await p$11.text({
2241
+ const customDir = await p$12.text({
1786
2242
  message: "Skills directory path:",
1787
2243
  placeholder: "~/.claude/skills",
1788
2244
  validate: (v) => v.length === 0 ? "Path cannot be empty" : void 0
1789
2245
  });
1790
- if (p$11.isCancel(customDir)) {
1791
- p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2246
+ if (p$12.isCancel(customDir)) {
2247
+ p$12.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1792
2248
  process.exit(0);
1793
2249
  }
1794
- targetDir = customDir.replace(/^~/, os$2.homedir());
2250
+ targetDir = customDir.replace(/^~/, os$3.homedir());
1795
2251
  }
1796
2252
  }
1797
2253
  const summary = buildSummary(selectedSkills, uniqueTools, missing, taps, interfaceMode, projectDir);
1798
- p$11.note(summary, "Here's what we're fetching");
2254
+ p$12.note(summary, "Here's what we're fetching");
1799
2255
  if (!opts.yes) {
1800
- const proceed = await p$11.confirm({
2256
+ const proceed = await p$12.confirm({
1801
2257
  message: "Ready to fetch all these goodies?",
1802
2258
  initialValue: true
1803
2259
  });
1804
- if (p$11.isCancel(proceed) || !proceed) {
1805
- p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2260
+ if (p$12.isCancel(proceed) || !proceed) {
2261
+ p$12.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
1806
2262
  process.exit(0);
1807
2263
  }
1808
2264
  }
1809
2265
  if (opts.dryRun) {
1810
- p$11.log.info(dim("Dry run — no changes made. Just sniffing around."));
1811
- p$11.outro(accent("openpaw dry run complete 🐾"));
2266
+ p$12.log.info(dim("Dry run — no changes made. Just sniffing around."));
2267
+ p$12.outro(accent("openpaw dry run complete 🐾"));
1812
2268
  return;
1813
2269
  }
1814
2270
  await pawStep("work", "Fetching your goodies...");
1815
- const s = p$11.spinner();
2271
+ const s = p$12.spinner();
1816
2272
  if (taps.size > 0) {
1817
2273
  s.start("🐾 Sniffing out Homebrew taps...");
1818
2274
  const tapResults = installTaps(taps);
@@ -1834,16 +2290,16 @@ async function setupCommand(opts = {}) {
1834
2290
  failedTools.push(tool.name);
1835
2291
  }
1836
2292
  }
1837
- else if (uniqueTools.length > 0) p$11.log.success("All tools already installed — clever pup!");
2293
+ else if (uniqueTools.length > 0) p$12.log.success("All tools already installed — clever pup!");
1838
2294
  const existingSkills = listInstalledSkills(targetDir);
1839
2295
  const overlapping = selectedSkills.filter((sk) => existingSkills.includes(sk.id));
1840
2296
  let updateExisting = true;
1841
2297
  if (overlapping.length > 0 && !opts.yes) {
1842
- const updateChoice = await p$11.confirm({
2298
+ const updateChoice = await p$12.confirm({
1843
2299
  message: `${overlapping.length} skill${overlapping.length > 1 ? "s" : ""} already installed. Update their templates?`,
1844
2300
  initialValue: true
1845
2301
  });
1846
- if (!p$11.isCancel(updateChoice)) updateExisting = updateChoice;
2302
+ if (!p$12.isCancel(updateChoice)) updateExisting = updateChoice;
1847
2303
  }
1848
2304
  installSkill("core", targetDir);
1849
2305
  installSkill("memory", targetDir);
@@ -1870,14 +2326,14 @@ async function setupCommand(opts = {}) {
1870
2326
  telegramConfig.workspaceDir = projectDir;
1871
2327
  telegramConfig.skills = selectedSkills.map((sk) => sk.id);
1872
2328
  writeTelegramConfig(telegramConfig);
1873
- p$11.log.success("Telegram bridge configured");
2329
+ p$12.log.success("Telegram bridge configured");
1874
2330
  }
1875
2331
  if (wantDashboard) {
1876
2332
  const dashConfig = readConfig();
1877
2333
  dashConfig.theme = dashboardTheme;
1878
2334
  dashConfig.botName = botName;
1879
2335
  writeConfig(dashConfig);
1880
- p$11.log.success(`Dashboard configured (theme: ${dashboardTheme})`);
2336
+ p$12.log.success(`Dashboard configured (theme: ${dashboardTheme})`);
1881
2337
  }
1882
2338
  s.start("🐾 Writing CLAUDE.md...");
1883
2339
  writeClaudeMd(botName, selectedSkills, wantDashboard);
@@ -1885,40 +2341,41 @@ async function setupCommand(opts = {}) {
1885
2341
  const authSteps = selectedSkills.flatMap((skill) => skill.authSteps ?? []).filter((step, i, arr) => arr.findIndex((s$1) => s$1.command === step.command) === i);
1886
2342
  if (authSteps.length > 0 && !opts.yes) {
1887
2343
  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({
2344
+ p$12.note(authList, "One-time auth needed");
2345
+ const runAuth = await p$12.confirm({
1890
2346
  message: "Want to sign in to these now?",
1891
2347
  initialValue: true
1892
2348
  });
1893
- if (!p$11.isCancel(runAuth) && runAuth) for (const step of authSteps) {
1894
- const runThis = await p$11.confirm({
2349
+ if (!p$12.isCancel(runAuth) && runAuth) for (const step of authSteps) {
2350
+ const runThis = await p$12.confirm({
1895
2351
  message: `Run ${bold(step.command)}? ${dim(step.description)}`,
1896
2352
  initialValue: true
1897
2353
  });
1898
- if (p$11.isCancel(runThis)) break;
2354
+ if (p$12.isCancel(runThis)) break;
1899
2355
  if (!runThis) {
1900
- p$11.log.info(dim(`Skipped ${step.command} — run it later when you need it`));
2356
+ p$12.log.info(dim(`Skipped ${step.command} — run it later when you need it`));
1901
2357
  continue;
1902
2358
  }
1903
- p$11.log.info(`Running ${accent(step.command)}...`);
2359
+ p$12.log.info(`Running ${accent(step.command)}...`);
1904
2360
  try {
1905
2361
  execSync(step.command, { stdio: "inherit" });
1906
- p$11.log.success(`${step.command} — signed in`);
2362
+ p$12.log.success(`${step.command} — signed in`);
1907
2363
  } catch {
1908
- p$11.log.warn(`${step.command} — failed or cancelled (you can run it later)`);
2364
+ p$12.log.warn(`${step.command} — failed or cancelled (you can run it later)`);
1909
2365
  }
1910
2366
  }
1911
- else p$11.log.info(dim("No problem — run these commands when you need each skill"));
2367
+ else p$12.log.info(dim("No problem — run these commands when you need each skill"));
1912
2368
  } else if (authSteps.length > 0) {
1913
2369
  const authList = authSteps.map((st) => `${chalk.yellow("→")} ${chalk.bold(st.command)} ${dim(st.description)}`).join("\n");
1914
- p$11.note(authList, "One-time auth needed (run these later)");
2370
+ p$12.note(authList, "One-time auth needed (run these later)");
1915
2371
  }
1916
2372
  const summaryLines = [`${bold("Skills:")} ${installed.length} installed`, `${bold("Tools:")} ${uniqueTools.length - missing.length} ready` + (installedTools.length > 0 ? `, ${installedTools.length} newly installed` : "")];
1917
2373
  if (failedTools.length > 0) summaryLines.push(`${bold("Failed:")} ${chalk.red(failedTools.join(", "))}`);
1918
2374
  if (wantDashboard) summaryLines.push(`${bold("Dashboard:")} ${dashboardTheme} theme on :3141`);
2375
+ if (wantScheduling) summaryLines.push(`${bold("Scheduling:")} $${schedulingCap}/day cap` + (schedulingJobCount > 0 ? `, ${schedulingJobCount} job` : ""));
1919
2376
  summaryLines.push(`${bold("CLAUDE.md:")} ${botName} is self-aware`);
1920
2377
  summaryLines.push(`${bold("Memory:")} ~/.claude/memory/`);
1921
- p$11.note(summaryLines.join("\n"), "Setup Complete");
2378
+ p$12.note(summaryLines.join("\n"), "Setup Complete");
1922
2379
  await pawStep("done", "All done! *tail wag intensifies*");
1923
2380
  console.log("");
1924
2381
  console.log(dim(` ${botName} is ready to play! Try saying:`));
@@ -1927,80 +2384,80 @@ async function setupCommand(opts = {}) {
1927
2384
  console.log(` ${subtle("\"Go to hacker news and summarize the top posts\"")}`);
1928
2385
  console.log("");
1929
2386
  if (wantDashboard) {
1930
- const { startDashboard: startDashboard$1 } = await import("./dashboard-server-Pnv4DFlV.js");
2387
+ const { startDashboard: startDashboard$1 } = await import("./dashboard-server-BLUIYHCS.js");
1931
2388
  startDashboard$1({
1932
2389
  theme: dashboardTheme,
1933
2390
  botName
1934
2391
  });
1935
- p$11.log.success("Dashboard launched in your browser");
2392
+ p$12.log.success("Dashboard launched in your browser");
1936
2393
  }
1937
2394
  if (opts.yes) {
1938
- p$11.outro(accent("openpaw setup complete 🐾"));
2395
+ p$12.outro(accent("openpaw setup complete 🐾"));
1939
2396
  return;
1940
2397
  }
1941
- const launch = await p$11.confirm({
2398
+ const launch = await p$12.confirm({
1942
2399
  message: "Time to go for a walk? (Launch your assistant)",
1943
2400
  initialValue: true
1944
2401
  });
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!"));
2402
+ if (p$12.isCancel(launch) || !launch) {
2403
+ if (interfaceMode === "telegram" || interfaceMode === "both") p$12.log.info(`Start the Telegram bridge anytime with: ${bold("openpaw telegram")}`);
2404
+ p$12.outro(accent("openpaw setup complete 🐾 — come back anytime!"));
1948
2405
  return;
1949
2406
  }
1950
2407
  let useDangerousMode = false;
1951
2408
  {
1952
2409
  showPuppyDisclaimer();
1953
- const acceptDanger = await p$11.confirm({
2410
+ const acceptDanger = await p$12.confirm({
1954
2411
  message: "Unleash full paw-er? *excited tail wag*",
1955
2412
  initialValue: true
1956
2413
  });
1957
- if (!p$11.isCancel(acceptDanger)) useDangerousMode = acceptDanger;
2414
+ if (!p$12.isCancel(acceptDanger)) useDangerousMode = acceptDanger;
1958
2415
  }
1959
2416
  let useTmux = false;
1960
2417
  if (isTmuxAvailable() && !isInTmux()) {
1961
2418
  const tmuxDefault = interfaceMode === "both";
1962
- const tmuxChoice = await p$11.confirm({
2419
+ const tmuxChoice = await p$12.confirm({
1963
2420
  message: "Run in tmux? (keeps going when you close the terminal)",
1964
2421
  initialValue: tmuxDefault
1965
2422
  });
1966
- if (!p$11.isCancel(tmuxChoice)) useTmux = tmuxChoice;
2423
+ if (!p$12.isCancel(tmuxChoice)) useTmux = tmuxChoice;
1967
2424
  }
1968
2425
  const dangerFlag = useDangerousMode ? " --dangerously-skip-permissions" : "";
1969
2426
  const nativeCmd = `claude${dangerFlag}`;
1970
2427
  const telegramCmd = "npx openpaw telegram";
1971
2428
  if (useTmux) {
1972
- p$11.outro(accent("Launching in tmux... 🐾"));
2429
+ p$12.outro(accent("Launching in tmux... 🐾"));
1973
2430
  launchInTmux({
1974
2431
  nativeCmd,
1975
2432
  telegramCmd: interfaceMode === "both" ? telegramCmd : void 0,
1976
2433
  workDir: projectDir
1977
2434
  });
1978
2435
  } else if (interfaceMode === "native") {
1979
- p$11.outro(accent("Starting Claude Code... 🐾"));
2436
+ p$12.outro(accent("Starting Claude Code... 🐾"));
1980
2437
  try {
1981
2438
  execSync(nativeCmd, {
1982
2439
  stdio: "inherit",
1983
2440
  cwd: projectDir
1984
2441
  });
1985
2442
  } catch {
1986
- p$11.log.warn("Could not launch Claude Code. Make sure it's installed: https://claude.ai/code");
2443
+ p$12.log.warn("Could not launch Claude Code. Make sure it's installed: https://claude.ai/code");
1987
2444
  }
1988
2445
  } else {
1989
- p$11.log.info(dim("Starting Telegram bridge in background..."));
2446
+ p$12.log.info(dim("Starting Telegram bridge in background..."));
1990
2447
  launchInBackground(telegramCmd);
1991
- p$11.outro(accent("Starting Claude Code... 🐾"));
2448
+ p$12.outro(accent("Starting Claude Code... 🐾"));
1992
2449
  try {
1993
2450
  execSync(nativeCmd, {
1994
2451
  stdio: "inherit",
1995
2452
  cwd: projectDir
1996
2453
  });
1997
2454
  } catch {
1998
- p$11.log.warn("Could not launch Claude Code. Make sure it's installed: https://claude.ai/code");
2455
+ p$12.log.warn("Could not launch Claude Code. Make sure it's installed: https://claude.ai/code");
1999
2456
  }
2000
2457
  }
2001
2458
  }
2002
- async function selectSkills(os$6) {
2003
- const mode = await p$11.select({
2459
+ async function selectSkills(os$8) {
2460
+ const mode = await p$12.select({
2004
2461
  message: "How should we set things up, human?",
2005
2462
  options: [{
2006
2463
  value: "preset",
@@ -2012,15 +2469,15 @@ async function selectSkills(os$6) {
2012
2469
  hint: "sniff through skills one by one"
2013
2470
  }]
2014
2471
  });
2015
- if (p$11.isCancel(mode)) {
2016
- p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2472
+ if (p$12.isCancel(mode)) {
2473
+ p$12.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2017
2474
  process.exit(0);
2018
2475
  }
2019
- if (mode === "preset") return await selectFromPreset(os$6);
2020
- return await selectCustom(os$6);
2476
+ if (mode === "preset") return await selectFromPreset(os$8);
2477
+ return await selectCustom(os$8);
2021
2478
  }
2022
- async function selectFromPreset(os$6) {
2023
- const presetChoice = await p$11.select({
2479
+ async function selectFromPreset(os$8) {
2480
+ const presetChoice = await p$12.select({
2024
2481
  message: "Pick a treat... I mean, a preset!",
2025
2482
  options: presets.map((pr) => ({
2026
2483
  value: pr.id,
@@ -2028,17 +2485,17 @@ async function selectFromPreset(os$6) {
2028
2485
  hint: pr.description
2029
2486
  }))
2030
2487
  });
2031
- if (p$11.isCancel(presetChoice)) {
2032
- p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2488
+ if (p$12.isCancel(presetChoice)) {
2489
+ p$12.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2033
2490
  process.exit(0);
2034
2491
  }
2035
- const presetSkills = getPresetSkills(presetChoice, os$6);
2492
+ const presetSkills = getPresetSkills(presetChoice, os$8);
2036
2493
  const skillNames = presetSkills.map((s) => s.name).join(", ");
2037
- p$11.log.info(`${dim("Includes:")} ${skillNames}`);
2494
+ p$12.log.info(`${dim("Includes:")} ${skillNames}`);
2038
2495
  return presetSkills;
2039
2496
  }
2040
- async function selectCustom(os$6) {
2041
- const grouped = getSkillsByCategory(os$6);
2497
+ async function selectCustom(os$8) {
2498
+ const grouped = getSkillsByCategory(os$8);
2042
2499
  const options = [];
2043
2500
  for (const [category, catSkills] of grouped) {
2044
2501
  const icon = CATEGORY_ICONS[category] ?? "📦";
@@ -2054,13 +2511,13 @@ async function selectCustom(os$6) {
2054
2511
  isFirst = false;
2055
2512
  }
2056
2513
  }
2057
- const selected = await p$11.multiselect({
2514
+ const selected = await p$12.multiselect({
2058
2515
  message: "Pick your skills (space to select, enter to confirm)",
2059
2516
  options,
2060
2517
  required: false
2061
2518
  });
2062
- if (p$11.isCancel(selected)) {
2063
- p$11.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2519
+ if (p$12.isCancel(selected)) {
2520
+ p$12.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
2064
2521
  process.exit(0);
2065
2522
  }
2066
2523
  const ids = selected;
@@ -2073,7 +2530,7 @@ function buildSummary(selectedSkills, uniqueTools, missing, taps, interfaceMode,
2073
2530
  if (taps.size > 0) lines.push(`${bold("Taps:")} ${[...taps].join(", ")}`);
2074
2531
  const modeLabel = interfaceMode === "native" ? "Terminal" : "Terminal + Telegram";
2075
2532
  lines.push(`${bold("Interface:")} ${modeLabel}`);
2076
- lines.push(`${bold("Workspace:")} ${projectDir.replace(os$2.homedir(), "~")}`);
2533
+ lines.push(`${bold("Workspace:")} ${projectDir.replace(os$3.homedir(), "~")}`);
2077
2534
  lines.push(`${bold("Memory:")} ~/.claude/memory/`);
2078
2535
  lines.push(`${bold("Soul:")} ~/.claude/SOUL.md`);
2079
2536
  return lines.join("\n");
@@ -2100,18 +2557,18 @@ async function addCommand(skillIds) {
2100
2557
  showMini();
2101
2558
  console.log("");
2102
2559
  if (skillIds.length === 0) {
2103
- p$10.log.error("Specify skills to add: openpaw add notes music email");
2560
+ p$11.log.error("Specify skills to add: openpaw add notes music email");
2104
2561
  return;
2105
2562
  }
2106
- const s = p$10.spinner();
2563
+ const s = p$11.spinner();
2107
2564
  for (const id of skillIds) {
2108
2565
  const skill = getSkillById(id);
2109
2566
  if (!skill) {
2110
- p$10.log.error(`Unknown skill: ${id}`);
2567
+ p$11.log.error(`Unknown skill: ${id}`);
2111
2568
  continue;
2112
2569
  }
2113
2570
  if (isSkillInstalled(id)) {
2114
- p$10.log.info(`c-${id} already installed, skipping`);
2571
+ p$11.log.info(`c-${id} already installed, skipping`);
2115
2572
  continue;
2116
2573
  }
2117
2574
  const taps = getAllTaps([skill]);
@@ -2124,23 +2581,23 @@ async function addCommand(skillIds) {
2124
2581
  }
2125
2582
  installSkill(id);
2126
2583
  addPermissions(skill.tools);
2127
- p$10.log.success(`c-${id} installed`);
2584
+ p$11.log.success(`c-${id} installed`);
2128
2585
  if (skill.authSteps?.length) for (const step of skill.authSteps) {
2129
- const runThis = await p$10.confirm({
2586
+ const runThis = await p$11.confirm({
2130
2587
  message: `Run ${chalk.bold(step.command)}? ${dim(step.description)}`,
2131
2588
  initialValue: true
2132
2589
  });
2133
- if (p$10.isCancel(runThis)) break;
2590
+ if (p$11.isCancel(runThis)) break;
2134
2591
  if (!runThis) {
2135
- p$10.log.info(dim(`Skipped ${step.command} — run it later`));
2592
+ p$11.log.info(dim(`Skipped ${step.command} — run it later`));
2136
2593
  continue;
2137
2594
  }
2138
- p$10.log.info(`Running ${accent(step.command)}...`);
2595
+ p$11.log.info(`Running ${accent(step.command)}...`);
2139
2596
  try {
2140
2597
  execSync(step.command, { stdio: "inherit" });
2141
- p$10.log.success(`${step.command} — signed in`);
2598
+ p$11.log.success(`${step.command} — signed in`);
2142
2599
  } catch {
2143
- p$10.log.warn(`${step.command} — failed or cancelled (run it later)`);
2600
+ p$11.log.warn(`${step.command} — failed or cancelled (run it later)`);
2144
2601
  }
2145
2602
  }
2146
2603
  }
@@ -2153,22 +2610,22 @@ async function removeCommand(skillIds) {
2153
2610
  showMini();
2154
2611
  console.log("");
2155
2612
  if (skillIds.length === 0) {
2156
- p$9.log.error("Specify skills to remove: openpaw remove notes music");
2613
+ p$10.log.error("Specify skills to remove: openpaw remove notes music");
2157
2614
  return;
2158
2615
  }
2159
2616
  for (const id of skillIds) {
2160
2617
  if (id === "core") {
2161
- p$9.log.warn("Cannot remove c-core (coordinator). Use 'openpaw reset' instead.");
2618
+ p$10.log.warn("Cannot remove c-core (coordinator). Use 'openpaw reset' instead.");
2162
2619
  continue;
2163
2620
  }
2164
2621
  if (!isSkillInstalled(id)) {
2165
- p$9.log.info(`c-${id} is not installed`);
2622
+ p$10.log.info(`c-${id} is not installed`);
2166
2623
  continue;
2167
2624
  }
2168
2625
  const skill = getSkillById(id);
2169
2626
  removeSkill(id);
2170
2627
  if (skill) removePermissions(skill.tools);
2171
- p$9.log.success(`${chalk.bold(`c-${id}`)} removed`);
2628
+ p$10.log.success(`${chalk.bold(`c-${id}`)} removed`);
2172
2629
  }
2173
2630
  regenerateClaudeMd();
2174
2631
  }
@@ -2180,10 +2637,10 @@ async function statusCommand() {
2180
2637
  console.log("");
2181
2638
  const installed = listInstalledSkills();
2182
2639
  if (installed.length === 0) {
2183
- p$8.log.warn("No OpenPaw skills installed. Run: openpaw setup");
2640
+ p$9.log.warn("No OpenPaw skills installed. Run: openpaw setup");
2184
2641
  return;
2185
2642
  }
2186
- p$8.log.info(`${chalk.bold(installed.length)} skills installed:\n`);
2643
+ p$9.log.info(`${chalk.bold(installed.length)} skills installed:\n`);
2187
2644
  for (const skillId of installed) {
2188
2645
  if (skillId === "core") {
2189
2646
  console.log(` ${chalk.green("●")} ${chalk.bold("c-core")} ${chalk.dim("— coordinator")}`);
@@ -2211,7 +2668,7 @@ async function statusCommand() {
2211
2668
  async function doctorCommand() {
2212
2669
  showMini();
2213
2670
  console.log("");
2214
- p$7.log.info("Running diagnostics...\n");
2671
+ p$8.log.info("Running diagnostics...\n");
2215
2672
  let issues = 0;
2216
2673
  const platform = detectPlatform();
2217
2674
  console.log(` ${chalk.green("✓")} Platform: ${platform.osName} ${platform.osVersion}`);
@@ -2250,8 +2707,8 @@ async function doctorCommand() {
2250
2707
  issues++;
2251
2708
  }
2252
2709
  console.log("");
2253
- if (issues === 0) p$7.log.success("All checks passed!");
2254
- else p$7.log.warn(`${issues} issue${issues > 1 ? "s" : ""} found`);
2710
+ if (issues === 0) p$8.log.success("All checks passed!");
2711
+ else p$8.log.warn(`${issues} issue${issues > 1 ? "s" : ""} found`);
2255
2712
  }
2256
2713
 
2257
2714
  //#endregion
@@ -2261,10 +2718,10 @@ async function updateCommand() {
2261
2718
  console.log("");
2262
2719
  const installed = listInstalledSkills();
2263
2720
  if (installed.length === 0) {
2264
- p$6.log.warn("No skills installed. Run: openpaw setup");
2721
+ p$7.log.warn("No skills installed. Run: openpaw setup");
2265
2722
  return;
2266
2723
  }
2267
- const s = p$6.spinner();
2724
+ const s = p$7.spinner();
2268
2725
  const brewTools = [];
2269
2726
  for (const skillId of installed) {
2270
2727
  const skill = skills.find((sk) => sk.id === skillId);
@@ -2272,7 +2729,7 @@ async function updateCommand() {
2272
2729
  for (const tool of skill.tools) if ((tool.installMethod === "brew" || tool.installMethod === "brew-tap") && isToolInstalled(tool.command)) brewTools.push(tool.name);
2273
2730
  }
2274
2731
  if (brewTools.length === 0) {
2275
- p$6.log.info("No Homebrew-installed tools to update");
2732
+ p$7.log.info("No Homebrew-installed tools to update");
2276
2733
  return;
2277
2734
  }
2278
2735
  s.start(`Updating ${brewTools.length} tools via Homebrew...`);
@@ -2298,15 +2755,15 @@ async function resetCommand() {
2298
2755
  console.log("");
2299
2756
  const installed = listInstalledSkills();
2300
2757
  if (installed.length === 0) {
2301
- p$5.log.info("Nothing to reset — no OpenPaw skills installed.");
2758
+ p$6.log.info("Nothing to reset — no OpenPaw skills installed.");
2302
2759
  return;
2303
2760
  }
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.");
2761
+ const confirm = await p$6.confirm({ message: `Remove all ${installed.length} OpenPaw skills and permissions?` });
2762
+ if (p$6.isCancel(confirm) || !confirm) {
2763
+ p$6.cancel("Reset cancelled.");
2307
2764
  return;
2308
2765
  }
2309
- const s = p$5.spinner();
2766
+ const s = p$6.spinner();
2310
2767
  s.start("Removing skills and permissions...");
2311
2768
  for (const skillId of installed) {
2312
2769
  const skill = skills.find((sk) => sk.id === skillId);
@@ -2315,9 +2772,9 @@ async function resetCommand() {
2315
2772
  }
2316
2773
  removeSafetyHooks();
2317
2774
  s.stop(`${chalk.green("✓")} Removed ${installed.length} skills, permissions, and hooks`);
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.");
2775
+ p$6.log.info(chalk.dim("CLI tools were not uninstalled (you may still want them)."));
2776
+ p$6.log.info(chalk.dim("To uninstall tools: brew uninstall <tool>"));
2777
+ p$6.outro("OpenPaw reset complete.");
2321
2778
  }
2322
2779
 
2323
2780
  //#endregion
@@ -2358,36 +2815,36 @@ async function listCommand() {
2358
2815
  //#region src/commands/soul.ts
2359
2816
  async function soulCommand() {
2360
2817
  showMini();
2361
- p$4.intro(accent(" openpaw soul "));
2818
+ p$5.intro(accent(" openpaw soul "));
2362
2819
  if (soulExists()) {
2363
- p$4.log.info(dim("SOUL.md already exists at ~/.claude/SOUL.md"));
2364
- const overwrite = await p$4.confirm({
2820
+ p$5.log.info(dim("SOUL.md already exists at ~/.claude/SOUL.md"));
2821
+ const overwrite = await p$5.confirm({
2365
2822
  message: "Overwrite existing personality?",
2366
2823
  initialValue: false
2367
2824
  });
2368
- if (p$4.isCancel(overwrite) || !overwrite) {
2369
- p$4.log.info("Keeping existing SOUL.md");
2370
- p$4.outro(accent("Done"));
2825
+ if (p$5.isCancel(overwrite) || !overwrite) {
2826
+ p$5.log.info("Keeping existing SOUL.md");
2827
+ p$5.outro(accent("Done"));
2371
2828
  return;
2372
2829
  }
2373
2830
  }
2374
2831
  const soul = await soulQuestionnaire();
2375
2832
  if (!soul) {
2376
- p$4.cancel("Cancelled.");
2833
+ p$5.cancel("Cancelled.");
2377
2834
  return;
2378
2835
  }
2379
2836
  writeSoul(soul);
2380
2837
  showSoulSummary(soul);
2381
- p$4.log.success("Personality saved to ~/.claude/SOUL.md");
2382
- p$4.outro(accent("Claude will use this personality next session 🐾"));
2838
+ p$5.log.success("Personality saved to ~/.claude/SOUL.md");
2839
+ p$5.outro(accent("Claude will use this personality next session 🐾"));
2383
2840
  }
2384
2841
 
2385
2842
  //#endregion
2386
2843
  //#region src/commands/export.ts
2387
2844
  async function exportCommand() {
2388
2845
  showMini();
2389
- p$3.intro(accent(" openpaw export "));
2390
- const claudeDir = path$1.join(os$1.homedir(), ".claude");
2846
+ p$4.intro(accent(" openpaw export "));
2847
+ const claudeDir = path$2.join(os$2.homedir(), ".claude");
2391
2848
  const bundle = {
2392
2849
  version: "1",
2393
2850
  exportedAt: new Date().toISOString(),
@@ -2398,63 +2855,63 @@ async function exportCommand() {
2398
2855
  };
2399
2856
  const installed = listInstalledSkills();
2400
2857
  bundle.skills = installed;
2401
- p$3.log.info(`${installed.length} skills found`);
2858
+ p$4.log.info(`${installed.length} skills found`);
2402
2859
  const settings = readSettings();
2403
2860
  bundle.permissions = settings.permissions?.allow ?? [];
2404
- const soulPath = path$1.join(claudeDir, "SOUL.md");
2405
- if (fs$1.existsSync(soulPath)) {
2406
- bundle.soul = fs$1.readFileSync(soulPath, "utf-8");
2407
- p$3.log.info("SOUL.md included");
2408
- }
2409
- const memoryDir = path$1.join(claudeDir, "memory");
2410
- if (fs$1.existsSync(memoryDir)) {
2411
- const files = fs$1.readdirSync(memoryDir).filter((f) => f.endsWith(".md"));
2412
- for (const file of files) bundle.memory[file] = fs$1.readFileSync(path$1.join(memoryDir, file), "utf-8");
2413
- p$3.log.info(`${files.length} memory files included`);
2414
- }
2415
- const outputPath = path$1.resolve("openpaw-export.json");
2416
- fs$1.writeFileSync(outputPath, JSON.stringify(bundle, null, 2), "utf-8");
2417
- p$3.log.success(`Exported to ${dim(outputPath)}`);
2418
- p$3.outro(accent("Import on another machine: openpaw import openpaw-export.json"));
2861
+ const soulPath = path$2.join(claudeDir, "SOUL.md");
2862
+ if (fs$2.existsSync(soulPath)) {
2863
+ bundle.soul = fs$2.readFileSync(soulPath, "utf-8");
2864
+ p$4.log.info("SOUL.md included");
2865
+ }
2866
+ const memoryDir = path$2.join(claudeDir, "memory");
2867
+ if (fs$2.existsSync(memoryDir)) {
2868
+ const files = fs$2.readdirSync(memoryDir).filter((f) => f.endsWith(".md"));
2869
+ for (const file of files) bundle.memory[file] = fs$2.readFileSync(path$2.join(memoryDir, file), "utf-8");
2870
+ p$4.log.info(`${files.length} memory files included`);
2871
+ }
2872
+ const outputPath = path$2.resolve("openpaw-export.json");
2873
+ fs$2.writeFileSync(outputPath, JSON.stringify(bundle, null, 2), "utf-8");
2874
+ p$4.log.success(`Exported to ${dim(outputPath)}`);
2875
+ p$4.outro(accent("Import on another machine: openpaw import openpaw-export.json"));
2419
2876
  }
2420
2877
  async function importCommand(file) {
2421
2878
  showMini();
2422
- p$3.intro(accent(" openpaw import "));
2423
- const filePath = path$1.resolve(file);
2424
- if (!fs$1.existsSync(filePath)) {
2425
- p$3.log.error(`File not found: ${filePath}`);
2879
+ p$4.intro(accent(" openpaw import "));
2880
+ const filePath = path$2.resolve(file);
2881
+ if (!fs$2.existsSync(filePath)) {
2882
+ p$4.log.error(`File not found: ${filePath}`);
2426
2883
  process.exit(1);
2427
2884
  }
2428
2885
  let bundle;
2429
2886
  try {
2430
- bundle = JSON.parse(fs$1.readFileSync(filePath, "utf-8"));
2887
+ bundle = JSON.parse(fs$2.readFileSync(filePath, "utf-8"));
2431
2888
  } catch {
2432
- p$3.log.error("Invalid export file — must be valid JSON");
2889
+ p$4.log.error("Invalid export file — must be valid JSON");
2433
2890
  process.exit(1);
2434
2891
  }
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({
2892
+ p$4.log.info(`Export from ${dim(bundle.exportedAt)}`);
2893
+ p$4.log.info(`${bundle.skills.length} skills, ${Object.keys(bundle.memory).length} memory files`);
2894
+ const proceed = await p$4.confirm({
2438
2895
  message: "Import this configuration?",
2439
2896
  initialValue: true
2440
2897
  });
2441
- if (p$3.isCancel(proceed) || !proceed) {
2442
- p$3.cancel("Import cancelled.");
2898
+ if (p$4.isCancel(proceed) || !proceed) {
2899
+ p$4.cancel("Import cancelled.");
2443
2900
  return;
2444
2901
  }
2445
- const claudeDir = path$1.join(os$1.homedir(), ".claude");
2446
- const s = p$3.spinner();
2902
+ const claudeDir = path$2.join(os$2.homedir(), ".claude");
2903
+ const s = p$4.spinner();
2447
2904
  if (bundle.soul) {
2448
2905
  s.start("🐾 Restoring SOUL.md...");
2449
- fs$1.mkdirSync(claudeDir, { recursive: true });
2450
- fs$1.writeFileSync(path$1.join(claudeDir, "SOUL.md"), bundle.soul, "utf-8");
2906
+ fs$2.mkdirSync(claudeDir, { recursive: true });
2907
+ fs$2.writeFileSync(path$2.join(claudeDir, "SOUL.md"), bundle.soul, "utf-8");
2451
2908
  s.stop("🐾 SOUL.md restored");
2452
2909
  }
2453
2910
  if (Object.keys(bundle.memory).length > 0) {
2454
2911
  s.start("🐾 Restoring memory...");
2455
- const memoryDir = path$1.join(claudeDir, "memory");
2456
- fs$1.mkdirSync(memoryDir, { recursive: true });
2457
- for (const [file$1, content] of Object.entries(bundle.memory)) fs$1.writeFileSync(path$1.join(memoryDir, file$1), content, "utf-8");
2912
+ const memoryDir = path$2.join(claudeDir, "memory");
2913
+ fs$2.mkdirSync(memoryDir, { recursive: true });
2914
+ for (const [file$1, content] of Object.entries(bundle.memory)) fs$2.writeFileSync(path$2.join(memoryDir, file$1), content, "utf-8");
2458
2915
  s.stop(`🐾 ${Object.keys(bundle.memory).length} memory files restored`);
2459
2916
  }
2460
2917
  if (bundle.skills.length > 0) {
@@ -2472,7 +2929,7 @@ async function importCommand(file) {
2472
2929
  s.start("🐾 Restoring permissions...");
2473
2930
  const settings = readSettings();
2474
2931
  const existing = new Set(settings.permissions?.allow ?? []);
2475
- const newPerms = bundle.permissions.filter((p$13) => !existing.has(p$13));
2932
+ const newPerms = bundle.permissions.filter((p$14) => !existing.has(p$14));
2476
2933
  if (newPerms.length > 0) {
2477
2934
  if (!settings.permissions) settings.permissions = {};
2478
2935
  settings.permissions.allow = [...existing, ...newPerms];
@@ -2481,8 +2938,8 @@ async function importCommand(file) {
2481
2938
  }
2482
2939
  s.stop(`🐾 ${newPerms.length} permissions added`);
2483
2940
  }
2484
- p$3.log.success("Import complete");
2485
- p$3.outro(accent("Run openpaw status to verify 🐾"));
2941
+ p$4.log.success("Import complete");
2942
+ p$4.outro(accent("Run openpaw status to verify 🐾"));
2486
2943
  }
2487
2944
 
2488
2945
  //#endregion
@@ -2491,34 +2948,34 @@ async function telegramCommand() {
2491
2948
  showMini();
2492
2949
  const config = readTelegramConfig();
2493
2950
  if (!config) {
2494
- p$2.log.error("Telegram not configured yet.");
2495
- p$2.log.info(`Run ${bold("openpaw telegram setup")} or ${bold("openpaw setup")} first.`);
2951
+ p$3.log.error("Telegram not configured yet.");
2952
+ p$3.log.info(`Run ${bold("openpaw telegram setup")} or ${bold("openpaw setup")} first.`);
2496
2953
  process.exit(1);
2497
2954
  }
2498
2955
  await startTelegramBot(config);
2499
2956
  }
2500
2957
  async function telegramSetupCommand() {
2501
2958
  showMini();
2502
- p$2.intro(accent(" Telegram Bridge Setup "));
2959
+ p$3.intro(accent(" Telegram Bridge Setup "));
2503
2960
  if (telegramConfigExists()) {
2504
- const overwrite = await p$2.confirm({
2961
+ const overwrite = await p$3.confirm({
2505
2962
  message: "Telegram is already configured. Reconfigure?",
2506
2963
  initialValue: false
2507
2964
  });
2508
- if (p$2.isCancel(overwrite) || !overwrite) {
2509
- p$2.outro("Keeping existing config. 🐾");
2965
+ if (p$3.isCancel(overwrite) || !overwrite) {
2966
+ p$3.outro("Keeping existing config. 🐾");
2510
2967
  return;
2511
2968
  }
2512
2969
  }
2513
2970
  const config = await telegramQuestionnaire();
2514
2971
  if (!config) {
2515
- p$2.cancel("Setup cancelled.");
2972
+ p$3.cancel("Setup cancelled.");
2516
2973
  process.exit(0);
2517
2974
  }
2518
2975
  writeTelegramConfig(config);
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 🐾"));
2976
+ p$3.log.success("Telegram config saved!");
2977
+ p$3.log.info(`Start the bridge with: ${bold("openpaw telegram")}`);
2978
+ p$3.outro(accent("Telegram setup complete 🐾"));
2522
2979
  }
2523
2980
 
2524
2981
  //#endregion
@@ -2528,7 +2985,8 @@ function dashboardCommand(opts) {
2528
2985
  const theme = opts.theme && (opts.theme === "paw" || opts.theme === "midnight" || opts.theme === "neon") ? opts.theme : void 0;
2529
2986
  startDashboard({
2530
2987
  port,
2531
- theme
2988
+ theme,
2989
+ noOpen: opts.open === false
2532
2990
  });
2533
2991
  }
2534
2992
 
@@ -2537,7 +2995,7 @@ function dashboardCommand(opts) {
2537
2995
  async function configureCommand() {
2538
2996
  showMini();
2539
2997
  console.log("");
2540
- const action = await p$1.select({
2998
+ const action = await p$2.select({
2541
2999
  message: "What would you like to configure?",
2542
3000
  options: [
2543
3001
  {
@@ -2550,6 +3008,11 @@ async function configureCommand() {
2550
3008
  label: "Remove skills",
2551
3009
  hint: "uninstall capabilities"
2552
3010
  },
3011
+ {
3012
+ value: "model",
3013
+ label: "Model preferences",
3014
+ hint: "default model for Telegram + scheduling"
3015
+ },
2553
3016
  {
2554
3017
  value: "soul",
2555
3018
  label: "Edit personality",
@@ -2565,6 +3028,11 @@ async function configureCommand() {
2565
3028
  label: "Telegram setup",
2566
3029
  hint: "configure bot bridge"
2567
3030
  },
3031
+ {
3032
+ value: "lockin setup",
3033
+ label: "Lock In Mode",
3034
+ hint: "block distractions, set the mood"
3035
+ },
2568
3036
  {
2569
3037
  value: "schedule",
2570
3038
  label: "Manage schedules",
@@ -2582,13 +3050,17 @@ async function configureCommand() {
2582
3050
  }
2583
3051
  ]
2584
3052
  });
2585
- if (p$1.isCancel(action)) {
2586
- p$1.outro(dim("Come back anytime!"));
3053
+ if (p$2.isCancel(action)) {
3054
+ p$2.outro(dim("Come back anytime!"));
3055
+ return;
3056
+ }
3057
+ if (action === "model") {
3058
+ await modelPreferences();
2587
3059
  return;
2588
3060
  }
2589
3061
  const cmd = `openpaw ${action}`;
2590
3062
  console.log("");
2591
- p$1.log.info(`Running ${accent(cmd)}...`);
3063
+ p$2.log.info(`Running ${accent(cmd)}...`);
2592
3064
  console.log("");
2593
3065
  try {
2594
3066
  execSync(`node ${process.argv[1]} ${action}`, {
@@ -2597,6 +3069,744 @@ async function configureCommand() {
2597
3069
  });
2598
3070
  } catch {}
2599
3071
  }
3072
+ async function modelPreferences() {
3073
+ console.log("");
3074
+ const hasTelegram = telegramConfigExists();
3075
+ let hasScheduling = false;
3076
+ try {
3077
+ const schedConfig = readScheduleConfig();
3078
+ hasScheduling = schedConfig.dailyCostCapUsd > 0 || schedConfig.jobs.length > 0;
3079
+ } catch {}
3080
+ if (!hasTelegram && !hasScheduling) {
3081
+ p$2.log.info("No Telegram or scheduling configured yet — model preferences apply to those features.");
3082
+ p$2.log.info(`Run ${bold("openpaw telegram setup")} or enable scheduling in the wizard.`);
3083
+ return;
3084
+ }
3085
+ const lines = [];
3086
+ if (hasTelegram) {
3087
+ const tgConfig = readTelegramConfig();
3088
+ lines.push(`${bold("Telegram:")} ${tgConfig?.model || "sonnet"}`);
3089
+ }
3090
+ if (hasScheduling) {
3091
+ const schedConfig = readScheduleConfig();
3092
+ lines.push(`${bold("Scheduling:")} ${schedConfig.defaultModel || "sonnet"}`);
3093
+ lines.push(`${bold("Cost cap:")} $${schedConfig.dailyCostCapUsd}/day`);
3094
+ }
3095
+ p$2.note(lines.join("\n"), "Current Model Settings");
3096
+ if (hasTelegram) {
3097
+ const tgConfig = readTelegramConfig();
3098
+ const newModel = await p$2.select({
3099
+ message: "Default model for Telegram",
3100
+ options: [
3101
+ {
3102
+ value: "sonnet",
3103
+ label: "Sonnet",
3104
+ hint: `fast + capable${tgConfig.model === "sonnet" ? " (current)" : ""}`
3105
+ },
3106
+ {
3107
+ value: "haiku",
3108
+ label: "Haiku",
3109
+ hint: `cheapest${tgConfig.model === "haiku" ? " (current)" : ""}`
3110
+ },
3111
+ {
3112
+ value: "opus",
3113
+ label: "Opus",
3114
+ hint: `most capable${tgConfig.model === "opus" ? " (current)" : ""}`
3115
+ }
3116
+ ]
3117
+ });
3118
+ if (!p$2.isCancel(newModel) && newModel !== tgConfig.model) {
3119
+ tgConfig.model = newModel;
3120
+ writeTelegramConfig(tgConfig);
3121
+ p$2.log.success(`Telegram model → ${accent(newModel)}`);
3122
+ }
3123
+ }
3124
+ if (hasScheduling) {
3125
+ const schedConfig = readScheduleConfig();
3126
+ const newModel = await p$2.select({
3127
+ message: "Default model for scheduled jobs",
3128
+ options: [
3129
+ {
3130
+ value: "sonnet",
3131
+ label: "Sonnet",
3132
+ hint: `fast + capable${schedConfig.defaultModel === "sonnet" ? " (current)" : ""}`
3133
+ },
3134
+ {
3135
+ value: "haiku",
3136
+ label: "Haiku",
3137
+ hint: `cheapest${schedConfig.defaultModel === "haiku" ? " (current)" : ""}`
3138
+ },
3139
+ {
3140
+ value: "opus",
3141
+ label: "Opus",
3142
+ hint: `most capable${schedConfig.defaultModel === "opus" ? " (current)" : ""}`
3143
+ }
3144
+ ]
3145
+ });
3146
+ if (!p$2.isCancel(newModel) && newModel !== schedConfig.defaultModel) {
3147
+ schedConfig.defaultModel = newModel;
3148
+ writeScheduleConfig(schedConfig);
3149
+ p$2.log.success(`Schedule default model → ${accent(newModel)}`);
3150
+ }
3151
+ const newCap = await p$2.text({
3152
+ message: "Daily cost cap in USD",
3153
+ initialValue: String(schedConfig.dailyCostCapUsd),
3154
+ validate: (v) => {
3155
+ const n = Number.parseFloat(v);
3156
+ return Number.isNaN(n) || n <= 0 ? "Enter a valid dollar amount" : void 0;
3157
+ }
3158
+ });
3159
+ if (!p$2.isCancel(newCap)) {
3160
+ const cap = Number.parseFloat(newCap);
3161
+ if (cap !== schedConfig.dailyCostCapUsd) {
3162
+ schedConfig.dailyCostCapUsd = cap;
3163
+ writeScheduleConfig(schedConfig);
3164
+ p$2.log.success(`Daily cap → ${accent("$" + cap)}`);
3165
+ }
3166
+ }
3167
+ }
3168
+ regenerateClaudeMd();
3169
+ p$2.outro(dim("Model preferences updated"));
3170
+ }
3171
+
3172
+ //#endregion
3173
+ //#region src/commands/lockin.ts
3174
+ function lockInCommand() {
3175
+ showMini();
3176
+ console.log("");
3177
+ const config = readLockInConfig();
3178
+ if (!config) {
3179
+ p$1.log.warn("Lock In Mode isn't set up yet.");
3180
+ p$1.log.info(`Run ${bold("openpaw lockin setup")} to configure your environment.`);
3181
+ return;
3182
+ }
3183
+ const session = readLockInSession();
3184
+ if (session) {
3185
+ const endsAt = new Date(session.endsAt);
3186
+ const now = new Date();
3187
+ if (endsAt > now) {
3188
+ const remaining = Math.round((endsAt.getTime() - now.getTime()) / 6e4);
3189
+ const elapsed = Math.round((now.getTime() - new Date(session.startedAt).getTime()) / 6e4);
3190
+ p$1.note([
3191
+ `${bold("Status:")} ${accent("active")}`,
3192
+ `${bold("Elapsed:")} ${elapsed} min`,
3193
+ `${bold("Remaining:")} ${remaining} min`,
3194
+ `${bold("Ends at:")} ${endsAt.toLocaleTimeString()}`
3195
+ ].join("\n"), "Lock In Session");
3196
+ p$1.log.info(dim("Claude is managing this session. Tell Claude to end it."));
3197
+ return;
3198
+ }
3199
+ p$1.log.warn("Previous session expired. Tell Claude to clean up, or delete ~/.config/openpaw/lockin-session.json");
3200
+ }
3201
+ printConfig(config);
3202
+ console.log("");
3203
+ p$1.log.info(dim("Tell Claude to ") + bold("lock in") + dim(" to start a session."));
3204
+ p$1.log.info(dim("Reconfigure: ") + bold("openpaw lockin setup"));
3205
+ }
3206
+ const PAW_FRAMES = [
3207
+ "🐾",
3208
+ " 🐾",
3209
+ " 🐾",
3210
+ " 🐾",
3211
+ " 🐾",
3212
+ " 🐾"
3213
+ ];
3214
+ function sleep(ms) {
3215
+ return new Promise((r) => setTimeout(r, ms));
3216
+ }
3217
+ async function pawWalk(label) {
3218
+ for (const frame of PAW_FRAMES) {
3219
+ process.stdout.write(`\r ${subtle(frame)} ${dim(label)}`);
3220
+ await sleep(60);
3221
+ }
3222
+ process.stdout.write(`\r\x1B[2K`);
3223
+ console.log(` ${accent("🐾")} ${label}`);
3224
+ }
3225
+ async function lockInSetupCommand() {
3226
+ showMini();
3227
+ console.log("");
3228
+ const caps = detectCapabilities();
3229
+ const detected = [];
3230
+ if (caps.hasBluetooth) detected.push("Bluetooth");
3231
+ if (caps.hasSpotify) detected.push("Spotify");
3232
+ if (caps.hasAppleMusic) detected.push("Apple Music");
3233
+ if (caps.hasSonos) detected.push("Sonos");
3234
+ if (caps.hasHue) detected.push("Hue lights");
3235
+ if (caps.hasSlack) detected.push("Slack");
3236
+ if (caps.hasObsidian) detected.push("Obsidian");
3237
+ if (caps.hasTerminalNotifier) detected.push("Notifications");
3238
+ p$1.intro(accent("Lock In Mode Setup"));
3239
+ if (lockInConfigExists()) {
3240
+ const existing = readLockInConfig();
3241
+ p$1.log.info("You already have a lock-in config. This will update it.");
3242
+ printConfig(existing);
3243
+ console.log("");
3244
+ }
3245
+ if (detected.length > 0) {
3246
+ await pawWalk("Sniffing your system...");
3247
+ p$1.log.info(`Detected: ${detected.map((d) => accent(d)).join(", ")}`);
3248
+ }
3249
+ const duration = await p$1.text({
3250
+ message: "How long is your typical lock-in session? (minutes)",
3251
+ initialValue: "90",
3252
+ validate: (v) => {
3253
+ const n = parseInt(v, 10);
3254
+ return isNaN(n) || n < 5 || n > 480 ? "Enter 5–480 minutes" : void 0;
3255
+ }
3256
+ });
3257
+ if (p$1.isCancel(duration)) return;
3258
+ const config = {
3259
+ duration: parseInt(duration, 10),
3260
+ dnd: false,
3261
+ slackDnd: false,
3262
+ timer: false,
3263
+ obsidianLog: false
3264
+ };
3265
+ await pawWalk("Distractions...");
3266
+ const wantSites = await p$1.confirm({
3267
+ message: "Block distracting websites when locked in?",
3268
+ initialValue: true
3269
+ });
3270
+ if (!p$1.isCancel(wantSites) && wantSites) {
3271
+ const selectedSites = await p$1.multiselect({
3272
+ message: "Which sites to block?",
3273
+ options: [...COMMON_BLOCKED_SITES.map((s) => ({
3274
+ value: s,
3275
+ label: s
3276
+ })), {
3277
+ value: "_custom",
3278
+ label: "Custom...",
3279
+ hint: "type your own"
3280
+ }],
3281
+ required: false
3282
+ });
3283
+ const raw = p$1.isCancel(selectedSites) ? [] : selectedSites;
3284
+ const hasCustom = raw.includes("_custom");
3285
+ const siteList = raw.filter((s) => s !== "_custom");
3286
+ if (hasCustom) {
3287
+ const customSites = await p$1.text({
3288
+ message: "Type sites to block (comma-separated)",
3289
+ defaultValue: ""
3290
+ });
3291
+ if (!p$1.isCancel(customSites) && customSites.trim()) siteList.push(...customSites.split(",").map((s) => s.trim()).filter(Boolean));
3292
+ }
3293
+ if (siteList.length > 0) {
3294
+ const askEach = await p$1.multiselect({
3295
+ message: "Any of these you sometimes need? (they'll ask each session)",
3296
+ options: siteList.map((s) => ({
3297
+ value: s,
3298
+ label: s
3299
+ })),
3300
+ required: false
3301
+ });
3302
+ const askList = p$1.isCancel(askEach) ? [] : askEach;
3303
+ const alwaysList = siteList.filter((s) => !askList.includes(s));
3304
+ config.blockedSites = {
3305
+ always: alwaysList,
3306
+ askEachTime: askList
3307
+ };
3308
+ }
3309
+ }
3310
+ await pawWalk("Apps...");
3311
+ const wantApps = await p$1.confirm({
3312
+ message: "Quit distracting apps when locked in?",
3313
+ initialValue: true
3314
+ });
3315
+ if (!p$1.isCancel(wantApps) && wantApps) {
3316
+ const appOptions = [...new Set([...COMMON_QUIT_APPS, ...caps.runningApps.filter((a) => ![
3317
+ "Finder",
3318
+ "loginwindow",
3319
+ "SystemUIServer",
3320
+ "Dock",
3321
+ "WindowServer"
3322
+ ].includes(a))])];
3323
+ const selectedApps = await p$1.multiselect({
3324
+ message: "Which apps?",
3325
+ options: appOptions.slice(0, 15).map((a) => ({
3326
+ value: a,
3327
+ label: a,
3328
+ hint: caps.runningApps.includes(a) ? "running" : void 0
3329
+ })),
3330
+ required: false
3331
+ });
3332
+ const appList = p$1.isCancel(selectedApps) ? [] : selectedApps;
3333
+ if (appList.length > 0) {
3334
+ const askEach = await p$1.multiselect({
3335
+ message: "Any of these you sometimes need?",
3336
+ options: appList.map((a) => ({
3337
+ value: a,
3338
+ label: a
3339
+ })),
3340
+ required: false
3341
+ });
3342
+ const askList = p$1.isCancel(askEach) ? [] : askEach;
3343
+ const alwaysList = appList.filter((a) => !askList.includes(a));
3344
+ config.quitApps = {
3345
+ always: alwaysList,
3346
+ askEachTime: askList
3347
+ };
3348
+ }
3349
+ }
3350
+ if (caps.hasBluetooth && caps.bluetoothDevices.length > 0) {
3351
+ await pawWalk("Bluetooth...");
3352
+ const wantBt = await p$1.confirm({
3353
+ message: "Auto-connect a Bluetooth device (headphones)?",
3354
+ initialValue: true
3355
+ });
3356
+ if (!p$1.isCancel(wantBt) && wantBt) {
3357
+ const device = await p$1.select({
3358
+ message: "Which device?",
3359
+ options: [...caps.bluetoothDevices.map((d) => ({
3360
+ value: d,
3361
+ label: d
3362
+ })), {
3363
+ value: "_custom",
3364
+ label: "Type a name..."
3365
+ }]
3366
+ });
3367
+ if (!p$1.isCancel(device)) {
3368
+ let deviceName = device;
3369
+ if (deviceName === "_custom") {
3370
+ const custom = await p$1.text({ message: "Device name" });
3371
+ if (!p$1.isCancel(custom)) deviceName = custom;
3372
+ }
3373
+ if (deviceName !== "_custom") config.bluetooth = { device: deviceName };
3374
+ }
3375
+ }
3376
+ }
3377
+ await pawWalk("Vibes...");
3378
+ const musicSources = [];
3379
+ if (caps.hasSpotify) musicSources.push({
3380
+ value: "spotify",
3381
+ label: "Spotify",
3382
+ hint: "spogo"
3383
+ });
3384
+ if (caps.hasAppleMusic) musicSources.push({
3385
+ value: "apple-music",
3386
+ label: "Apple Music"
3387
+ });
3388
+ if (caps.hasSonos) musicSources.push({
3389
+ value: "sonos",
3390
+ label: "Sonos"
3391
+ });
3392
+ if (caps.hasYtDlp) musicSources.push({
3393
+ value: "youtube",
3394
+ label: "YouTube (audio)",
3395
+ hint: "yt-dlp"
3396
+ });
3397
+ const wantMusic = musicSources.length > 0 ? await p$1.confirm({
3398
+ message: "Play music when locked in?",
3399
+ initialValue: true
3400
+ }) : false;
3401
+ if (!p$1.isCancel(wantMusic) && wantMusic) {
3402
+ const source = await p$1.select({
3403
+ message: "Music source",
3404
+ options: musicSources
3405
+ });
3406
+ if (!p$1.isCancel(source)) {
3407
+ const presets$1 = {
3408
+ spotify: [
3409
+ {
3410
+ value: "lo-fi beats",
3411
+ label: "Lo-fi beats"
3412
+ },
3413
+ {
3414
+ value: "deep focus",
3415
+ label: "Deep focus"
3416
+ },
3417
+ {
3418
+ value: "white noise",
3419
+ label: "White noise"
3420
+ },
3421
+ {
3422
+ value: "nature sounds",
3423
+ label: "Nature sounds"
3424
+ },
3425
+ {
3426
+ value: "classical focus",
3427
+ label: "Classical"
3428
+ },
3429
+ {
3430
+ value: "_custom",
3431
+ label: "Custom..."
3432
+ }
3433
+ ],
3434
+ "apple-music": [
3435
+ {
3436
+ value: "Focus",
3437
+ label: "Focus"
3438
+ },
3439
+ {
3440
+ value: "Chill",
3441
+ label: "Chill"
3442
+ },
3443
+ {
3444
+ value: "Classical",
3445
+ label: "Classical"
3446
+ },
3447
+ {
3448
+ value: "_custom",
3449
+ label: "Custom..."
3450
+ }
3451
+ ],
3452
+ sonos: [{
3453
+ value: "_custom",
3454
+ label: "Type a playlist or station..."
3455
+ }],
3456
+ youtube: [
3457
+ {
3458
+ value: "https://www.youtube.com/watch?v=nMfPqeZjc2c",
3459
+ label: "White noise",
3460
+ hint: "10h"
3461
+ },
3462
+ {
3463
+ value: "https://www.youtube.com/watch?v=jfKfPfyJRdk",
3464
+ label: "Lo-fi hip hop",
3465
+ hint: "Lofi Girl livestream"
3466
+ },
3467
+ {
3468
+ value: "https://www.youtube.com/watch?v=jX6kn9_U8qk",
3469
+ label: "Rain sounds",
3470
+ hint: "10h"
3471
+ },
3472
+ {
3473
+ value: "https://www.youtube.com/watch?v=jkLRith2wcc",
3474
+ label: "Water/stream sounds",
3475
+ hint: "10h"
3476
+ },
3477
+ {
3478
+ value: "https://www.youtube.com/watch?v=GSaJXDsb3N8",
3479
+ label: "Brown noise",
3480
+ hint: "8h"
3481
+ },
3482
+ {
3483
+ value: "_custom",
3484
+ label: "Custom URL..."
3485
+ }
3486
+ ]
3487
+ };
3488
+ const sourcePresets = presets$1[source] ?? [{
3489
+ value: "_custom",
3490
+ label: "Custom..."
3491
+ }];
3492
+ let query;
3493
+ if (sourcePresets.length === 1 && sourcePresets[0].value === "_custom") {
3494
+ const custom = await p$1.text({
3495
+ message: "Playlist or station name",
3496
+ defaultValue: ""
3497
+ });
3498
+ if (!p$1.isCancel(custom) && custom.trim()) query = custom;
3499
+ } else {
3500
+ const picked = await p$1.select({
3501
+ message: "What to play?",
3502
+ options: sourcePresets
3503
+ });
3504
+ if (!p$1.isCancel(picked)) if (picked === "_custom") {
3505
+ const custom = await p$1.text({
3506
+ message: source === "youtube" ? "YouTube URL" : "Playlist or search query",
3507
+ defaultValue: ""
3508
+ });
3509
+ if (!p$1.isCancel(custom) && custom.trim()) query = custom;
3510
+ } else query = picked;
3511
+ }
3512
+ if (query) config.music = {
3513
+ source,
3514
+ query
3515
+ };
3516
+ }
3517
+ }
3518
+ await pawWalk("Lights...");
3519
+ const wantLights = await p$1.confirm({
3520
+ message: "Set Hue lights when locked in?",
3521
+ initialValue: caps.hasHue
3522
+ });
3523
+ if (!p$1.isCancel(wantLights) && wantLights) {
3524
+ let hueReady = caps.hasHue;
3525
+ if (!hueReady) {
3526
+ p$1.log.warn("openhue CLI is not installed (controls Philips Hue).");
3527
+ const installHue = await p$1.confirm({
3528
+ message: "Install via Homebrew? (brew install openhue-cli)",
3529
+ initialValue: true
3530
+ });
3531
+ if (!p$1.isCancel(installHue) && installHue) {
3532
+ const s = p$1.spinner();
3533
+ s.start("Installing openhue-cli...");
3534
+ try {
3535
+ execSync("brew install openhue-cli", {
3536
+ stdio: "pipe",
3537
+ timeout: 12e4
3538
+ });
3539
+ hueReady = true;
3540
+ s.stop(accent("openhue installed!"));
3541
+ p$1.log.info(dim("Pair with your bridge: ") + bold("openhue setup"));
3542
+ } catch {
3543
+ s.stop("Install failed. Install manually: brew install openhue-cli");
3544
+ }
3545
+ }
3546
+ }
3547
+ if (hueReady && caps.hueRooms.length === 0) p$1.log.info(dim("No Hue rooms found. Run ") + bold("openhue setup") + dim(" to pair with your bridge."));
3548
+ if (hueReady || caps.hueRooms.length > 0) {
3549
+ let room = "Office";
3550
+ if (caps.hueRooms.length > 0) {
3551
+ const selected = await p$1.select({
3552
+ message: "Which room?",
3553
+ options: [...caps.hueRooms.map((r) => ({
3554
+ value: r,
3555
+ label: r
3556
+ })), {
3557
+ value: "_custom",
3558
+ label: "Type a name..."
3559
+ }]
3560
+ });
3561
+ if (!p$1.isCancel(selected)) {
3562
+ room = selected;
3563
+ if (room === "_custom") {
3564
+ const custom = await p$1.text({ message: "Room name" });
3565
+ if (!p$1.isCancel(custom)) room = custom;
3566
+ }
3567
+ }
3568
+ } else {
3569
+ const custom = await p$1.text({
3570
+ message: "Room name",
3571
+ initialValue: "Office"
3572
+ });
3573
+ if (!p$1.isCancel(custom)) room = custom;
3574
+ }
3575
+ const brightnessVal = await p$1.text({
3576
+ message: "Brightness (0-100)",
3577
+ initialValue: "30",
3578
+ validate: (v) => {
3579
+ const n = parseInt(v, 10);
3580
+ return isNaN(n) || n < 0 || n > 100 ? "Enter 0-100" : void 0;
3581
+ }
3582
+ });
3583
+ if (!p$1.isCancel(brightnessVal)) {
3584
+ config.lights = {
3585
+ room,
3586
+ brightness: parseInt(brightnessVal, 10)
3587
+ };
3588
+ const color = await p$1.select({
3589
+ message: "Light color",
3590
+ options: [
3591
+ {
3592
+ value: "",
3593
+ label: "No preference",
3594
+ hint: "keep current"
3595
+ },
3596
+ {
3597
+ value: "warm",
3598
+ label: "Warm",
3599
+ hint: "relaxed, cozy"
3600
+ },
3601
+ {
3602
+ value: "cool",
3603
+ label: "Cool",
3604
+ hint: "bright, alert"
3605
+ },
3606
+ {
3607
+ value: "red",
3608
+ label: "Red",
3609
+ hint: "low stimulation"
3610
+ },
3611
+ {
3612
+ value: "orange",
3613
+ label: "Orange",
3614
+ hint: "sunset vibe"
3615
+ },
3616
+ {
3617
+ value: "blue",
3618
+ label: "Blue",
3619
+ hint: "calm focus"
3620
+ }
3621
+ ]
3622
+ });
3623
+ if (!p$1.isCancel(color) && color) config.lights.color = color;
3624
+ }
3625
+ } else p$1.log.info(dim("Skipping lights. Install later: ") + bold("brew install openhue-cli"));
3626
+ }
3627
+ await pawWalk("Finishing up...");
3628
+ const toggles = await p$1.multiselect({
3629
+ message: "Enable when locked in",
3630
+ options: [
3631
+ {
3632
+ value: "dnd",
3633
+ label: "macOS Do Not Disturb",
3634
+ hint: "silence all notifications"
3635
+ },
3636
+ ...caps.hasSlack ? [{
3637
+ value: "slackDnd",
3638
+ label: "Slack DND",
3639
+ hint: `auto-set for ${config.duration} min`
3640
+ }] : [],
3641
+ ...caps.hasTerminalNotifier ? [{
3642
+ value: "timer",
3643
+ label: "Timer notification",
3644
+ hint: "notify when session ends"
3645
+ }] : [],
3646
+ ...caps.hasObsidian ? [{
3647
+ value: "obsidianLog",
3648
+ label: "Log to Obsidian",
3649
+ hint: "save session receipt"
3650
+ }] : []
3651
+ ],
3652
+ required: false
3653
+ });
3654
+ if (!p$1.isCancel(toggles)) {
3655
+ const selected = toggles;
3656
+ config.dnd = selected.includes("dnd");
3657
+ config.slackDnd = selected.includes("slackDnd");
3658
+ config.timer = selected.includes("timer");
3659
+ config.obsidianLog = selected.includes("obsidianLog");
3660
+ }
3661
+ const defaultDir = getDefaultSkillsDir();
3662
+ const installed = listInstalledSkills(defaultDir);
3663
+ const hint = installed.length > 0 ? `${installed.length} skills installed here` : "recommended";
3664
+ const skillsDir = await p$1.select({
3665
+ message: "Where should the lock-in skill live?",
3666
+ options: [
3667
+ {
3668
+ value: defaultDir,
3669
+ label: `Global ${dim("~/.claude/skills/")}`,
3670
+ hint
3671
+ },
3672
+ {
3673
+ value: ".claude/skills",
3674
+ label: `Project ${dim(".claude/skills/")}`
3675
+ },
3676
+ {
3677
+ value: "custom",
3678
+ label: "Custom path"
3679
+ }
3680
+ ]
3681
+ });
3682
+ let targetDir = p$1.isCancel(skillsDir) ? defaultDir : skillsDir;
3683
+ if (targetDir === "custom") {
3684
+ const customDir = await p$1.text({
3685
+ message: "Skills directory path:",
3686
+ defaultValue: "",
3687
+ validate: (v) => v.length === 0 ? "Path cannot be empty" : void 0
3688
+ });
3689
+ if (p$1.isCancel(customDir)) targetDir = defaultDir;
3690
+ else targetDir = customDir.replace(/^~/, os$1.homedir());
3691
+ }
3692
+ await pawWalk("Saving your lock-in config...");
3693
+ writeLockInConfig(config);
3694
+ installLockInSkillMd(targetDir);
3695
+ console.log("");
3696
+ printConfig(config);
3697
+ p$1.outro(accent("Lock In Mode configured! ") + dim("Tell Claude to \"lock in\" to start a session. 🐾"));
3698
+ }
3699
+ function installLockInSkillMd(skillsDir) {
3700
+ const skillDir = path$1.join(skillsDir, "c-lockin");
3701
+ fs$1.mkdirSync(skillDir, { recursive: true });
3702
+ const md = `---
3703
+ name: c-lockin
3704
+ description: Lock In Mode — orchestrate distraction blocking, environment setup, and session tracking.
3705
+ tags: [lockin, focus, productivity, deep-work, pomodoro, distraction-blocking]
3706
+ ---
3707
+
3708
+ ## Behavior
3709
+
3710
+ You run lock-in sessions using generated shell scripts — fast, one command.
3711
+ Be FAST. Say one short line like "Locking you in for 90 min" then run TWO commands max. Don't narrate steps. Only speak again when done ("Locked in until HH:MM") or if something fails.
3712
+
3713
+ ## Starting a Session
3714
+
3715
+ When the user says "lock in", "focus", "deep work", etc:
3716
+
3717
+ 1. Read \`~/.config/openpaw/lockin.json\`. If missing → suggest \`openpaw lockin setup\`
3718
+ 2. Check \`~/.config/openpaw/lockin-session.json\` — if \`endsAt\` is in the future, session is already active. Tell the user.
3719
+ 3. If config has \`askEachTime\` sites or apps, ask the user briefly which to include this session
3720
+ 4. Calculate \`endsAt\` = now + duration minutes (ISO 8601 format, e.g. \`2026-03-03T16:30:00.000Z\`)
3721
+ 5. Say ONE short line like "Locking you in for 90 min"
3722
+ 6. Generate scripts + run:
3723
+
3724
+ \`\`\`bash
3725
+ openpaw lockin gen-scripts --ends "ENDS_AT_ISO8601" --extra-sites "site1,site2" --extra-apps "App1,App2"
3726
+ \`\`\`
3727
+
3728
+ Omit \`--extra-sites\` and \`--extra-apps\` if none were chosen from askEachTime.
3729
+
3730
+ Then immediately run:
3731
+
3732
+ \`\`\`bash
3733
+ bash /tmp/lockin-start.sh
3734
+ \`\`\`
3735
+
3736
+ 7. Say "Locked in until HH:MM"
3737
+
3738
+ That's it — TWO bash commands to start a session.
3739
+
3740
+ ## Ending a Session
3741
+
3742
+ When the user says "stop", "end session", "I'm done":
3743
+
3744
+ 1. Read \`~/.config/openpaw/lockin-session.json\` to get session data
3745
+ 2. Run:
3746
+
3747
+ \`\`\`bash
3748
+ bash /tmp/lockin-end.sh
3749
+ \`\`\`
3750
+
3751
+ 3. Read the output for git stats
3752
+ 4. If \`obsidianLog\` is true in config: \`obsidian-cli append daily "## Lock In Session\\n- Duration: X min\\n- Commits: N\\n..."\`
3753
+ 5. Give a brief warm summary: duration, commits + messages, lines changed, encouraging note referencing SOUL.md personality
3754
+
3755
+ ## Reconfigure
3756
+
3757
+ \`\`\`bash
3758
+ openpaw lockin setup
3759
+ \`\`\`
3760
+
3761
+ ## Guidelines
3762
+
3763
+ - Be FAST — one line to start, two commands, one line when done
3764
+ - Never explain or narrate each step — just do it
3765
+ - If something fails, mention it briefly and move on
3766
+ - Only start when the user explicitly asks
3767
+ - Reference SOUL.md for personality in summaries
3768
+ `;
3769
+ fs$1.writeFileSync(path$1.join(skillDir, "SKILL.md"), md);
3770
+ }
3771
+ function lockInGenScriptsCommand(opts) {
3772
+ const config = readLockInConfig();
3773
+ if (!config) {
3774
+ console.error("No lock-in config found. Run: openpaw lockin setup");
3775
+ process.exit(1);
3776
+ }
3777
+ const extraSites = opts.extraSites ? opts.extraSites.split(",").map((s) => s.trim()).filter(Boolean) : [];
3778
+ const extraApps = opts.extraApps ? opts.extraApps.split(",").map((s) => s.trim()).filter(Boolean) : [];
3779
+ const startScript = generateStartScript({
3780
+ config,
3781
+ endsAt: opts.ends,
3782
+ extraSites,
3783
+ extraApps
3784
+ });
3785
+ const endScript = generateEndScript(config);
3786
+ fs$1.writeFileSync("/tmp/lockin-start.sh", startScript, { mode: 493 });
3787
+ fs$1.writeFileSync("/tmp/lockin-end.sh", endScript, { mode: 493 });
3788
+ console.log("/tmp/lockin-start.sh");
3789
+ console.log("/tmp/lockin-end.sh");
3790
+ }
3791
+ async function lockInConfigureCommand() {
3792
+ return lockInSetupCommand();
3793
+ }
3794
+ function printConfig(config) {
3795
+ const lines = [];
3796
+ lines.push(`${bold("Duration:")} ${config.duration} min`);
3797
+ if (config.blockedSites) lines.push(`${bold("Sites:")} ${config.blockedSites.always.length} always blocked, ${config.blockedSites.askEachTime.length} ask-each-time`);
3798
+ if (config.quitApps) lines.push(`${bold("Apps:")} ${config.quitApps.always.length} always quit, ${config.quitApps.askEachTime.length} ask-each-time`);
3799
+ if (config.bluetooth) lines.push(`${bold("Bluetooth:")} ${config.bluetooth.device}`);
3800
+ if (config.music) lines.push(`${bold("Music:")} ${config.music.source} → ${config.music.query}`);
3801
+ if (config.lights) lines.push(`${bold("Lights:")} ${config.lights.room} at ${config.lights.brightness}%${config.lights.color ? ` (${config.lights.color})` : ""}`);
3802
+ const flags = [];
3803
+ if (config.dnd) flags.push("DND");
3804
+ if (config.slackDnd) flags.push("Slack DND");
3805
+ if (config.timer) flags.push("Timer");
3806
+ if (config.obsidianLog) flags.push("Obsidian log");
3807
+ if (flags.length) lines.push(`${bold("Extras:")} ${flags.join(", ")}`);
3808
+ p$1.note(lines.join("\n"), "Lock In Config");
3809
+ }
2600
3810
 
2601
3811
  //#endregion
2602
3812
  //#region src/commands/schedule.ts
@@ -2829,15 +4039,15 @@ async function scheduleSetCapCommand(amount) {
2829
4039
  }
2830
4040
  const config = readScheduleConfig();
2831
4041
  config.dailyCostCapUsd = usd;
2832
- const { writeScheduleConfig } = await import("./scheduler-DppXPNqK.js");
2833
- writeScheduleConfig(config);
4042
+ const { writeScheduleConfig: writeScheduleConfig$1 } = await import("./scheduler-DppXPNqK.js");
4043
+ writeScheduleConfig$1(config);
2834
4044
  p.log.success(`Daily cost cap set to ${accent(`$${usd.toFixed(2)}`)}`);
2835
4045
  }
2836
4046
 
2837
4047
  //#endregion
2838
4048
  //#region src/index.ts
2839
4049
  const program = new Command();
2840
- program.name("openpaw").description("Personal Assistant Wizard for Claude Code").version("1.3.0");
4050
+ program.name("openpaw").description("Personal Assistant Wizard for Claude Code").version("1.5.0");
2841
4051
  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);
2842
4052
  program.command("add").description("Add skill(s) by name").argument("<skills...>", "Skill IDs to add (e.g. notes music email)").action(addCommand);
2843
4053
  program.command("remove").description("Remove skill(s) by name").argument("<skills...>", "Skill IDs to remove").action(removeCommand);
@@ -2849,8 +4059,13 @@ program.command("reset").description("Remove all OpenPaw skills, permissions, an
2849
4059
  program.command("soul").description("Set up or edit Claude's personality (SOUL.md)").action(soulCommand);
2850
4060
  program.command("export").description("Export skills, memory, and config to a file").action(exportCommand);
2851
4061
  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);
4062
+ 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").option("--no-open", "Start server without opening browser").action(dashboardCommand);
2853
4063
  program.command("configure").alias("config").description("Configure your setup — add skills, change personality, manage dashboard").action(configureCommand);
4064
+ const lockin = program.command("lockin").description("Start a lock-in session — block distractions, set the mood, get in the zone");
4065
+ lockin.action(lockInCommand);
4066
+ lockin.command("setup").description("Set up or reconfigure Lock In Mode").action(lockInSetupCommand);
4067
+ lockin.command("configure").alias("config").description("Reconfigure Lock In Mode (alias for setup)").action(lockInConfigureCommand);
4068
+ lockin.command("gen-scripts").description("Generate start/end shell scripts from config (used by Claude)").requiredOption("--ends <iso>", "Session end time (ISO 8601)").option("--extra-sites <sites>", "Comma-separated extra sites to block").option("--extra-apps <apps>", "Comma-separated extra apps to quit").action(lockInGenScriptsCommand);
2854
4069
  const tg = program.command("telegram").description("Start the Telegram bridge — talk to Claude from your phone");
2855
4070
  tg.action(telegramCommand);
2856
4071
  tg.command("setup").description("Set up or reconfigure the Telegram bot").action(telegramSetupCommand);