pawmode 1.0.0 → 1.2.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 +229 -72
- package/dist/dashboard-server--wwlA0Pa.js +426 -0
- package/dist/dashboard-server-Cg_1CvKn.js +3 -0
- package/dist/index.js +684 -872
- 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-calendar/SKILL.md +12 -11
- 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/skills/c-social/SKILL.md +0 -57
- /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,6 +1,8 @@
|
|
|
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--wwlA0Pa.js";
|
|
4
6
|
import { Command } from "commander";
|
|
5
7
|
import * as p$11 from "@clack/prompts";
|
|
6
8
|
import * as p$10 from "@clack/prompts";
|
|
@@ -14,8 +16,6 @@ import * as p$3 from "@clack/prompts";
|
|
|
14
16
|
import * as p$2 from "@clack/prompts";
|
|
15
17
|
import * as p$1 from "@clack/prompts";
|
|
16
18
|
import * as p from "@clack/prompts";
|
|
17
|
-
import * as os$6 from "node:os";
|
|
18
|
-
import * as os$5 from "node:os";
|
|
19
19
|
import * as os$4 from "node:os";
|
|
20
20
|
import * as os$3 from "node:os";
|
|
21
21
|
import * as os$2 from "node:os";
|
|
@@ -23,27 +23,22 @@ import * as os$1 from "node:os";
|
|
|
23
23
|
import os from "node:os";
|
|
24
24
|
import chalk from "chalk";
|
|
25
25
|
import { execSync, spawn } from "node:child_process";
|
|
26
|
-
import * as fs$4 from "node:fs";
|
|
27
26
|
import * as fs$3 from "node:fs";
|
|
28
27
|
import * as fs$2 from "node:fs";
|
|
29
28
|
import * as fs$1 from "node:fs";
|
|
30
29
|
import fs from "node:fs";
|
|
31
|
-
import * as path$4 from "node:path";
|
|
32
30
|
import * as path$3 from "node:path";
|
|
33
31
|
import * as path$2 from "node:path";
|
|
34
32
|
import * as path$1 from "node:path";
|
|
35
33
|
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
34
|
|
|
40
35
|
//#region src/catalog/index.ts
|
|
41
36
|
const memo = {
|
|
42
37
|
name: "memo",
|
|
43
38
|
command: "memo",
|
|
44
|
-
installCmd: "brew install
|
|
39
|
+
installCmd: "brew install antoniorodr/memo/memo",
|
|
45
40
|
installMethod: "brew-tap",
|
|
46
|
-
tap: "
|
|
41
|
+
tap: "antoniorodr/memo",
|
|
47
42
|
platforms: ["darwin"]
|
|
48
43
|
};
|
|
49
44
|
const remindctl = {
|
|
@@ -69,7 +64,7 @@ const obsidianCli = {
|
|
|
69
64
|
const notionCli = {
|
|
70
65
|
name: "notion-cli",
|
|
71
66
|
command: "notion-cli",
|
|
72
|
-
installCmd: "npm install -g notion-cli
|
|
67
|
+
installCmd: "npm install -g @litencatt/notion-cli",
|
|
73
68
|
installMethod: "npm",
|
|
74
69
|
platforms: [
|
|
75
70
|
"darwin",
|
|
@@ -87,7 +82,7 @@ const todoistCli = {
|
|
|
87
82
|
const thingsCli = {
|
|
88
83
|
name: "things-cli",
|
|
89
84
|
command: "things-cli",
|
|
90
|
-
installCmd: "
|
|
85
|
+
installCmd: "pipx install things-cli",
|
|
91
86
|
installMethod: "pip",
|
|
92
87
|
platforms: ["darwin"]
|
|
93
88
|
};
|
|
@@ -117,10 +112,10 @@ const himalaya = {
|
|
|
117
112
|
installMethod: "brew",
|
|
118
113
|
platforms: ["darwin", "linux"]
|
|
119
114
|
};
|
|
120
|
-
const
|
|
121
|
-
name: "
|
|
122
|
-
command: "
|
|
123
|
-
installCmd: "brew install
|
|
115
|
+
const icalBuddy = {
|
|
116
|
+
name: "ical-buddy",
|
|
117
|
+
command: "icalBuddy",
|
|
118
|
+
installCmd: "brew install ical-buddy",
|
|
124
119
|
installMethod: "brew",
|
|
125
120
|
platforms: ["darwin"]
|
|
126
121
|
};
|
|
@@ -143,17 +138,8 @@ const wacli = {
|
|
|
143
138
|
const slackCli = {
|
|
144
139
|
name: "slack-cli",
|
|
145
140
|
command: "slack",
|
|
146
|
-
installCmd: "
|
|
147
|
-
installMethod: "
|
|
148
|
-
tap: "rockymadden/rockymadden",
|
|
149
|
-
platforms: ["darwin", "linux"]
|
|
150
|
-
};
|
|
151
|
-
const bird = {
|
|
152
|
-
name: "bird",
|
|
153
|
-
command: "bird",
|
|
154
|
-
installCmd: "brew install steipete/tap/bird",
|
|
155
|
-
installMethod: "brew-tap",
|
|
156
|
-
tap: "steipete/tap",
|
|
141
|
+
installCmd: "npm install -g slack-cli",
|
|
142
|
+
installMethod: "npm",
|
|
157
143
|
platforms: [
|
|
158
144
|
"darwin",
|
|
159
145
|
"linux",
|
|
@@ -219,10 +205,11 @@ const sag = {
|
|
|
219
205
|
platforms: ["darwin"]
|
|
220
206
|
};
|
|
221
207
|
const openhue = {
|
|
222
|
-
name: "openhue",
|
|
208
|
+
name: "openhue-cli",
|
|
223
209
|
command: "openhue",
|
|
224
|
-
installCmd: "brew install openhue-cli",
|
|
225
|
-
installMethod: "brew",
|
|
210
|
+
installCmd: "brew install openhue/cli/openhue-cli",
|
|
211
|
+
installMethod: "brew-tap",
|
|
212
|
+
tap: "openhue/cli",
|
|
226
213
|
platforms: [
|
|
227
214
|
"darwin",
|
|
228
215
|
"linux",
|
|
@@ -343,6 +330,24 @@ const jiraCli = {
|
|
|
343
330
|
installMethod: "brew",
|
|
344
331
|
platforms: ["darwin", "linux"]
|
|
345
332
|
};
|
|
333
|
+
const remotion = {
|
|
334
|
+
name: "remotion",
|
|
335
|
+
command: "remotion",
|
|
336
|
+
installCmd: "npm install -g remotion @remotion/cli",
|
|
337
|
+
installMethod: "npm",
|
|
338
|
+
platforms: [
|
|
339
|
+
"darwin",
|
|
340
|
+
"linux",
|
|
341
|
+
"win32"
|
|
342
|
+
]
|
|
343
|
+
};
|
|
344
|
+
const editly = {
|
|
345
|
+
name: "editly",
|
|
346
|
+
command: "editly",
|
|
347
|
+
installCmd: "npm install -g editly",
|
|
348
|
+
installMethod: "npm",
|
|
349
|
+
platforms: ["darwin", "linux"]
|
|
350
|
+
};
|
|
346
351
|
const agentBrowser = {
|
|
347
352
|
name: "agent-browser",
|
|
348
353
|
command: "agent-browser",
|
|
@@ -462,6 +467,17 @@ const aichat = {
|
|
|
462
467
|
"win32"
|
|
463
468
|
]
|
|
464
469
|
};
|
|
470
|
+
const curl = {
|
|
471
|
+
name: "curl",
|
|
472
|
+
command: "curl",
|
|
473
|
+
installCmd: "brew install curl",
|
|
474
|
+
installMethod: "builtin",
|
|
475
|
+
platforms: [
|
|
476
|
+
"darwin",
|
|
477
|
+
"linux",
|
|
478
|
+
"win32"
|
|
479
|
+
]
|
|
480
|
+
};
|
|
465
481
|
const skills = [
|
|
466
482
|
{
|
|
467
483
|
id: "notes",
|
|
@@ -586,12 +602,12 @@ const skills = [
|
|
|
586
602
|
{
|
|
587
603
|
label: "Apple Calendar (macOS)",
|
|
588
604
|
value: "apple",
|
|
589
|
-
tools: [
|
|
605
|
+
tools: [icalBuddy]
|
|
590
606
|
},
|
|
591
607
|
{
|
|
592
608
|
label: "Both",
|
|
593
609
|
value: "both",
|
|
594
|
-
tools: [gogcli,
|
|
610
|
+
tools: [gogcli, icalBuddy]
|
|
595
611
|
}
|
|
596
612
|
]
|
|
597
613
|
},
|
|
@@ -627,23 +643,6 @@ const skills = [
|
|
|
627
643
|
description: "Configure Slack token"
|
|
628
644
|
}]
|
|
629
645
|
},
|
|
630
|
-
{
|
|
631
|
-
id: "social",
|
|
632
|
-
name: "Social / Twitter",
|
|
633
|
-
description: "Post tweets, read timeline, search Twitter/X",
|
|
634
|
-
category: "communication",
|
|
635
|
-
tools: [bird],
|
|
636
|
-
platforms: [
|
|
637
|
-
"darwin",
|
|
638
|
-
"linux",
|
|
639
|
-
"win32"
|
|
640
|
-
],
|
|
641
|
-
authSteps: [{
|
|
642
|
-
tool: "bird",
|
|
643
|
-
command: "bird auth",
|
|
644
|
-
description: "Connect Twitter/X"
|
|
645
|
-
}]
|
|
646
|
-
},
|
|
647
646
|
{
|
|
648
647
|
id: "telegram",
|
|
649
648
|
name: "Telegram Bridge",
|
|
@@ -685,6 +684,18 @@ const skills = [
|
|
|
685
684
|
"win32"
|
|
686
685
|
]
|
|
687
686
|
},
|
|
687
|
+
{
|
|
688
|
+
id: "video-edit",
|
|
689
|
+
name: "Video Editing",
|
|
690
|
+
description: "Programmatic video creation — Remotion (React) and Editly (JSON-based)",
|
|
691
|
+
category: "media",
|
|
692
|
+
tools: [
|
|
693
|
+
remotion,
|
|
694
|
+
editly,
|
|
695
|
+
ffmpeg
|
|
696
|
+
],
|
|
697
|
+
platforms: ["darwin", "linux"]
|
|
698
|
+
},
|
|
688
699
|
{
|
|
689
700
|
id: "screen",
|
|
690
701
|
name: "Screen & Vision",
|
|
@@ -738,6 +749,18 @@ const skills = [
|
|
|
738
749
|
tools: [blucli],
|
|
739
750
|
platforms: ["darwin"]
|
|
740
751
|
},
|
|
752
|
+
{
|
|
753
|
+
id: "weather",
|
|
754
|
+
name: "Weather",
|
|
755
|
+
description: "Forecasts and conditions — current, hourly, multi-day via wttr.in",
|
|
756
|
+
category: "research",
|
|
757
|
+
tools: [curl],
|
|
758
|
+
platforms: [
|
|
759
|
+
"darwin",
|
|
760
|
+
"linux",
|
|
761
|
+
"win32"
|
|
762
|
+
]
|
|
763
|
+
},
|
|
741
764
|
{
|
|
742
765
|
id: "research",
|
|
743
766
|
name: "Web Research",
|
|
@@ -850,6 +873,14 @@ const skills = [
|
|
|
850
873
|
description: "Configure Jira instance"
|
|
851
874
|
}]
|
|
852
875
|
},
|
|
876
|
+
{
|
|
877
|
+
id: "schedule",
|
|
878
|
+
name: "Smart Scheduling",
|
|
879
|
+
description: "Automate recurring Claude tasks with built-in cost control and Telegram delivery",
|
|
880
|
+
category: "automation",
|
|
881
|
+
tools: [],
|
|
882
|
+
platforms: ["darwin", "linux"]
|
|
883
|
+
},
|
|
853
884
|
{
|
|
854
885
|
id: "briefing",
|
|
855
886
|
name: "Daily Briefing",
|
|
@@ -903,6 +934,30 @@ const skills = [
|
|
|
903
934
|
tools: [lunchyGo],
|
|
904
935
|
platforms: ["darwin"]
|
|
905
936
|
},
|
|
937
|
+
{
|
|
938
|
+
id: "clipboard",
|
|
939
|
+
name: "Clipboard",
|
|
940
|
+
description: "Copy, paste, and transform clipboard content",
|
|
941
|
+
category: "system",
|
|
942
|
+
tools: [],
|
|
943
|
+
platforms: ["darwin", "linux"]
|
|
944
|
+
},
|
|
945
|
+
{
|
|
946
|
+
id: "contacts",
|
|
947
|
+
name: "Contacts",
|
|
948
|
+
description: "Search and look up contacts from macOS Address Book",
|
|
949
|
+
category: "system",
|
|
950
|
+
tools: [],
|
|
951
|
+
platforms: ["darwin"]
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
id: "timer",
|
|
955
|
+
name: "Timer & Pomodoro",
|
|
956
|
+
description: "Countdown timers, alarms, and pomodoro with native notifications",
|
|
957
|
+
category: "system",
|
|
958
|
+
tools: [terminalNotifier],
|
|
959
|
+
platforms: ["darwin"]
|
|
960
|
+
},
|
|
906
961
|
{
|
|
907
962
|
id: "system",
|
|
908
963
|
name: "System Control",
|
|
@@ -1026,12 +1081,16 @@ const presets = [
|
|
|
1026
1081
|
{
|
|
1027
1082
|
id: "essentials",
|
|
1028
1083
|
name: "Essentials",
|
|
1029
|
-
description: "Email, calendar, notes, music, browser, system",
|
|
1084
|
+
description: "Email, calendar, notes, music, weather, clipboard, browser, system",
|
|
1030
1085
|
skillIds: [
|
|
1031
1086
|
"email",
|
|
1032
1087
|
"calendar",
|
|
1033
1088
|
"notes",
|
|
1034
1089
|
"music",
|
|
1090
|
+
"weather",
|
|
1091
|
+
"clipboard",
|
|
1092
|
+
"contacts",
|
|
1093
|
+
"timer",
|
|
1035
1094
|
"browser",
|
|
1036
1095
|
"system",
|
|
1037
1096
|
"notify"
|
|
@@ -1174,163 +1233,6 @@ function isToolInstalled(command) {
|
|
|
1174
1233
|
return commandExists(command);
|
|
1175
1234
|
}
|
|
1176
1235
|
|
|
1177
|
-
//#endregion
|
|
1178
|
-
//#region src/core/branding.ts
|
|
1179
|
-
const accent = chalk.hex("#b4783c");
|
|
1180
|
-
const subtle = chalk.hex("#8a5a2a");
|
|
1181
|
-
const dim = chalk.dim;
|
|
1182
|
-
const bold = chalk.bold;
|
|
1183
|
-
const pawClr = chalk.hex("#b4783c");
|
|
1184
|
-
const PAW_ART = [
|
|
1185
|
-
" ▃▅",
|
|
1186
|
-
" ▁██▁ ▄█▁",
|
|
1187
|
-
" ▁▁▂▆▇██▃ ▅█▆",
|
|
1188
|
-
" ▅▆█▇▆▄███▆▂ ▁▂▆▇██▇▅▁",
|
|
1189
|
-
" ▁▃█▆▁ ▄███▄▁ ▆▇▇▅▄▄███▇▃▁",
|
|
1190
|
-
" ▁▃█▄ ▁████▂ ▁▅█▃ ▁▃████▂",
|
|
1191
|
-
" ▄█▇▁ ▂████▄▁ ▁▃█▇ ▁████▅▂",
|
|
1192
|
-
" ▅█▆ ▂█████▁ ▄█▇▁ ▂████▃",
|
|
1193
|
-
" ▂▁ ▆█▆ ▂█████▁ ▄█▇ ▁▂████▂",
|
|
1194
|
-
" ▁▅█▂ ▆█▆▁ ▁▃▇████▆▁ ▄█▇ ▂▄█████▂",
|
|
1195
|
-
" ▁██▄▂ ▂██▇▆▃▄▇██████ ▄██▄▇▇▃▃▆█████▁",
|
|
1196
|
-
" ▂█████▇▅▂▁ ▁▄▇█████████▆▂ ▃▇████████████▁ ▁▂▁",
|
|
1197
|
-
" ▄█▇▃ ▁███▆▁ ▁▃▆▇▇▇▇▃▄▂ ▂▆█████████▅ ▁▇█▂",
|
|
1198
|
-
" ▃▆▄▁ ▅███▆▃ ▄▄▅▅▅▅▂▁ ▂▄▃▄██▂",
|
|
1199
|
-
" ▆█▄ ▃████▄ ▁▄▄██████▄▃▃▂ ▁▂▆▇▇▆████▇▁",
|
|
1200
|
-
" ▆█▄ ▂████▄ ▂▅▇▇▅▅▁▃▅█▇████▆▂ ▅▇▇▅▄▁ ▂▆████▂",
|
|
1201
|
-
" ▇█▄ ▅████▄ ▇█▄ ▁▃▇▆████▇▂ ▂▃█▇▃ ▁ ▃███▂",
|
|
1202
|
-
" ▇██▅ ▂▆████▄▁ ▆█▄▁ ▁ ▅▄▅█████▅ ▆█▆▂ ▃▆███▁",
|
|
1203
|
-
" ▁████████████▃ ▂▇▆▂ ▄█████▆ ▃██▂ ▂▆███▆",
|
|
1204
|
-
" ▃█████████▅▂ ██▂ ▄█████▆ ▅██▁ ▁████▇",
|
|
1205
|
-
" ▂▄▄▄▄▄▄ ▇█▃▁ ▁▅█████▅ ▃██▄▇▆▁▁▄▇████▆",
|
|
1206
|
-
" ▁▁▁▇█▂▁ ▁ ▄██████▆▂ ▇███████████▅▁",
|
|
1207
|
-
" ▁▁▄▅▆██▅▂ ▁▁ ▄███████▄ ▄▅██████▆▅▂",
|
|
1208
|
-
" ▄██▆▅▄▂▁ ▁ ▃▆██████▇▄▂ ▁▁▁▁▁▁▁",
|
|
1209
|
-
" ▂██▄▁▁ ▁▄▅██████▇▆▂▁",
|
|
1210
|
-
" ▃▇▇▂ ▁▁ ▁ ▃▇██████▇▃▁",
|
|
1211
|
-
" ▁▄█▆ ▁ ▁▆█▇█████▆▂",
|
|
1212
|
-
" ▄██▇ ▃▅███████▅▁",
|
|
1213
|
-
" ▄██▇▁ ▂▂▂▁▁▂▆▇▇▇▇▇▃▁ ▄▆▄▂▇█████▃",
|
|
1214
|
-
" ▃▇██▇▃ ▁▂▆▆██▆▇█████████▆▄▁▁ ▁▁▁▄▇██████▃",
|
|
1215
|
-
" ▄█████▆▆▇███████████████████▆▃▁ ▁▄▇███████▃",
|
|
1216
|
-
" ▁▅█████████████████████████████▅█████████▄▁",
|
|
1217
|
-
" ▁▂▆█████████████████████████████████████▂",
|
|
1218
|
-
" ▁▂▆▆▆▆▆▆▃▂▂▂▂▂▂▂▂▂▂▂▂▂▅▆███████████▇▆▁",
|
|
1219
|
-
" ▃▃▇▇▇▇▇▇▇▃▃"
|
|
1220
|
-
];
|
|
1221
|
-
const PAW_ROWS = PAW_ART.length;
|
|
1222
|
-
function sleep(ms) {
|
|
1223
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
1224
|
-
}
|
|
1225
|
-
function renderBox(title, subtitle) {
|
|
1226
|
-
const boxW = 48;
|
|
1227
|
-
const center = (s, w) => {
|
|
1228
|
-
const pad = w - s.length;
|
|
1229
|
-
const left = Math.floor(pad / 2);
|
|
1230
|
-
return " ".repeat(left) + s + " ".repeat(pad - left);
|
|
1231
|
-
};
|
|
1232
|
-
const margin = " ";
|
|
1233
|
-
const lines = [
|
|
1234
|
-
pawClr(margin + "┌" + "─".repeat(boxW) + "┐"),
|
|
1235
|
-
pawClr(margin + "│" + " ".repeat(boxW) + "│"),
|
|
1236
|
-
pawClr(margin + "│" + center(title, boxW) + "│"),
|
|
1237
|
-
dim(margin + "│" + center(subtitle, boxW) + "│"),
|
|
1238
|
-
pawClr(margin + "│" + " ".repeat(boxW) + "│"),
|
|
1239
|
-
pawClr(margin + "└" + "─".repeat(boxW) + "┘")
|
|
1240
|
-
];
|
|
1241
|
-
return lines.join("\n");
|
|
1242
|
-
}
|
|
1243
|
-
const MOOD_HEX = {
|
|
1244
|
-
wave: "#b4783c",
|
|
1245
|
-
think: "#b4783c",
|
|
1246
|
-
happy: "#b4783c",
|
|
1247
|
-
work: "#9a6832",
|
|
1248
|
-
done: "#c88a48",
|
|
1249
|
-
warn: "#dca03c"
|
|
1250
|
-
};
|
|
1251
|
-
function pawColor(mood) {
|
|
1252
|
-
return chalk.hex(MOOD_HEX[mood]);
|
|
1253
|
-
}
|
|
1254
|
-
function renderPaw(color) {
|
|
1255
|
-
return PAW_ART.map((line) => color(line)).join("\n");
|
|
1256
|
-
}
|
|
1257
|
-
/**
|
|
1258
|
-
* Animated banner: fade in paw → pulse → title box.
|
|
1259
|
-
*/
|
|
1260
|
-
async function showBanner() {
|
|
1261
|
-
process.stdout.write("\x1B[?25l");
|
|
1262
|
-
process.stdout.write(renderPaw(chalk.hex("#3d2810")) + "\n");
|
|
1263
|
-
await sleep(60);
|
|
1264
|
-
process.stdout.write(`\x1B[${PAW_ROWS}A\x1B[J`);
|
|
1265
|
-
process.stdout.write(renderPaw(chalk.hex("#7a501e")) + "\n");
|
|
1266
|
-
await sleep(60);
|
|
1267
|
-
process.stdout.write(`\x1B[${PAW_ROWS}A\x1B[J`);
|
|
1268
|
-
process.stdout.write(renderPaw(pawClr) + "\n");
|
|
1269
|
-
await sleep(60);
|
|
1270
|
-
process.stdout.write(`\x1B[${PAW_ROWS}A\x1B[J`);
|
|
1271
|
-
process.stdout.write(renderPaw(chalk.hex("#d4984c")) + "\n");
|
|
1272
|
-
await sleep(80);
|
|
1273
|
-
process.stdout.write(`\x1B[${PAW_ROWS}A\x1B[J`);
|
|
1274
|
-
process.stdout.write(renderPaw(pawClr) + "\n");
|
|
1275
|
-
process.stdout.write("\x1B[?25h");
|
|
1276
|
-
console.log("");
|
|
1277
|
-
console.log(renderBox("O P E N P A W", "Personal Assistant Wizard for Claude Code"));
|
|
1278
|
-
console.log("");
|
|
1279
|
-
}
|
|
1280
|
-
/**
|
|
1281
|
-
* Show paw between wizard steps — mood-colored, brief flash, then clears.
|
|
1282
|
-
*/
|
|
1283
|
-
async function pawStep(mood, message) {
|
|
1284
|
-
const color = pawColor(mood);
|
|
1285
|
-
process.stdout.write(renderPaw(color) + "\n");
|
|
1286
|
-
if (message) console.log(` ${accent(message)}`);
|
|
1287
|
-
await sleep(300);
|
|
1288
|
-
const lines = PAW_ROWS + (message ? 1 : 0);
|
|
1289
|
-
process.stdout.write(`\x1B[${lines}A\x1B[J`);
|
|
1290
|
-
}
|
|
1291
|
-
/**
|
|
1292
|
-
* Inline pulse indicator for quick transitions.
|
|
1293
|
-
*/
|
|
1294
|
-
async function pawPulse(mood, message) {
|
|
1295
|
-
if (!message) return;
|
|
1296
|
-
const line = ` ${accent("◉")} ${subtle(message)}`;
|
|
1297
|
-
for (let i = 0; i < 3; i++) {
|
|
1298
|
-
if (i > 0) process.stdout.write("\x1B[1A");
|
|
1299
|
-
const s = i % 2 === 0 ? bold : dim;
|
|
1300
|
-
process.stdout.write(`\x1B[2K${s(line)}\n`);
|
|
1301
|
-
await sleep(80);
|
|
1302
|
-
}
|
|
1303
|
-
process.stdout.write(`\x1B[1A\x1B[2K${line}\n`);
|
|
1304
|
-
}
|
|
1305
|
-
/**
|
|
1306
|
-
* Mini one-liner.
|
|
1307
|
-
*/
|
|
1308
|
-
function showMini() {
|
|
1309
|
-
console.log(accent(" ◉ openpaw") + dim(" — Personal Assistant Wizard for Claude Code"));
|
|
1310
|
-
}
|
|
1311
|
-
/**
|
|
1312
|
-
* Puppy disclaimer about --dangerously-skip-permissions.
|
|
1313
|
-
*/
|
|
1314
|
-
function showPuppyDisclaimer() {
|
|
1315
|
-
console.log("");
|
|
1316
|
-
console.log(pawClr(" /\\_/\\"));
|
|
1317
|
-
console.log(pawClr(" ( o.o )") + ` ${bold("WOOF! One important sniff...")}`);
|
|
1318
|
-
console.log(pawClr(" > ^ <"));
|
|
1319
|
-
console.log("");
|
|
1320
|
-
console.log(` ${accent("You're about to let Claude off the leash!")}`);
|
|
1321
|
-
console.log(dim(" (--dangerously-skip-permissions)"));
|
|
1322
|
-
console.log("");
|
|
1323
|
-
console.log(" This lets Claude run commands without asking each time.");
|
|
1324
|
-
console.log(" It's how your assistant actually gets things done —");
|
|
1325
|
-
console.log(" checking email, playing music, managing files.");
|
|
1326
|
-
console.log("");
|
|
1327
|
-
console.log(dim(" OpenPaw's safety hooks still block the dangerous stuff"));
|
|
1328
|
-
console.log(dim(" (mass deletes, credential leaks, etc)."));
|
|
1329
|
-
console.log("");
|
|
1330
|
-
console.log(dim(" You can always run 'claude' normally without this."));
|
|
1331
|
-
console.log("");
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
1236
|
//#endregion
|
|
1335
1237
|
//#region src/core/installer.ts
|
|
1336
1238
|
function getMissingTools(tools) {
|
|
@@ -1461,141 +1363,27 @@ function removeSafetyHooks() {
|
|
|
1461
1363
|
}
|
|
1462
1364
|
}
|
|
1463
1365
|
|
|
1464
|
-
//#endregion
|
|
1465
|
-
//#region src/core/mcp.ts
|
|
1466
|
-
const mcpServers = [
|
|
1467
|
-
{
|
|
1468
|
-
id: "filesystem",
|
|
1469
|
-
name: "Filesystem",
|
|
1470
|
-
description: "Read, write, search, and manage files with advanced operations",
|
|
1471
|
-
command: "npx",
|
|
1472
|
-
args: [
|
|
1473
|
-
"-y",
|
|
1474
|
-
"@modelcontextprotocol/server-filesystem",
|
|
1475
|
-
os$6.homedir()
|
|
1476
|
-
],
|
|
1477
|
-
category: "system"
|
|
1478
|
-
},
|
|
1479
|
-
{
|
|
1480
|
-
id: "fetch",
|
|
1481
|
-
name: "Fetch",
|
|
1482
|
-
description: "Fetch and convert web content to markdown for analysis",
|
|
1483
|
-
command: "npx",
|
|
1484
|
-
args: ["-y", "@modelcontextprotocol/server-fetch"],
|
|
1485
|
-
category: "research"
|
|
1486
|
-
},
|
|
1487
|
-
{
|
|
1488
|
-
id: "memory",
|
|
1489
|
-
name: "Memory (KG)",
|
|
1490
|
-
description: "Persistent knowledge graph memory — entities, relations, observations",
|
|
1491
|
-
command: "npx",
|
|
1492
|
-
args: ["-y", "@modelcontextprotocol/server-memory"],
|
|
1493
|
-
category: "productivity"
|
|
1494
|
-
},
|
|
1495
|
-
{
|
|
1496
|
-
id: "github",
|
|
1497
|
-
name: "GitHub",
|
|
1498
|
-
description: "Repos, PRs, issues, branches, file operations via GitHub API",
|
|
1499
|
-
command: "npx",
|
|
1500
|
-
args: ["-y", "@modelcontextprotocol/server-github"],
|
|
1501
|
-
env: { GITHUB_PERSONAL_ACCESS_TOKEN: "" },
|
|
1502
|
-
envPlaceholders: { GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_your_token_here" },
|
|
1503
|
-
category: "developer"
|
|
1504
|
-
},
|
|
1505
|
-
{
|
|
1506
|
-
id: "slack",
|
|
1507
|
-
name: "Slack",
|
|
1508
|
-
description: "Read/send Slack messages, manage channels, users, reactions",
|
|
1509
|
-
command: "npx",
|
|
1510
|
-
args: ["-y", "@modelcontextprotocol/server-slack"],
|
|
1511
|
-
env: {
|
|
1512
|
-
SLACK_BOT_TOKEN: "",
|
|
1513
|
-
SLACK_TEAM_ID: ""
|
|
1514
|
-
},
|
|
1515
|
-
envPlaceholders: {
|
|
1516
|
-
SLACK_BOT_TOKEN: "xoxb-your-token",
|
|
1517
|
-
SLACK_TEAM_ID: "T00000000"
|
|
1518
|
-
},
|
|
1519
|
-
category: "communication"
|
|
1520
|
-
},
|
|
1521
|
-
{
|
|
1522
|
-
id: "google-drive",
|
|
1523
|
-
name: "Google Drive",
|
|
1524
|
-
description: "Search and read Google Drive files, Docs, Sheets",
|
|
1525
|
-
command: "npx",
|
|
1526
|
-
args: ["-y", "@modelcontextprotocol/server-gdrive"],
|
|
1527
|
-
category: "productivity"
|
|
1528
|
-
},
|
|
1529
|
-
{
|
|
1530
|
-
id: "postgres",
|
|
1531
|
-
name: "PostgreSQL",
|
|
1532
|
-
description: "Query PostgreSQL databases with read-only access",
|
|
1533
|
-
command: "npx",
|
|
1534
|
-
args: ["-y", "@modelcontextprotocol/server-postgres"],
|
|
1535
|
-
env: { POSTGRES_CONNECTION_STRING: "" },
|
|
1536
|
-
envPlaceholders: { POSTGRES_CONNECTION_STRING: "postgresql://user:pass@localhost/db" },
|
|
1537
|
-
category: "developer"
|
|
1538
|
-
},
|
|
1539
|
-
{
|
|
1540
|
-
id: "brave-search",
|
|
1541
|
-
name: "Brave Search",
|
|
1542
|
-
description: "Web and local search using Brave Search API",
|
|
1543
|
-
command: "npx",
|
|
1544
|
-
args: ["-y", "@modelcontextprotocol/server-brave-search"],
|
|
1545
|
-
env: { BRAVE_API_KEY: "" },
|
|
1546
|
-
envPlaceholders: { BRAVE_API_KEY: "your_api_key" },
|
|
1547
|
-
category: "research"
|
|
1548
|
-
},
|
|
1549
|
-
{
|
|
1550
|
-
id: "puppeteer",
|
|
1551
|
-
name: "Puppeteer",
|
|
1552
|
-
description: "Browser automation — navigate, screenshot, interact with web pages",
|
|
1553
|
-
command: "npx",
|
|
1554
|
-
args: ["-y", "@modelcontextprotocol/server-puppeteer"],
|
|
1555
|
-
category: "automation"
|
|
1556
|
-
},
|
|
1557
|
-
{
|
|
1558
|
-
id: "sequential-thinking",
|
|
1559
|
-
name: "Sequential Thinking",
|
|
1560
|
-
description: "Step-by-step reasoning and problem-solving tool",
|
|
1561
|
-
command: "npx",
|
|
1562
|
-
args: ["-y", "@modelcontextprotocol/server-sequential-thinking"],
|
|
1563
|
-
category: "research"
|
|
1564
|
-
}
|
|
1565
|
-
];
|
|
1566
|
-
function installMcpServer(server, envValues) {
|
|
1567
|
-
const settings = readSettings();
|
|
1568
|
-
if (!settings.mcpServers) settings.mcpServers = {};
|
|
1569
|
-
const mcpSection = settings.mcpServers;
|
|
1570
|
-
const config = {
|
|
1571
|
-
command: server.command,
|
|
1572
|
-
args: server.args
|
|
1573
|
-
};
|
|
1574
|
-
if (server.env) {
|
|
1575
|
-
const env = {};
|
|
1576
|
-
for (const [key, defaultVal] of Object.entries(server.env)) env[key] = envValues?.[key] ?? defaultVal;
|
|
1577
|
-
config.env = env;
|
|
1578
|
-
}
|
|
1579
|
-
mcpSection[server.id] = config;
|
|
1580
|
-
writeSettings(settings);
|
|
1581
|
-
return true;
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
1366
|
//#endregion
|
|
1585
1367
|
//#region src/core/soul.ts
|
|
1586
1368
|
function getSoulPath() {
|
|
1587
|
-
return path$
|
|
1369
|
+
return path$3.join(os$4.homedir(), ".claude", "SOUL.md");
|
|
1588
1370
|
}
|
|
1589
1371
|
function soulExists() {
|
|
1590
|
-
return fs$
|
|
1372
|
+
return fs$3.existsSync(getSoulPath());
|
|
1591
1373
|
}
|
|
1592
1374
|
async function soulQuestionnaire() {
|
|
1593
1375
|
const name = await p$11.text({
|
|
1594
|
-
message: "What should
|
|
1376
|
+
message: "What should your assistant call you?",
|
|
1595
1377
|
placeholder: "Your name or nickname",
|
|
1596
1378
|
validate: (v) => v.length === 0 ? "Name cannot be empty" : void 0
|
|
1597
1379
|
});
|
|
1598
1380
|
if (p$11.isCancel(name)) return null;
|
|
1381
|
+
const botName = await p$11.text({
|
|
1382
|
+
message: "Name your assistant:",
|
|
1383
|
+
placeholder: "Paw",
|
|
1384
|
+
defaultValue: "Paw"
|
|
1385
|
+
});
|
|
1386
|
+
if (p$11.isCancel(botName)) return null;
|
|
1599
1387
|
const tone = await p$11.select({
|
|
1600
1388
|
message: "Communication style?",
|
|
1601
1389
|
options: [
|
|
@@ -1652,6 +1440,7 @@ async function soulQuestionnaire() {
|
|
|
1652
1440
|
const extras = extrasResult.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1653
1441
|
return {
|
|
1654
1442
|
name,
|
|
1443
|
+
botName: botName || "Paw",
|
|
1655
1444
|
tone,
|
|
1656
1445
|
verbosity,
|
|
1657
1446
|
proactive,
|
|
@@ -1672,11 +1461,12 @@ function writeSoul(config) {
|
|
|
1672
1461
|
const lines = [
|
|
1673
1462
|
"# SOUL.md — OpenPaw Personality",
|
|
1674
1463
|
"",
|
|
1675
|
-
`You are ${config.name}'s personal assistant, powered by OpenPaw.`,
|
|
1464
|
+
`You are **${config.botName}**, ${config.name}'s personal assistant, powered by OpenPaw.`,
|
|
1676
1465
|
"",
|
|
1677
1466
|
"## Identity",
|
|
1678
1467
|
"",
|
|
1679
|
-
`- **
|
|
1468
|
+
`- **Your name**: ${config.botName} — use this when introducing yourself or signing off`,
|
|
1469
|
+
`- **User's name**: Call the user "${config.name}"`,
|
|
1680
1470
|
`- **Role**: Personal assistant with access to system tools, apps, and services`,
|
|
1681
1471
|
"- **Source**: Configured by OpenPaw (open-source, no daemon, free forever)",
|
|
1682
1472
|
"",
|
|
@@ -1692,15 +1482,16 @@ function writeSoul(config) {
|
|
|
1692
1482
|
for (const extra of config.extras) lines.push(`- ${extra}`);
|
|
1693
1483
|
lines.push("");
|
|
1694
1484
|
}
|
|
1695
|
-
lines.push("## PAW MODE", "", "You are running in PAW MODE — full personal assistant mode powered by OpenPaw.",
|
|
1485
|
+
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!').`, "");
|
|
1696
1486
|
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", "");
|
|
1697
|
-
const soulDir = path$
|
|
1698
|
-
if (!fs$
|
|
1699
|
-
fs$
|
|
1487
|
+
const soulDir = path$3.dirname(getSoulPath());
|
|
1488
|
+
if (!fs$3.existsSync(soulDir)) fs$3.mkdirSync(soulDir, { recursive: true });
|
|
1489
|
+
fs$3.writeFileSync(getSoulPath(), lines.join("\n"), "utf-8");
|
|
1700
1490
|
}
|
|
1701
1491
|
function showSoulSummary(config) {
|
|
1702
1492
|
const lines = [
|
|
1703
|
-
`${accent("
|
|
1493
|
+
`${accent("You:")} ${config.name}`,
|
|
1494
|
+
`${accent("Assistant:")} ${config.botName}`,
|
|
1704
1495
|
`${accent("Tone:")} ${config.tone}`,
|
|
1705
1496
|
`${accent("Verbosity:")} ${config.verbosity}`,
|
|
1706
1497
|
`${accent("Proactive:")} ${config.proactive ? "yes" : "no"}`
|
|
@@ -1711,7 +1502,7 @@ function showSoulSummary(config) {
|
|
|
1711
1502
|
|
|
1712
1503
|
//#endregion
|
|
1713
1504
|
//#region src/core/memory.ts
|
|
1714
|
-
const MEMORY_DIR = path$
|
|
1505
|
+
const MEMORY_DIR = path$2.join(os$3.homedir(), ".claude", "memory");
|
|
1715
1506
|
const INITIAL_MEMORY = `# Memory
|
|
1716
1507
|
|
|
1717
1508
|
## User
|
|
@@ -1724,12 +1515,12 @@ const INITIAL_MEMORY = `# Memory
|
|
|
1724
1515
|
- (Claude will track projects mentioned in conversation)
|
|
1725
1516
|
`;
|
|
1726
1517
|
function setupMemory(userName) {
|
|
1727
|
-
if (!fs$
|
|
1728
|
-
const memoryPath = path$
|
|
1729
|
-
if (!fs$
|
|
1518
|
+
if (!fs$2.existsSync(MEMORY_DIR)) fs$2.mkdirSync(MEMORY_DIR, { recursive: true });
|
|
1519
|
+
const memoryPath = path$2.join(MEMORY_DIR, "MEMORY.md");
|
|
1520
|
+
if (!fs$2.existsSync(memoryPath)) {
|
|
1730
1521
|
let content = INITIAL_MEMORY;
|
|
1731
1522
|
if (userName) content = content.replace("(will be filled in as we learn)", userName);
|
|
1732
|
-
fs$
|
|
1523
|
+
fs$2.writeFileSync(memoryPath, content, "utf-8");
|
|
1733
1524
|
}
|
|
1734
1525
|
const topicFiles = [
|
|
1735
1526
|
"people.md",
|
|
@@ -1738,265 +1529,11 @@ function setupMemory(userName) {
|
|
|
1738
1529
|
"journal.md"
|
|
1739
1530
|
];
|
|
1740
1531
|
for (const file of topicFiles) {
|
|
1741
|
-
const filePath = path$
|
|
1742
|
-
if (!fs$
|
|
1532
|
+
const filePath = path$2.join(MEMORY_DIR, file);
|
|
1533
|
+
if (!fs$2.existsSync(filePath)) {
|
|
1743
1534
|
const title = file.replace(".md", "");
|
|
1744
|
-
fs$
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
//#endregion
|
|
1750
|
-
//#region src/core/telegram.ts
|
|
1751
|
-
const CONFIG_DIR = path$2.join(os$3.homedir(), ".config", "openpaw");
|
|
1752
|
-
const CONFIG_PATH = path$2.join(CONFIG_DIR, "telegram.json");
|
|
1753
|
-
function writeTelegramConfig(config) {
|
|
1754
|
-
fs$2.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
1755
|
-
fs$2.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
1756
|
-
fs$2.chmodSync(CONFIG_PATH, 384);
|
|
1757
|
-
}
|
|
1758
|
-
function readTelegramConfig() {
|
|
1759
|
-
try {
|
|
1760
|
-
const raw = fs$2.readFileSync(CONFIG_PATH, "utf-8");
|
|
1761
|
-
return JSON.parse(raw);
|
|
1762
|
-
} catch {
|
|
1763
|
-
return null;
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
|
-
function telegramConfigExists() {
|
|
1767
|
-
return fs$2.existsSync(CONFIG_PATH);
|
|
1768
|
-
}
|
|
1769
|
-
async function telegramQuestionnaire() {
|
|
1770
|
-
p$10.log.info(dim("Let's set up your Telegram bot! You'll need:"));
|
|
1771
|
-
p$10.log.info(` ${accent("1.")} Message ${bold("@BotFather")} on Telegram → /newbot`);
|
|
1772
|
-
p$10.log.info(` ${accent("2.")} Message ${bold("@userinfobot")} to get your user ID`);
|
|
1773
|
-
console.log("");
|
|
1774
|
-
const botToken = await p$10.text({
|
|
1775
|
-
message: "Paste your bot token (from @BotFather):",
|
|
1776
|
-
placeholder: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
|
|
1777
|
-
validate: (v) => {
|
|
1778
|
-
if (v.length === 0) return "Bot token is required";
|
|
1779
|
-
if (!v.includes(":")) return "That doesn't look like a bot token (should contain ':')";
|
|
1780
|
-
return void 0;
|
|
1781
|
-
}
|
|
1782
|
-
});
|
|
1783
|
-
if (p$10.isCancel(botToken)) return null;
|
|
1784
|
-
const userId = await p$10.text({
|
|
1785
|
-
message: "Your Telegram user ID (from @userinfobot):",
|
|
1786
|
-
placeholder: "123456789",
|
|
1787
|
-
validate: (v) => {
|
|
1788
|
-
if (v.length === 0) return "User ID is required";
|
|
1789
|
-
if (!/^\d+$/.test(v)) return "User ID should be a number";
|
|
1790
|
-
return void 0;
|
|
1791
|
-
}
|
|
1792
|
-
});
|
|
1793
|
-
if (p$10.isCancel(userId)) return null;
|
|
1794
|
-
return {
|
|
1795
|
-
botToken,
|
|
1796
|
-
allowedUserIds: [userId.trim()],
|
|
1797
|
-
workspaceDir: os$3.homedir(),
|
|
1798
|
-
model: "sonnet",
|
|
1799
|
-
skills: []
|
|
1800
|
-
};
|
|
1801
|
-
}
|
|
1802
|
-
const sessions = new Map();
|
|
1803
|
-
const MODEL_MAP = {
|
|
1804
|
-
sonnet: "claude-sonnet-4-5-20250514",
|
|
1805
|
-
opus: "claude-opus-4-6",
|
|
1806
|
-
haiku: "claude-haiku-4-5-20251001"
|
|
1807
|
-
};
|
|
1808
|
-
function getModelId(shortName) {
|
|
1809
|
-
return MODEL_MAP[shortName] || MODEL_MAP.sonnet;
|
|
1810
|
-
}
|
|
1811
|
-
async function startTelegramBot(config) {
|
|
1812
|
-
const bot = new Bot(config.botToken);
|
|
1813
|
-
bot.use(hydrate());
|
|
1814
|
-
const allowedIds = new Set(config.allowedUserIds.map(Number));
|
|
1815
|
-
let currentModel = config.model || "sonnet";
|
|
1816
|
-
bot.use(async (ctx, next) => {
|
|
1817
|
-
if (!ctx.from || !allowedIds.has(ctx.from.id)) {
|
|
1818
|
-
await ctx.reply("Woof! I don't know you. Unauthorized. 🐾");
|
|
1819
|
-
return;
|
|
1820
|
-
}
|
|
1821
|
-
await next();
|
|
1822
|
-
});
|
|
1823
|
-
const installedSkills = listInstalledSkills();
|
|
1824
|
-
const skillCommands = installedSkills.filter((id) => id !== "core" && id !== "memory").map((id) => ({
|
|
1825
|
-
command: id,
|
|
1826
|
-
description: `Use the ${id} skill`
|
|
1827
|
-
}));
|
|
1828
|
-
const allCommands = [
|
|
1829
|
-
{
|
|
1830
|
-
command: "start",
|
|
1831
|
-
description: "Start the bot"
|
|
1832
|
-
},
|
|
1833
|
-
{
|
|
1834
|
-
command: "model",
|
|
1835
|
-
description: "Switch Claude model (sonnet/opus/haiku)"
|
|
1836
|
-
},
|
|
1837
|
-
{
|
|
1838
|
-
command: "skills",
|
|
1839
|
-
description: "List installed skills"
|
|
1840
|
-
},
|
|
1841
|
-
{
|
|
1842
|
-
command: "stop",
|
|
1843
|
-
description: "Cancel current operation"
|
|
1844
|
-
},
|
|
1845
|
-
{
|
|
1846
|
-
command: "clear",
|
|
1847
|
-
description: "Reset conversation"
|
|
1848
|
-
},
|
|
1849
|
-
...skillCommands
|
|
1850
|
-
];
|
|
1851
|
-
try {
|
|
1852
|
-
await bot.api.setMyCommands(allCommands);
|
|
1853
|
-
} catch {}
|
|
1854
|
-
bot.command("start", async (ctx) => {
|
|
1855
|
-
const skills$1 = installedSkills.filter((id) => id !== "core" && id !== "memory");
|
|
1856
|
-
await ctx.reply(`*PAW MODE active* 🐾
|
|
1857
|
-
|
|
1858
|
-
I'm your personal assistant, powered by OpenPaw.
|
|
1859
|
-
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" });
|
|
1860
|
-
});
|
|
1861
|
-
bot.command("model", async (ctx) => {
|
|
1862
|
-
const arg = ctx.match?.trim().toLowerCase();
|
|
1863
|
-
if (!arg || ![
|
|
1864
|
-
"sonnet",
|
|
1865
|
-
"opus",
|
|
1866
|
-
"haiku"
|
|
1867
|
-
].includes(arg)) {
|
|
1868
|
-
await ctx.reply(`Current model: \`${currentModel}\`\n\nSwitch with:\n/model sonnet\n/model opus\n/model haiku`, { parse_mode: "Markdown" });
|
|
1869
|
-
return;
|
|
1870
|
-
}
|
|
1871
|
-
currentModel = arg;
|
|
1872
|
-
config.model = arg;
|
|
1873
|
-
writeTelegramConfig(config);
|
|
1874
|
-
await ctx.reply(`Model switched to \`${currentModel}\` 🐾`, { parse_mode: "Markdown" });
|
|
1875
|
-
});
|
|
1876
|
-
bot.command("skills", async (ctx) => {
|
|
1877
|
-
const skills$1 = installedSkills.filter((id) => id !== "core" && id !== "memory");
|
|
1878
|
-
if (skills$1.length === 0) {
|
|
1879
|
-
await ctx.reply("No skills installed yet. Run `openpaw setup` first! 🐾");
|
|
1880
|
-
return;
|
|
1881
|
-
}
|
|
1882
|
-
const list = skills$1.map((s) => `• /${s}`).join("\n");
|
|
1883
|
-
await ctx.reply(`*Installed skills:*\n\n${list}`, { parse_mode: "Markdown" });
|
|
1884
|
-
});
|
|
1885
|
-
bot.command("stop", async (ctx) => {
|
|
1886
|
-
const userId = ctx.from.id;
|
|
1887
|
-
const session = sessions.get(userId);
|
|
1888
|
-
if (session?.controller) {
|
|
1889
|
-
session.controller.abort();
|
|
1890
|
-
sessions.delete(userId);
|
|
1891
|
-
await ctx.reply("Operation cancelled. 🐾");
|
|
1892
|
-
} else await ctx.reply("Nothing running right now. 🐾");
|
|
1893
|
-
});
|
|
1894
|
-
bot.command("clear", async (ctx) => {
|
|
1895
|
-
const userId = ctx.from.id;
|
|
1896
|
-
sessions.delete(userId);
|
|
1897
|
-
await ctx.reply("Conversation cleared! Fresh start. 🐾");
|
|
1898
|
-
});
|
|
1899
|
-
for (const skillId of installedSkills) {
|
|
1900
|
-
if (skillId === "core" || skillId === "memory") continue;
|
|
1901
|
-
bot.command(skillId, async (ctx) => {
|
|
1902
|
-
const args = ctx.match || "";
|
|
1903
|
-
const prompt = args ? `Use the c-${skillId} skill: ${args}` : `What can the c-${skillId} skill do? Give a brief overview.`;
|
|
1904
|
-
await handleClaudeMessage(ctx, prompt, currentModel, config);
|
|
1905
|
-
});
|
|
1906
|
-
}
|
|
1907
|
-
bot.on("message:text", async (ctx) => {
|
|
1908
|
-
await handleClaudeMessage(ctx, ctx.msg.text, currentModel, config);
|
|
1909
|
-
});
|
|
1910
|
-
bot.catch((err) => {
|
|
1911
|
-
console.error("Bot error:", err.message || err);
|
|
1912
|
-
});
|
|
1913
|
-
process.on("SIGINT", () => {
|
|
1914
|
-
console.log("\nShutting down gracefully... 🐾");
|
|
1915
|
-
bot.stop();
|
|
1916
|
-
process.exit(0);
|
|
1917
|
-
});
|
|
1918
|
-
process.on("SIGTERM", () => {
|
|
1919
|
-
bot.stop();
|
|
1920
|
-
process.exit(0);
|
|
1921
|
-
});
|
|
1922
|
-
console.log("");
|
|
1923
|
-
console.log(` 🐾 ${bold("OpenPaw Telegram Bridge")}`);
|
|
1924
|
-
console.log(` Model: ${accent(currentModel)}`);
|
|
1925
|
-
console.log(` Skills: ${accent(String(installedSkills.length))}`);
|
|
1926
|
-
console.log(` Workspace: ${dim(config.workspaceDir)}`);
|
|
1927
|
-
console.log(` Allowed users: ${dim(config.allowedUserIds.join(", "))}`);
|
|
1928
|
-
console.log("");
|
|
1929
|
-
console.log(dim(" Listening for messages... (Ctrl+C to stop)"));
|
|
1930
|
-
console.log("");
|
|
1931
|
-
await bot.start();
|
|
1932
|
-
}
|
|
1933
|
-
async function handleClaudeMessage(ctx, prompt, model, config) {
|
|
1934
|
-
const userId = ctx.from.id;
|
|
1935
|
-
const existing = sessions.get(userId);
|
|
1936
|
-
if (existing?.controller) existing.controller.abort();
|
|
1937
|
-
const controller = new AbortController();
|
|
1938
|
-
const session = sessions.get(userId) || {};
|
|
1939
|
-
session.controller = controller;
|
|
1940
|
-
sessions.set(userId, session);
|
|
1941
|
-
const statusMsg = await ctx.reply("Thinking... 🐾");
|
|
1942
|
-
let fullText = "";
|
|
1943
|
-
let lastEditTime = 0;
|
|
1944
|
-
const EDIT_INTERVAL = 1500;
|
|
1945
|
-
try {
|
|
1946
|
-
const q = query({
|
|
1947
|
-
prompt,
|
|
1948
|
-
options: {
|
|
1949
|
-
model: getModelId(model),
|
|
1950
|
-
permissionMode: "bypassPermissions",
|
|
1951
|
-
allowDangerouslySkipPermissions: true,
|
|
1952
|
-
cwd: config.workspaceDir,
|
|
1953
|
-
abortController: controller,
|
|
1954
|
-
maxTurns: 25,
|
|
1955
|
-
...session.sessionId ? { resume: session.sessionId } : {}
|
|
1956
|
-
}
|
|
1957
|
-
});
|
|
1958
|
-
for await (const message of q) {
|
|
1959
|
-
if (controller.signal.aborted) break;
|
|
1960
|
-
if (message.type === "system" && "session_id" in message) session.sessionId = message.session_id;
|
|
1961
|
-
if (message.type === "assistant") {
|
|
1962
|
-
const msgContent = message.message;
|
|
1963
|
-
const text = msgContent.content.filter((block) => block.type === "text").map((block) => block.text || "").join("");
|
|
1964
|
-
if (text) {
|
|
1965
|
-
fullText = text;
|
|
1966
|
-
const now = Date.now();
|
|
1967
|
-
if (now - lastEditTime > EDIT_INTERVAL) {
|
|
1968
|
-
lastEditTime = now;
|
|
1969
|
-
const truncated = fullText.length > 4e3 ? `${fullText.slice(0, 4e3)}...` : fullText;
|
|
1970
|
-
try {
|
|
1971
|
-
await statusMsg.editText(truncated);
|
|
1972
|
-
} catch {}
|
|
1973
|
-
}
|
|
1974
|
-
}
|
|
1975
|
-
}
|
|
1976
|
-
if (message.type === "result") {
|
|
1977
|
-
const result = message.result;
|
|
1978
|
-
if (result) fullText = result;
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
1981
|
-
if (fullText) {
|
|
1982
|
-
const truncated = fullText.length > 4e3 ? `${fullText.slice(0, 4e3)}...` : fullText;
|
|
1983
|
-
try {
|
|
1984
|
-
await statusMsg.editText(truncated);
|
|
1985
|
-
} catch {
|
|
1986
|
-
await ctx.reply(truncated);
|
|
1987
|
-
}
|
|
1988
|
-
} else await statusMsg.editText("Done! (no text output) 🐾");
|
|
1989
|
-
} catch (err) {
|
|
1990
|
-
const errorMsg = err instanceof Error ? err.message : "Unknown error";
|
|
1991
|
-
if (errorMsg.includes("abort") || controller.signal.aborted) return;
|
|
1992
|
-
try {
|
|
1993
|
-
await statusMsg.editText(`Woof, something went wrong: ${errorMsg.slice(0, 200)} 🐾`);
|
|
1994
|
-
} catch {
|
|
1995
|
-
await ctx.reply(`Woof, something went wrong: ${errorMsg.slice(0, 200)} 🐾`);
|
|
1535
|
+
fs$2.writeFileSync(filePath, `# ${title.charAt(0).toUpperCase() + title.slice(1)}\n`, "utf-8");
|
|
1996
1536
|
}
|
|
1997
|
-
} finally {
|
|
1998
|
-
session.controller = void 0;
|
|
1999
|
-
sessions.set(userId, session);
|
|
2000
1537
|
}
|
|
2001
1538
|
}
|
|
2002
1539
|
|
|
@@ -2049,60 +1586,95 @@ const CATEGORY_ICONS = {
|
|
|
2049
1586
|
async function setupCommand(opts = {}) {
|
|
2050
1587
|
await showBanner();
|
|
2051
1588
|
const platform = detectPlatform();
|
|
2052
|
-
p$
|
|
1589
|
+
p$10.intro(accent(" openpaw setup "));
|
|
2053
1590
|
const brewStatus = platform.hasBrew ? chalk.green("✓ brew") : chalk.red("✗ brew");
|
|
2054
1591
|
const npmStatus = platform.hasNpm ? chalk.green("✓ npm") : chalk.red("✗ npm");
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
1592
|
+
const pipStatus = platform.hasPip ? chalk.green("✓ pip") : chalk.dim("○ pip");
|
|
1593
|
+
p$10.log.info(`${chalk.bold(platform.osName)} ${platform.osVersion} ${brewStatus} ${npmStatus} ${pipStatus}`);
|
|
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$10.note(missingPrereqs.join("\n\n"), "Missing prerequisites");
|
|
1599
|
+
if (!opts.yes) {
|
|
1600
|
+
const cont = await p$10.confirm({
|
|
1601
|
+
message: "Continue anyway? (some tool installs may fail)",
|
|
1602
|
+
initialValue: true
|
|
1603
|
+
});
|
|
1604
|
+
if (p$10.isCancel(cont) || !cont) {
|
|
1605
|
+
p$10.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$10.confirm({
|
|
1613
|
+
message: "Existing personality found (~/.claude/SOUL.md). Update it?",
|
|
1614
|
+
initialValue: false
|
|
1615
|
+
});
|
|
1616
|
+
if (!p$10.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$10.log.success("Personality updated");
|
|
1625
|
+
}
|
|
1626
|
+
} else setupMemory();
|
|
1627
|
+
} else {
|
|
2058
1628
|
await pawPulse("think", "Let's get to know you...");
|
|
2059
|
-
const wantSoul = await p$
|
|
1629
|
+
const wantSoul = await p$10.confirm({
|
|
2060
1630
|
message: "Teach me your name and preferences? (makes me a better pup)",
|
|
2061
1631
|
initialValue: true
|
|
2062
1632
|
});
|
|
2063
|
-
if (!p$
|
|
1633
|
+
if (!p$10.isCancel(wantSoul) && wantSoul) {
|
|
2064
1634
|
const soul = await soulQuestionnaire();
|
|
2065
1635
|
if (soul) {
|
|
1636
|
+
botName = soul.botName;
|
|
2066
1637
|
writeSoul(soul);
|
|
2067
1638
|
setupMemory(soul.name);
|
|
2068
1639
|
showSoulSummary(soul);
|
|
2069
|
-
p$
|
|
1640
|
+
p$10.log.success("Personality saved to ~/.claude/SOUL.md");
|
|
2070
1641
|
}
|
|
2071
1642
|
} else setupMemory();
|
|
2072
|
-
}
|
|
1643
|
+
}
|
|
1644
|
+
else setupMemory();
|
|
2073
1645
|
let selectedSkills;
|
|
2074
1646
|
if (opts.preset) {
|
|
2075
1647
|
selectedSkills = getPresetSkills(opts.preset, platform.os);
|
|
2076
1648
|
if (selectedSkills.length === 0) {
|
|
2077
|
-
p$
|
|
2078
|
-
p$
|
|
1649
|
+
p$10.log.error(`Unknown preset: ${opts.preset}`);
|
|
1650
|
+
p$10.log.info(`Available: ${presets.map((pr) => pr.id).join(", ")}`);
|
|
2079
1651
|
process.exit(1);
|
|
2080
1652
|
}
|
|
2081
|
-
p$
|
|
1653
|
+
p$10.log.info(`Using preset: ${bold(opts.preset)} (${selectedSkills.length} skills)`);
|
|
2082
1654
|
} else selectedSkills = await selectSkills(platform.os);
|
|
2083
1655
|
if (selectedSkills.length === 0) {
|
|
2084
|
-
p$
|
|
2085
|
-
p$
|
|
1656
|
+
p$10.log.warn("No skills selected. Run openpaw again when you're ready!");
|
|
1657
|
+
p$10.outro("I'll be here napping... come back soon! 🐾");
|
|
2086
1658
|
return;
|
|
2087
1659
|
}
|
|
2088
1660
|
const resolved = resolveDependencies(selectedSkills);
|
|
2089
1661
|
if (resolved.length > 0) {
|
|
2090
1662
|
const depNames = resolved.map((s$1) => s$1.name).join(", ");
|
|
2091
|
-
p$
|
|
1663
|
+
p$10.log.info(`${dim("Auto-fetching dependencies:")} ${depNames}`);
|
|
2092
1664
|
selectedSkills.push(...resolved);
|
|
2093
1665
|
}
|
|
2094
1666
|
await pawPulse("happy", `${selectedSkills.length} skill${selectedSkills.length > 1 ? "s" : ""} selected — good taste!`);
|
|
2095
1667
|
if (!opts.yes) {
|
|
2096
1668
|
for (const skill of selectedSkills) if (skill.subChoices) {
|
|
2097
|
-
const choice = await p$
|
|
1669
|
+
const choice = await p$10.select({
|
|
2098
1670
|
message: `${skill.name}: ${skill.subChoices.question}`,
|
|
2099
1671
|
options: skill.subChoices.options.map((o) => ({
|
|
2100
1672
|
value: o.value,
|
|
2101
1673
|
label: o.label
|
|
2102
1674
|
}))
|
|
2103
1675
|
});
|
|
2104
|
-
if (p$
|
|
2105
|
-
p$
|
|
1676
|
+
if (p$10.isCancel(choice)) {
|
|
1677
|
+
p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2106
1678
|
process.exit(0);
|
|
2107
1679
|
}
|
|
2108
1680
|
const chosen = skill.subChoices.options.find((o) => o.value === choice);
|
|
@@ -2112,37 +1684,29 @@ async function setupCommand(opts = {}) {
|
|
|
2112
1684
|
let interfaceMode = "native";
|
|
2113
1685
|
let telegramConfig = null;
|
|
2114
1686
|
if (!opts.yes) {
|
|
2115
|
-
const modeChoice = await p$
|
|
1687
|
+
const modeChoice = await p$10.select({
|
|
2116
1688
|
message: "How do you want to talk to Claude? 🐾",
|
|
2117
|
-
options: [
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
hint: "talk to Claude from your phone"
|
|
2127
|
-
},
|
|
2128
|
-
{
|
|
2129
|
-
value: "both",
|
|
2130
|
-
label: "🖥📱 Both",
|
|
2131
|
-
hint: "terminal + Telegram"
|
|
2132
|
-
}
|
|
2133
|
-
]
|
|
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
|
+
}]
|
|
2134
1698
|
});
|
|
2135
|
-
if (p$
|
|
2136
|
-
p$
|
|
1699
|
+
if (p$10.isCancel(modeChoice)) {
|
|
1700
|
+
p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2137
1701
|
process.exit(0);
|
|
2138
1702
|
}
|
|
2139
1703
|
interfaceMode = modeChoice;
|
|
2140
1704
|
if (interfaceMode === "telegram" || interfaceMode === "both") {
|
|
2141
|
-
if (telegramConfigExists()) p$
|
|
1705
|
+
if (telegramConfigExists()) p$10.log.info(dim("Telegram already configured — keeping existing config"));
|
|
2142
1706
|
else {
|
|
2143
1707
|
telegramConfig = await telegramQuestionnaire();
|
|
2144
1708
|
if (!telegramConfig) {
|
|
2145
|
-
p$
|
|
1709
|
+
p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2146
1710
|
process.exit(0);
|
|
2147
1711
|
}
|
|
2148
1712
|
}
|
|
@@ -2152,37 +1716,39 @@ async function setupCommand(opts = {}) {
|
|
|
2152
1716
|
}
|
|
2153
1717
|
}
|
|
2154
1718
|
}
|
|
2155
|
-
let
|
|
1719
|
+
let wantDashboard = false;
|
|
1720
|
+
let dashboardTheme = "paw";
|
|
2156
1721
|
if (!opts.yes) {
|
|
2157
|
-
const
|
|
2158
|
-
message:
|
|
2159
|
-
|
|
2160
|
-
value: "home",
|
|
2161
|
-
label: `Home directory ${dim("~")}`,
|
|
2162
|
-
hint: "recommended for general assistant"
|
|
2163
|
-
}, {
|
|
2164
|
-
value: "custom",
|
|
2165
|
-
label: "Pick a project directory",
|
|
2166
|
-
hint: "for project-focused work"
|
|
2167
|
-
}]
|
|
1722
|
+
const dashChoice = await p$10.confirm({
|
|
1723
|
+
message: `Want a task dashboard for ${botName}?`,
|
|
1724
|
+
initialValue: false
|
|
2168
1725
|
});
|
|
2169
|
-
if (p$
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
1726
|
+
if (!p$10.isCancel(dashChoice) && dashChoice) {
|
|
1727
|
+
wantDashboard = true;
|
|
1728
|
+
const themeChoice = await p$10.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
|
+
]
|
|
2178
1747
|
});
|
|
2179
|
-
if (p$
|
|
2180
|
-
p$9.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2181
|
-
process.exit(0);
|
|
2182
|
-
}
|
|
2183
|
-
projectDir = customDir.replace(/^~/, os$2.homedir());
|
|
1748
|
+
if (!p$10.isCancel(themeChoice)) dashboardTheme = themeChoice;
|
|
2184
1749
|
}
|
|
2185
1750
|
}
|
|
1751
|
+
const projectDir = os$2.homedir();
|
|
2186
1752
|
const allTools = [];
|
|
2187
1753
|
for (const skill of selectedSkills) allTools.push(...skill.tools);
|
|
2188
1754
|
const uniqueTools = [...new Map(allTools.map((t) => [t.command, t])).values()];
|
|
@@ -2190,26 +1756,63 @@ async function setupCommand(opts = {}) {
|
|
|
2190
1756
|
const missing = getMissingTools(uniqueTools);
|
|
2191
1757
|
let targetDir;
|
|
2192
1758
|
if (opts.yes) targetDir = getDefaultSkillsDir();
|
|
2193
|
-
else
|
|
1759
|
+
else {
|
|
1760
|
+
const defaultDir = getDefaultSkillsDir();
|
|
1761
|
+
const skillsDir = await p$10.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$10.isCancel(skillsDir)) {
|
|
1780
|
+
p$10.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$10.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$10.isCancel(customDir)) {
|
|
1791
|
+
p$10.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
|
+
}
|
|
2194
1797
|
const summary = buildSummary(selectedSkills, uniqueTools, missing, taps, interfaceMode, projectDir);
|
|
2195
|
-
p$
|
|
1798
|
+
p$10.note(summary, "Here's what we're fetching");
|
|
2196
1799
|
if (!opts.yes) {
|
|
2197
|
-
const proceed = await p$
|
|
1800
|
+
const proceed = await p$10.confirm({
|
|
2198
1801
|
message: "Ready to fetch all these goodies?",
|
|
2199
1802
|
initialValue: true
|
|
2200
1803
|
});
|
|
2201
|
-
if (p$
|
|
2202
|
-
p$
|
|
1804
|
+
if (p$10.isCancel(proceed) || !proceed) {
|
|
1805
|
+
p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2203
1806
|
process.exit(0);
|
|
2204
1807
|
}
|
|
2205
1808
|
}
|
|
2206
1809
|
if (opts.dryRun) {
|
|
2207
|
-
p$
|
|
2208
|
-
p$
|
|
1810
|
+
p$10.log.info(dim("Dry run — no changes made. Just sniffing around."));
|
|
1811
|
+
p$10.outro(accent("openpaw dry run complete 🐾"));
|
|
2209
1812
|
return;
|
|
2210
1813
|
}
|
|
2211
1814
|
await pawStep("work", "Fetching your goodies...");
|
|
2212
|
-
const s = p$
|
|
1815
|
+
const s = p$10.spinner();
|
|
2213
1816
|
if (taps.size > 0) {
|
|
2214
1817
|
s.start("🐾 Sniffing out Homebrew taps...");
|
|
2215
1818
|
const tapResults = installTaps(taps);
|
|
@@ -2224,12 +1827,28 @@ async function setupCommand(opts = {}) {
|
|
|
2224
1827
|
if (result.success) s.stop(`${chalk.green("✓")} ${tool.name}`);
|
|
2225
1828
|
else s.stop(`${chalk.red("✗")} ${tool.name} — ${result.error?.slice(0, 50)}`);
|
|
2226
1829
|
}
|
|
2227
|
-
else if (uniqueTools.length > 0) p$
|
|
1830
|
+
else if (uniqueTools.length > 0) p$10.log.success("All tools already installed — clever pup!");
|
|
1831
|
+
const existingSkills = listInstalledSkills(targetDir);
|
|
1832
|
+
const overlapping = selectedSkills.filter((sk) => existingSkills.includes(sk.id));
|
|
1833
|
+
let updateExisting = true;
|
|
1834
|
+
if (overlapping.length > 0 && !opts.yes) {
|
|
1835
|
+
const updateChoice = await p$10.confirm({
|
|
1836
|
+
message: `${overlapping.length} skill${overlapping.length > 1 ? "s" : ""} already installed. Update their templates?`,
|
|
1837
|
+
initialValue: true
|
|
1838
|
+
});
|
|
1839
|
+
if (!p$10.isCancel(updateChoice)) updateExisting = updateChoice;
|
|
1840
|
+
}
|
|
2228
1841
|
s.start("🐾 Burying treats in ~/.claude/skills/...");
|
|
2229
1842
|
installSkill("core", targetDir);
|
|
2230
1843
|
installSkill("memory", targetDir);
|
|
2231
1844
|
const installed = ["c-core", "c-memory"];
|
|
2232
|
-
for (const skill of selectedSkills)
|
|
1845
|
+
for (const skill of selectedSkills) {
|
|
1846
|
+
if (!updateExisting && existingSkills.includes(skill.id)) {
|
|
1847
|
+
installed.push(`c-${skill.id}`);
|
|
1848
|
+
continue;
|
|
1849
|
+
}
|
|
1850
|
+
if (installSkill(skill.id, targetDir)) installed.push(`c-${skill.id}`);
|
|
1851
|
+
}
|
|
2233
1852
|
s.stop(`🐾 ${installed.length} skills buried`);
|
|
2234
1853
|
s.start("🐾 Setting up the doggy door...");
|
|
2235
1854
|
const added = addPermissions(uniqueTools);
|
|
@@ -2241,128 +1860,102 @@ async function setupCommand(opts = {}) {
|
|
|
2241
1860
|
telegramConfig.workspaceDir = projectDir;
|
|
2242
1861
|
telegramConfig.skills = selectedSkills.map((sk) => sk.id);
|
|
2243
1862
|
writeTelegramConfig(telegramConfig);
|
|
2244
|
-
p$
|
|
1863
|
+
p$10.log.success("Telegram bridge configured");
|
|
2245
1864
|
}
|
|
2246
|
-
if (
|
|
2247
|
-
const
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
const mcpChoices = await p$9.multiselect({
|
|
2253
|
-
message: "🔌 MCP Servers",
|
|
2254
|
-
options: mcpServers.map((srv) => ({
|
|
2255
|
-
value: srv.id,
|
|
2256
|
-
label: srv.name,
|
|
2257
|
-
hint: srv.description
|
|
2258
|
-
})),
|
|
2259
|
-
required: false
|
|
2260
|
-
});
|
|
2261
|
-
if (!p$9.isCancel(mcpChoices)) {
|
|
2262
|
-
const chosen = mcpChoices;
|
|
2263
|
-
if (chosen.length > 0) {
|
|
2264
|
-
s.start("🐾 Configuring MCP servers...");
|
|
2265
|
-
let mcpCount = 0;
|
|
2266
|
-
for (const id of chosen) {
|
|
2267
|
-
const srv = mcpServers.find((m) => m.id === id);
|
|
2268
|
-
if (srv && installMcpServer(srv)) mcpCount++;
|
|
2269
|
-
}
|
|
2270
|
-
s.stop(`🐾 ${mcpCount} MCP server${mcpCount > 1 ? "s" : ""} configured`);
|
|
2271
|
-
const needsEnv = chosen.map((id) => mcpServers.find((m) => m.id === id)).filter((srv) => !!srv?.envPlaceholders);
|
|
2272
|
-
if (needsEnv.length > 0) {
|
|
2273
|
-
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");
|
|
2274
|
-
p$9.note(envList, "MCP servers need API keys");
|
|
2275
|
-
}
|
|
2276
|
-
}
|
|
2277
|
-
}
|
|
2278
|
-
}
|
|
1865
|
+
if (wantDashboard) {
|
|
1866
|
+
const dashConfig = readConfig();
|
|
1867
|
+
dashConfig.theme = dashboardTheme;
|
|
1868
|
+
dashConfig.botName = botName;
|
|
1869
|
+
writeConfig(dashConfig);
|
|
1870
|
+
p$10.log.success(`Dashboard configured (theme: ${dashboardTheme})`);
|
|
2279
1871
|
}
|
|
2280
1872
|
const authSteps = selectedSkills.flatMap((skill) => skill.authSteps ?? []).filter((step, i, arr) => arr.findIndex((s$1) => s$1.command === step.command) === i);
|
|
2281
1873
|
if (authSteps.length > 0) {
|
|
2282
1874
|
const authList = authSteps.map((st) => `${chalk.yellow("→")} ${chalk.bold(st.command)} ${dim(st.description)}`).join("\n");
|
|
2283
|
-
p$
|
|
1875
|
+
p$10.note(authList, "One-time auth needed");
|
|
2284
1876
|
}
|
|
2285
1877
|
await pawStep("done", "All done! *tail wag intensifies*");
|
|
2286
1878
|
console.log("");
|
|
2287
|
-
console.log(dim(
|
|
1879
|
+
console.log(dim(` ${botName} is ready to play! Try saying:`));
|
|
2288
1880
|
console.log(` ${subtle("\"What are my latest emails?\"")}`);
|
|
2289
1881
|
console.log(` ${subtle("\"Play some jazz on Spotify\"")}`);
|
|
2290
1882
|
console.log(` ${subtle("\"Go to hacker news and summarize the top posts\"")}`);
|
|
2291
1883
|
console.log("");
|
|
1884
|
+
if (wantDashboard) {
|
|
1885
|
+
const { startDashboard: startDashboard$1 } = await import("./dashboard-server-Cg_1CvKn.js");
|
|
1886
|
+
startDashboard$1({
|
|
1887
|
+
theme: dashboardTheme,
|
|
1888
|
+
botName
|
|
1889
|
+
});
|
|
1890
|
+
p$10.log.success("Dashboard launched in your browser");
|
|
1891
|
+
}
|
|
2292
1892
|
if (opts.yes) {
|
|
2293
|
-
p$
|
|
1893
|
+
p$10.outro(accent("openpaw setup complete 🐾"));
|
|
2294
1894
|
return;
|
|
2295
1895
|
}
|
|
2296
|
-
const launch = await p$
|
|
1896
|
+
const launch = await p$10.confirm({
|
|
2297
1897
|
message: "Time to go for a walk? (Launch your assistant)",
|
|
2298
1898
|
initialValue: true
|
|
2299
1899
|
});
|
|
2300
|
-
if (p$
|
|
2301
|
-
if (interfaceMode === "telegram" || interfaceMode === "both") p$
|
|
2302
|
-
p$
|
|
1900
|
+
if (p$10.isCancel(launch) || !launch) {
|
|
1901
|
+
if (interfaceMode === "telegram" || interfaceMode === "both") p$10.log.info(`Start the Telegram bridge anytime with: ${bold("openpaw telegram")}`);
|
|
1902
|
+
p$10.outro(accent("openpaw setup complete 🐾 — come back anytime!"));
|
|
2303
1903
|
return;
|
|
2304
1904
|
}
|
|
2305
1905
|
let useDangerousMode = false;
|
|
2306
|
-
|
|
1906
|
+
{
|
|
2307
1907
|
showPuppyDisclaimer();
|
|
2308
|
-
const acceptDanger = await p$
|
|
1908
|
+
const acceptDanger = await p$10.confirm({
|
|
2309
1909
|
message: "Unleash full paw-er? *excited tail wag*",
|
|
2310
1910
|
initialValue: true
|
|
2311
1911
|
});
|
|
2312
|
-
if (!p$
|
|
1912
|
+
if (!p$10.isCancel(acceptDanger)) useDangerousMode = acceptDanger;
|
|
2313
1913
|
}
|
|
2314
1914
|
let useTmux = false;
|
|
2315
1915
|
if (isTmuxAvailable() && !isInTmux()) {
|
|
2316
1916
|
const tmuxDefault = interfaceMode === "both";
|
|
2317
|
-
const tmuxChoice = await p$
|
|
1917
|
+
const tmuxChoice = await p$10.confirm({
|
|
2318
1918
|
message: "Run in tmux? (keeps going when you close the terminal)",
|
|
2319
1919
|
initialValue: tmuxDefault
|
|
2320
1920
|
});
|
|
2321
|
-
if (!p$
|
|
1921
|
+
if (!p$10.isCancel(tmuxChoice)) useTmux = tmuxChoice;
|
|
2322
1922
|
}
|
|
2323
1923
|
const dangerFlag = useDangerousMode ? " --dangerously-skip-permissions" : "";
|
|
2324
1924
|
const nativeCmd = `claude${dangerFlag}`;
|
|
2325
1925
|
const telegramCmd = "npx openpaw telegram";
|
|
2326
1926
|
if (useTmux) {
|
|
2327
|
-
p$
|
|
1927
|
+
p$10.outro(accent("Launching in tmux... 🐾"));
|
|
2328
1928
|
launchInTmux({
|
|
2329
|
-
nativeCmd
|
|
2330
|
-
telegramCmd: interfaceMode === "
|
|
1929
|
+
nativeCmd,
|
|
1930
|
+
telegramCmd: interfaceMode === "both" ? telegramCmd : void 0,
|
|
2331
1931
|
workDir: projectDir
|
|
2332
1932
|
});
|
|
2333
1933
|
} else if (interfaceMode === "native") {
|
|
2334
|
-
p$
|
|
1934
|
+
p$10.outro(accent("Starting Claude Code... 🐾"));
|
|
2335
1935
|
try {
|
|
2336
1936
|
execSync(nativeCmd, {
|
|
2337
1937
|
stdio: "inherit",
|
|
2338
1938
|
cwd: projectDir
|
|
2339
1939
|
});
|
|
2340
1940
|
} catch {
|
|
2341
|
-
p$
|
|
2342
|
-
}
|
|
2343
|
-
} else if (interfaceMode === "telegram") {
|
|
2344
|
-
p$9.outro(accent("Starting Telegram bridge... 🐾"));
|
|
2345
|
-
try {
|
|
2346
|
-
execSync(telegramCmd, { stdio: "inherit" });
|
|
2347
|
-
} catch {
|
|
2348
|
-
p$9.log.warn("Telegram bridge failed to start.");
|
|
1941
|
+
p$10.log.warn("Could not launch Claude Code. Make sure it's installed: https://claude.ai/code");
|
|
2349
1942
|
}
|
|
2350
1943
|
} else {
|
|
2351
|
-
p$
|
|
1944
|
+
p$10.log.info(dim("Starting Telegram bridge in background..."));
|
|
2352
1945
|
launchInBackground(telegramCmd);
|
|
2353
|
-
p$
|
|
1946
|
+
p$10.outro(accent("Starting Claude Code... 🐾"));
|
|
2354
1947
|
try {
|
|
2355
1948
|
execSync(nativeCmd, {
|
|
2356
1949
|
stdio: "inherit",
|
|
2357
1950
|
cwd: projectDir
|
|
2358
1951
|
});
|
|
2359
1952
|
} catch {
|
|
2360
|
-
p$
|
|
1953
|
+
p$10.log.warn("Could not launch Claude Code. Make sure it's installed: https://claude.ai/code");
|
|
2361
1954
|
}
|
|
2362
1955
|
}
|
|
2363
1956
|
}
|
|
2364
|
-
async function selectSkills(os$
|
|
2365
|
-
const mode = await p$
|
|
1957
|
+
async function selectSkills(os$5) {
|
|
1958
|
+
const mode = await p$10.select({
|
|
2366
1959
|
message: "How should we set things up, human?",
|
|
2367
1960
|
options: [{
|
|
2368
1961
|
value: "preset",
|
|
@@ -2374,15 +1967,15 @@ async function selectSkills(os$7) {
|
|
|
2374
1967
|
hint: "sniff through skills one by one"
|
|
2375
1968
|
}]
|
|
2376
1969
|
});
|
|
2377
|
-
if (p$
|
|
2378
|
-
p$
|
|
1970
|
+
if (p$10.isCancel(mode)) {
|
|
1971
|
+
p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2379
1972
|
process.exit(0);
|
|
2380
1973
|
}
|
|
2381
|
-
if (mode === "preset") return await selectFromPreset(os$
|
|
2382
|
-
return await selectCustom(os$
|
|
1974
|
+
if (mode === "preset") return await selectFromPreset(os$5);
|
|
1975
|
+
return await selectCustom(os$5);
|
|
2383
1976
|
}
|
|
2384
|
-
async function selectFromPreset(os$
|
|
2385
|
-
const presetChoice = await p$
|
|
1977
|
+
async function selectFromPreset(os$5) {
|
|
1978
|
+
const presetChoice = await p$10.select({
|
|
2386
1979
|
message: "Pick a treat... I mean, a preset!",
|
|
2387
1980
|
options: presets.map((pr) => ({
|
|
2388
1981
|
value: pr.id,
|
|
@@ -2390,88 +1983,50 @@ async function selectFromPreset(os$7) {
|
|
|
2390
1983
|
hint: pr.description
|
|
2391
1984
|
}))
|
|
2392
1985
|
});
|
|
2393
|
-
if (p$
|
|
2394
|
-
p$
|
|
1986
|
+
if (p$10.isCancel(presetChoice)) {
|
|
1987
|
+
p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2395
1988
|
process.exit(0);
|
|
2396
1989
|
}
|
|
2397
|
-
const presetSkills = getPresetSkills(presetChoice, os$
|
|
1990
|
+
const presetSkills = getPresetSkills(presetChoice, os$5);
|
|
2398
1991
|
const skillNames = presetSkills.map((s) => s.name).join(", ");
|
|
2399
|
-
p$
|
|
1992
|
+
p$10.log.info(`${dim("Includes:")} ${skillNames}`);
|
|
2400
1993
|
return presetSkills;
|
|
2401
1994
|
}
|
|
2402
|
-
async function selectCustom(os$
|
|
2403
|
-
const grouped = getSkillsByCategory(os$
|
|
2404
|
-
const
|
|
2405
|
-
for (const [category,
|
|
2406
|
-
const label = categoryLabels[category] ?? category;
|
|
1995
|
+
async function selectCustom(os$5) {
|
|
1996
|
+
const grouped = getSkillsByCategory(os$5);
|
|
1997
|
+
const options = [];
|
|
1998
|
+
for (const [category, catSkills] of grouped) {
|
|
2407
1999
|
const icon = CATEGORY_ICONS[category] ?? "📦";
|
|
2408
|
-
const
|
|
2409
|
-
|
|
2410
|
-
|
|
2000
|
+
const catLabel = categoryLabels[category] ?? category;
|
|
2001
|
+
let isFirst = true;
|
|
2002
|
+
for (const skill of catSkills) {
|
|
2003
|
+
if (skill.id === "telegram") continue;
|
|
2004
|
+
options.push({
|
|
2411
2005
|
value: skill.id,
|
|
2412
|
-
label: skill.name
|
|
2413
|
-
hint: skill.description
|
|
2414
|
-
})
|
|
2415
|
-
|
|
2416
|
-
});
|
|
2417
|
-
if (p$9.isCancel(selected)) {
|
|
2418
|
-
p$9.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2419
|
-
process.exit(0);
|
|
2420
|
-
}
|
|
2421
|
-
const ids = selected;
|
|
2422
|
-
for (const id of ids) {
|
|
2423
|
-
const skill = skills.find((s) => s.id === id);
|
|
2424
|
-
if (skill) allSelected.push(skill);
|
|
2006
|
+
label: `${icon} ${skill.name}`,
|
|
2007
|
+
hint: isFirst ? `── ${catLabel} ── ${skill.description}` : skill.description
|
|
2008
|
+
});
|
|
2009
|
+
isFirst = false;
|
|
2425
2010
|
}
|
|
2426
|
-
if (ids.length > 0) p$9.log.step(`${ids.length} selected from ${label}`);
|
|
2427
2011
|
}
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
const skillsDir = await p$9.select({
|
|
2433
|
-
message: "Where should skills live?",
|
|
2434
|
-
options: [
|
|
2435
|
-
{
|
|
2436
|
-
value: defaultDir,
|
|
2437
|
-
label: `Global ${dim("~/.claude/skills/")}`,
|
|
2438
|
-
hint: "recommended"
|
|
2439
|
-
},
|
|
2440
|
-
{
|
|
2441
|
-
value: ".claude/skills",
|
|
2442
|
-
label: `Project ${dim(".claude/skills/")}`
|
|
2443
|
-
},
|
|
2444
|
-
{
|
|
2445
|
-
value: "custom",
|
|
2446
|
-
label: "Custom path"
|
|
2447
|
-
}
|
|
2448
|
-
]
|
|
2012
|
+
const selected = await p$10.multiselect({
|
|
2013
|
+
message: "Pick your skills (space to select, enter to confirm)",
|
|
2014
|
+
options,
|
|
2015
|
+
required: false
|
|
2449
2016
|
});
|
|
2450
|
-
if (p$
|
|
2451
|
-
p$
|
|
2017
|
+
if (p$10.isCancel(selected)) {
|
|
2018
|
+
p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2452
2019
|
process.exit(0);
|
|
2453
2020
|
}
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
const customDir = await p$9.text({
|
|
2457
|
-
message: "Skills directory path:",
|
|
2458
|
-
placeholder: "~/.claude/skills",
|
|
2459
|
-
validate: (v) => v.length === 0 ? "Path cannot be empty" : void 0
|
|
2460
|
-
});
|
|
2461
|
-
if (p$9.isCancel(customDir)) {
|
|
2462
|
-
p$9.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2463
|
-
process.exit(0);
|
|
2464
|
-
}
|
|
2465
|
-
targetDir = customDir;
|
|
2466
|
-
}
|
|
2467
|
-
return targetDir;
|
|
2021
|
+
const ids = selected;
|
|
2022
|
+
return ids.map((id) => skills.find((s) => s.id === id)).filter((s) => !!s);
|
|
2468
2023
|
}
|
|
2469
2024
|
function buildSummary(selectedSkills, uniqueTools, missing, taps, interfaceMode, projectDir) {
|
|
2470
2025
|
const lines = [];
|
|
2471
2026
|
lines.push(`${bold("Skills:")} ${selectedSkills.map((s) => s.name).join(", ")}`);
|
|
2472
2027
|
lines.push(`${bold("Tools:")} ${uniqueTools.length} total, ${missing.length} to install`);
|
|
2473
2028
|
if (taps.size > 0) lines.push(`${bold("Taps:")} ${[...taps].join(", ")}`);
|
|
2474
|
-
const modeLabel = interfaceMode === "native" ? "Terminal" :
|
|
2029
|
+
const modeLabel = interfaceMode === "native" ? "Terminal" : "Terminal + Telegram";
|
|
2475
2030
|
lines.push(`${bold("Interface:")} ${modeLabel}`);
|
|
2476
2031
|
lines.push(`${bold("Workspace:")} ${projectDir.replace(os$2.homedir(), "~")}`);
|
|
2477
2032
|
lines.push(`${bold("Memory:")} ~/.claude/memory/`);
|
|
@@ -2500,18 +2055,18 @@ async function addCommand(skillIds) {
|
|
|
2500
2055
|
showMini();
|
|
2501
2056
|
console.log("");
|
|
2502
2057
|
if (skillIds.length === 0) {
|
|
2503
|
-
p$
|
|
2058
|
+
p$9.log.error("Specify skills to add: openpaw add notes music email");
|
|
2504
2059
|
return;
|
|
2505
2060
|
}
|
|
2506
|
-
const s = p$
|
|
2061
|
+
const s = p$9.spinner();
|
|
2507
2062
|
for (const id of skillIds) {
|
|
2508
2063
|
const skill = getSkillById(id);
|
|
2509
2064
|
if (!skill) {
|
|
2510
|
-
p$
|
|
2065
|
+
p$9.log.error(`Unknown skill: ${id}`);
|
|
2511
2066
|
continue;
|
|
2512
2067
|
}
|
|
2513
2068
|
if (isSkillInstalled(id)) {
|
|
2514
|
-
p$
|
|
2069
|
+
p$9.log.info(`c-${id} already installed, skipping`);
|
|
2515
2070
|
continue;
|
|
2516
2071
|
}
|
|
2517
2072
|
const taps = getAllTaps([skill]);
|
|
@@ -2524,7 +2079,7 @@ async function addCommand(skillIds) {
|
|
|
2524
2079
|
}
|
|
2525
2080
|
installSkill(id);
|
|
2526
2081
|
addPermissions(skill.tools);
|
|
2527
|
-
p$
|
|
2082
|
+
p$9.log.success(`c-${id} installed`);
|
|
2528
2083
|
if (skill.authSteps?.length) for (const step of skill.authSteps) console.log(` ${chalk.yellow("→")} ${step.command} — ${step.description}`);
|
|
2529
2084
|
}
|
|
2530
2085
|
}
|
|
@@ -2535,22 +2090,22 @@ async function removeCommand(skillIds) {
|
|
|
2535
2090
|
showMini();
|
|
2536
2091
|
console.log("");
|
|
2537
2092
|
if (skillIds.length === 0) {
|
|
2538
|
-
p$
|
|
2093
|
+
p$8.log.error("Specify skills to remove: openpaw remove notes music");
|
|
2539
2094
|
return;
|
|
2540
2095
|
}
|
|
2541
2096
|
for (const id of skillIds) {
|
|
2542
2097
|
if (id === "core") {
|
|
2543
|
-
p$
|
|
2098
|
+
p$8.log.warn("Cannot remove c-core (coordinator). Use 'openpaw reset' instead.");
|
|
2544
2099
|
continue;
|
|
2545
2100
|
}
|
|
2546
2101
|
if (!isSkillInstalled(id)) {
|
|
2547
|
-
p$
|
|
2102
|
+
p$8.log.info(`c-${id} is not installed`);
|
|
2548
2103
|
continue;
|
|
2549
2104
|
}
|
|
2550
2105
|
const skill = getSkillById(id);
|
|
2551
2106
|
removeSkill(id);
|
|
2552
2107
|
if (skill) removePermissions(skill.tools);
|
|
2553
|
-
p$
|
|
2108
|
+
p$8.log.success(`${chalk.bold(`c-${id}`)} removed`);
|
|
2554
2109
|
}
|
|
2555
2110
|
}
|
|
2556
2111
|
|
|
@@ -2561,10 +2116,10 @@ async function statusCommand() {
|
|
|
2561
2116
|
console.log("");
|
|
2562
2117
|
const installed = listInstalledSkills();
|
|
2563
2118
|
if (installed.length === 0) {
|
|
2564
|
-
p$
|
|
2119
|
+
p$7.log.warn("No OpenPaw skills installed. Run: openpaw setup");
|
|
2565
2120
|
return;
|
|
2566
2121
|
}
|
|
2567
|
-
p$
|
|
2122
|
+
p$7.log.info(`${chalk.bold(installed.length)} skills installed:\n`);
|
|
2568
2123
|
for (const skillId of installed) {
|
|
2569
2124
|
if (skillId === "core") {
|
|
2570
2125
|
console.log(` ${chalk.green("●")} ${chalk.bold("c-core")} ${chalk.dim("— coordinator")}`);
|
|
@@ -2592,7 +2147,7 @@ async function statusCommand() {
|
|
|
2592
2147
|
async function doctorCommand() {
|
|
2593
2148
|
showMini();
|
|
2594
2149
|
console.log("");
|
|
2595
|
-
p$
|
|
2150
|
+
p$6.log.info("Running diagnostics...\n");
|
|
2596
2151
|
let issues = 0;
|
|
2597
2152
|
const platform = detectPlatform();
|
|
2598
2153
|
console.log(` ${chalk.green("✓")} Platform: ${platform.osName} ${platform.osVersion}`);
|
|
@@ -2631,8 +2186,8 @@ async function doctorCommand() {
|
|
|
2631
2186
|
issues++;
|
|
2632
2187
|
}
|
|
2633
2188
|
console.log("");
|
|
2634
|
-
if (issues === 0) p$
|
|
2635
|
-
else p$
|
|
2189
|
+
if (issues === 0) p$6.log.success("All checks passed!");
|
|
2190
|
+
else p$6.log.warn(`${issues} issue${issues > 1 ? "s" : ""} found`);
|
|
2636
2191
|
}
|
|
2637
2192
|
|
|
2638
2193
|
//#endregion
|
|
@@ -2642,10 +2197,10 @@ async function updateCommand() {
|
|
|
2642
2197
|
console.log("");
|
|
2643
2198
|
const installed = listInstalledSkills();
|
|
2644
2199
|
if (installed.length === 0) {
|
|
2645
|
-
p$
|
|
2200
|
+
p$5.log.warn("No skills installed. Run: openpaw setup");
|
|
2646
2201
|
return;
|
|
2647
2202
|
}
|
|
2648
|
-
const s = p$
|
|
2203
|
+
const s = p$5.spinner();
|
|
2649
2204
|
const brewTools = [];
|
|
2650
2205
|
for (const skillId of installed) {
|
|
2651
2206
|
const skill = skills.find((sk) => sk.id === skillId);
|
|
@@ -2653,7 +2208,7 @@ async function updateCommand() {
|
|
|
2653
2208
|
for (const tool of skill.tools) if ((tool.installMethod === "brew" || tool.installMethod === "brew-tap") && isToolInstalled(tool.command)) brewTools.push(tool.name);
|
|
2654
2209
|
}
|
|
2655
2210
|
if (brewTools.length === 0) {
|
|
2656
|
-
p$
|
|
2211
|
+
p$5.log.info("No Homebrew-installed tools to update");
|
|
2657
2212
|
return;
|
|
2658
2213
|
}
|
|
2659
2214
|
s.start(`Updating ${brewTools.length} tools via Homebrew...`);
|
|
@@ -2679,15 +2234,15 @@ async function resetCommand() {
|
|
|
2679
2234
|
console.log("");
|
|
2680
2235
|
const installed = listInstalledSkills();
|
|
2681
2236
|
if (installed.length === 0) {
|
|
2682
|
-
p$
|
|
2237
|
+
p$4.log.info("Nothing to reset — no OpenPaw skills installed.");
|
|
2683
2238
|
return;
|
|
2684
2239
|
}
|
|
2685
|
-
const confirm = await p$
|
|
2686
|
-
if (p$
|
|
2687
|
-
p$
|
|
2240
|
+
const confirm = await p$4.confirm({ message: `Remove all ${installed.length} OpenPaw skills and permissions?` });
|
|
2241
|
+
if (p$4.isCancel(confirm) || !confirm) {
|
|
2242
|
+
p$4.cancel("Reset cancelled.");
|
|
2688
2243
|
return;
|
|
2689
2244
|
}
|
|
2690
|
-
const s = p$
|
|
2245
|
+
const s = p$4.spinner();
|
|
2691
2246
|
s.start("Removing skills and permissions...");
|
|
2692
2247
|
for (const skillId of installed) {
|
|
2693
2248
|
const skill = skills.find((sk) => sk.id === skillId);
|
|
@@ -2696,9 +2251,9 @@ async function resetCommand() {
|
|
|
2696
2251
|
}
|
|
2697
2252
|
removeSafetyHooks();
|
|
2698
2253
|
s.stop(`${chalk.green("✓")} Removed ${installed.length} skills, permissions, and hooks`);
|
|
2699
|
-
p$
|
|
2700
|
-
p$
|
|
2701
|
-
p$
|
|
2254
|
+
p$4.log.info(chalk.dim("CLI tools were not uninstalled (you may still want them)."));
|
|
2255
|
+
p$4.log.info(chalk.dim("To uninstall tools: brew uninstall <tool>"));
|
|
2256
|
+
p$4.outro("OpenPaw reset complete.");
|
|
2702
2257
|
}
|
|
2703
2258
|
|
|
2704
2259
|
//#endregion
|
|
@@ -2739,35 +2294,35 @@ async function listCommand() {
|
|
|
2739
2294
|
//#region src/commands/soul.ts
|
|
2740
2295
|
async function soulCommand() {
|
|
2741
2296
|
showMini();
|
|
2742
|
-
p$
|
|
2297
|
+
p$3.intro(accent(" openpaw soul "));
|
|
2743
2298
|
if (soulExists()) {
|
|
2744
|
-
p$
|
|
2745
|
-
const overwrite = await p$
|
|
2299
|
+
p$3.log.info(dim("SOUL.md already exists at ~/.claude/SOUL.md"));
|
|
2300
|
+
const overwrite = await p$3.confirm({
|
|
2746
2301
|
message: "Overwrite existing personality?",
|
|
2747
2302
|
initialValue: false
|
|
2748
2303
|
});
|
|
2749
|
-
if (p$
|
|
2750
|
-
p$
|
|
2751
|
-
p$
|
|
2304
|
+
if (p$3.isCancel(overwrite) || !overwrite) {
|
|
2305
|
+
p$3.log.info("Keeping existing SOUL.md");
|
|
2306
|
+
p$3.outro(accent("Done"));
|
|
2752
2307
|
return;
|
|
2753
2308
|
}
|
|
2754
2309
|
}
|
|
2755
2310
|
const soul = await soulQuestionnaire();
|
|
2756
2311
|
if (!soul) {
|
|
2757
|
-
p$
|
|
2312
|
+
p$3.cancel("Cancelled.");
|
|
2758
2313
|
return;
|
|
2759
2314
|
}
|
|
2760
2315
|
writeSoul(soul);
|
|
2761
2316
|
showSoulSummary(soul);
|
|
2762
|
-
p$
|
|
2763
|
-
p$
|
|
2317
|
+
p$3.log.success("Personality saved to ~/.claude/SOUL.md");
|
|
2318
|
+
p$3.outro(accent("Claude will use this personality next session 🐾"));
|
|
2764
2319
|
}
|
|
2765
2320
|
|
|
2766
2321
|
//#endregion
|
|
2767
2322
|
//#region src/commands/export.ts
|
|
2768
2323
|
async function exportCommand() {
|
|
2769
2324
|
showMini();
|
|
2770
|
-
p$
|
|
2325
|
+
p$2.intro(accent(" openpaw export "));
|
|
2771
2326
|
const claudeDir = path$1.join(os$1.homedir(), ".claude");
|
|
2772
2327
|
const bundle = {
|
|
2773
2328
|
version: "1",
|
|
@@ -2779,52 +2334,52 @@ async function exportCommand() {
|
|
|
2779
2334
|
};
|
|
2780
2335
|
const installed = listInstalledSkills();
|
|
2781
2336
|
bundle.skills = installed;
|
|
2782
|
-
p$
|
|
2337
|
+
p$2.log.info(`${installed.length} skills found`);
|
|
2783
2338
|
const settings = readSettings();
|
|
2784
2339
|
bundle.permissions = settings.permissions?.allow ?? [];
|
|
2785
2340
|
const soulPath = path$1.join(claudeDir, "SOUL.md");
|
|
2786
2341
|
if (fs$1.existsSync(soulPath)) {
|
|
2787
2342
|
bundle.soul = fs$1.readFileSync(soulPath, "utf-8");
|
|
2788
|
-
p$
|
|
2343
|
+
p$2.log.info("SOUL.md included");
|
|
2789
2344
|
}
|
|
2790
2345
|
const memoryDir = path$1.join(claudeDir, "memory");
|
|
2791
2346
|
if (fs$1.existsSync(memoryDir)) {
|
|
2792
2347
|
const files = fs$1.readdirSync(memoryDir).filter((f) => f.endsWith(".md"));
|
|
2793
2348
|
for (const file of files) bundle.memory[file] = fs$1.readFileSync(path$1.join(memoryDir, file), "utf-8");
|
|
2794
|
-
p$
|
|
2349
|
+
p$2.log.info(`${files.length} memory files included`);
|
|
2795
2350
|
}
|
|
2796
2351
|
const outputPath = path$1.resolve("openpaw-export.json");
|
|
2797
2352
|
fs$1.writeFileSync(outputPath, JSON.stringify(bundle, null, 2), "utf-8");
|
|
2798
|
-
p$
|
|
2799
|
-
p$
|
|
2353
|
+
p$2.log.success(`Exported to ${dim(outputPath)}`);
|
|
2354
|
+
p$2.outro(accent("Import on another machine: openpaw import openpaw-export.json"));
|
|
2800
2355
|
}
|
|
2801
2356
|
async function importCommand(file) {
|
|
2802
2357
|
showMini();
|
|
2803
|
-
p$
|
|
2358
|
+
p$2.intro(accent(" openpaw import "));
|
|
2804
2359
|
const filePath = path$1.resolve(file);
|
|
2805
2360
|
if (!fs$1.existsSync(filePath)) {
|
|
2806
|
-
p$
|
|
2361
|
+
p$2.log.error(`File not found: ${filePath}`);
|
|
2807
2362
|
process.exit(1);
|
|
2808
2363
|
}
|
|
2809
2364
|
let bundle;
|
|
2810
2365
|
try {
|
|
2811
2366
|
bundle = JSON.parse(fs$1.readFileSync(filePath, "utf-8"));
|
|
2812
2367
|
} catch {
|
|
2813
|
-
p$
|
|
2368
|
+
p$2.log.error("Invalid export file — must be valid JSON");
|
|
2814
2369
|
process.exit(1);
|
|
2815
2370
|
}
|
|
2816
|
-
p$
|
|
2817
|
-
p$
|
|
2818
|
-
const proceed = await p$
|
|
2371
|
+
p$2.log.info(`Export from ${dim(bundle.exportedAt)}`);
|
|
2372
|
+
p$2.log.info(`${bundle.skills.length} skills, ${Object.keys(bundle.memory).length} memory files`);
|
|
2373
|
+
const proceed = await p$2.confirm({
|
|
2819
2374
|
message: "Import this configuration?",
|
|
2820
2375
|
initialValue: true
|
|
2821
2376
|
});
|
|
2822
|
-
if (p$
|
|
2823
|
-
p$
|
|
2377
|
+
if (p$2.isCancel(proceed) || !proceed) {
|
|
2378
|
+
p$2.cancel("Import cancelled.");
|
|
2824
2379
|
return;
|
|
2825
2380
|
}
|
|
2826
2381
|
const claudeDir = path$1.join(os$1.homedir(), ".claude");
|
|
2827
|
-
const s = p$
|
|
2382
|
+
const s = p$2.spinner();
|
|
2828
2383
|
if (bundle.soul) {
|
|
2829
2384
|
s.start("🐾 Restoring SOUL.md...");
|
|
2830
2385
|
fs$1.mkdirSync(claudeDir, { recursive: true });
|
|
@@ -2839,7 +2394,7 @@ async function importCommand(file) {
|
|
|
2839
2394
|
s.stop(`🐾 ${Object.keys(bundle.memory).length} memory files restored`);
|
|
2840
2395
|
}
|
|
2841
2396
|
if (bundle.skills.length > 0) {
|
|
2842
|
-
const { installSkill: installSkill$1 } = await import("./skills-
|
|
2397
|
+
const { installSkill: installSkill$1 } = await import("./skills-CUY0swcW.js");
|
|
2843
2398
|
s.start("🐾 Reinstalling skills...");
|
|
2844
2399
|
const targetDir = getDefaultSkillsDir();
|
|
2845
2400
|
let count = 0;
|
|
@@ -2857,13 +2412,13 @@ async function importCommand(file) {
|
|
|
2857
2412
|
if (newPerms.length > 0) {
|
|
2858
2413
|
if (!settings.permissions) settings.permissions = {};
|
|
2859
2414
|
settings.permissions.allow = [...existing, ...newPerms];
|
|
2860
|
-
const { writeSettings: writeSettings$1 } = await import("./permissions-
|
|
2415
|
+
const { writeSettings: writeSettings$1 } = await import("./permissions-AJXigU7k.js");
|
|
2861
2416
|
writeSettings$1(settings);
|
|
2862
2417
|
}
|
|
2863
2418
|
s.stop(`🐾 ${newPerms.length} permissions added`);
|
|
2864
2419
|
}
|
|
2865
|
-
p$
|
|
2866
|
-
p$
|
|
2420
|
+
p$2.log.success("Import complete");
|
|
2421
|
+
p$2.outro(accent("Run openpaw status to verify 🐾"));
|
|
2867
2422
|
}
|
|
2868
2423
|
|
|
2869
2424
|
//#endregion
|
|
@@ -2872,40 +2427,287 @@ async function telegramCommand() {
|
|
|
2872
2427
|
showMini();
|
|
2873
2428
|
const config = readTelegramConfig();
|
|
2874
2429
|
if (!config) {
|
|
2875
|
-
p.log.error("Telegram not configured yet.");
|
|
2876
|
-
p.log.info(`Run ${bold("openpaw telegram setup")} or ${bold("openpaw setup")} first.`);
|
|
2430
|
+
p$1.log.error("Telegram not configured yet.");
|
|
2431
|
+
p$1.log.info(`Run ${bold("openpaw telegram setup")} or ${bold("openpaw setup")} first.`);
|
|
2877
2432
|
process.exit(1);
|
|
2878
2433
|
}
|
|
2879
2434
|
await startTelegramBot(config);
|
|
2880
2435
|
}
|
|
2881
2436
|
async function telegramSetupCommand() {
|
|
2882
2437
|
showMini();
|
|
2883
|
-
p.intro(accent(" Telegram Bridge Setup "));
|
|
2438
|
+
p$1.intro(accent(" Telegram Bridge Setup "));
|
|
2884
2439
|
if (telegramConfigExists()) {
|
|
2885
|
-
const overwrite = await p.confirm({
|
|
2440
|
+
const overwrite = await p$1.confirm({
|
|
2886
2441
|
message: "Telegram is already configured. Reconfigure?",
|
|
2887
2442
|
initialValue: false
|
|
2888
2443
|
});
|
|
2889
|
-
if (p.isCancel(overwrite) || !overwrite) {
|
|
2890
|
-
p.outro("Keeping existing config. 🐾");
|
|
2444
|
+
if (p$1.isCancel(overwrite) || !overwrite) {
|
|
2445
|
+
p$1.outro("Keeping existing config. 🐾");
|
|
2891
2446
|
return;
|
|
2892
2447
|
}
|
|
2893
2448
|
}
|
|
2894
2449
|
const config = await telegramQuestionnaire();
|
|
2895
2450
|
if (!config) {
|
|
2896
|
-
p.cancel("Setup cancelled.");
|
|
2451
|
+
p$1.cancel("Setup cancelled.");
|
|
2897
2452
|
process.exit(0);
|
|
2898
2453
|
}
|
|
2899
2454
|
writeTelegramConfig(config);
|
|
2900
|
-
p.log.success("Telegram config saved!");
|
|
2901
|
-
p.log.info(`Start the bridge with: ${bold("openpaw telegram")}`);
|
|
2902
|
-
p.outro(accent("Telegram setup complete 🐾"));
|
|
2455
|
+
p$1.log.success("Telegram config saved!");
|
|
2456
|
+
p$1.log.info(`Start the bridge with: ${bold("openpaw telegram")}`);
|
|
2457
|
+
p$1.outro(accent("Telegram setup complete 🐾"));
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
//#endregion
|
|
2461
|
+
//#region src/commands/dashboard.ts
|
|
2462
|
+
function dashboardCommand(opts) {
|
|
2463
|
+
const port = opts.port ? Number.parseInt(opts.port, 10) : void 0;
|
|
2464
|
+
const theme = opts.theme && (opts.theme === "paw" || opts.theme === "midnight" || opts.theme === "neon") ? opts.theme : void 0;
|
|
2465
|
+
startDashboard({
|
|
2466
|
+
port,
|
|
2467
|
+
theme
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
//#endregion
|
|
2472
|
+
//#region src/commands/schedule.ts
|
|
2473
|
+
async function scheduleAddCommand(schedule, opts) {
|
|
2474
|
+
showMini();
|
|
2475
|
+
console.log("");
|
|
2476
|
+
let scheduleStr;
|
|
2477
|
+
let prompt;
|
|
2478
|
+
let model;
|
|
2479
|
+
let budgetUsd;
|
|
2480
|
+
let deliveryType;
|
|
2481
|
+
if (opts.run && schedule) {
|
|
2482
|
+
scheduleStr = schedule;
|
|
2483
|
+
prompt = opts.run;
|
|
2484
|
+
model = opts.model || "sonnet";
|
|
2485
|
+
budgetUsd = opts.budget ? Number.parseFloat(opts.budget) : 1;
|
|
2486
|
+
deliveryType = opts.delivery || "file";
|
|
2487
|
+
} else {
|
|
2488
|
+
p.intro(accent("Let's schedule a new job! 🐾"));
|
|
2489
|
+
const schedInput = await p.text({
|
|
2490
|
+
message: "When should this run?",
|
|
2491
|
+
placeholder: "e.g. \"weekdays 8am\", \"daily 9pm\", \"every 30 minutes\"",
|
|
2492
|
+
validate: (v) => v.length === 0 ? "Schedule is required" : void 0
|
|
2493
|
+
});
|
|
2494
|
+
if (p.isCancel(schedInput)) return;
|
|
2495
|
+
scheduleStr = schedInput;
|
|
2496
|
+
const promptInput = await p.text({
|
|
2497
|
+
message: "What should Claude do?",
|
|
2498
|
+
placeholder: "e.g. check my email and summarize the important ones",
|
|
2499
|
+
validate: (v) => v.length === 0 ? "Prompt is required" : void 0
|
|
2500
|
+
});
|
|
2501
|
+
if (p.isCancel(promptInput)) return;
|
|
2502
|
+
prompt = promptInput;
|
|
2503
|
+
const deliveryOptions = [{
|
|
2504
|
+
value: "file",
|
|
2505
|
+
label: "Save to file",
|
|
2506
|
+
hint: "~/.config/openpaw/schedule-results/"
|
|
2507
|
+
}];
|
|
2508
|
+
if (telegramConfigExists()) deliveryOptions.unshift({
|
|
2509
|
+
value: "telegram",
|
|
2510
|
+
label: "Telegram",
|
|
2511
|
+
hint: "send to your phone"
|
|
2512
|
+
});
|
|
2513
|
+
deliveryOptions.push({
|
|
2514
|
+
value: "notify",
|
|
2515
|
+
label: "macOS Notification",
|
|
2516
|
+
hint: "requires terminal-notifier"
|
|
2517
|
+
});
|
|
2518
|
+
const deliveryChoice = await p.select({
|
|
2519
|
+
message: "Where should results be delivered?",
|
|
2520
|
+
options: deliveryOptions
|
|
2521
|
+
});
|
|
2522
|
+
if (p.isCancel(deliveryChoice)) return;
|
|
2523
|
+
deliveryType = deliveryChoice;
|
|
2524
|
+
const modelChoice = await p.select({
|
|
2525
|
+
message: "Which model?",
|
|
2526
|
+
options: [
|
|
2527
|
+
{
|
|
2528
|
+
value: "sonnet",
|
|
2529
|
+
label: "Sonnet",
|
|
2530
|
+
hint: "fast, good for routine tasks ($)"
|
|
2531
|
+
},
|
|
2532
|
+
{
|
|
2533
|
+
value: "haiku",
|
|
2534
|
+
label: "Haiku",
|
|
2535
|
+
hint: "fastest, cheapest (¢)"
|
|
2536
|
+
},
|
|
2537
|
+
{
|
|
2538
|
+
value: "opus",
|
|
2539
|
+
label: "Opus",
|
|
2540
|
+
hint: "most capable, expensive ($$$)"
|
|
2541
|
+
}
|
|
2542
|
+
]
|
|
2543
|
+
});
|
|
2544
|
+
if (p.isCancel(modelChoice)) return;
|
|
2545
|
+
model = modelChoice;
|
|
2546
|
+
const budgetInput = await p.text({
|
|
2547
|
+
message: "Per-run budget cap (USD)?",
|
|
2548
|
+
placeholder: "1.00",
|
|
2549
|
+
defaultValue: "1.00",
|
|
2550
|
+
validate: (v) => {
|
|
2551
|
+
const n = Number.parseFloat(v);
|
|
2552
|
+
if (Number.isNaN(n) || n <= 0) return "Must be a positive number";
|
|
2553
|
+
return void 0;
|
|
2554
|
+
}
|
|
2555
|
+
});
|
|
2556
|
+
if (p.isCancel(budgetInput)) return;
|
|
2557
|
+
budgetUsd = Number.parseFloat(budgetInput);
|
|
2558
|
+
}
|
|
2559
|
+
const parsed = parseHumanSchedule(scheduleStr);
|
|
2560
|
+
const job = addJob({
|
|
2561
|
+
name: prompt.slice(0, 60),
|
|
2562
|
+
prompt,
|
|
2563
|
+
schedule: parsed.cron,
|
|
2564
|
+
scheduleHuman: parsed.human,
|
|
2565
|
+
enabled: true,
|
|
2566
|
+
model,
|
|
2567
|
+
maxBudgetUsd: budgetUsd,
|
|
2568
|
+
delivery: { type: deliveryType }
|
|
2569
|
+
});
|
|
2570
|
+
const installed = installSystemJob(job);
|
|
2571
|
+
console.log("");
|
|
2572
|
+
p.log.success(`Job created: ${accent(job.id)}`);
|
|
2573
|
+
p.log.info(` Schedule: ${bold(parsed.human)} (${dim(parsed.cron)})`);
|
|
2574
|
+
p.log.info(` Prompt: ${dim(prompt.slice(0, 80))}`);
|
|
2575
|
+
p.log.info(` Model: ${model}`);
|
|
2576
|
+
p.log.info(` Budget: $${budgetUsd.toFixed(2)}/run`);
|
|
2577
|
+
p.log.info(` Delivery: ${deliveryType}`);
|
|
2578
|
+
if (installed) p.log.success(process.platform === "darwin" ? "Registered with launchd (runs even when terminal is closed)" : "Added to crontab");
|
|
2579
|
+
else p.log.warn("Could not register system job. Run manually with: openpaw schedule run " + job.id);
|
|
2580
|
+
console.log("");
|
|
2581
|
+
p.log.info(dim(`Test it now: ${accent("openpaw schedule run " + job.id)}`));
|
|
2582
|
+
}
|
|
2583
|
+
async function scheduleListCommand() {
|
|
2584
|
+
showMini();
|
|
2585
|
+
console.log("");
|
|
2586
|
+
const jobs = listJobs();
|
|
2587
|
+
if (jobs.length === 0) {
|
|
2588
|
+
p.log.info("No scheduled jobs yet. Create one with:");
|
|
2589
|
+
p.log.info(accent(" openpaw schedule add \"weekdays 8am\" --run \"check email\""));
|
|
2590
|
+
return;
|
|
2591
|
+
}
|
|
2592
|
+
const config = readScheduleConfig();
|
|
2593
|
+
const todayCost = getTodaysCost();
|
|
2594
|
+
console.log(` ${bold("Scheduled Jobs")} ${dim(`(daily cap: $${config.dailyCostCapUsd.toFixed(2)}, today: $${todayCost.toFixed(2)})`)}`);
|
|
2595
|
+
console.log("");
|
|
2596
|
+
for (const job of jobs) {
|
|
2597
|
+
const status = job.enabled ? chalk.green("ON ") : chalk.red("OFF");
|
|
2598
|
+
const lastRun = job.lastRunAt ? dim(` last: ${new Date(job.lastRunAt).toLocaleDateString()} ${job.lastRunResult || ""}`) : "";
|
|
2599
|
+
const cost = job.lastRunCostUsd ? dim(` $${job.lastRunCostUsd.toFixed(3)}`) : "";
|
|
2600
|
+
console.log(` ${status} ${accent(job.id)} ${bold(job.scheduleHuman)}`);
|
|
2601
|
+
console.log(` ${dim(job.prompt.slice(0, 70))}${lastRun}${cost}`);
|
|
2602
|
+
console.log(` ${dim(`model: ${job.model} | budget: $${job.maxBudgetUsd.toFixed(2)} | delivery: ${job.delivery.type}`)}`);
|
|
2603
|
+
console.log("");
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
async function scheduleRemoveCommand(id) {
|
|
2607
|
+
showMini();
|
|
2608
|
+
console.log("");
|
|
2609
|
+
if (!id) {
|
|
2610
|
+
p.log.error("Usage: openpaw schedule remove <id>");
|
|
2611
|
+
return;
|
|
2612
|
+
}
|
|
2613
|
+
const removed = removeJob(id);
|
|
2614
|
+
if (removed) p.log.success(`Job ${accent(id)} removed and unregistered from system scheduler.`);
|
|
2615
|
+
else p.log.error(`Job ${id} not found.`);
|
|
2616
|
+
}
|
|
2617
|
+
async function scheduleRunCommand(id) {
|
|
2618
|
+
if (!id) {
|
|
2619
|
+
p.log.error("Usage: openpaw schedule run <id>");
|
|
2620
|
+
return;
|
|
2621
|
+
}
|
|
2622
|
+
const isInteractive = process.stdout.isTTY;
|
|
2623
|
+
if (isInteractive) {
|
|
2624
|
+
showMini();
|
|
2625
|
+
console.log("");
|
|
2626
|
+
const s = p.spinner();
|
|
2627
|
+
s.start(`Running job ${accent(id)}...`);
|
|
2628
|
+
const result = await runJob(id);
|
|
2629
|
+
if (result.success) {
|
|
2630
|
+
s.stop(`Job completed! Cost: $${(result.costUsd || 0).toFixed(3)}`);
|
|
2631
|
+
if (result.result) {
|
|
2632
|
+
console.log("");
|
|
2633
|
+
console.log(dim(" ─── Result ───"));
|
|
2634
|
+
console.log("");
|
|
2635
|
+
const lines = result.result.split("\n").slice(0, 20);
|
|
2636
|
+
for (const line of lines) console.log(` ${line}`);
|
|
2637
|
+
if (result.result.split("\n").length > 20) console.log(dim(" ... (truncated)"));
|
|
2638
|
+
}
|
|
2639
|
+
} else s.stop(`Job failed: ${result.error}`);
|
|
2640
|
+
} else {
|
|
2641
|
+
const result = await runJob(id);
|
|
2642
|
+
if (!result.success) {
|
|
2643
|
+
console.error(`[openpaw] Job ${id} failed: ${result.error}`);
|
|
2644
|
+
process.exit(1);
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
async function scheduleToggleCommand(id, enabled) {
|
|
2649
|
+
showMini();
|
|
2650
|
+
console.log("");
|
|
2651
|
+
if (!id) {
|
|
2652
|
+
p.log.error(`Usage: openpaw schedule ${enabled ? "enable" : "disable"} <id>`);
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
2655
|
+
const ok = toggleJob(id, enabled);
|
|
2656
|
+
if (ok) p.log.success(`Job ${accent(id)} ${enabled ? "enabled" : "disabled"}.`);
|
|
2657
|
+
else p.log.error(`Job ${id} not found.`);
|
|
2658
|
+
}
|
|
2659
|
+
async function scheduleCostsCommand() {
|
|
2660
|
+
showMini();
|
|
2661
|
+
console.log("");
|
|
2662
|
+
const config = readScheduleConfig();
|
|
2663
|
+
const tracker = readCostTracker();
|
|
2664
|
+
const todayCost = getTodaysCost();
|
|
2665
|
+
const cap = config.dailyCostCapUsd;
|
|
2666
|
+
const pct = cap > 0 ? Math.round(todayCost / cap * 100) : 0;
|
|
2667
|
+
console.log(` ${bold("Cost Tracker")}`);
|
|
2668
|
+
console.log("");
|
|
2669
|
+
console.log(` Today: ${accent(`$${todayCost.toFixed(3)}`)} / $${cap.toFixed(2)} (${pct}%)`);
|
|
2670
|
+
console.log("");
|
|
2671
|
+
const days = Object.entries(tracker.dailyTotals).sort(([a], [b]) => b.localeCompare(a)).slice(0, 7);
|
|
2672
|
+
if (days.length > 0) {
|
|
2673
|
+
console.log(` ${dim("Recent days:")}`);
|
|
2674
|
+
for (const [date, cost] of days) {
|
|
2675
|
+
const bar = "█".repeat(Math.ceil(cost / cap * 20));
|
|
2676
|
+
console.log(` ${dim(date)} $${cost.toFixed(3)} ${accent(bar)}`);
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
console.log("");
|
|
2680
|
+
const todayStr = new Date().toISOString().slice(0, 10);
|
|
2681
|
+
const todayEntries = tracker.entries.filter((e) => e.date === todayStr);
|
|
2682
|
+
if (todayEntries.length > 0) {
|
|
2683
|
+
console.log(` ${dim("Today's runs:")}`);
|
|
2684
|
+
for (const entry of todayEntries) {
|
|
2685
|
+
const time = new Date(entry.timestamp).toLocaleTimeString();
|
|
2686
|
+
console.log(` ${dim(time)} ${entry.jobId} $${entry.costUsd.toFixed(3)}`);
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
console.log("");
|
|
2690
|
+
p.log.info(dim(`Daily cap: openpaw schedule set-cap <usd>`));
|
|
2691
|
+
}
|
|
2692
|
+
async function scheduleSetCapCommand(amount) {
|
|
2693
|
+
showMini();
|
|
2694
|
+
console.log("");
|
|
2695
|
+
const usd = Number.parseFloat(amount);
|
|
2696
|
+
if (Number.isNaN(usd) || usd <= 0) {
|
|
2697
|
+
p.log.error("Amount must be a positive number (e.g. 5.00)");
|
|
2698
|
+
return;
|
|
2699
|
+
}
|
|
2700
|
+
const config = readScheduleConfig();
|
|
2701
|
+
config.dailyCostCapUsd = usd;
|
|
2702
|
+
const { writeScheduleConfig } = await import("./scheduler-DppXPNqK.js");
|
|
2703
|
+
writeScheduleConfig(config);
|
|
2704
|
+
p.log.success(`Daily cost cap set to ${accent(`$${usd.toFixed(2)}`)}`);
|
|
2903
2705
|
}
|
|
2904
2706
|
|
|
2905
2707
|
//#endregion
|
|
2906
2708
|
//#region src/index.ts
|
|
2907
2709
|
const program = new Command();
|
|
2908
|
-
program.name("openpaw").description("Personal Assistant Wizard for Claude Code").version("1.
|
|
2710
|
+
program.name("openpaw").description("Personal Assistant Wizard for Claude Code").version("1.2.0");
|
|
2909
2711
|
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);
|
|
2910
2712
|
program.command("add").description("Add skill(s) by name").argument("<skills...>", "Skill IDs to add (e.g. notes music email)").action(addCommand);
|
|
2911
2713
|
program.command("remove").description("Remove skill(s) by name").argument("<skills...>", "Skill IDs to remove").action(removeCommand);
|
|
@@ -2917,9 +2719,19 @@ program.command("reset").description("Remove all OpenPaw skills, permissions, an
|
|
|
2917
2719
|
program.command("soul").description("Set up or edit Claude's personality (SOUL.md)").action(soulCommand);
|
|
2918
2720
|
program.command("export").description("Export skills, memory, and config to a file").action(exportCommand);
|
|
2919
2721
|
program.command("import").description("Import skills, memory, and config from a file").argument("<file>", "Path to openpaw-export.json").action(importCommand);
|
|
2722
|
+
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);
|
|
2920
2723
|
const tg = program.command("telegram").description("Start the Telegram bridge — talk to Claude from your phone");
|
|
2921
2724
|
tg.action(telegramCommand);
|
|
2922
2725
|
tg.command("setup").description("Set up or reconfigure the Telegram bot").action(telegramSetupCommand);
|
|
2726
|
+
const sched = program.command("schedule").description("Manage scheduled jobs — automate recurring tasks with cost control");
|
|
2727
|
+
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));
|
|
2728
|
+
sched.command("list").alias("ls").description("List all scheduled jobs").action(() => scheduleListCommand());
|
|
2729
|
+
sched.command("remove <id>").alias("rm").description("Remove a scheduled job").action((id) => scheduleRemoveCommand(id));
|
|
2730
|
+
sched.command("run <id>").description("Manually trigger a scheduled job").action((id) => scheduleRunCommand(id));
|
|
2731
|
+
sched.command("enable <id>").description("Enable a scheduled job").action((id) => scheduleToggleCommand(id, true));
|
|
2732
|
+
sched.command("disable <id>").description("Disable a scheduled job").action((id) => scheduleToggleCommand(id, false));
|
|
2733
|
+
sched.command("costs").description("Show today's cost usage and daily cap").action(() => scheduleCostsCommand());
|
|
2734
|
+
sched.command("set-cap <usd>").description("Set the daily cost cap in USD").action((usd) => scheduleSetCapCommand(usd));
|
|
2923
2735
|
program.parse();
|
|
2924
2736
|
|
|
2925
2737
|
//#endregion
|