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