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