pawmode 1.0.1 → 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 +224 -66
- package/dist/dashboard-server--wwlA0Pa.js +426 -0
- package/dist/dashboard-server-Cg_1CvKn.js +3 -0
- package/dist/index.js +668 -831
- package/dist/permissions-AJXigU7k.js +3 -0
- package/dist/scheduler-DAmd0GzB.js +888 -0
- package/dist/scheduler-DppXPNqK.js +4 -0
- package/dist/{skills-DwMXaN3R.js → skills-CUY0swcW.js} +1 -1
- package/package.json +1 -1
- package/skills/c-clipboard/SKILL.md +53 -0
- package/skills/c-contacts/SKILL.md +63 -0
- package/skills/c-core/SKILL.md +17 -1
- package/skills/c-memory/SKILL.md +32 -0
- package/skills/c-obsidian/SKILL.md +22 -3
- package/skills/c-schedule/SKILL.md +98 -0
- package/skills/c-timer/SKILL.md +59 -0
- package/skills/c-video-edit/SKILL.md +147 -0
- package/skills/c-weather/SKILL.md +61 -0
- package/dist/permissions-CoaVX2ZM.js +0 -3
- /package/dist/{permissions-BHOAvP8i.js → permissions-BlGEHCXO.js} +0 -0
- /package/dist/{skills-CJ_pyPlv.js → skills-CMqq9k1-.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,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,19 +23,14 @@ 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 = {
|
|
@@ -335,6 +330,24 @@ const jiraCli = {
|
|
|
335
330
|
installMethod: "brew",
|
|
336
331
|
platforms: ["darwin", "linux"]
|
|
337
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
|
+
};
|
|
338
351
|
const agentBrowser = {
|
|
339
352
|
name: "agent-browser",
|
|
340
353
|
command: "agent-browser",
|
|
@@ -454,6 +467,17 @@ const aichat = {
|
|
|
454
467
|
"win32"
|
|
455
468
|
]
|
|
456
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
|
+
};
|
|
457
481
|
const skills = [
|
|
458
482
|
{
|
|
459
483
|
id: "notes",
|
|
@@ -660,6 +684,18 @@ const skills = [
|
|
|
660
684
|
"win32"
|
|
661
685
|
]
|
|
662
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
|
+
},
|
|
663
699
|
{
|
|
664
700
|
id: "screen",
|
|
665
701
|
name: "Screen & Vision",
|
|
@@ -713,6 +749,18 @@ const skills = [
|
|
|
713
749
|
tools: [blucli],
|
|
714
750
|
platforms: ["darwin"]
|
|
715
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
|
+
},
|
|
716
764
|
{
|
|
717
765
|
id: "research",
|
|
718
766
|
name: "Web Research",
|
|
@@ -825,6 +873,14 @@ const skills = [
|
|
|
825
873
|
description: "Configure Jira instance"
|
|
826
874
|
}]
|
|
827
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
|
+
},
|
|
828
884
|
{
|
|
829
885
|
id: "briefing",
|
|
830
886
|
name: "Daily Briefing",
|
|
@@ -878,6 +934,30 @@ const skills = [
|
|
|
878
934
|
tools: [lunchyGo],
|
|
879
935
|
platforms: ["darwin"]
|
|
880
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
|
+
},
|
|
881
961
|
{
|
|
882
962
|
id: "system",
|
|
883
963
|
name: "System Control",
|
|
@@ -1001,12 +1081,16 @@ const presets = [
|
|
|
1001
1081
|
{
|
|
1002
1082
|
id: "essentials",
|
|
1003
1083
|
name: "Essentials",
|
|
1004
|
-
description: "Email, calendar, notes, music, browser, system",
|
|
1084
|
+
description: "Email, calendar, notes, music, weather, clipboard, browser, system",
|
|
1005
1085
|
skillIds: [
|
|
1006
1086
|
"email",
|
|
1007
1087
|
"calendar",
|
|
1008
1088
|
"notes",
|
|
1009
1089
|
"music",
|
|
1090
|
+
"weather",
|
|
1091
|
+
"clipboard",
|
|
1092
|
+
"contacts",
|
|
1093
|
+
"timer",
|
|
1010
1094
|
"browser",
|
|
1011
1095
|
"system",
|
|
1012
1096
|
"notify"
|
|
@@ -1149,163 +1233,6 @@ function isToolInstalled(command) {
|
|
|
1149
1233
|
return commandExists(command);
|
|
1150
1234
|
}
|
|
1151
1235
|
|
|
1152
|
-
//#endregion
|
|
1153
|
-
//#region src/core/branding.ts
|
|
1154
|
-
const accent = chalk.hex("#b4783c");
|
|
1155
|
-
const subtle = chalk.hex("#8a5a2a");
|
|
1156
|
-
const dim = chalk.dim;
|
|
1157
|
-
const bold = chalk.bold;
|
|
1158
|
-
const pawClr = chalk.hex("#b4783c");
|
|
1159
|
-
const PAW_ART = [
|
|
1160
|
-
" ▃▅",
|
|
1161
|
-
" ▁██▁ ▄█▁",
|
|
1162
|
-
" ▁▁▂▆▇██▃ ▅█▆",
|
|
1163
|
-
" ▅▆█▇▆▄███▆▂ ▁▂▆▇██▇▅▁",
|
|
1164
|
-
" ▁▃█▆▁ ▄███▄▁ ▆▇▇▅▄▄███▇▃▁",
|
|
1165
|
-
" ▁▃█▄ ▁████▂ ▁▅█▃ ▁▃████▂",
|
|
1166
|
-
" ▄█▇▁ ▂████▄▁ ▁▃█▇ ▁████▅▂",
|
|
1167
|
-
" ▅█▆ ▂█████▁ ▄█▇▁ ▂████▃",
|
|
1168
|
-
" ▂▁ ▆█▆ ▂█████▁ ▄█▇ ▁▂████▂",
|
|
1169
|
-
" ▁▅█▂ ▆█▆▁ ▁▃▇████▆▁ ▄█▇ ▂▄█████▂",
|
|
1170
|
-
" ▁██▄▂ ▂██▇▆▃▄▇██████ ▄██▄▇▇▃▃▆█████▁",
|
|
1171
|
-
" ▂█████▇▅▂▁ ▁▄▇█████████▆▂ ▃▇████████████▁ ▁▂▁",
|
|
1172
|
-
" ▄█▇▃ ▁███▆▁ ▁▃▆▇▇▇▇▃▄▂ ▂▆█████████▅ ▁▇█▂",
|
|
1173
|
-
" ▃▆▄▁ ▅███▆▃ ▄▄▅▅▅▅▂▁ ▂▄▃▄██▂",
|
|
1174
|
-
" ▆█▄ ▃████▄ ▁▄▄██████▄▃▃▂ ▁▂▆▇▇▆████▇▁",
|
|
1175
|
-
" ▆█▄ ▂████▄ ▂▅▇▇▅▅▁▃▅█▇████▆▂ ▅▇▇▅▄▁ ▂▆████▂",
|
|
1176
|
-
" ▇█▄ ▅████▄ ▇█▄ ▁▃▇▆████▇▂ ▂▃█▇▃ ▁ ▃███▂",
|
|
1177
|
-
" ▇██▅ ▂▆████▄▁ ▆█▄▁ ▁ ▅▄▅█████▅ ▆█▆▂ ▃▆███▁",
|
|
1178
|
-
" ▁████████████▃ ▂▇▆▂ ▄█████▆ ▃██▂ ▂▆███▆",
|
|
1179
|
-
" ▃█████████▅▂ ██▂ ▄█████▆ ▅██▁ ▁████▇",
|
|
1180
|
-
" ▂▄▄▄▄▄▄ ▇█▃▁ ▁▅█████▅ ▃██▄▇▆▁▁▄▇████▆",
|
|
1181
|
-
" ▁▁▁▇█▂▁ ▁ ▄██████▆▂ ▇███████████▅▁",
|
|
1182
|
-
" ▁▁▄▅▆██▅▂ ▁▁ ▄███████▄ ▄▅██████▆▅▂",
|
|
1183
|
-
" ▄██▆▅▄▂▁ ▁ ▃▆██████▇▄▂ ▁▁▁▁▁▁▁",
|
|
1184
|
-
" ▂██▄▁▁ ▁▄▅██████▇▆▂▁",
|
|
1185
|
-
" ▃▇▇▂ ▁▁ ▁ ▃▇██████▇▃▁",
|
|
1186
|
-
" ▁▄█▆ ▁ ▁▆█▇█████▆▂",
|
|
1187
|
-
" ▄██▇ ▃▅███████▅▁",
|
|
1188
|
-
" ▄██▇▁ ▂▂▂▁▁▂▆▇▇▇▇▇▃▁ ▄▆▄▂▇█████▃",
|
|
1189
|
-
" ▃▇██▇▃ ▁▂▆▆██▆▇█████████▆▄▁▁ ▁▁▁▄▇██████▃",
|
|
1190
|
-
" ▄█████▆▆▇███████████████████▆▃▁ ▁▄▇███████▃",
|
|
1191
|
-
" ▁▅█████████████████████████████▅█████████▄▁",
|
|
1192
|
-
" ▁▂▆█████████████████████████████████████▂",
|
|
1193
|
-
" ▁▂▆▆▆▆▆▆▃▂▂▂▂▂▂▂▂▂▂▂▂▂▅▆███████████▇▆▁",
|
|
1194
|
-
" ▃▃▇▇▇▇▇▇▇▃▃"
|
|
1195
|
-
];
|
|
1196
|
-
const PAW_ROWS = PAW_ART.length;
|
|
1197
|
-
function sleep(ms) {
|
|
1198
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
1199
|
-
}
|
|
1200
|
-
function renderBox(title, subtitle) {
|
|
1201
|
-
const boxW = 48;
|
|
1202
|
-
const center = (s, w) => {
|
|
1203
|
-
const pad = w - s.length;
|
|
1204
|
-
const left = Math.floor(pad / 2);
|
|
1205
|
-
return " ".repeat(left) + s + " ".repeat(pad - left);
|
|
1206
|
-
};
|
|
1207
|
-
const margin = " ";
|
|
1208
|
-
const lines = [
|
|
1209
|
-
pawClr(margin + "┌" + "─".repeat(boxW) + "┐"),
|
|
1210
|
-
pawClr(margin + "│" + " ".repeat(boxW) + "│"),
|
|
1211
|
-
pawClr(margin + "│" + center(title, boxW) + "│"),
|
|
1212
|
-
dim(margin + "│" + center(subtitle, boxW) + "│"),
|
|
1213
|
-
pawClr(margin + "│" + " ".repeat(boxW) + "│"),
|
|
1214
|
-
pawClr(margin + "└" + "─".repeat(boxW) + "┘")
|
|
1215
|
-
];
|
|
1216
|
-
return lines.join("\n");
|
|
1217
|
-
}
|
|
1218
|
-
const MOOD_HEX = {
|
|
1219
|
-
wave: "#b4783c",
|
|
1220
|
-
think: "#b4783c",
|
|
1221
|
-
happy: "#b4783c",
|
|
1222
|
-
work: "#9a6832",
|
|
1223
|
-
done: "#c88a48",
|
|
1224
|
-
warn: "#dca03c"
|
|
1225
|
-
};
|
|
1226
|
-
function pawColor(mood) {
|
|
1227
|
-
return chalk.hex(MOOD_HEX[mood]);
|
|
1228
|
-
}
|
|
1229
|
-
function renderPaw(color) {
|
|
1230
|
-
return PAW_ART.map((line) => color(line)).join("\n");
|
|
1231
|
-
}
|
|
1232
|
-
/**
|
|
1233
|
-
* Animated banner: fade in paw → pulse → title box.
|
|
1234
|
-
*/
|
|
1235
|
-
async function showBanner() {
|
|
1236
|
-
process.stdout.write("\x1B[?25l");
|
|
1237
|
-
process.stdout.write(renderPaw(chalk.hex("#3d2810")) + "\n");
|
|
1238
|
-
await sleep(60);
|
|
1239
|
-
process.stdout.write(`\x1B[${PAW_ROWS}A\x1B[J`);
|
|
1240
|
-
process.stdout.write(renderPaw(chalk.hex("#7a501e")) + "\n");
|
|
1241
|
-
await sleep(60);
|
|
1242
|
-
process.stdout.write(`\x1B[${PAW_ROWS}A\x1B[J`);
|
|
1243
|
-
process.stdout.write(renderPaw(pawClr) + "\n");
|
|
1244
|
-
await sleep(60);
|
|
1245
|
-
process.stdout.write(`\x1B[${PAW_ROWS}A\x1B[J`);
|
|
1246
|
-
process.stdout.write(renderPaw(chalk.hex("#d4984c")) + "\n");
|
|
1247
|
-
await sleep(80);
|
|
1248
|
-
process.stdout.write(`\x1B[${PAW_ROWS}A\x1B[J`);
|
|
1249
|
-
process.stdout.write(renderPaw(pawClr) + "\n");
|
|
1250
|
-
process.stdout.write("\x1B[?25h");
|
|
1251
|
-
console.log("");
|
|
1252
|
-
console.log(renderBox("O P E N P A W", "Personal Assistant Wizard for Claude Code"));
|
|
1253
|
-
console.log("");
|
|
1254
|
-
}
|
|
1255
|
-
/**
|
|
1256
|
-
* Show paw between wizard steps — mood-colored, brief flash, then clears.
|
|
1257
|
-
*/
|
|
1258
|
-
async function pawStep(mood, message) {
|
|
1259
|
-
const color = pawColor(mood);
|
|
1260
|
-
process.stdout.write(renderPaw(color) + "\n");
|
|
1261
|
-
if (message) console.log(` ${accent(message)}`);
|
|
1262
|
-
await sleep(300);
|
|
1263
|
-
const lines = PAW_ROWS + (message ? 1 : 0);
|
|
1264
|
-
process.stdout.write(`\x1B[${lines}A\x1B[J`);
|
|
1265
|
-
}
|
|
1266
|
-
/**
|
|
1267
|
-
* Inline pulse indicator for quick transitions.
|
|
1268
|
-
*/
|
|
1269
|
-
async function pawPulse(mood, message) {
|
|
1270
|
-
if (!message) return;
|
|
1271
|
-
const line = ` ${accent("◉")} ${subtle(message)}`;
|
|
1272
|
-
for (let i = 0; i < 3; i++) {
|
|
1273
|
-
if (i > 0) process.stdout.write("\x1B[1A");
|
|
1274
|
-
const s = i % 2 === 0 ? bold : dim;
|
|
1275
|
-
process.stdout.write(`\x1B[2K${s(line)}\n`);
|
|
1276
|
-
await sleep(80);
|
|
1277
|
-
}
|
|
1278
|
-
process.stdout.write(`\x1B[1A\x1B[2K${line}\n`);
|
|
1279
|
-
}
|
|
1280
|
-
/**
|
|
1281
|
-
* Mini one-liner.
|
|
1282
|
-
*/
|
|
1283
|
-
function showMini() {
|
|
1284
|
-
console.log(accent(" ◉ openpaw") + dim(" — Personal Assistant Wizard for Claude Code"));
|
|
1285
|
-
}
|
|
1286
|
-
/**
|
|
1287
|
-
* Puppy disclaimer about --dangerously-skip-permissions.
|
|
1288
|
-
*/
|
|
1289
|
-
function showPuppyDisclaimer() {
|
|
1290
|
-
console.log("");
|
|
1291
|
-
console.log(pawClr(" /\\_/\\"));
|
|
1292
|
-
console.log(pawClr(" ( o.o )") + ` ${bold("WOOF! One important sniff...")}`);
|
|
1293
|
-
console.log(pawClr(" > ^ <"));
|
|
1294
|
-
console.log("");
|
|
1295
|
-
console.log(` ${accent("You're about to let Claude off the leash!")}`);
|
|
1296
|
-
console.log(dim(" (--dangerously-skip-permissions)"));
|
|
1297
|
-
console.log("");
|
|
1298
|
-
console.log(" This lets Claude run commands without asking each time.");
|
|
1299
|
-
console.log(" It's how your assistant actually gets things done —");
|
|
1300
|
-
console.log(" checking email, playing music, managing files.");
|
|
1301
|
-
console.log("");
|
|
1302
|
-
console.log(dim(" OpenPaw's safety hooks still block the dangerous stuff"));
|
|
1303
|
-
console.log(dim(" (mass deletes, credential leaks, etc)."));
|
|
1304
|
-
console.log("");
|
|
1305
|
-
console.log(dim(" You can always run 'claude' normally without this."));
|
|
1306
|
-
console.log("");
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
1236
|
//#endregion
|
|
1310
1237
|
//#region src/core/installer.ts
|
|
1311
1238
|
function getMissingTools(tools) {
|
|
@@ -1436,141 +1363,27 @@ function removeSafetyHooks() {
|
|
|
1436
1363
|
}
|
|
1437
1364
|
}
|
|
1438
1365
|
|
|
1439
|
-
//#endregion
|
|
1440
|
-
//#region src/core/mcp.ts
|
|
1441
|
-
const mcpServers = [
|
|
1442
|
-
{
|
|
1443
|
-
id: "filesystem",
|
|
1444
|
-
name: "Filesystem",
|
|
1445
|
-
description: "Read, write, search, and manage files with advanced operations",
|
|
1446
|
-
command: "npx",
|
|
1447
|
-
args: [
|
|
1448
|
-
"-y",
|
|
1449
|
-
"@modelcontextprotocol/server-filesystem",
|
|
1450
|
-
os$6.homedir()
|
|
1451
|
-
],
|
|
1452
|
-
category: "system"
|
|
1453
|
-
},
|
|
1454
|
-
{
|
|
1455
|
-
id: "fetch",
|
|
1456
|
-
name: "Fetch",
|
|
1457
|
-
description: "Fetch and convert web content to markdown for analysis",
|
|
1458
|
-
command: "npx",
|
|
1459
|
-
args: ["-y", "@modelcontextprotocol/server-fetch"],
|
|
1460
|
-
category: "research"
|
|
1461
|
-
},
|
|
1462
|
-
{
|
|
1463
|
-
id: "memory",
|
|
1464
|
-
name: "Memory (KG)",
|
|
1465
|
-
description: "Persistent knowledge graph memory — entities, relations, observations",
|
|
1466
|
-
command: "npx",
|
|
1467
|
-
args: ["-y", "@modelcontextprotocol/server-memory"],
|
|
1468
|
-
category: "productivity"
|
|
1469
|
-
},
|
|
1470
|
-
{
|
|
1471
|
-
id: "github",
|
|
1472
|
-
name: "GitHub",
|
|
1473
|
-
description: "Repos, PRs, issues, branches, file operations via GitHub API",
|
|
1474
|
-
command: "npx",
|
|
1475
|
-
args: ["-y", "@modelcontextprotocol/server-github"],
|
|
1476
|
-
env: { GITHUB_PERSONAL_ACCESS_TOKEN: "" },
|
|
1477
|
-
envPlaceholders: { GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_your_token_here" },
|
|
1478
|
-
category: "developer"
|
|
1479
|
-
},
|
|
1480
|
-
{
|
|
1481
|
-
id: "slack",
|
|
1482
|
-
name: "Slack",
|
|
1483
|
-
description: "Read/send Slack messages, manage channels, users, reactions",
|
|
1484
|
-
command: "npx",
|
|
1485
|
-
args: ["-y", "@modelcontextprotocol/server-slack"],
|
|
1486
|
-
env: {
|
|
1487
|
-
SLACK_BOT_TOKEN: "",
|
|
1488
|
-
SLACK_TEAM_ID: ""
|
|
1489
|
-
},
|
|
1490
|
-
envPlaceholders: {
|
|
1491
|
-
SLACK_BOT_TOKEN: "xoxb-your-token",
|
|
1492
|
-
SLACK_TEAM_ID: "T00000000"
|
|
1493
|
-
},
|
|
1494
|
-
category: "communication"
|
|
1495
|
-
},
|
|
1496
|
-
{
|
|
1497
|
-
id: "google-drive",
|
|
1498
|
-
name: "Google Drive",
|
|
1499
|
-
description: "Search and read Google Drive files, Docs, Sheets",
|
|
1500
|
-
command: "npx",
|
|
1501
|
-
args: ["-y", "@modelcontextprotocol/server-gdrive"],
|
|
1502
|
-
category: "productivity"
|
|
1503
|
-
},
|
|
1504
|
-
{
|
|
1505
|
-
id: "postgres",
|
|
1506
|
-
name: "PostgreSQL",
|
|
1507
|
-
description: "Query PostgreSQL databases with read-only access",
|
|
1508
|
-
command: "npx",
|
|
1509
|
-
args: ["-y", "@modelcontextprotocol/server-postgres"],
|
|
1510
|
-
env: { POSTGRES_CONNECTION_STRING: "" },
|
|
1511
|
-
envPlaceholders: { POSTGRES_CONNECTION_STRING: "postgresql://user:pass@localhost/db" },
|
|
1512
|
-
category: "developer"
|
|
1513
|
-
},
|
|
1514
|
-
{
|
|
1515
|
-
id: "brave-search",
|
|
1516
|
-
name: "Brave Search",
|
|
1517
|
-
description: "Web and local search using Brave Search API",
|
|
1518
|
-
command: "npx",
|
|
1519
|
-
args: ["-y", "@modelcontextprotocol/server-brave-search"],
|
|
1520
|
-
env: { BRAVE_API_KEY: "" },
|
|
1521
|
-
envPlaceholders: { BRAVE_API_KEY: "your_api_key" },
|
|
1522
|
-
category: "research"
|
|
1523
|
-
},
|
|
1524
|
-
{
|
|
1525
|
-
id: "puppeteer",
|
|
1526
|
-
name: "Puppeteer",
|
|
1527
|
-
description: "Browser automation — navigate, screenshot, interact with web pages",
|
|
1528
|
-
command: "npx",
|
|
1529
|
-
args: ["-y", "@modelcontextprotocol/server-puppeteer"],
|
|
1530
|
-
category: "automation"
|
|
1531
|
-
},
|
|
1532
|
-
{
|
|
1533
|
-
id: "sequential-thinking",
|
|
1534
|
-
name: "Sequential Thinking",
|
|
1535
|
-
description: "Step-by-step reasoning and problem-solving tool",
|
|
1536
|
-
command: "npx",
|
|
1537
|
-
args: ["-y", "@modelcontextprotocol/server-sequential-thinking"],
|
|
1538
|
-
category: "research"
|
|
1539
|
-
}
|
|
1540
|
-
];
|
|
1541
|
-
function installMcpServer(server, envValues) {
|
|
1542
|
-
const settings = readSettings();
|
|
1543
|
-
if (!settings.mcpServers) settings.mcpServers = {};
|
|
1544
|
-
const mcpSection = settings.mcpServers;
|
|
1545
|
-
const config = {
|
|
1546
|
-
command: server.command,
|
|
1547
|
-
args: server.args
|
|
1548
|
-
};
|
|
1549
|
-
if (server.env) {
|
|
1550
|
-
const env = {};
|
|
1551
|
-
for (const [key, defaultVal] of Object.entries(server.env)) env[key] = envValues?.[key] ?? defaultVal;
|
|
1552
|
-
config.env = env;
|
|
1553
|
-
}
|
|
1554
|
-
mcpSection[server.id] = config;
|
|
1555
|
-
writeSettings(settings);
|
|
1556
|
-
return true;
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
1366
|
//#endregion
|
|
1560
1367
|
//#region src/core/soul.ts
|
|
1561
1368
|
function getSoulPath() {
|
|
1562
|
-
return path$
|
|
1369
|
+
return path$3.join(os$4.homedir(), ".claude", "SOUL.md");
|
|
1563
1370
|
}
|
|
1564
1371
|
function soulExists() {
|
|
1565
|
-
return fs$
|
|
1372
|
+
return fs$3.existsSync(getSoulPath());
|
|
1566
1373
|
}
|
|
1567
1374
|
async function soulQuestionnaire() {
|
|
1568
1375
|
const name = await p$11.text({
|
|
1569
|
-
message: "What should
|
|
1376
|
+
message: "What should your assistant call you?",
|
|
1570
1377
|
placeholder: "Your name or nickname",
|
|
1571
1378
|
validate: (v) => v.length === 0 ? "Name cannot be empty" : void 0
|
|
1572
1379
|
});
|
|
1573
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;
|
|
1574
1387
|
const tone = await p$11.select({
|
|
1575
1388
|
message: "Communication style?",
|
|
1576
1389
|
options: [
|
|
@@ -1627,6 +1440,7 @@ async function soulQuestionnaire() {
|
|
|
1627
1440
|
const extras = extrasResult.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1628
1441
|
return {
|
|
1629
1442
|
name,
|
|
1443
|
+
botName: botName || "Paw",
|
|
1630
1444
|
tone,
|
|
1631
1445
|
verbosity,
|
|
1632
1446
|
proactive,
|
|
@@ -1647,11 +1461,12 @@ function writeSoul(config) {
|
|
|
1647
1461
|
const lines = [
|
|
1648
1462
|
"# SOUL.md — OpenPaw Personality",
|
|
1649
1463
|
"",
|
|
1650
|
-
`You are ${config.name}'s personal assistant, powered by OpenPaw.`,
|
|
1464
|
+
`You are **${config.botName}**, ${config.name}'s personal assistant, powered by OpenPaw.`,
|
|
1651
1465
|
"",
|
|
1652
1466
|
"## Identity",
|
|
1653
1467
|
"",
|
|
1654
|
-
`- **
|
|
1468
|
+
`- **Your name**: ${config.botName} — use this when introducing yourself or signing off`,
|
|
1469
|
+
`- **User's name**: Call the user "${config.name}"`,
|
|
1655
1470
|
`- **Role**: Personal assistant with access to system tools, apps, and services`,
|
|
1656
1471
|
"- **Source**: Configured by OpenPaw (open-source, no daemon, free forever)",
|
|
1657
1472
|
"",
|
|
@@ -1667,15 +1482,16 @@ function writeSoul(config) {
|
|
|
1667
1482
|
for (const extra of config.extras) lines.push(`- ${extra}`);
|
|
1668
1483
|
lines.push("");
|
|
1669
1484
|
}
|
|
1670
|
-
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!').`, "");
|
|
1671
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", "");
|
|
1672
|
-
const soulDir = path$
|
|
1673
|
-
if (!fs$
|
|
1674
|
-
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");
|
|
1675
1490
|
}
|
|
1676
1491
|
function showSoulSummary(config) {
|
|
1677
1492
|
const lines = [
|
|
1678
|
-
`${accent("
|
|
1493
|
+
`${accent("You:")} ${config.name}`,
|
|
1494
|
+
`${accent("Assistant:")} ${config.botName}`,
|
|
1679
1495
|
`${accent("Tone:")} ${config.tone}`,
|
|
1680
1496
|
`${accent("Verbosity:")} ${config.verbosity}`,
|
|
1681
1497
|
`${accent("Proactive:")} ${config.proactive ? "yes" : "no"}`
|
|
@@ -1686,7 +1502,7 @@ function showSoulSummary(config) {
|
|
|
1686
1502
|
|
|
1687
1503
|
//#endregion
|
|
1688
1504
|
//#region src/core/memory.ts
|
|
1689
|
-
const MEMORY_DIR = path$
|
|
1505
|
+
const MEMORY_DIR = path$2.join(os$3.homedir(), ".claude", "memory");
|
|
1690
1506
|
const INITIAL_MEMORY = `# Memory
|
|
1691
1507
|
|
|
1692
1508
|
## User
|
|
@@ -1699,12 +1515,12 @@ const INITIAL_MEMORY = `# Memory
|
|
|
1699
1515
|
- (Claude will track projects mentioned in conversation)
|
|
1700
1516
|
`;
|
|
1701
1517
|
function setupMemory(userName) {
|
|
1702
|
-
if (!fs$
|
|
1703
|
-
const memoryPath = path$
|
|
1704
|
-
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)) {
|
|
1705
1521
|
let content = INITIAL_MEMORY;
|
|
1706
1522
|
if (userName) content = content.replace("(will be filled in as we learn)", userName);
|
|
1707
|
-
fs$
|
|
1523
|
+
fs$2.writeFileSync(memoryPath, content, "utf-8");
|
|
1708
1524
|
}
|
|
1709
1525
|
const topicFiles = [
|
|
1710
1526
|
"people.md",
|
|
@@ -1713,265 +1529,11 @@ function setupMemory(userName) {
|
|
|
1713
1529
|
"journal.md"
|
|
1714
1530
|
];
|
|
1715
1531
|
for (const file of topicFiles) {
|
|
1716
|
-
const filePath = path$
|
|
1717
|
-
if (!fs$
|
|
1532
|
+
const filePath = path$2.join(MEMORY_DIR, file);
|
|
1533
|
+
if (!fs$2.existsSync(filePath)) {
|
|
1718
1534
|
const title = file.replace(".md", "");
|
|
1719
|
-
fs$
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
//#endregion
|
|
1725
|
-
//#region src/core/telegram.ts
|
|
1726
|
-
const CONFIG_DIR = path$2.join(os$3.homedir(), ".config", "openpaw");
|
|
1727
|
-
const CONFIG_PATH = path$2.join(CONFIG_DIR, "telegram.json");
|
|
1728
|
-
function writeTelegramConfig(config) {
|
|
1729
|
-
fs$2.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
1730
|
-
fs$2.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
1731
|
-
fs$2.chmodSync(CONFIG_PATH, 384);
|
|
1732
|
-
}
|
|
1733
|
-
function readTelegramConfig() {
|
|
1734
|
-
try {
|
|
1735
|
-
const raw = fs$2.readFileSync(CONFIG_PATH, "utf-8");
|
|
1736
|
-
return JSON.parse(raw);
|
|
1737
|
-
} catch {
|
|
1738
|
-
return null;
|
|
1739
|
-
}
|
|
1740
|
-
}
|
|
1741
|
-
function telegramConfigExists() {
|
|
1742
|
-
return fs$2.existsSync(CONFIG_PATH);
|
|
1743
|
-
}
|
|
1744
|
-
async function telegramQuestionnaire() {
|
|
1745
|
-
p$10.log.info(dim("Let's set up your Telegram bot! You'll need:"));
|
|
1746
|
-
p$10.log.info(` ${accent("1.")} Message ${bold("@BotFather")} on Telegram → /newbot`);
|
|
1747
|
-
p$10.log.info(` ${accent("2.")} Message ${bold("@userinfobot")} to get your user ID`);
|
|
1748
|
-
console.log("");
|
|
1749
|
-
const botToken = await p$10.text({
|
|
1750
|
-
message: "Paste your bot token (from @BotFather):",
|
|
1751
|
-
placeholder: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
|
|
1752
|
-
validate: (v) => {
|
|
1753
|
-
if (v.length === 0) return "Bot token is required";
|
|
1754
|
-
if (!v.includes(":")) return "That doesn't look like a bot token (should contain ':')";
|
|
1755
|
-
return void 0;
|
|
1756
|
-
}
|
|
1757
|
-
});
|
|
1758
|
-
if (p$10.isCancel(botToken)) return null;
|
|
1759
|
-
const userId = await p$10.text({
|
|
1760
|
-
message: "Your Telegram user ID (from @userinfobot):",
|
|
1761
|
-
placeholder: "123456789",
|
|
1762
|
-
validate: (v) => {
|
|
1763
|
-
if (v.length === 0) return "User ID is required";
|
|
1764
|
-
if (!/^\d+$/.test(v)) return "User ID should be a number";
|
|
1765
|
-
return void 0;
|
|
1766
|
-
}
|
|
1767
|
-
});
|
|
1768
|
-
if (p$10.isCancel(userId)) return null;
|
|
1769
|
-
return {
|
|
1770
|
-
botToken,
|
|
1771
|
-
allowedUserIds: [userId.trim()],
|
|
1772
|
-
workspaceDir: os$3.homedir(),
|
|
1773
|
-
model: "sonnet",
|
|
1774
|
-
skills: []
|
|
1775
|
-
};
|
|
1776
|
-
}
|
|
1777
|
-
const sessions = new Map();
|
|
1778
|
-
const MODEL_MAP = {
|
|
1779
|
-
sonnet: "claude-sonnet-4-5-20250514",
|
|
1780
|
-
opus: "claude-opus-4-6",
|
|
1781
|
-
haiku: "claude-haiku-4-5-20251001"
|
|
1782
|
-
};
|
|
1783
|
-
function getModelId(shortName) {
|
|
1784
|
-
return MODEL_MAP[shortName] || MODEL_MAP.sonnet;
|
|
1785
|
-
}
|
|
1786
|
-
async function startTelegramBot(config) {
|
|
1787
|
-
const bot = new Bot(config.botToken);
|
|
1788
|
-
bot.use(hydrate());
|
|
1789
|
-
const allowedIds = new Set(config.allowedUserIds.map(Number));
|
|
1790
|
-
let currentModel = config.model || "sonnet";
|
|
1791
|
-
bot.use(async (ctx, next) => {
|
|
1792
|
-
if (!ctx.from || !allowedIds.has(ctx.from.id)) {
|
|
1793
|
-
await ctx.reply("Woof! I don't know you. Unauthorized. 🐾");
|
|
1794
|
-
return;
|
|
1795
|
-
}
|
|
1796
|
-
await next();
|
|
1797
|
-
});
|
|
1798
|
-
const installedSkills = listInstalledSkills();
|
|
1799
|
-
const skillCommands = installedSkills.filter((id) => id !== "core" && id !== "memory").map((id) => ({
|
|
1800
|
-
command: id,
|
|
1801
|
-
description: `Use the ${id} skill`
|
|
1802
|
-
}));
|
|
1803
|
-
const allCommands = [
|
|
1804
|
-
{
|
|
1805
|
-
command: "start",
|
|
1806
|
-
description: "Start the bot"
|
|
1807
|
-
},
|
|
1808
|
-
{
|
|
1809
|
-
command: "model",
|
|
1810
|
-
description: "Switch Claude model (sonnet/opus/haiku)"
|
|
1811
|
-
},
|
|
1812
|
-
{
|
|
1813
|
-
command: "skills",
|
|
1814
|
-
description: "List installed skills"
|
|
1815
|
-
},
|
|
1816
|
-
{
|
|
1817
|
-
command: "stop",
|
|
1818
|
-
description: "Cancel current operation"
|
|
1819
|
-
},
|
|
1820
|
-
{
|
|
1821
|
-
command: "clear",
|
|
1822
|
-
description: "Reset conversation"
|
|
1823
|
-
},
|
|
1824
|
-
...skillCommands
|
|
1825
|
-
];
|
|
1826
|
-
try {
|
|
1827
|
-
await bot.api.setMyCommands(allCommands);
|
|
1828
|
-
} catch {}
|
|
1829
|
-
bot.command("start", async (ctx) => {
|
|
1830
|
-
const skills$1 = installedSkills.filter((id) => id !== "core" && id !== "memory");
|
|
1831
|
-
await ctx.reply(`*PAW MODE active* 🐾
|
|
1832
|
-
|
|
1833
|
-
I'm your personal assistant, powered by OpenPaw.
|
|
1834
|
-
Model: \`${currentModel}\`\nSkills: ${skills$1.length > 0 ? skills$1.map((s) => `/${s}`).join(", ") : "none"}\n\nJust send me a message or use a /command!`, { parse_mode: "Markdown" });
|
|
1835
|
-
});
|
|
1836
|
-
bot.command("model", async (ctx) => {
|
|
1837
|
-
const arg = ctx.match?.trim().toLowerCase();
|
|
1838
|
-
if (!arg || ![
|
|
1839
|
-
"sonnet",
|
|
1840
|
-
"opus",
|
|
1841
|
-
"haiku"
|
|
1842
|
-
].includes(arg)) {
|
|
1843
|
-
await ctx.reply(`Current model: \`${currentModel}\`\n\nSwitch with:\n/model sonnet\n/model opus\n/model haiku`, { parse_mode: "Markdown" });
|
|
1844
|
-
return;
|
|
1535
|
+
fs$2.writeFileSync(filePath, `# ${title.charAt(0).toUpperCase() + title.slice(1)}\n`, "utf-8");
|
|
1845
1536
|
}
|
|
1846
|
-
currentModel = arg;
|
|
1847
|
-
config.model = arg;
|
|
1848
|
-
writeTelegramConfig(config);
|
|
1849
|
-
await ctx.reply(`Model switched to \`${currentModel}\` 🐾`, { parse_mode: "Markdown" });
|
|
1850
|
-
});
|
|
1851
|
-
bot.command("skills", async (ctx) => {
|
|
1852
|
-
const skills$1 = installedSkills.filter((id) => id !== "core" && id !== "memory");
|
|
1853
|
-
if (skills$1.length === 0) {
|
|
1854
|
-
await ctx.reply("No skills installed yet. Run `openpaw setup` first! 🐾");
|
|
1855
|
-
return;
|
|
1856
|
-
}
|
|
1857
|
-
const list = skills$1.map((s) => `• /${s}`).join("\n");
|
|
1858
|
-
await ctx.reply(`*Installed skills:*\n\n${list}`, { parse_mode: "Markdown" });
|
|
1859
|
-
});
|
|
1860
|
-
bot.command("stop", async (ctx) => {
|
|
1861
|
-
const userId = ctx.from.id;
|
|
1862
|
-
const session = sessions.get(userId);
|
|
1863
|
-
if (session?.controller) {
|
|
1864
|
-
session.controller.abort();
|
|
1865
|
-
sessions.delete(userId);
|
|
1866
|
-
await ctx.reply("Operation cancelled. 🐾");
|
|
1867
|
-
} else await ctx.reply("Nothing running right now. 🐾");
|
|
1868
|
-
});
|
|
1869
|
-
bot.command("clear", async (ctx) => {
|
|
1870
|
-
const userId = ctx.from.id;
|
|
1871
|
-
sessions.delete(userId);
|
|
1872
|
-
await ctx.reply("Conversation cleared! Fresh start. 🐾");
|
|
1873
|
-
});
|
|
1874
|
-
for (const skillId of installedSkills) {
|
|
1875
|
-
if (skillId === "core" || skillId === "memory") continue;
|
|
1876
|
-
bot.command(skillId, async (ctx) => {
|
|
1877
|
-
const args = ctx.match || "";
|
|
1878
|
-
const prompt = args ? `Use the c-${skillId} skill: ${args}` : `What can the c-${skillId} skill do? Give a brief overview.`;
|
|
1879
|
-
await handleClaudeMessage(ctx, prompt, currentModel, config);
|
|
1880
|
-
});
|
|
1881
|
-
}
|
|
1882
|
-
bot.on("message:text", async (ctx) => {
|
|
1883
|
-
await handleClaudeMessage(ctx, ctx.msg.text, currentModel, config);
|
|
1884
|
-
});
|
|
1885
|
-
bot.catch((err) => {
|
|
1886
|
-
console.error("Bot error:", err.message || err);
|
|
1887
|
-
});
|
|
1888
|
-
process.on("SIGINT", () => {
|
|
1889
|
-
console.log("\nShutting down gracefully... 🐾");
|
|
1890
|
-
bot.stop();
|
|
1891
|
-
process.exit(0);
|
|
1892
|
-
});
|
|
1893
|
-
process.on("SIGTERM", () => {
|
|
1894
|
-
bot.stop();
|
|
1895
|
-
process.exit(0);
|
|
1896
|
-
});
|
|
1897
|
-
console.log("");
|
|
1898
|
-
console.log(` 🐾 ${bold("OpenPaw Telegram Bridge")}`);
|
|
1899
|
-
console.log(` Model: ${accent(currentModel)}`);
|
|
1900
|
-
console.log(` Skills: ${accent(String(installedSkills.length))}`);
|
|
1901
|
-
console.log(` Workspace: ${dim(config.workspaceDir)}`);
|
|
1902
|
-
console.log(` Allowed users: ${dim(config.allowedUserIds.join(", "))}`);
|
|
1903
|
-
console.log("");
|
|
1904
|
-
console.log(dim(" Listening for messages... (Ctrl+C to stop)"));
|
|
1905
|
-
console.log("");
|
|
1906
|
-
await bot.start();
|
|
1907
|
-
}
|
|
1908
|
-
async function handleClaudeMessage(ctx, prompt, model, config) {
|
|
1909
|
-
const userId = ctx.from.id;
|
|
1910
|
-
const existing = sessions.get(userId);
|
|
1911
|
-
if (existing?.controller) existing.controller.abort();
|
|
1912
|
-
const controller = new AbortController();
|
|
1913
|
-
const session = sessions.get(userId) || {};
|
|
1914
|
-
session.controller = controller;
|
|
1915
|
-
sessions.set(userId, session);
|
|
1916
|
-
const statusMsg = await ctx.reply("Thinking... 🐾");
|
|
1917
|
-
let fullText = "";
|
|
1918
|
-
let lastEditTime = 0;
|
|
1919
|
-
const EDIT_INTERVAL = 1500;
|
|
1920
|
-
try {
|
|
1921
|
-
const q = query({
|
|
1922
|
-
prompt,
|
|
1923
|
-
options: {
|
|
1924
|
-
model: getModelId(model),
|
|
1925
|
-
permissionMode: "bypassPermissions",
|
|
1926
|
-
allowDangerouslySkipPermissions: true,
|
|
1927
|
-
cwd: config.workspaceDir,
|
|
1928
|
-
abortController: controller,
|
|
1929
|
-
maxTurns: 25,
|
|
1930
|
-
...session.sessionId ? { resume: session.sessionId } : {}
|
|
1931
|
-
}
|
|
1932
|
-
});
|
|
1933
|
-
for await (const message of q) {
|
|
1934
|
-
if (controller.signal.aborted) break;
|
|
1935
|
-
if (message.type === "system" && "session_id" in message) session.sessionId = message.session_id;
|
|
1936
|
-
if (message.type === "assistant") {
|
|
1937
|
-
const msgContent = message.message;
|
|
1938
|
-
const text = msgContent.content.filter((block) => block.type === "text").map((block) => block.text || "").join("");
|
|
1939
|
-
if (text) {
|
|
1940
|
-
fullText = text;
|
|
1941
|
-
const now = Date.now();
|
|
1942
|
-
if (now - lastEditTime > EDIT_INTERVAL) {
|
|
1943
|
-
lastEditTime = now;
|
|
1944
|
-
const truncated = fullText.length > 4e3 ? `${fullText.slice(0, 4e3)}...` : fullText;
|
|
1945
|
-
try {
|
|
1946
|
-
await statusMsg.editText(truncated);
|
|
1947
|
-
} catch {}
|
|
1948
|
-
}
|
|
1949
|
-
}
|
|
1950
|
-
}
|
|
1951
|
-
if (message.type === "result") {
|
|
1952
|
-
const result = message.result;
|
|
1953
|
-
if (result) fullText = result;
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
if (fullText) {
|
|
1957
|
-
const truncated = fullText.length > 4e3 ? `${fullText.slice(0, 4e3)}...` : fullText;
|
|
1958
|
-
try {
|
|
1959
|
-
await statusMsg.editText(truncated);
|
|
1960
|
-
} catch {
|
|
1961
|
-
await ctx.reply(truncated);
|
|
1962
|
-
}
|
|
1963
|
-
} else await statusMsg.editText("Done! (no text output) 🐾");
|
|
1964
|
-
} catch (err) {
|
|
1965
|
-
const errorMsg = err instanceof Error ? err.message : "Unknown error";
|
|
1966
|
-
if (errorMsg.includes("abort") || controller.signal.aborted) return;
|
|
1967
|
-
try {
|
|
1968
|
-
await statusMsg.editText(`Woof, something went wrong: ${errorMsg.slice(0, 200)} 🐾`);
|
|
1969
|
-
} catch {
|
|
1970
|
-
await ctx.reply(`Woof, something went wrong: ${errorMsg.slice(0, 200)} 🐾`);
|
|
1971
|
-
}
|
|
1972
|
-
} finally {
|
|
1973
|
-
session.controller = void 0;
|
|
1974
|
-
sessions.set(userId, session);
|
|
1975
1537
|
}
|
|
1976
1538
|
}
|
|
1977
1539
|
|
|
@@ -2024,60 +1586,95 @@ const CATEGORY_ICONS = {
|
|
|
2024
1586
|
async function setupCommand(opts = {}) {
|
|
2025
1587
|
await showBanner();
|
|
2026
1588
|
const platform = detectPlatform();
|
|
2027
|
-
p$
|
|
1589
|
+
p$10.intro(accent(" openpaw setup "));
|
|
2028
1590
|
const brewStatus = platform.hasBrew ? chalk.green("✓ brew") : chalk.red("✗ brew");
|
|
2029
1591
|
const npmStatus = platform.hasNpm ? chalk.green("✓ npm") : chalk.red("✗ npm");
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
1592
|
+
const pipStatus = platform.hasPip ? chalk.green("✓ pip") : chalk.dim("○ pip");
|
|
1593
|
+
p$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 {
|
|
2033
1628
|
await pawPulse("think", "Let's get to know you...");
|
|
2034
|
-
const wantSoul = await p$
|
|
1629
|
+
const wantSoul = await p$10.confirm({
|
|
2035
1630
|
message: "Teach me your name and preferences? (makes me a better pup)",
|
|
2036
1631
|
initialValue: true
|
|
2037
1632
|
});
|
|
2038
|
-
if (!p$
|
|
1633
|
+
if (!p$10.isCancel(wantSoul) && wantSoul) {
|
|
2039
1634
|
const soul = await soulQuestionnaire();
|
|
2040
1635
|
if (soul) {
|
|
1636
|
+
botName = soul.botName;
|
|
2041
1637
|
writeSoul(soul);
|
|
2042
1638
|
setupMemory(soul.name);
|
|
2043
1639
|
showSoulSummary(soul);
|
|
2044
|
-
p$
|
|
1640
|
+
p$10.log.success("Personality saved to ~/.claude/SOUL.md");
|
|
2045
1641
|
}
|
|
2046
1642
|
} else setupMemory();
|
|
2047
|
-
}
|
|
1643
|
+
}
|
|
1644
|
+
else setupMemory();
|
|
2048
1645
|
let selectedSkills;
|
|
2049
1646
|
if (opts.preset) {
|
|
2050
1647
|
selectedSkills = getPresetSkills(opts.preset, platform.os);
|
|
2051
1648
|
if (selectedSkills.length === 0) {
|
|
2052
|
-
p$
|
|
2053
|
-
p$
|
|
1649
|
+
p$10.log.error(`Unknown preset: ${opts.preset}`);
|
|
1650
|
+
p$10.log.info(`Available: ${presets.map((pr) => pr.id).join(", ")}`);
|
|
2054
1651
|
process.exit(1);
|
|
2055
1652
|
}
|
|
2056
|
-
p$
|
|
1653
|
+
p$10.log.info(`Using preset: ${bold(opts.preset)} (${selectedSkills.length} skills)`);
|
|
2057
1654
|
} else selectedSkills = await selectSkills(platform.os);
|
|
2058
1655
|
if (selectedSkills.length === 0) {
|
|
2059
|
-
p$
|
|
2060
|
-
p$
|
|
1656
|
+
p$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! 🐾");
|
|
2061
1658
|
return;
|
|
2062
1659
|
}
|
|
2063
1660
|
const resolved = resolveDependencies(selectedSkills);
|
|
2064
1661
|
if (resolved.length > 0) {
|
|
2065
1662
|
const depNames = resolved.map((s$1) => s$1.name).join(", ");
|
|
2066
|
-
p$
|
|
1663
|
+
p$10.log.info(`${dim("Auto-fetching dependencies:")} ${depNames}`);
|
|
2067
1664
|
selectedSkills.push(...resolved);
|
|
2068
1665
|
}
|
|
2069
1666
|
await pawPulse("happy", `${selectedSkills.length} skill${selectedSkills.length > 1 ? "s" : ""} selected — good taste!`);
|
|
2070
1667
|
if (!opts.yes) {
|
|
2071
1668
|
for (const skill of selectedSkills) if (skill.subChoices) {
|
|
2072
|
-
const choice = await p$
|
|
1669
|
+
const choice = await p$10.select({
|
|
2073
1670
|
message: `${skill.name}: ${skill.subChoices.question}`,
|
|
2074
1671
|
options: skill.subChoices.options.map((o) => ({
|
|
2075
1672
|
value: o.value,
|
|
2076
1673
|
label: o.label
|
|
2077
1674
|
}))
|
|
2078
1675
|
});
|
|
2079
|
-
if (p$
|
|
2080
|
-
p$
|
|
1676
|
+
if (p$10.isCancel(choice)) {
|
|
1677
|
+
p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2081
1678
|
process.exit(0);
|
|
2082
1679
|
}
|
|
2083
1680
|
const chosen = skill.subChoices.options.find((o) => o.value === choice);
|
|
@@ -2087,37 +1684,29 @@ async function setupCommand(opts = {}) {
|
|
|
2087
1684
|
let interfaceMode = "native";
|
|
2088
1685
|
let telegramConfig = null;
|
|
2089
1686
|
if (!opts.yes) {
|
|
2090
|
-
const modeChoice = await p$
|
|
1687
|
+
const modeChoice = await p$10.select({
|
|
2091
1688
|
message: "How do you want to talk to Claude? 🐾",
|
|
2092
|
-
options: [
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
hint: "talk to Claude from your phone"
|
|
2102
|
-
},
|
|
2103
|
-
{
|
|
2104
|
-
value: "both",
|
|
2105
|
-
label: "🖥📱 Both",
|
|
2106
|
-
hint: "terminal + Telegram"
|
|
2107
|
-
}
|
|
2108
|
-
]
|
|
1689
|
+
options: [{
|
|
1690
|
+
value: "native",
|
|
1691
|
+
label: "🖥 Terminal only",
|
|
1692
|
+
hint: "Claude Code in your terminal"
|
|
1693
|
+
}, {
|
|
1694
|
+
value: "both",
|
|
1695
|
+
label: "🖥📱 Terminal + Telegram",
|
|
1696
|
+
hint: "terminal + talk from your phone"
|
|
1697
|
+
}]
|
|
2109
1698
|
});
|
|
2110
|
-
if (p$
|
|
2111
|
-
p$
|
|
1699
|
+
if (p$10.isCancel(modeChoice)) {
|
|
1700
|
+
p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2112
1701
|
process.exit(0);
|
|
2113
1702
|
}
|
|
2114
1703
|
interfaceMode = modeChoice;
|
|
2115
1704
|
if (interfaceMode === "telegram" || interfaceMode === "both") {
|
|
2116
|
-
if (telegramConfigExists()) p$
|
|
1705
|
+
if (telegramConfigExists()) p$10.log.info(dim("Telegram already configured — keeping existing config"));
|
|
2117
1706
|
else {
|
|
2118
1707
|
telegramConfig = await telegramQuestionnaire();
|
|
2119
1708
|
if (!telegramConfig) {
|
|
2120
|
-
p$
|
|
1709
|
+
p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2121
1710
|
process.exit(0);
|
|
2122
1711
|
}
|
|
2123
1712
|
}
|
|
@@ -2127,37 +1716,39 @@ async function setupCommand(opts = {}) {
|
|
|
2127
1716
|
}
|
|
2128
1717
|
}
|
|
2129
1718
|
}
|
|
2130
|
-
let
|
|
1719
|
+
let wantDashboard = false;
|
|
1720
|
+
let dashboardTheme = "paw";
|
|
2131
1721
|
if (!opts.yes) {
|
|
2132
|
-
const
|
|
2133
|
-
message:
|
|
2134
|
-
|
|
2135
|
-
value: "home",
|
|
2136
|
-
label: `Home directory ${dim("~")}`,
|
|
2137
|
-
hint: "recommended for general assistant"
|
|
2138
|
-
}, {
|
|
2139
|
-
value: "custom",
|
|
2140
|
-
label: "Pick a project directory",
|
|
2141
|
-
hint: "for project-focused work"
|
|
2142
|
-
}]
|
|
1722
|
+
const dashChoice = await p$10.confirm({
|
|
1723
|
+
message: `Want a task dashboard for ${botName}?`,
|
|
1724
|
+
initialValue: false
|
|
2143
1725
|
});
|
|
2144
|
-
if (p$
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
1726
|
+
if (!p$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
|
+
]
|
|
2153
1747
|
});
|
|
2154
|
-
if (p$
|
|
2155
|
-
p$9.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2156
|
-
process.exit(0);
|
|
2157
|
-
}
|
|
2158
|
-
projectDir = customDir.replace(/^~/, os$2.homedir());
|
|
1748
|
+
if (!p$10.isCancel(themeChoice)) dashboardTheme = themeChoice;
|
|
2159
1749
|
}
|
|
2160
1750
|
}
|
|
1751
|
+
const projectDir = os$2.homedir();
|
|
2161
1752
|
const allTools = [];
|
|
2162
1753
|
for (const skill of selectedSkills) allTools.push(...skill.tools);
|
|
2163
1754
|
const uniqueTools = [...new Map(allTools.map((t) => [t.command, t])).values()];
|
|
@@ -2165,26 +1756,63 @@ async function setupCommand(opts = {}) {
|
|
|
2165
1756
|
const missing = getMissingTools(uniqueTools);
|
|
2166
1757
|
let targetDir;
|
|
2167
1758
|
if (opts.yes) targetDir = getDefaultSkillsDir();
|
|
2168
|
-
else
|
|
1759
|
+
else {
|
|
1760
|
+
const defaultDir = getDefaultSkillsDir();
|
|
1761
|
+
const skillsDir = await p$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
|
+
}
|
|
2169
1797
|
const summary = buildSummary(selectedSkills, uniqueTools, missing, taps, interfaceMode, projectDir);
|
|
2170
|
-
p$
|
|
1798
|
+
p$10.note(summary, "Here's what we're fetching");
|
|
2171
1799
|
if (!opts.yes) {
|
|
2172
|
-
const proceed = await p$
|
|
1800
|
+
const proceed = await p$10.confirm({
|
|
2173
1801
|
message: "Ready to fetch all these goodies?",
|
|
2174
1802
|
initialValue: true
|
|
2175
1803
|
});
|
|
2176
|
-
if (p$
|
|
2177
|
-
p$
|
|
1804
|
+
if (p$10.isCancel(proceed) || !proceed) {
|
|
1805
|
+
p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2178
1806
|
process.exit(0);
|
|
2179
1807
|
}
|
|
2180
1808
|
}
|
|
2181
1809
|
if (opts.dryRun) {
|
|
2182
|
-
p$
|
|
2183
|
-
p$
|
|
1810
|
+
p$10.log.info(dim("Dry run — no changes made. Just sniffing around."));
|
|
1811
|
+
p$10.outro(accent("openpaw dry run complete 🐾"));
|
|
2184
1812
|
return;
|
|
2185
1813
|
}
|
|
2186
1814
|
await pawStep("work", "Fetching your goodies...");
|
|
2187
|
-
const s = p$
|
|
1815
|
+
const s = p$10.spinner();
|
|
2188
1816
|
if (taps.size > 0) {
|
|
2189
1817
|
s.start("🐾 Sniffing out Homebrew taps...");
|
|
2190
1818
|
const tapResults = installTaps(taps);
|
|
@@ -2199,12 +1827,28 @@ async function setupCommand(opts = {}) {
|
|
|
2199
1827
|
if (result.success) s.stop(`${chalk.green("✓")} ${tool.name}`);
|
|
2200
1828
|
else s.stop(`${chalk.red("✗")} ${tool.name} — ${result.error?.slice(0, 50)}`);
|
|
2201
1829
|
}
|
|
2202
|
-
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
|
+
}
|
|
2203
1841
|
s.start("🐾 Burying treats in ~/.claude/skills/...");
|
|
2204
1842
|
installSkill("core", targetDir);
|
|
2205
1843
|
installSkill("memory", targetDir);
|
|
2206
1844
|
const installed = ["c-core", "c-memory"];
|
|
2207
|
-
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
|
+
}
|
|
2208
1852
|
s.stop(`🐾 ${installed.length} skills buried`);
|
|
2209
1853
|
s.start("🐾 Setting up the doggy door...");
|
|
2210
1854
|
const added = addPermissions(uniqueTools);
|
|
@@ -2216,128 +1860,102 @@ async function setupCommand(opts = {}) {
|
|
|
2216
1860
|
telegramConfig.workspaceDir = projectDir;
|
|
2217
1861
|
telegramConfig.skills = selectedSkills.map((sk) => sk.id);
|
|
2218
1862
|
writeTelegramConfig(telegramConfig);
|
|
2219
|
-
p$
|
|
1863
|
+
p$10.log.success("Telegram bridge configured");
|
|
2220
1864
|
}
|
|
2221
|
-
if (
|
|
2222
|
-
const
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
const mcpChoices = await p$9.multiselect({
|
|
2228
|
-
message: "🔌 MCP Servers",
|
|
2229
|
-
options: mcpServers.map((srv) => ({
|
|
2230
|
-
value: srv.id,
|
|
2231
|
-
label: srv.name,
|
|
2232
|
-
hint: srv.description
|
|
2233
|
-
})),
|
|
2234
|
-
required: false
|
|
2235
|
-
});
|
|
2236
|
-
if (!p$9.isCancel(mcpChoices)) {
|
|
2237
|
-
const chosen = mcpChoices;
|
|
2238
|
-
if (chosen.length > 0) {
|
|
2239
|
-
s.start("🐾 Configuring MCP servers...");
|
|
2240
|
-
let mcpCount = 0;
|
|
2241
|
-
for (const id of chosen) {
|
|
2242
|
-
const srv = mcpServers.find((m) => m.id === id);
|
|
2243
|
-
if (srv && installMcpServer(srv)) mcpCount++;
|
|
2244
|
-
}
|
|
2245
|
-
s.stop(`🐾 ${mcpCount} MCP server${mcpCount > 1 ? "s" : ""} configured`);
|
|
2246
|
-
const needsEnv = chosen.map((id) => mcpServers.find((m) => m.id === id)).filter((srv) => !!srv?.envPlaceholders);
|
|
2247
|
-
if (needsEnv.length > 0) {
|
|
2248
|
-
const envList = needsEnv.flatMap((srv) => Object.entries(srv.envPlaceholders).map(([key, _placeholder]) => `${chalk.yellow("→")} ${bold(srv.name)}: Set ${dim(key)} in ~/.claude/settings.json`)).join("\n");
|
|
2249
|
-
p$9.note(envList, "MCP servers need API keys");
|
|
2250
|
-
}
|
|
2251
|
-
}
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
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})`);
|
|
2254
1871
|
}
|
|
2255
1872
|
const authSteps = selectedSkills.flatMap((skill) => skill.authSteps ?? []).filter((step, i, arr) => arr.findIndex((s$1) => s$1.command === step.command) === i);
|
|
2256
1873
|
if (authSteps.length > 0) {
|
|
2257
1874
|
const authList = authSteps.map((st) => `${chalk.yellow("→")} ${chalk.bold(st.command)} ${dim(st.description)}`).join("\n");
|
|
2258
|
-
p$
|
|
1875
|
+
p$10.note(authList, "One-time auth needed");
|
|
2259
1876
|
}
|
|
2260
1877
|
await pawStep("done", "All done! *tail wag intensifies*");
|
|
2261
1878
|
console.log("");
|
|
2262
|
-
console.log(dim(
|
|
1879
|
+
console.log(dim(` ${botName} is ready to play! Try saying:`));
|
|
2263
1880
|
console.log(` ${subtle("\"What are my latest emails?\"")}`);
|
|
2264
1881
|
console.log(` ${subtle("\"Play some jazz on Spotify\"")}`);
|
|
2265
1882
|
console.log(` ${subtle("\"Go to hacker news and summarize the top posts\"")}`);
|
|
2266
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
|
+
}
|
|
2267
1892
|
if (opts.yes) {
|
|
2268
|
-
p$
|
|
1893
|
+
p$10.outro(accent("openpaw setup complete 🐾"));
|
|
2269
1894
|
return;
|
|
2270
1895
|
}
|
|
2271
|
-
const launch = await p$
|
|
1896
|
+
const launch = await p$10.confirm({
|
|
2272
1897
|
message: "Time to go for a walk? (Launch your assistant)",
|
|
2273
1898
|
initialValue: true
|
|
2274
1899
|
});
|
|
2275
|
-
if (p$
|
|
2276
|
-
if (interfaceMode === "telegram" || interfaceMode === "both") p$
|
|
2277
|
-
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!"));
|
|
2278
1903
|
return;
|
|
2279
1904
|
}
|
|
2280
1905
|
let useDangerousMode = false;
|
|
2281
|
-
|
|
1906
|
+
{
|
|
2282
1907
|
showPuppyDisclaimer();
|
|
2283
|
-
const acceptDanger = await p$
|
|
1908
|
+
const acceptDanger = await p$10.confirm({
|
|
2284
1909
|
message: "Unleash full paw-er? *excited tail wag*",
|
|
2285
1910
|
initialValue: true
|
|
2286
1911
|
});
|
|
2287
|
-
if (!p$
|
|
1912
|
+
if (!p$10.isCancel(acceptDanger)) useDangerousMode = acceptDanger;
|
|
2288
1913
|
}
|
|
2289
1914
|
let useTmux = false;
|
|
2290
1915
|
if (isTmuxAvailable() && !isInTmux()) {
|
|
2291
1916
|
const tmuxDefault = interfaceMode === "both";
|
|
2292
|
-
const tmuxChoice = await p$
|
|
1917
|
+
const tmuxChoice = await p$10.confirm({
|
|
2293
1918
|
message: "Run in tmux? (keeps going when you close the terminal)",
|
|
2294
1919
|
initialValue: tmuxDefault
|
|
2295
1920
|
});
|
|
2296
|
-
if (!p$
|
|
1921
|
+
if (!p$10.isCancel(tmuxChoice)) useTmux = tmuxChoice;
|
|
2297
1922
|
}
|
|
2298
1923
|
const dangerFlag = useDangerousMode ? " --dangerously-skip-permissions" : "";
|
|
2299
1924
|
const nativeCmd = `claude${dangerFlag}`;
|
|
2300
1925
|
const telegramCmd = "npx openpaw telegram";
|
|
2301
1926
|
if (useTmux) {
|
|
2302
|
-
p$
|
|
1927
|
+
p$10.outro(accent("Launching in tmux... 🐾"));
|
|
2303
1928
|
launchInTmux({
|
|
2304
|
-
nativeCmd
|
|
2305
|
-
telegramCmd: interfaceMode === "
|
|
1929
|
+
nativeCmd,
|
|
1930
|
+
telegramCmd: interfaceMode === "both" ? telegramCmd : void 0,
|
|
2306
1931
|
workDir: projectDir
|
|
2307
1932
|
});
|
|
2308
1933
|
} else if (interfaceMode === "native") {
|
|
2309
|
-
p$
|
|
1934
|
+
p$10.outro(accent("Starting Claude Code... 🐾"));
|
|
2310
1935
|
try {
|
|
2311
1936
|
execSync(nativeCmd, {
|
|
2312
1937
|
stdio: "inherit",
|
|
2313
1938
|
cwd: projectDir
|
|
2314
1939
|
});
|
|
2315
1940
|
} catch {
|
|
2316
|
-
p$
|
|
2317
|
-
}
|
|
2318
|
-
} else if (interfaceMode === "telegram") {
|
|
2319
|
-
p$9.outro(accent("Starting Telegram bridge... 🐾"));
|
|
2320
|
-
try {
|
|
2321
|
-
execSync(telegramCmd, { stdio: "inherit" });
|
|
2322
|
-
} catch {
|
|
2323
|
-
p$9.log.warn("Telegram bridge failed to start.");
|
|
1941
|
+
p$10.log.warn("Could not launch Claude Code. Make sure it's installed: https://claude.ai/code");
|
|
2324
1942
|
}
|
|
2325
1943
|
} else {
|
|
2326
|
-
p$
|
|
1944
|
+
p$10.log.info(dim("Starting Telegram bridge in background..."));
|
|
2327
1945
|
launchInBackground(telegramCmd);
|
|
2328
|
-
p$
|
|
1946
|
+
p$10.outro(accent("Starting Claude Code... 🐾"));
|
|
2329
1947
|
try {
|
|
2330
1948
|
execSync(nativeCmd, {
|
|
2331
1949
|
stdio: "inherit",
|
|
2332
1950
|
cwd: projectDir
|
|
2333
1951
|
});
|
|
2334
1952
|
} catch {
|
|
2335
|
-
p$
|
|
1953
|
+
p$10.log.warn("Could not launch Claude Code. Make sure it's installed: https://claude.ai/code");
|
|
2336
1954
|
}
|
|
2337
1955
|
}
|
|
2338
1956
|
}
|
|
2339
|
-
async function selectSkills(os$
|
|
2340
|
-
const mode = await p$
|
|
1957
|
+
async function selectSkills(os$5) {
|
|
1958
|
+
const mode = await p$10.select({
|
|
2341
1959
|
message: "How should we set things up, human?",
|
|
2342
1960
|
options: [{
|
|
2343
1961
|
value: "preset",
|
|
@@ -2349,15 +1967,15 @@ async function selectSkills(os$7) {
|
|
|
2349
1967
|
hint: "sniff through skills one by one"
|
|
2350
1968
|
}]
|
|
2351
1969
|
});
|
|
2352
|
-
if (p$
|
|
2353
|
-
p$
|
|
1970
|
+
if (p$10.isCancel(mode)) {
|
|
1971
|
+
p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2354
1972
|
process.exit(0);
|
|
2355
1973
|
}
|
|
2356
|
-
if (mode === "preset") return await selectFromPreset(os$
|
|
2357
|
-
return await selectCustom(os$
|
|
1974
|
+
if (mode === "preset") return await selectFromPreset(os$5);
|
|
1975
|
+
return await selectCustom(os$5);
|
|
2358
1976
|
}
|
|
2359
|
-
async function selectFromPreset(os$
|
|
2360
|
-
const presetChoice = await p$
|
|
1977
|
+
async function selectFromPreset(os$5) {
|
|
1978
|
+
const presetChoice = await p$10.select({
|
|
2361
1979
|
message: "Pick a treat... I mean, a preset!",
|
|
2362
1980
|
options: presets.map((pr) => ({
|
|
2363
1981
|
value: pr.id,
|
|
@@ -2365,88 +1983,50 @@ async function selectFromPreset(os$7) {
|
|
|
2365
1983
|
hint: pr.description
|
|
2366
1984
|
}))
|
|
2367
1985
|
});
|
|
2368
|
-
if (p$
|
|
2369
|
-
p$
|
|
1986
|
+
if (p$10.isCancel(presetChoice)) {
|
|
1987
|
+
p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2370
1988
|
process.exit(0);
|
|
2371
1989
|
}
|
|
2372
|
-
const presetSkills = getPresetSkills(presetChoice, os$
|
|
1990
|
+
const presetSkills = getPresetSkills(presetChoice, os$5);
|
|
2373
1991
|
const skillNames = presetSkills.map((s) => s.name).join(", ");
|
|
2374
|
-
p$
|
|
1992
|
+
p$10.log.info(`${dim("Includes:")} ${skillNames}`);
|
|
2375
1993
|
return presetSkills;
|
|
2376
1994
|
}
|
|
2377
|
-
async function selectCustom(os$
|
|
2378
|
-
const grouped = getSkillsByCategory(os$
|
|
2379
|
-
const
|
|
2380
|
-
for (const [category,
|
|
2381
|
-
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) {
|
|
2382
1999
|
const icon = CATEGORY_ICONS[category] ?? "📦";
|
|
2383
|
-
const
|
|
2384
|
-
|
|
2385
|
-
|
|
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({
|
|
2386
2005
|
value: skill.id,
|
|
2387
|
-
label: skill.name
|
|
2388
|
-
hint: skill.description
|
|
2389
|
-
})
|
|
2390
|
-
|
|
2391
|
-
});
|
|
2392
|
-
if (p$9.isCancel(selected)) {
|
|
2393
|
-
p$9.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2394
|
-
process.exit(0);
|
|
2395
|
-
}
|
|
2396
|
-
const ids = selected;
|
|
2397
|
-
for (const id of ids) {
|
|
2398
|
-
const skill = skills.find((s) => s.id === id);
|
|
2399
|
-
if (skill) allSelected.push(skill);
|
|
2006
|
+
label: `${icon} ${skill.name}`,
|
|
2007
|
+
hint: isFirst ? `── ${catLabel} ── ${skill.description}` : skill.description
|
|
2008
|
+
});
|
|
2009
|
+
isFirst = false;
|
|
2400
2010
|
}
|
|
2401
|
-
if (ids.length > 0) p$9.log.step(`${ids.length} selected from ${label}`);
|
|
2402
2011
|
}
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
const skillsDir = await p$9.select({
|
|
2408
|
-
message: "Where should skills live?",
|
|
2409
|
-
options: [
|
|
2410
|
-
{
|
|
2411
|
-
value: defaultDir,
|
|
2412
|
-
label: `Global ${dim("~/.claude/skills/")}`,
|
|
2413
|
-
hint: "recommended"
|
|
2414
|
-
},
|
|
2415
|
-
{
|
|
2416
|
-
value: ".claude/skills",
|
|
2417
|
-
label: `Project ${dim(".claude/skills/")}`
|
|
2418
|
-
},
|
|
2419
|
-
{
|
|
2420
|
-
value: "custom",
|
|
2421
|
-
label: "Custom path"
|
|
2422
|
-
}
|
|
2423
|
-
]
|
|
2012
|
+
const selected = await p$10.multiselect({
|
|
2013
|
+
message: "Pick your skills (space to select, enter to confirm)",
|
|
2014
|
+
options,
|
|
2015
|
+
required: false
|
|
2424
2016
|
});
|
|
2425
|
-
if (p$
|
|
2426
|
-
p$
|
|
2017
|
+
if (p$10.isCancel(selected)) {
|
|
2018
|
+
p$10.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2427
2019
|
process.exit(0);
|
|
2428
2020
|
}
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
const customDir = await p$9.text({
|
|
2432
|
-
message: "Skills directory path:",
|
|
2433
|
-
placeholder: "~/.claude/skills",
|
|
2434
|
-
validate: (v) => v.length === 0 ? "Path cannot be empty" : void 0
|
|
2435
|
-
});
|
|
2436
|
-
if (p$9.isCancel(customDir)) {
|
|
2437
|
-
p$9.cancel("Ok, I'll be here when you're ready *sad puppy eyes*");
|
|
2438
|
-
process.exit(0);
|
|
2439
|
-
}
|
|
2440
|
-
targetDir = customDir;
|
|
2441
|
-
}
|
|
2442
|
-
return targetDir;
|
|
2021
|
+
const ids = selected;
|
|
2022
|
+
return ids.map((id) => skills.find((s) => s.id === id)).filter((s) => !!s);
|
|
2443
2023
|
}
|
|
2444
2024
|
function buildSummary(selectedSkills, uniqueTools, missing, taps, interfaceMode, projectDir) {
|
|
2445
2025
|
const lines = [];
|
|
2446
2026
|
lines.push(`${bold("Skills:")} ${selectedSkills.map((s) => s.name).join(", ")}`);
|
|
2447
2027
|
lines.push(`${bold("Tools:")} ${uniqueTools.length} total, ${missing.length} to install`);
|
|
2448
2028
|
if (taps.size > 0) lines.push(`${bold("Taps:")} ${[...taps].join(", ")}`);
|
|
2449
|
-
const modeLabel = interfaceMode === "native" ? "Terminal" :
|
|
2029
|
+
const modeLabel = interfaceMode === "native" ? "Terminal" : "Terminal + Telegram";
|
|
2450
2030
|
lines.push(`${bold("Interface:")} ${modeLabel}`);
|
|
2451
2031
|
lines.push(`${bold("Workspace:")} ${projectDir.replace(os$2.homedir(), "~")}`);
|
|
2452
2032
|
lines.push(`${bold("Memory:")} ~/.claude/memory/`);
|
|
@@ -2475,18 +2055,18 @@ async function addCommand(skillIds) {
|
|
|
2475
2055
|
showMini();
|
|
2476
2056
|
console.log("");
|
|
2477
2057
|
if (skillIds.length === 0) {
|
|
2478
|
-
p$
|
|
2058
|
+
p$9.log.error("Specify skills to add: openpaw add notes music email");
|
|
2479
2059
|
return;
|
|
2480
2060
|
}
|
|
2481
|
-
const s = p$
|
|
2061
|
+
const s = p$9.spinner();
|
|
2482
2062
|
for (const id of skillIds) {
|
|
2483
2063
|
const skill = getSkillById(id);
|
|
2484
2064
|
if (!skill) {
|
|
2485
|
-
p$
|
|
2065
|
+
p$9.log.error(`Unknown skill: ${id}`);
|
|
2486
2066
|
continue;
|
|
2487
2067
|
}
|
|
2488
2068
|
if (isSkillInstalled(id)) {
|
|
2489
|
-
p$
|
|
2069
|
+
p$9.log.info(`c-${id} already installed, skipping`);
|
|
2490
2070
|
continue;
|
|
2491
2071
|
}
|
|
2492
2072
|
const taps = getAllTaps([skill]);
|
|
@@ -2499,7 +2079,7 @@ async function addCommand(skillIds) {
|
|
|
2499
2079
|
}
|
|
2500
2080
|
installSkill(id);
|
|
2501
2081
|
addPermissions(skill.tools);
|
|
2502
|
-
p$
|
|
2082
|
+
p$9.log.success(`c-${id} installed`);
|
|
2503
2083
|
if (skill.authSteps?.length) for (const step of skill.authSteps) console.log(` ${chalk.yellow("→")} ${step.command} — ${step.description}`);
|
|
2504
2084
|
}
|
|
2505
2085
|
}
|
|
@@ -2510,22 +2090,22 @@ async function removeCommand(skillIds) {
|
|
|
2510
2090
|
showMini();
|
|
2511
2091
|
console.log("");
|
|
2512
2092
|
if (skillIds.length === 0) {
|
|
2513
|
-
p$
|
|
2093
|
+
p$8.log.error("Specify skills to remove: openpaw remove notes music");
|
|
2514
2094
|
return;
|
|
2515
2095
|
}
|
|
2516
2096
|
for (const id of skillIds) {
|
|
2517
2097
|
if (id === "core") {
|
|
2518
|
-
p$
|
|
2098
|
+
p$8.log.warn("Cannot remove c-core (coordinator). Use 'openpaw reset' instead.");
|
|
2519
2099
|
continue;
|
|
2520
2100
|
}
|
|
2521
2101
|
if (!isSkillInstalled(id)) {
|
|
2522
|
-
p$
|
|
2102
|
+
p$8.log.info(`c-${id} is not installed`);
|
|
2523
2103
|
continue;
|
|
2524
2104
|
}
|
|
2525
2105
|
const skill = getSkillById(id);
|
|
2526
2106
|
removeSkill(id);
|
|
2527
2107
|
if (skill) removePermissions(skill.tools);
|
|
2528
|
-
p$
|
|
2108
|
+
p$8.log.success(`${chalk.bold(`c-${id}`)} removed`);
|
|
2529
2109
|
}
|
|
2530
2110
|
}
|
|
2531
2111
|
|
|
@@ -2536,10 +2116,10 @@ async function statusCommand() {
|
|
|
2536
2116
|
console.log("");
|
|
2537
2117
|
const installed = listInstalledSkills();
|
|
2538
2118
|
if (installed.length === 0) {
|
|
2539
|
-
p$
|
|
2119
|
+
p$7.log.warn("No OpenPaw skills installed. Run: openpaw setup");
|
|
2540
2120
|
return;
|
|
2541
2121
|
}
|
|
2542
|
-
p$
|
|
2122
|
+
p$7.log.info(`${chalk.bold(installed.length)} skills installed:\n`);
|
|
2543
2123
|
for (const skillId of installed) {
|
|
2544
2124
|
if (skillId === "core") {
|
|
2545
2125
|
console.log(` ${chalk.green("●")} ${chalk.bold("c-core")} ${chalk.dim("— coordinator")}`);
|
|
@@ -2567,7 +2147,7 @@ async function statusCommand() {
|
|
|
2567
2147
|
async function doctorCommand() {
|
|
2568
2148
|
showMini();
|
|
2569
2149
|
console.log("");
|
|
2570
|
-
p$
|
|
2150
|
+
p$6.log.info("Running diagnostics...\n");
|
|
2571
2151
|
let issues = 0;
|
|
2572
2152
|
const platform = detectPlatform();
|
|
2573
2153
|
console.log(` ${chalk.green("✓")} Platform: ${platform.osName} ${platform.osVersion}`);
|
|
@@ -2606,8 +2186,8 @@ async function doctorCommand() {
|
|
|
2606
2186
|
issues++;
|
|
2607
2187
|
}
|
|
2608
2188
|
console.log("");
|
|
2609
|
-
if (issues === 0) p$
|
|
2610
|
-
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`);
|
|
2611
2191
|
}
|
|
2612
2192
|
|
|
2613
2193
|
//#endregion
|
|
@@ -2617,10 +2197,10 @@ async function updateCommand() {
|
|
|
2617
2197
|
console.log("");
|
|
2618
2198
|
const installed = listInstalledSkills();
|
|
2619
2199
|
if (installed.length === 0) {
|
|
2620
|
-
p$
|
|
2200
|
+
p$5.log.warn("No skills installed. Run: openpaw setup");
|
|
2621
2201
|
return;
|
|
2622
2202
|
}
|
|
2623
|
-
const s = p$
|
|
2203
|
+
const s = p$5.spinner();
|
|
2624
2204
|
const brewTools = [];
|
|
2625
2205
|
for (const skillId of installed) {
|
|
2626
2206
|
const skill = skills.find((sk) => sk.id === skillId);
|
|
@@ -2628,7 +2208,7 @@ async function updateCommand() {
|
|
|
2628
2208
|
for (const tool of skill.tools) if ((tool.installMethod === "brew" || tool.installMethod === "brew-tap") && isToolInstalled(tool.command)) brewTools.push(tool.name);
|
|
2629
2209
|
}
|
|
2630
2210
|
if (brewTools.length === 0) {
|
|
2631
|
-
p$
|
|
2211
|
+
p$5.log.info("No Homebrew-installed tools to update");
|
|
2632
2212
|
return;
|
|
2633
2213
|
}
|
|
2634
2214
|
s.start(`Updating ${brewTools.length} tools via Homebrew...`);
|
|
@@ -2654,15 +2234,15 @@ async function resetCommand() {
|
|
|
2654
2234
|
console.log("");
|
|
2655
2235
|
const installed = listInstalledSkills();
|
|
2656
2236
|
if (installed.length === 0) {
|
|
2657
|
-
p$
|
|
2237
|
+
p$4.log.info("Nothing to reset — no OpenPaw skills installed.");
|
|
2658
2238
|
return;
|
|
2659
2239
|
}
|
|
2660
|
-
const confirm = await p$
|
|
2661
|
-
if (p$
|
|
2662
|
-
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.");
|
|
2663
2243
|
return;
|
|
2664
2244
|
}
|
|
2665
|
-
const s = p$
|
|
2245
|
+
const s = p$4.spinner();
|
|
2666
2246
|
s.start("Removing skills and permissions...");
|
|
2667
2247
|
for (const skillId of installed) {
|
|
2668
2248
|
const skill = skills.find((sk) => sk.id === skillId);
|
|
@@ -2671,9 +2251,9 @@ async function resetCommand() {
|
|
|
2671
2251
|
}
|
|
2672
2252
|
removeSafetyHooks();
|
|
2673
2253
|
s.stop(`${chalk.green("✓")} Removed ${installed.length} skills, permissions, and hooks`);
|
|
2674
|
-
p$
|
|
2675
|
-
p$
|
|
2676
|
-
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.");
|
|
2677
2257
|
}
|
|
2678
2258
|
|
|
2679
2259
|
//#endregion
|
|
@@ -2714,35 +2294,35 @@ async function listCommand() {
|
|
|
2714
2294
|
//#region src/commands/soul.ts
|
|
2715
2295
|
async function soulCommand() {
|
|
2716
2296
|
showMini();
|
|
2717
|
-
p$
|
|
2297
|
+
p$3.intro(accent(" openpaw soul "));
|
|
2718
2298
|
if (soulExists()) {
|
|
2719
|
-
p$
|
|
2720
|
-
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({
|
|
2721
2301
|
message: "Overwrite existing personality?",
|
|
2722
2302
|
initialValue: false
|
|
2723
2303
|
});
|
|
2724
|
-
if (p$
|
|
2725
|
-
p$
|
|
2726
|
-
p$
|
|
2304
|
+
if (p$3.isCancel(overwrite) || !overwrite) {
|
|
2305
|
+
p$3.log.info("Keeping existing SOUL.md");
|
|
2306
|
+
p$3.outro(accent("Done"));
|
|
2727
2307
|
return;
|
|
2728
2308
|
}
|
|
2729
2309
|
}
|
|
2730
2310
|
const soul = await soulQuestionnaire();
|
|
2731
2311
|
if (!soul) {
|
|
2732
|
-
p$
|
|
2312
|
+
p$3.cancel("Cancelled.");
|
|
2733
2313
|
return;
|
|
2734
2314
|
}
|
|
2735
2315
|
writeSoul(soul);
|
|
2736
2316
|
showSoulSummary(soul);
|
|
2737
|
-
p$
|
|
2738
|
-
p$
|
|
2317
|
+
p$3.log.success("Personality saved to ~/.claude/SOUL.md");
|
|
2318
|
+
p$3.outro(accent("Claude will use this personality next session 🐾"));
|
|
2739
2319
|
}
|
|
2740
2320
|
|
|
2741
2321
|
//#endregion
|
|
2742
2322
|
//#region src/commands/export.ts
|
|
2743
2323
|
async function exportCommand() {
|
|
2744
2324
|
showMini();
|
|
2745
|
-
p$
|
|
2325
|
+
p$2.intro(accent(" openpaw export "));
|
|
2746
2326
|
const claudeDir = path$1.join(os$1.homedir(), ".claude");
|
|
2747
2327
|
const bundle = {
|
|
2748
2328
|
version: "1",
|
|
@@ -2754,52 +2334,52 @@ async function exportCommand() {
|
|
|
2754
2334
|
};
|
|
2755
2335
|
const installed = listInstalledSkills();
|
|
2756
2336
|
bundle.skills = installed;
|
|
2757
|
-
p$
|
|
2337
|
+
p$2.log.info(`${installed.length} skills found`);
|
|
2758
2338
|
const settings = readSettings();
|
|
2759
2339
|
bundle.permissions = settings.permissions?.allow ?? [];
|
|
2760
2340
|
const soulPath = path$1.join(claudeDir, "SOUL.md");
|
|
2761
2341
|
if (fs$1.existsSync(soulPath)) {
|
|
2762
2342
|
bundle.soul = fs$1.readFileSync(soulPath, "utf-8");
|
|
2763
|
-
p$
|
|
2343
|
+
p$2.log.info("SOUL.md included");
|
|
2764
2344
|
}
|
|
2765
2345
|
const memoryDir = path$1.join(claudeDir, "memory");
|
|
2766
2346
|
if (fs$1.existsSync(memoryDir)) {
|
|
2767
2347
|
const files = fs$1.readdirSync(memoryDir).filter((f) => f.endsWith(".md"));
|
|
2768
2348
|
for (const file of files) bundle.memory[file] = fs$1.readFileSync(path$1.join(memoryDir, file), "utf-8");
|
|
2769
|
-
p$
|
|
2349
|
+
p$2.log.info(`${files.length} memory files included`);
|
|
2770
2350
|
}
|
|
2771
2351
|
const outputPath = path$1.resolve("openpaw-export.json");
|
|
2772
2352
|
fs$1.writeFileSync(outputPath, JSON.stringify(bundle, null, 2), "utf-8");
|
|
2773
|
-
p$
|
|
2774
|
-
p$
|
|
2353
|
+
p$2.log.success(`Exported to ${dim(outputPath)}`);
|
|
2354
|
+
p$2.outro(accent("Import on another machine: openpaw import openpaw-export.json"));
|
|
2775
2355
|
}
|
|
2776
2356
|
async function importCommand(file) {
|
|
2777
2357
|
showMini();
|
|
2778
|
-
p$
|
|
2358
|
+
p$2.intro(accent(" openpaw import "));
|
|
2779
2359
|
const filePath = path$1.resolve(file);
|
|
2780
2360
|
if (!fs$1.existsSync(filePath)) {
|
|
2781
|
-
p$
|
|
2361
|
+
p$2.log.error(`File not found: ${filePath}`);
|
|
2782
2362
|
process.exit(1);
|
|
2783
2363
|
}
|
|
2784
2364
|
let bundle;
|
|
2785
2365
|
try {
|
|
2786
2366
|
bundle = JSON.parse(fs$1.readFileSync(filePath, "utf-8"));
|
|
2787
2367
|
} catch {
|
|
2788
|
-
p$
|
|
2368
|
+
p$2.log.error("Invalid export file — must be valid JSON");
|
|
2789
2369
|
process.exit(1);
|
|
2790
2370
|
}
|
|
2791
|
-
p$
|
|
2792
|
-
p$
|
|
2793
|
-
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({
|
|
2794
2374
|
message: "Import this configuration?",
|
|
2795
2375
|
initialValue: true
|
|
2796
2376
|
});
|
|
2797
|
-
if (p$
|
|
2798
|
-
p$
|
|
2377
|
+
if (p$2.isCancel(proceed) || !proceed) {
|
|
2378
|
+
p$2.cancel("Import cancelled.");
|
|
2799
2379
|
return;
|
|
2800
2380
|
}
|
|
2801
2381
|
const claudeDir = path$1.join(os$1.homedir(), ".claude");
|
|
2802
|
-
const s = p$
|
|
2382
|
+
const s = p$2.spinner();
|
|
2803
2383
|
if (bundle.soul) {
|
|
2804
2384
|
s.start("🐾 Restoring SOUL.md...");
|
|
2805
2385
|
fs$1.mkdirSync(claudeDir, { recursive: true });
|
|
@@ -2814,7 +2394,7 @@ async function importCommand(file) {
|
|
|
2814
2394
|
s.stop(`🐾 ${Object.keys(bundle.memory).length} memory files restored`);
|
|
2815
2395
|
}
|
|
2816
2396
|
if (bundle.skills.length > 0) {
|
|
2817
|
-
const { installSkill: installSkill$1 } = await import("./skills-
|
|
2397
|
+
const { installSkill: installSkill$1 } = await import("./skills-CUY0swcW.js");
|
|
2818
2398
|
s.start("🐾 Reinstalling skills...");
|
|
2819
2399
|
const targetDir = getDefaultSkillsDir();
|
|
2820
2400
|
let count = 0;
|
|
@@ -2832,13 +2412,13 @@ async function importCommand(file) {
|
|
|
2832
2412
|
if (newPerms.length > 0) {
|
|
2833
2413
|
if (!settings.permissions) settings.permissions = {};
|
|
2834
2414
|
settings.permissions.allow = [...existing, ...newPerms];
|
|
2835
|
-
const { writeSettings: writeSettings$1 } = await import("./permissions-
|
|
2415
|
+
const { writeSettings: writeSettings$1 } = await import("./permissions-AJXigU7k.js");
|
|
2836
2416
|
writeSettings$1(settings);
|
|
2837
2417
|
}
|
|
2838
2418
|
s.stop(`🐾 ${newPerms.length} permissions added`);
|
|
2839
2419
|
}
|
|
2840
|
-
p$
|
|
2841
|
-
p$
|
|
2420
|
+
p$2.log.success("Import complete");
|
|
2421
|
+
p$2.outro(accent("Run openpaw status to verify 🐾"));
|
|
2842
2422
|
}
|
|
2843
2423
|
|
|
2844
2424
|
//#endregion
|
|
@@ -2847,40 +2427,287 @@ async function telegramCommand() {
|
|
|
2847
2427
|
showMini();
|
|
2848
2428
|
const config = readTelegramConfig();
|
|
2849
2429
|
if (!config) {
|
|
2850
|
-
p.log.error("Telegram not configured yet.");
|
|
2851
|
-
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.`);
|
|
2852
2432
|
process.exit(1);
|
|
2853
2433
|
}
|
|
2854
2434
|
await startTelegramBot(config);
|
|
2855
2435
|
}
|
|
2856
2436
|
async function telegramSetupCommand() {
|
|
2857
2437
|
showMini();
|
|
2858
|
-
p.intro(accent(" Telegram Bridge Setup "));
|
|
2438
|
+
p$1.intro(accent(" Telegram Bridge Setup "));
|
|
2859
2439
|
if (telegramConfigExists()) {
|
|
2860
|
-
const overwrite = await p.confirm({
|
|
2440
|
+
const overwrite = await p$1.confirm({
|
|
2861
2441
|
message: "Telegram is already configured. Reconfigure?",
|
|
2862
2442
|
initialValue: false
|
|
2863
2443
|
});
|
|
2864
|
-
if (p.isCancel(overwrite) || !overwrite) {
|
|
2865
|
-
p.outro("Keeping existing config. 🐾");
|
|
2444
|
+
if (p$1.isCancel(overwrite) || !overwrite) {
|
|
2445
|
+
p$1.outro("Keeping existing config. 🐾");
|
|
2866
2446
|
return;
|
|
2867
2447
|
}
|
|
2868
2448
|
}
|
|
2869
2449
|
const config = await telegramQuestionnaire();
|
|
2870
2450
|
if (!config) {
|
|
2871
|
-
p.cancel("Setup cancelled.");
|
|
2451
|
+
p$1.cancel("Setup cancelled.");
|
|
2872
2452
|
process.exit(0);
|
|
2873
2453
|
}
|
|
2874
2454
|
writeTelegramConfig(config);
|
|
2875
|
-
p.log.success("Telegram config saved!");
|
|
2876
|
-
p.log.info(`Start the bridge with: ${bold("openpaw telegram")}`);
|
|
2877
|
-
p.outro(accent("Telegram setup complete 🐾"));
|
|
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)}`)}`);
|
|
2878
2705
|
}
|
|
2879
2706
|
|
|
2880
2707
|
//#endregion
|
|
2881
2708
|
//#region src/index.ts
|
|
2882
2709
|
const program = new Command();
|
|
2883
|
-
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");
|
|
2884
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);
|
|
2885
2712
|
program.command("add").description("Add skill(s) by name").argument("<skills...>", "Skill IDs to add (e.g. notes music email)").action(addCommand);
|
|
2886
2713
|
program.command("remove").description("Remove skill(s) by name").argument("<skills...>", "Skill IDs to remove").action(removeCommand);
|
|
@@ -2892,9 +2719,19 @@ program.command("reset").description("Remove all OpenPaw skills, permissions, an
|
|
|
2892
2719
|
program.command("soul").description("Set up or edit Claude's personality (SOUL.md)").action(soulCommand);
|
|
2893
2720
|
program.command("export").description("Export skills, memory, and config to a file").action(exportCommand);
|
|
2894
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);
|
|
2895
2723
|
const tg = program.command("telegram").description("Start the Telegram bridge — talk to Claude from your phone");
|
|
2896
2724
|
tg.action(telegramCommand);
|
|
2897
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));
|
|
2898
2735
|
program.parse();
|
|
2899
2736
|
|
|
2900
2737
|
//#endregion
|