@pulso/companion 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +304 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
2
8
|
|
|
3
9
|
// src/index.ts
|
|
4
10
|
import WebSocket from "ws";
|
|
@@ -62,12 +68,33 @@ function runAppleScript(script) {
|
|
|
62
68
|
}
|
|
63
69
|
function runShell(cmd, timeout = 1e4) {
|
|
64
70
|
return new Promise((resolve2, reject) => {
|
|
65
|
-
|
|
71
|
+
const shell = process.env.SHELL || "/bin/zsh";
|
|
72
|
+
exec(cmd, { timeout, shell, env: { ...process.env, PATH: augmentedPath() } }, (err, stdout, stderr) => {
|
|
66
73
|
if (err) reject(new Error(stderr || err.message));
|
|
67
74
|
else resolve2(stdout.trim());
|
|
68
75
|
});
|
|
69
76
|
});
|
|
70
77
|
}
|
|
78
|
+
function augmentedPath() {
|
|
79
|
+
const base = process.env.PATH || "/usr/local/bin:/usr/bin:/bin";
|
|
80
|
+
const home = process.env.HOME || "";
|
|
81
|
+
const extras = [];
|
|
82
|
+
const nvmDir = process.env.NVM_DIR || `${home}/.nvm`;
|
|
83
|
+
try {
|
|
84
|
+
const fs = __require("fs");
|
|
85
|
+
const defaultAlias = fs.readFileSync(`${nvmDir}/alias/default`, "utf8").trim();
|
|
86
|
+
const versionsDir = `${nvmDir}/versions/node`;
|
|
87
|
+
const dirs = fs.readdirSync(versionsDir);
|
|
88
|
+
const match = dirs.filter((d) => d.includes(defaultAlias)).sort().pop() || dirs.sort().pop();
|
|
89
|
+
if (match) extras.push(`${versionsDir}/${match}/bin`);
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
if (home) extras.push(`${home}/.volta/bin`);
|
|
93
|
+
if (home) extras.push(`${home}/.fnm/aliases/default/bin`);
|
|
94
|
+
extras.push("/opt/homebrew/bin", "/usr/local/bin");
|
|
95
|
+
const allParts = [...extras, ...base.split(":")];
|
|
96
|
+
return [...new Set(allParts)].join(":");
|
|
97
|
+
}
|
|
71
98
|
function runSwift(code, timeout = 1e4) {
|
|
72
99
|
return new Promise((resolve2, reject) => {
|
|
73
100
|
const child = exec(`swift -`, { timeout }, (err, stdout, stderr) => {
|
|
@@ -534,11 +561,12 @@ async function handleCommand(command, params) {
|
|
|
534
561
|
try {
|
|
535
562
|
await runAppleScript(`
|
|
536
563
|
tell application "Google Chrome"
|
|
564
|
+
make new window
|
|
565
|
+
set URL of active tab of front window to "${sanitizedUrl}"
|
|
537
566
|
activate
|
|
538
|
-
tell front window to make new tab with properties {URL:"${sanitizedUrl}"}
|
|
539
567
|
end tell`);
|
|
540
568
|
} catch {
|
|
541
|
-
await runShell(`open "${url.replace(/"/g, "")}"`);
|
|
569
|
+
await runShell(`open -n "${url.replace(/"/g, "")}"`);
|
|
542
570
|
}
|
|
543
571
|
return { success: true, data: { opened: url } };
|
|
544
572
|
}
|
|
@@ -1201,18 +1229,15 @@ print("\\(x),\\(y)")`;
|
|
|
1201
1229
|
if (browser === "Safari") {
|
|
1202
1230
|
await runAppleScript(`
|
|
1203
1231
|
tell application "Safari"
|
|
1232
|
+
make new document with properties {URL:"${url.replace(/"/g, '\\"')}"}
|
|
1204
1233
|
activate
|
|
1205
|
-
if (count of windows) = 0 then make new document
|
|
1206
|
-
tell front window to set current tab to (make new tab with properties {URL:"${url.replace(/"/g, '\\"')}"})
|
|
1207
1234
|
end tell`);
|
|
1208
1235
|
} else {
|
|
1209
1236
|
await runAppleScript(`
|
|
1210
1237
|
tell application "${browser.replace(/"/g, '\\"')}"
|
|
1238
|
+
make new window
|
|
1239
|
+
set URL of active tab of front window to "${url.replace(/"/g, '\\"')}"
|
|
1211
1240
|
activate
|
|
1212
|
-
if (count of windows) = 0 then
|
|
1213
|
-
make new window
|
|
1214
|
-
end if
|
|
1215
|
-
tell front window to make new tab with properties {URL:"${url.replace(/"/g, '\\"')}"}
|
|
1216
1241
|
end tell`);
|
|
1217
1242
|
}
|
|
1218
1243
|
return { success: true, data: { navigated: url, browser } };
|
|
@@ -1231,21 +1256,22 @@ print("\\(x),\\(y)")`;
|
|
|
1231
1256
|
if (browser === "Safari") {
|
|
1232
1257
|
await runAppleScript(`
|
|
1233
1258
|
tell application "Safari"
|
|
1259
|
+
make new document with properties {URL:"${url.replace(/"/g, '\\"')}"}
|
|
1234
1260
|
activate
|
|
1235
|
-
tell front window to set current tab to (make new tab with properties {URL:"${url.replace(/"/g, '\\"')}"})
|
|
1236
1261
|
end tell`);
|
|
1237
1262
|
} else {
|
|
1238
1263
|
await runAppleScript(`
|
|
1239
1264
|
tell application "${browser.replace(/"/g, '\\"')}"
|
|
1265
|
+
make new window
|
|
1266
|
+
set URL of active tab of front window to "${url.replace(/"/g, '\\"')}"
|
|
1240
1267
|
activate
|
|
1241
|
-
tell front window to make new tab with properties {URL:"${url.replace(/"/g, '\\"')}"}
|
|
1242
1268
|
end tell`);
|
|
1243
1269
|
}
|
|
1244
1270
|
return { success: true, data: { opened: url, browser } };
|
|
1245
1271
|
} catch (err) {
|
|
1246
1272
|
return {
|
|
1247
1273
|
success: false,
|
|
1248
|
-
error: `Failed to open
|
|
1274
|
+
error: `Failed to open window: ${err.message}`
|
|
1249
1275
|
};
|
|
1250
1276
|
}
|
|
1251
1277
|
}
|
|
@@ -2952,6 +2978,170 @@ print(result.stdout[:5000])
|
|
|
2952
2978
|
}
|
|
2953
2979
|
return { success: false, error: "Use action: list, start, stop" };
|
|
2954
2980
|
}
|
|
2981
|
+
// ── Claude Code Pipe (Max Subscription) ────────────────
|
|
2982
|
+
case "sys_claude_pipe": {
|
|
2983
|
+
const prompt = params.prompt;
|
|
2984
|
+
if (!prompt) return { success: false, error: "Missing prompt" };
|
|
2985
|
+
const model = params.model;
|
|
2986
|
+
const maxTurns = params.max_turns;
|
|
2987
|
+
const systemPrompt = params.system_prompt;
|
|
2988
|
+
const outputFormat = params.output_format || "json";
|
|
2989
|
+
const timeout = Number(params.timeout) || 12e4;
|
|
2990
|
+
const flags = ["-p", `--output-format ${outputFormat}`];
|
|
2991
|
+
if (model) flags.push(`--model ${model}`);
|
|
2992
|
+
if (maxTurns) flags.push(`--max-turns ${maxTurns}`);
|
|
2993
|
+
if (systemPrompt) flags.push(`--append-system-prompt ${JSON.stringify(systemPrompt)}`);
|
|
2994
|
+
flags.push("--allowedTools ''");
|
|
2995
|
+
const cmd = `claude ${flags.join(" ")}`;
|
|
2996
|
+
return new Promise((resolve2) => {
|
|
2997
|
+
const child = exec(cmd, { timeout }, (err, stdout, stderr) => {
|
|
2998
|
+
if (err) {
|
|
2999
|
+
resolve2({
|
|
3000
|
+
success: false,
|
|
3001
|
+
error: `Claude pipe error: ${stderr || err.message}`,
|
|
3002
|
+
errorCode: "CLAUDE_PIPE_FAILED"
|
|
3003
|
+
});
|
|
3004
|
+
} else {
|
|
3005
|
+
try {
|
|
3006
|
+
if (outputFormat === "json") {
|
|
3007
|
+
const parsed = JSON.parse(stdout);
|
|
3008
|
+
resolve2({
|
|
3009
|
+
success: true,
|
|
3010
|
+
data: {
|
|
3011
|
+
response: parsed.result || stdout.trim(),
|
|
3012
|
+
session_id: parsed.session_id,
|
|
3013
|
+
cost_usd: parsed.total_cost_usd ?? 0,
|
|
3014
|
+
duration_ms: parsed.duration_ms,
|
|
3015
|
+
num_turns: parsed.num_turns,
|
|
3016
|
+
model: model || "default",
|
|
3017
|
+
via: "claude-max-subscription"
|
|
3018
|
+
}
|
|
3019
|
+
});
|
|
3020
|
+
} else {
|
|
3021
|
+
resolve2({
|
|
3022
|
+
success: true,
|
|
3023
|
+
data: {
|
|
3024
|
+
response: stdout.trim(),
|
|
3025
|
+
model: model || "default",
|
|
3026
|
+
via: "claude-max-subscription"
|
|
3027
|
+
}
|
|
3028
|
+
});
|
|
3029
|
+
}
|
|
3030
|
+
} catch {
|
|
3031
|
+
resolve2({
|
|
3032
|
+
success: true,
|
|
3033
|
+
data: {
|
|
3034
|
+
response: stdout.trim(),
|
|
3035
|
+
model: model || "default",
|
|
3036
|
+
via: "claude-max-subscription"
|
|
3037
|
+
}
|
|
3038
|
+
});
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
});
|
|
3042
|
+
child.stdin?.write(prompt);
|
|
3043
|
+
child.stdin?.end();
|
|
3044
|
+
});
|
|
3045
|
+
}
|
|
3046
|
+
case "sys_claude_status": {
|
|
3047
|
+
try {
|
|
3048
|
+
const version = await runShell("claude --version 2>/dev/null", 5e3);
|
|
3049
|
+
let authStatus = "unknown";
|
|
3050
|
+
try {
|
|
3051
|
+
const status = await runShell("claude auth status 2>&1", 1e4);
|
|
3052
|
+
authStatus = status.includes("Authenticated") || status.includes("logged in") ? "authenticated" : "not_authenticated";
|
|
3053
|
+
} catch {
|
|
3054
|
+
authStatus = "not_authenticated";
|
|
3055
|
+
}
|
|
3056
|
+
return {
|
|
3057
|
+
success: true,
|
|
3058
|
+
data: {
|
|
3059
|
+
installed: true,
|
|
3060
|
+
version: version.trim(),
|
|
3061
|
+
authenticated: authStatus === "authenticated",
|
|
3062
|
+
status: authStatus
|
|
3063
|
+
}
|
|
3064
|
+
};
|
|
3065
|
+
} catch {
|
|
3066
|
+
return {
|
|
3067
|
+
success: true,
|
|
3068
|
+
data: {
|
|
3069
|
+
installed: false,
|
|
3070
|
+
version: null,
|
|
3071
|
+
authenticated: false,
|
|
3072
|
+
status: "not_installed"
|
|
3073
|
+
}
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
// ── OpenAI Codex CLI (ChatGPT Subscription) ────────────────
|
|
3078
|
+
case "sys_codex_status": {
|
|
3079
|
+
try {
|
|
3080
|
+
const version = await runShell("codex --version 2>/dev/null", 5e3);
|
|
3081
|
+
let authStatus = "unknown";
|
|
3082
|
+
try {
|
|
3083
|
+
const status = await runShell("codex auth whoami 2>&1 || codex --help 2>&1 | head -5", 1e4);
|
|
3084
|
+
const lc = status.toLowerCase();
|
|
3085
|
+
authStatus = lc.includes("not logged in") || lc.includes("not authenticated") || lc.includes("sign in") || lc.includes("no api key") ? "not_authenticated" : "authenticated";
|
|
3086
|
+
} catch {
|
|
3087
|
+
try {
|
|
3088
|
+
await runShell("security find-generic-password -s 'openai-codex' 2>/dev/null || security find-generic-password -s 'codex' 2>/dev/null", 5e3);
|
|
3089
|
+
authStatus = "authenticated";
|
|
3090
|
+
} catch {
|
|
3091
|
+
authStatus = "not_authenticated";
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
return {
|
|
3095
|
+
success: true,
|
|
3096
|
+
data: {
|
|
3097
|
+
installed: true,
|
|
3098
|
+
version: version.trim(),
|
|
3099
|
+
authenticated: authStatus === "authenticated",
|
|
3100
|
+
status: authStatus
|
|
3101
|
+
}
|
|
3102
|
+
};
|
|
3103
|
+
} catch {
|
|
3104
|
+
return {
|
|
3105
|
+
success: true,
|
|
3106
|
+
data: {
|
|
3107
|
+
installed: false,
|
|
3108
|
+
version: null,
|
|
3109
|
+
authenticated: false,
|
|
3110
|
+
status: "not_installed"
|
|
3111
|
+
}
|
|
3112
|
+
};
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
case "sys_codex_pipe": {
|
|
3116
|
+
const prompt = params.prompt;
|
|
3117
|
+
if (!prompt) return { success: false, error: "Missing prompt" };
|
|
3118
|
+
const model = params.model;
|
|
3119
|
+
const timeout = Number(params.timeout) || 12e4;
|
|
3120
|
+
const args = ["exec"];
|
|
3121
|
+
if (model) args.push("--model", model);
|
|
3122
|
+
args.push(JSON.stringify(prompt));
|
|
3123
|
+
const cmd = `codex ${args.join(" ")}`;
|
|
3124
|
+
return new Promise((resolve2) => {
|
|
3125
|
+
exec(cmd, { timeout }, (err, stdout, stderr) => {
|
|
3126
|
+
if (err) {
|
|
3127
|
+
resolve2({
|
|
3128
|
+
success: false,
|
|
3129
|
+
error: `Codex pipe error: ${stderr || err.message}`,
|
|
3130
|
+
errorCode: "CODEX_PIPE_FAILED"
|
|
3131
|
+
});
|
|
3132
|
+
} else {
|
|
3133
|
+
resolve2({
|
|
3134
|
+
success: true,
|
|
3135
|
+
data: {
|
|
3136
|
+
response: stdout.trim(),
|
|
3137
|
+
model: model || "default",
|
|
3138
|
+
via: "chatgpt-subscription"
|
|
3139
|
+
}
|
|
3140
|
+
});
|
|
3141
|
+
}
|
|
3142
|
+
});
|
|
3143
|
+
});
|
|
3144
|
+
}
|
|
2955
3145
|
default:
|
|
2956
3146
|
return { success: false, error: `Unknown command: ${command}` };
|
|
2957
3147
|
}
|
|
@@ -3086,8 +3276,81 @@ async function sonosRequest(baseUrl, path) {
|
|
|
3086
3276
|
var ws = null;
|
|
3087
3277
|
var reconnectTimer = null;
|
|
3088
3278
|
var heartbeatTimer = null;
|
|
3279
|
+
var imessageTimer = null;
|
|
3089
3280
|
var HEARTBEAT_INTERVAL = 3e4;
|
|
3281
|
+
var IMESSAGE_POLL_INTERVAL = 3e3;
|
|
3090
3282
|
var reconnectAttempts = 0;
|
|
3283
|
+
var lastImessageRowId = 0;
|
|
3284
|
+
function startImessageMonitor() {
|
|
3285
|
+
const chatDbPath = join(HOME, "Library/Messages/chat.db");
|
|
3286
|
+
if (!existsSync(chatDbPath)) {
|
|
3287
|
+
console.log(" \u26A0 iMessage: chat.db not found \u2014 monitor disabled");
|
|
3288
|
+
return;
|
|
3289
|
+
}
|
|
3290
|
+
try {
|
|
3291
|
+
const initResult = execSync(
|
|
3292
|
+
`sqlite3 "${chatDbPath}" "SELECT MAX(ROWID) FROM message"`,
|
|
3293
|
+
{ encoding: "utf8", timeout: 5e3 }
|
|
3294
|
+
).trim();
|
|
3295
|
+
lastImessageRowId = parseInt(initResult, 10) || 0;
|
|
3296
|
+
console.log(` \u2713 iMessage: monitoring from ROWID ${lastImessageRowId}`);
|
|
3297
|
+
} catch (err) {
|
|
3298
|
+
console.log(` \u26A0 iMessage: failed to read chat.db \u2014 ${err.message}`);
|
|
3299
|
+
console.log(" Grant Full Disk Access to Terminal/iTerm in System Settings \u2192 Privacy & Security");
|
|
3300
|
+
return;
|
|
3301
|
+
}
|
|
3302
|
+
imessageTimer = setInterval(async () => {
|
|
3303
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
3304
|
+
try {
|
|
3305
|
+
const query = `
|
|
3306
|
+
SELECT m.ROWID, m.text, m.date,
|
|
3307
|
+
COALESCE(h.id, '') as sender_id,
|
|
3308
|
+
COALESCE(c.display_name, h.id, 'Unknown') as sender_name,
|
|
3309
|
+
c.chat_identifier
|
|
3310
|
+
FROM message m
|
|
3311
|
+
LEFT JOIN handle h ON m.handle_id = h.ROWID
|
|
3312
|
+
LEFT JOIN chat_message_join cmj ON m.ROWID = cmj.message_id
|
|
3313
|
+
LEFT JOIN chat c ON cmj.chat_id = c.ROWID
|
|
3314
|
+
WHERE m.ROWID > ${lastImessageRowId}
|
|
3315
|
+
AND m.is_from_me = 0
|
|
3316
|
+
AND m.text IS NOT NULL
|
|
3317
|
+
AND m.text != ''
|
|
3318
|
+
ORDER BY m.ROWID ASC
|
|
3319
|
+
LIMIT 10
|
|
3320
|
+
`.replace(/\n/g, " ");
|
|
3321
|
+
const result = execSync(
|
|
3322
|
+
`sqlite3 -separator '|||' "${chatDbPath}" "${query}"`,
|
|
3323
|
+
{ encoding: "utf8", timeout: 5e3 }
|
|
3324
|
+
).trim();
|
|
3325
|
+
if (!result) return;
|
|
3326
|
+
const lines = result.split("\n").filter(Boolean);
|
|
3327
|
+
for (const line of lines) {
|
|
3328
|
+
const [rowIdStr, text, , senderId, senderName, chatId] = line.split("|||");
|
|
3329
|
+
const rowId = parseInt(rowIdStr || "0", 10);
|
|
3330
|
+
if (rowId <= lastImessageRowId) continue;
|
|
3331
|
+
lastImessageRowId = rowId;
|
|
3332
|
+
if (!text || text.startsWith("\uFFFC")) continue;
|
|
3333
|
+
console.log(`
|
|
3334
|
+
\u{1F4AC} iMessage from ${senderName || senderId}: ${text.slice(0, 80)}`);
|
|
3335
|
+
ws.send(JSON.stringify({
|
|
3336
|
+
type: "imessage_incoming",
|
|
3337
|
+
from: senderId || "unknown",
|
|
3338
|
+
fromName: senderName || senderId || "Unknown",
|
|
3339
|
+
chatId: chatId || senderId || "unknown",
|
|
3340
|
+
text,
|
|
3341
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3342
|
+
}));
|
|
3343
|
+
}
|
|
3344
|
+
} catch {
|
|
3345
|
+
}
|
|
3346
|
+
}, IMESSAGE_POLL_INTERVAL);
|
|
3347
|
+
}
|
|
3348
|
+
function stopImessageMonitor() {
|
|
3349
|
+
if (imessageTimer) {
|
|
3350
|
+
clearInterval(imessageTimer);
|
|
3351
|
+
imessageTimer = null;
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3091
3354
|
var CAPABILITY_PROBES = [
|
|
3092
3355
|
{
|
|
3093
3356
|
name: "calendar",
|
|
@@ -3197,6 +3460,30 @@ var CAPABILITY_PROBES = [
|
|
|
3197
3460
|
}
|
|
3198
3461
|
},
|
|
3199
3462
|
tools: ["sys_shortcuts_run", "sys_shortcuts_list"]
|
|
3463
|
+
},
|
|
3464
|
+
{
|
|
3465
|
+
name: "claude_cli",
|
|
3466
|
+
test: async () => {
|
|
3467
|
+
try {
|
|
3468
|
+
await runShell("which claude >/dev/null 2>&1 && claude --version >/dev/null 2>&1", 5e3);
|
|
3469
|
+
return true;
|
|
3470
|
+
} catch {
|
|
3471
|
+
return false;
|
|
3472
|
+
}
|
|
3473
|
+
},
|
|
3474
|
+
tools: ["sys_claude_pipe", "sys_claude_status"]
|
|
3475
|
+
},
|
|
3476
|
+
{
|
|
3477
|
+
name: "codex_cli",
|
|
3478
|
+
test: async () => {
|
|
3479
|
+
try {
|
|
3480
|
+
await runShell("which codex >/dev/null 2>&1 && codex --version >/dev/null 2>&1", 5e3);
|
|
3481
|
+
return true;
|
|
3482
|
+
} catch {
|
|
3483
|
+
return false;
|
|
3484
|
+
}
|
|
3485
|
+
},
|
|
3486
|
+
tools: ["sys_codex_pipe", "sys_codex_status"]
|
|
3200
3487
|
}
|
|
3201
3488
|
];
|
|
3202
3489
|
var verifiedCapabilities = {
|
|
@@ -3305,6 +3592,8 @@ function connect() {
|
|
|
3305
3592
|
platform: "macos",
|
|
3306
3593
|
version: "0.4.0",
|
|
3307
3594
|
accessLevel: ACCESS_LEVEL,
|
|
3595
|
+
homeDir: HOME,
|
|
3596
|
+
hostname: __require("os").hostname(),
|
|
3308
3597
|
capabilities: cap.available,
|
|
3309
3598
|
unavailable: cap.unavailable,
|
|
3310
3599
|
tools: cap.tools,
|
|
@@ -3317,6 +3606,8 @@ function connect() {
|
|
|
3317
3606
|
ws.send(JSON.stringify({ type: "ping" }));
|
|
3318
3607
|
}
|
|
3319
3608
|
}, HEARTBEAT_INTERVAL);
|
|
3609
|
+
stopImessageMonitor();
|
|
3610
|
+
startImessageMonitor();
|
|
3320
3611
|
});
|
|
3321
3612
|
ws.on("message", async (raw) => {
|
|
3322
3613
|
try {
|
|
@@ -3351,6 +3642,7 @@ function connect() {
|
|
|
3351
3642
|
const reasonStr = reason.toString() || "unknown";
|
|
3352
3643
|
console.log(`
|
|
3353
3644
|
\u{1F50C} Disconnected (${code}: ${reasonStr})`);
|
|
3645
|
+
stopImessageMonitor();
|
|
3354
3646
|
if (heartbeatTimer) {
|
|
3355
3647
|
clearInterval(heartbeatTimer);
|
|
3356
3648
|
heartbeatTimer = null;
|