@stackmemoryai/stackmemory 0.5.31 → 0.5.34
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/agents/core/agent-task-manager.js.map +1 -1
- package/dist/cli/claude-sm.js +199 -16
- package/dist/cli/claude-sm.js.map +2 -2
- package/dist/cli/commands/clear.js +1 -1
- package/dist/cli/commands/clear.js.map +1 -1
- package/dist/cli/commands/context.js +1 -12
- package/dist/cli/commands/context.js.map +2 -2
- package/dist/cli/commands/dashboard.js.map +1 -1
- package/dist/cli/commands/discovery.js +1 -1
- package/dist/cli/commands/discovery.js.map +1 -1
- package/dist/cli/commands/handoff.js +1 -1
- package/dist/cli/commands/handoff.js.map +1 -1
- package/dist/cli/commands/linear.js +1 -14
- package/dist/cli/commands/linear.js.map +2 -2
- package/dist/cli/commands/login.js +32 -10
- package/dist/cli/commands/login.js.map +2 -2
- package/dist/cli/commands/migrate.js +80 -22
- package/dist/cli/commands/migrate.js.map +2 -2
- package/dist/cli/commands/model.js +533 -0
- package/dist/cli/commands/model.js.map +7 -0
- package/dist/cli/commands/monitor.js +1 -1
- package/dist/cli/commands/monitor.js.map +1 -1
- package/dist/cli/commands/quality.js +1 -1
- package/dist/cli/commands/quality.js.map +1 -1
- package/dist/cli/commands/ralph.js +93 -28
- package/dist/cli/commands/ralph.js.map +2 -2
- package/dist/cli/commands/service.js +10 -3
- package/dist/cli/commands/service.js.map +2 -2
- package/dist/cli/commands/skills.js +61 -11
- package/dist/cli/commands/skills.js.map +2 -2
- package/dist/cli/commands/sms-notify.js +342 -22
- package/dist/cli/commands/sms-notify.js.map +3 -3
- package/dist/cli/commands/workflow.js +1 -1
- package/dist/cli/commands/workflow.js.map +1 -1
- package/dist/cli/commands/worktree.js +1 -1
- package/dist/cli/commands/worktree.js.map +1 -1
- package/dist/cli/index.js +3 -1
- package/dist/cli/index.js.map +2 -2
- package/dist/core/context/auto-context.js.map +1 -1
- package/dist/core/context/compaction-handler.js.map +2 -2
- package/dist/core/context/context-bridge.js.map +2 -2
- package/dist/core/context/dual-stack-manager.js +24 -8
- package/dist/core/context/dual-stack-manager.js.map +2 -2
- package/dist/core/context/enhanced-rehydration.js.map +1 -1
- package/dist/core/context/frame-database.js +41 -5
- package/dist/core/context/frame-database.js.map +2 -2
- package/dist/core/context/frame-digest.js +6 -1
- package/dist/core/context/frame-digest.js.map +2 -2
- package/dist/core/context/frame-handoff-manager.js.map +1 -1
- package/dist/core/context/frame-lifecycle-hooks.js +119 -0
- package/dist/core/context/frame-lifecycle-hooks.js.map +7 -0
- package/dist/core/context/frame-manager.js +56 -9
- package/dist/core/context/frame-manager.js.map +2 -2
- package/dist/core/context/frame-stack.js +29 -0
- package/dist/core/context/frame-stack.js.map +2 -2
- package/dist/core/context/incremental-gc.js.map +2 -2
- package/dist/core/context/index.js +4 -22
- package/dist/core/context/index.js.map +2 -2
- package/dist/core/context/permission-manager.js +0 -11
- package/dist/core/context/permission-manager.js.map +2 -2
- package/dist/core/context/recursive-context-manager.js +15 -9
- package/dist/core/context/recursive-context-manager.js.map +2 -2
- package/dist/core/context/refactored-frame-manager.js +140 -34
- package/dist/core/context/refactored-frame-manager.js.map +3 -3
- package/dist/core/context/shared-context-layer.js +0 -11
- package/dist/core/context/shared-context-layer.js.map +2 -2
- package/dist/core/context/stack-merge-resolver.js.map +1 -1
- package/dist/core/context/validation.js +6 -1
- package/dist/core/context/validation.js.map +2 -2
- package/dist/core/database/database-adapter.js.map +1 -1
- package/dist/core/database/paradedb-adapter.js.map +1 -1
- package/dist/core/database/query-router.js.map +1 -1
- package/dist/core/database/sqlite-adapter.js.map +1 -1
- package/dist/core/digest/frame-digest-integration.js.map +1 -1
- package/dist/core/digest/hybrid-digest-generator.js.map +1 -1
- package/dist/core/digest/types.js.map +1 -1
- package/dist/core/errors/index.js +249 -0
- package/dist/core/errors/index.js.map +2 -2
- package/dist/core/frame/workflow-templates.js.map +2 -2
- package/dist/core/merge/conflict-detector.js.map +1 -1
- package/dist/core/merge/resolution-engine.js.map +1 -1
- package/dist/core/merge/stack-diff.js.map +1 -1
- package/dist/core/models/fallback-monitor.js +229 -0
- package/dist/core/models/fallback-monitor.js.map +7 -0
- package/dist/core/models/model-router.js +340 -0
- package/dist/core/models/model-router.js.map +7 -0
- package/dist/core/monitoring/error-handler.js +37 -270
- package/dist/core/monitoring/error-handler.js.map +3 -3
- package/dist/core/monitoring/session-monitor.js.map +1 -1
- package/dist/core/performance/lazy-context-loader.js.map +1 -1
- package/dist/core/performance/optimized-frame-context.js.map +1 -1
- package/dist/core/retrieval/context-retriever.js.map +1 -1
- package/dist/core/retrieval/graph-retrieval.js.map +1 -1
- package/dist/core/retrieval/hierarchical-retrieval.js.map +1 -1
- package/dist/core/retrieval/llm-context-retrieval.js.map +1 -1
- package/dist/core/retrieval/retrieval-benchmarks.js.map +1 -1
- package/dist/core/retrieval/summary-generator.js.map +1 -1
- package/dist/core/retrieval/types.js.map +1 -1
- package/dist/core/storage/chromadb-adapter.js.map +1 -1
- package/dist/core/storage/infinite-storage.js.map +1 -1
- package/dist/core/storage/two-tier-storage.js.map +1 -1
- package/dist/features/tasks/task-aware-context.js.map +1 -1
- package/dist/features/web/server/index.js +1 -1
- package/dist/features/web/server/index.js.map +1 -1
- package/dist/hooks/claude-code-whatsapp-hook.js +197 -0
- package/dist/hooks/claude-code-whatsapp-hook.js.map +7 -0
- package/dist/hooks/linear-task-picker.js +1 -1
- package/dist/hooks/linear-task-picker.js.map +2 -2
- package/dist/hooks/schemas.js +105 -1
- package/dist/hooks/schemas.js.map +2 -2
- package/dist/hooks/session-summary.js +5 -1
- package/dist/hooks/session-summary.js.map +2 -2
- package/dist/hooks/sms-action-runner.js +16 -1
- package/dist/hooks/sms-action-runner.js.map +2 -2
- package/dist/hooks/sms-notify.js +4 -2
- package/dist/hooks/sms-notify.js.map +2 -2
- package/dist/hooks/sms-webhook.js +23 -2
- package/dist/hooks/sms-webhook.js.map +2 -2
- package/dist/hooks/whatsapp-commands.js +516 -0
- package/dist/hooks/whatsapp-commands.js.map +7 -0
- package/dist/hooks/whatsapp-scheduler.js +317 -0
- package/dist/hooks/whatsapp-scheduler.js.map +7 -0
- package/dist/hooks/whatsapp-sync.js +409 -0
- package/dist/hooks/whatsapp-sync.js.map +7 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/integrations/mcp/handlers/context-handlers.js.map +1 -1
- package/dist/integrations/mcp/handlers/discovery-handlers.js.map +1 -1
- package/dist/integrations/mcp/server.js +1 -1
- package/dist/integrations/mcp/server.js.map +1 -1
- package/dist/integrations/ralph/bridge/ralph-stackmemory-bridge.js +1 -1
- package/dist/integrations/ralph/bridge/ralph-stackmemory-bridge.js.map +1 -1
- package/dist/integrations/ralph/context/stackmemory-context-loader.js +1 -1
- package/dist/integrations/ralph/context/stackmemory-context-loader.js.map +1 -1
- package/dist/integrations/ralph/learning/pattern-learner.js +1 -1
- package/dist/integrations/ralph/learning/pattern-learner.js.map +1 -1
- package/dist/integrations/ralph/orchestration/multi-loop-orchestrator.js +1 -1
- package/dist/integrations/ralph/orchestration/multi-loop-orchestrator.js.map +1 -1
- package/dist/integrations/ralph/swarm/swarm-coordinator.js +1 -1
- package/dist/integrations/ralph/swarm/swarm-coordinator.js.map +1 -1
- package/dist/integrations/ralph/visualization/ralph-debugger.js +1 -1
- package/dist/integrations/ralph/visualization/ralph-debugger.js.map +1 -1
- package/dist/mcp/stackmemory-mcp-server.js +1 -1
- package/dist/mcp/stackmemory-mcp-server.js.map +1 -1
- package/dist/skills/claude-skills.js.map +1 -1
- package/dist/skills/recursive-agent-orchestrator.js.map +1 -1
- package/dist/skills/unified-rlm-orchestrator.js.map +1 -1
- package/package.json +2 -3
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { existsSync, readFileSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { execFileSync } from "child_process";
|
|
9
|
+
import { writeFileSecure, ensureSecureDir } from "./secure-fs.js";
|
|
10
|
+
import { WhatsAppCommandsConfigSchema, parseConfigSafe } from "./schemas.js";
|
|
11
|
+
import { executeActionSafe } from "./sms-action-runner.js";
|
|
12
|
+
import {
|
|
13
|
+
syncContext,
|
|
14
|
+
getFrameDigestData,
|
|
15
|
+
generateMobileDigest,
|
|
16
|
+
loadSyncOptions
|
|
17
|
+
} from "./whatsapp-sync.js";
|
|
18
|
+
import { sendNotification } from "./sms-notify.js";
|
|
19
|
+
const REGEX_TIMEOUT_MS = 100;
|
|
20
|
+
const MAX_REGEX_INPUT_LENGTH = 200;
|
|
21
|
+
function safeRegexTest(pattern, input) {
|
|
22
|
+
const safeInput = input.slice(0, MAX_REGEX_INPUT_LENGTH);
|
|
23
|
+
try {
|
|
24
|
+
const regex = new RegExp(pattern);
|
|
25
|
+
const startTime = Date.now();
|
|
26
|
+
const result = regex.test(safeInput);
|
|
27
|
+
const elapsed = Date.now() - startTime;
|
|
28
|
+
if (elapsed > REGEX_TIMEOUT_MS) {
|
|
29
|
+
console.warn(
|
|
30
|
+
`[whatsapp-commands] Slow regex detected: ${pattern} took ${elapsed}ms`
|
|
31
|
+
);
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
} catch {
|
|
36
|
+
console.warn(`[whatsapp-commands] Invalid regex pattern: ${pattern}`);
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const CONFIG_PATH = join(homedir(), ".stackmemory", "whatsapp-commands.json");
|
|
41
|
+
const REMOTE_SESSIONS_PATH = join(
|
|
42
|
+
homedir(),
|
|
43
|
+
".stackmemory",
|
|
44
|
+
"remote-sessions.json"
|
|
45
|
+
);
|
|
46
|
+
function loadRemoteSessions() {
|
|
47
|
+
try {
|
|
48
|
+
if (existsSync(REMOTE_SESSIONS_PATH)) {
|
|
49
|
+
return JSON.parse(readFileSync(REMOTE_SESSIONS_PATH, "utf8"));
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
return { sessions: [] };
|
|
54
|
+
}
|
|
55
|
+
function saveRemoteSessions(store) {
|
|
56
|
+
try {
|
|
57
|
+
ensureSecureDir(join(homedir(), ".stackmemory"));
|
|
58
|
+
writeFileSecure(REMOTE_SESSIONS_PATH, JSON.stringify(store, null, 2));
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function addRemoteSession(session) {
|
|
63
|
+
const store = loadRemoteSessions();
|
|
64
|
+
store.sessions = [session, ...store.sessions.slice(0, 19)];
|
|
65
|
+
saveRemoteSessions(store);
|
|
66
|
+
}
|
|
67
|
+
function getRemoteSessions() {
|
|
68
|
+
return loadRemoteSessions().sessions;
|
|
69
|
+
}
|
|
70
|
+
function getActiveRemoteSessions() {
|
|
71
|
+
return loadRemoteSessions().sessions.filter((s) => s.status === "active");
|
|
72
|
+
}
|
|
73
|
+
const DEFAULT_COMMANDS = [
|
|
74
|
+
{
|
|
75
|
+
name: "status",
|
|
76
|
+
description: "Get current task/frame status",
|
|
77
|
+
enabled: true
|
|
78
|
+
// No action - handled specially in-process
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "tasks",
|
|
82
|
+
description: "List active tasks",
|
|
83
|
+
enabled: true
|
|
84
|
+
// No action - handled specially in-process
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "context",
|
|
88
|
+
description: "Get latest context digest",
|
|
89
|
+
enabled: true
|
|
90
|
+
// No action - handled specially
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "approve",
|
|
94
|
+
description: "Approve a PR (requires PR number)",
|
|
95
|
+
enabled: true,
|
|
96
|
+
requiresArg: true,
|
|
97
|
+
argPattern: "^\\d+$"
|
|
98
|
+
// PR number must be numeric
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "merge",
|
|
102
|
+
description: "Merge a PR (requires PR number)",
|
|
103
|
+
enabled: true,
|
|
104
|
+
requiresArg: true,
|
|
105
|
+
argPattern: "^\\d+$"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "help",
|
|
109
|
+
description: "List available commands",
|
|
110
|
+
enabled: true
|
|
111
|
+
// No action - handled specially
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "sync",
|
|
115
|
+
description: "Push current context to WhatsApp",
|
|
116
|
+
enabled: true
|
|
117
|
+
// No action - handled specially
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "build",
|
|
121
|
+
description: "Run npm build",
|
|
122
|
+
enabled: true,
|
|
123
|
+
action: "npm run build"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: "test",
|
|
127
|
+
description: "Run tests",
|
|
128
|
+
enabled: true,
|
|
129
|
+
action: "npm run test:run"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "lint",
|
|
133
|
+
description: "Run linter",
|
|
134
|
+
enabled: true,
|
|
135
|
+
action: "npm run lint"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "log",
|
|
139
|
+
description: "Show recent git commits",
|
|
140
|
+
enabled: true,
|
|
141
|
+
action: "git log --oneline -5"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "diff",
|
|
145
|
+
description: "Show git diff summary",
|
|
146
|
+
enabled: true,
|
|
147
|
+
action: "git diff --stat"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "pr",
|
|
151
|
+
description: "List open PRs",
|
|
152
|
+
enabled: true,
|
|
153
|
+
action: "gh pr list"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "branch",
|
|
157
|
+
description: "Show current branch",
|
|
158
|
+
enabled: true,
|
|
159
|
+
action: "git branch --show-current"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: "remote",
|
|
163
|
+
description: "Launch remote Claude session (requires task prompt)",
|
|
164
|
+
enabled: true,
|
|
165
|
+
requiresArg: true
|
|
166
|
+
// No action - handled specially to capture session URL
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "sessions",
|
|
170
|
+
description: "List active remote sessions",
|
|
171
|
+
enabled: true
|
|
172
|
+
// No action - handled specially
|
|
173
|
+
}
|
|
174
|
+
];
|
|
175
|
+
const DEFAULT_CONFIG = {
|
|
176
|
+
enabled: true,
|
|
177
|
+
commands: DEFAULT_COMMANDS
|
|
178
|
+
};
|
|
179
|
+
function loadCommandsConfig() {
|
|
180
|
+
try {
|
|
181
|
+
if (existsSync(CONFIG_PATH)) {
|
|
182
|
+
const data = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
183
|
+
return parseConfigSafe(
|
|
184
|
+
WhatsAppCommandsConfigSchema,
|
|
185
|
+
{ ...DEFAULT_CONFIG, ...data },
|
|
186
|
+
DEFAULT_CONFIG,
|
|
187
|
+
"whatsapp-commands"
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
} catch {
|
|
191
|
+
}
|
|
192
|
+
return { ...DEFAULT_CONFIG };
|
|
193
|
+
}
|
|
194
|
+
function saveCommandsConfig(config) {
|
|
195
|
+
try {
|
|
196
|
+
ensureSecureDir(join(homedir(), ".stackmemory"));
|
|
197
|
+
writeFileSecure(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
198
|
+
} catch {
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function isCommand(message) {
|
|
202
|
+
const trimmed = message.trim().toLowerCase();
|
|
203
|
+
const config = loadCommandsConfig();
|
|
204
|
+
if (!config.enabled) return false;
|
|
205
|
+
const words = trimmed.split(/\s+/);
|
|
206
|
+
const firstWord = words[0];
|
|
207
|
+
return config.commands.some(
|
|
208
|
+
(cmd) => cmd.enabled && cmd.name.toLowerCase() === firstWord
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
function parseCommand(message) {
|
|
212
|
+
const trimmed = message.trim();
|
|
213
|
+
const words = trimmed.split(/\s+/);
|
|
214
|
+
if (words.length === 0) return null;
|
|
215
|
+
const name = words[0].toLowerCase();
|
|
216
|
+
const arg = words.slice(1).join(" ").trim() || void 0;
|
|
217
|
+
return { name, arg };
|
|
218
|
+
}
|
|
219
|
+
function generateHelpText(config) {
|
|
220
|
+
const lines = ["Available commands:"];
|
|
221
|
+
config.commands.filter((cmd) => cmd.enabled).forEach((cmd) => {
|
|
222
|
+
const argHint = cmd.requiresArg ? " <arg>" : "";
|
|
223
|
+
lines.push(` ${cmd.name}${argHint} - ${cmd.description}`);
|
|
224
|
+
});
|
|
225
|
+
lines.push("");
|
|
226
|
+
lines.push("Reply with command name to execute");
|
|
227
|
+
return lines.join("\n");
|
|
228
|
+
}
|
|
229
|
+
async function handleContextCommand() {
|
|
230
|
+
const data = await getFrameDigestData();
|
|
231
|
+
if (!data) {
|
|
232
|
+
return "No context available. Start a task first.";
|
|
233
|
+
}
|
|
234
|
+
const options = loadSyncOptions();
|
|
235
|
+
return generateMobileDigest(data, options);
|
|
236
|
+
}
|
|
237
|
+
async function handleSyncCommand() {
|
|
238
|
+
const result = await syncContext();
|
|
239
|
+
if (result.success) {
|
|
240
|
+
return `Context synced (${result.digestLength} chars)`;
|
|
241
|
+
} else {
|
|
242
|
+
return `Sync failed: ${result.error}`;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async function handleStatusCommand() {
|
|
246
|
+
try {
|
|
247
|
+
const data = await getFrameDigestData();
|
|
248
|
+
if (!data) {
|
|
249
|
+
return "No active session. Start with: claude-sm";
|
|
250
|
+
}
|
|
251
|
+
const lines = [];
|
|
252
|
+
lines.push(`Frame: ${data.name || data.frameId}`);
|
|
253
|
+
lines.push(`Status: ${data.status}`);
|
|
254
|
+
lines.push(`Files: ${data.filesModified?.length || 0} modified`);
|
|
255
|
+
lines.push(`Tools: ${data.toolCallCount || 0} calls`);
|
|
256
|
+
if (data.errors?.length > 0) {
|
|
257
|
+
const unresolved = data.errors.filter((e) => !e.resolved).length;
|
|
258
|
+
if (unresolved > 0) lines.push(`Errors: ${unresolved} unresolved`);
|
|
259
|
+
}
|
|
260
|
+
lines.push(`Duration: ${Math.round(data.durationSeconds / 60)}min`);
|
|
261
|
+
return lines.join("\n");
|
|
262
|
+
} catch {
|
|
263
|
+
return "Status unavailable";
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
async function handleTasksCommand() {
|
|
267
|
+
try {
|
|
268
|
+
const data = await getFrameDigestData();
|
|
269
|
+
if (!data) {
|
|
270
|
+
return "No active tasks";
|
|
271
|
+
}
|
|
272
|
+
const lines = [];
|
|
273
|
+
if (data.decisions?.length > 0) {
|
|
274
|
+
lines.push("Recent decisions:");
|
|
275
|
+
data.decisions.slice(0, 3).forEach((d, i) => {
|
|
276
|
+
lines.push(
|
|
277
|
+
`${i + 1}. ${d.substring(0, 50)}${d.length > 50 ? "..." : ""}`
|
|
278
|
+
);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
if (data.risks?.length > 0) {
|
|
282
|
+
lines.push("");
|
|
283
|
+
lines.push("Risks:");
|
|
284
|
+
data.risks.slice(0, 2).forEach((r) => {
|
|
285
|
+
lines.push(`- ${r.substring(0, 50)}${r.length > 50 ? "..." : ""}`);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
if (lines.length === 0) {
|
|
289
|
+
return "No active tasks or decisions";
|
|
290
|
+
}
|
|
291
|
+
return lines.join("\n");
|
|
292
|
+
} catch {
|
|
293
|
+
return "Tasks unavailable";
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async function handleRemoteCommand(prompt) {
|
|
297
|
+
try {
|
|
298
|
+
const sanitizedPrompt = prompt.replace(/[`$\\]/g, "").replace(/["']/g, "'").substring(0, 500);
|
|
299
|
+
if (!sanitizedPrompt.trim()) {
|
|
300
|
+
return "Please provide a task prompt. Usage: remote <your task>";
|
|
301
|
+
}
|
|
302
|
+
console.log(
|
|
303
|
+
`[whatsapp-commands] Launching remote session: ${sanitizedPrompt.substring(0, 50)}...`
|
|
304
|
+
);
|
|
305
|
+
const output = execFileSync("claude", ["--remote", sanitizedPrompt], {
|
|
306
|
+
encoding: "utf8",
|
|
307
|
+
timeout: 3e4,
|
|
308
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
309
|
+
});
|
|
310
|
+
const urlMatch = output.match(
|
|
311
|
+
/https:\/\/claude\.ai\/code\/session_[a-zA-Z0-9]+/
|
|
312
|
+
);
|
|
313
|
+
if (urlMatch) {
|
|
314
|
+
const sessionUrl = urlMatch[0];
|
|
315
|
+
const sessionId = sessionUrl.split("/").pop() || "unknown";
|
|
316
|
+
addRemoteSession({
|
|
317
|
+
id: sessionId,
|
|
318
|
+
url: sessionUrl,
|
|
319
|
+
prompt: sanitizedPrompt,
|
|
320
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
321
|
+
status: "active"
|
|
322
|
+
});
|
|
323
|
+
return `Remote session launched!
|
|
324
|
+
|
|
325
|
+
${sessionUrl}
|
|
326
|
+
|
|
327
|
+
Task: ${sanitizedPrompt.substring(0, 100)}`;
|
|
328
|
+
}
|
|
329
|
+
return `Session launched:
|
|
330
|
+
${output.substring(0, 300)}`;
|
|
331
|
+
} catch (err) {
|
|
332
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
333
|
+
console.error(`[whatsapp-commands] Remote launch failed: ${error}`);
|
|
334
|
+
return `Failed to launch remote session: ${error.substring(0, 100)}`;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function handleSessionsCommand() {
|
|
338
|
+
const sessions = getActiveRemoteSessions();
|
|
339
|
+
if (sessions.length === 0) {
|
|
340
|
+
return "No active remote sessions";
|
|
341
|
+
}
|
|
342
|
+
const lines = ["Active remote sessions:"];
|
|
343
|
+
sessions.slice(0, 5).forEach((s, i) => {
|
|
344
|
+
const age = Math.round(
|
|
345
|
+
(Date.now() - new Date(s.createdAt).getTime()) / 6e4
|
|
346
|
+
);
|
|
347
|
+
const ageStr = age < 60 ? `${age}m ago` : `${Math.round(age / 60)}h ago`;
|
|
348
|
+
lines.push(`${i + 1}. ${s.prompt.substring(0, 40)}... (${ageStr})`);
|
|
349
|
+
lines.push(` ${s.url}`);
|
|
350
|
+
});
|
|
351
|
+
return lines.join("\n");
|
|
352
|
+
}
|
|
353
|
+
async function processCommand(from, message) {
|
|
354
|
+
const config = loadCommandsConfig();
|
|
355
|
+
if (!config.enabled) {
|
|
356
|
+
return { handled: false };
|
|
357
|
+
}
|
|
358
|
+
const parsed = parseCommand(message);
|
|
359
|
+
if (!parsed) {
|
|
360
|
+
return { handled: false };
|
|
361
|
+
}
|
|
362
|
+
const command = config.commands.find(
|
|
363
|
+
(cmd) => cmd.enabled && cmd.name.toLowerCase() === parsed.name
|
|
364
|
+
);
|
|
365
|
+
if (!command) {
|
|
366
|
+
return { handled: false };
|
|
367
|
+
}
|
|
368
|
+
if (command.name === "help") {
|
|
369
|
+
const helpText = generateHelpText(config);
|
|
370
|
+
return { handled: true, response: helpText };
|
|
371
|
+
}
|
|
372
|
+
if (command.name === "context") {
|
|
373
|
+
const contextText = await handleContextCommand();
|
|
374
|
+
return { handled: true, response: contextText };
|
|
375
|
+
}
|
|
376
|
+
if (command.name === "sync") {
|
|
377
|
+
const syncText = await handleSyncCommand();
|
|
378
|
+
return { handled: true, response: syncText };
|
|
379
|
+
}
|
|
380
|
+
if (command.name === "status") {
|
|
381
|
+
const statusText = await handleStatusCommand();
|
|
382
|
+
return { handled: true, response: statusText };
|
|
383
|
+
}
|
|
384
|
+
if (command.name === "tasks") {
|
|
385
|
+
const tasksText = await handleTasksCommand();
|
|
386
|
+
return { handled: true, response: tasksText };
|
|
387
|
+
}
|
|
388
|
+
if (command.name === "remote") {
|
|
389
|
+
if (!parsed.arg) {
|
|
390
|
+
return {
|
|
391
|
+
handled: true,
|
|
392
|
+
response: "Usage: remote <task prompt>\nExample: remote Fix the login bug",
|
|
393
|
+
error: "Missing prompt"
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
const remoteText = await handleRemoteCommand(parsed.arg);
|
|
397
|
+
return { handled: true, response: remoteText };
|
|
398
|
+
}
|
|
399
|
+
if (command.name === "sessions") {
|
|
400
|
+
const sessionsText = handleSessionsCommand();
|
|
401
|
+
return { handled: true, response: sessionsText };
|
|
402
|
+
}
|
|
403
|
+
if (command.requiresArg && !parsed.arg) {
|
|
404
|
+
return {
|
|
405
|
+
handled: true,
|
|
406
|
+
response: `${command.name} requires an argument. Usage: ${command.name} <arg>`,
|
|
407
|
+
error: "Missing argument"
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
if (command.argPattern && parsed.arg) {
|
|
411
|
+
if (!safeRegexTest(command.argPattern, parsed.arg)) {
|
|
412
|
+
return {
|
|
413
|
+
handled: true,
|
|
414
|
+
response: `Invalid argument format for ${command.name}`,
|
|
415
|
+
error: "Invalid argument format"
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
let action = command.action;
|
|
420
|
+
if (action && parsed.arg) {
|
|
421
|
+
if (command.name === "approve") {
|
|
422
|
+
action = `gh pr review ${parsed.arg} --approve`;
|
|
423
|
+
} else if (command.name === "merge") {
|
|
424
|
+
action = `gh pr merge ${parsed.arg} --squash`;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (action) {
|
|
428
|
+
console.log(`[whatsapp-commands] Executing: ${action}`);
|
|
429
|
+
const result = await executeActionSafe(action, message);
|
|
430
|
+
if (result.success) {
|
|
431
|
+
const output = result.output?.slice(0, 200) || "Done";
|
|
432
|
+
return {
|
|
433
|
+
handled: true,
|
|
434
|
+
response: `${command.name}: ${output}`,
|
|
435
|
+
action
|
|
436
|
+
};
|
|
437
|
+
} else {
|
|
438
|
+
return {
|
|
439
|
+
handled: true,
|
|
440
|
+
response: `${command.name} failed: ${result.error?.slice(0, 100)}`,
|
|
441
|
+
error: result.error,
|
|
442
|
+
action
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
handled: true,
|
|
448
|
+
response: `Command ${command.name} acknowledged`
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
async function sendCommandResponse(response) {
|
|
452
|
+
const result = await sendNotification({
|
|
453
|
+
type: "custom",
|
|
454
|
+
title: "Command Result",
|
|
455
|
+
message: response
|
|
456
|
+
});
|
|
457
|
+
return { success: result.success, error: result.error };
|
|
458
|
+
}
|
|
459
|
+
function enableCommands() {
|
|
460
|
+
const config = loadCommandsConfig();
|
|
461
|
+
config.enabled = true;
|
|
462
|
+
saveCommandsConfig(config);
|
|
463
|
+
}
|
|
464
|
+
function disableCommands() {
|
|
465
|
+
const config = loadCommandsConfig();
|
|
466
|
+
config.enabled = false;
|
|
467
|
+
saveCommandsConfig(config);
|
|
468
|
+
}
|
|
469
|
+
function isCommandsEnabled() {
|
|
470
|
+
const config = loadCommandsConfig();
|
|
471
|
+
return config.enabled;
|
|
472
|
+
}
|
|
473
|
+
function addCommand(command) {
|
|
474
|
+
const config = loadCommandsConfig();
|
|
475
|
+
const existingIndex = config.commands.findIndex(
|
|
476
|
+
(c) => c.name.toLowerCase() === command.name.toLowerCase()
|
|
477
|
+
);
|
|
478
|
+
if (existingIndex >= 0) {
|
|
479
|
+
config.commands[existingIndex] = command;
|
|
480
|
+
} else {
|
|
481
|
+
config.commands.push(command);
|
|
482
|
+
}
|
|
483
|
+
saveCommandsConfig(config);
|
|
484
|
+
}
|
|
485
|
+
function removeCommand(name) {
|
|
486
|
+
const config = loadCommandsConfig();
|
|
487
|
+
const initialLength = config.commands.length;
|
|
488
|
+
config.commands = config.commands.filter(
|
|
489
|
+
(c) => c.name.toLowerCase() !== name.toLowerCase()
|
|
490
|
+
);
|
|
491
|
+
if (config.commands.length < initialLength) {
|
|
492
|
+
saveCommandsConfig(config);
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
function getAvailableCommands() {
|
|
498
|
+
const config = loadCommandsConfig();
|
|
499
|
+
return config.commands.filter((c) => c.enabled);
|
|
500
|
+
}
|
|
501
|
+
export {
|
|
502
|
+
addCommand,
|
|
503
|
+
disableCommands,
|
|
504
|
+
enableCommands,
|
|
505
|
+
getActiveRemoteSessions,
|
|
506
|
+
getAvailableCommands,
|
|
507
|
+
getRemoteSessions,
|
|
508
|
+
isCommand,
|
|
509
|
+
isCommandsEnabled,
|
|
510
|
+
loadCommandsConfig,
|
|
511
|
+
processCommand,
|
|
512
|
+
removeCommand,
|
|
513
|
+
saveCommandsConfig,
|
|
514
|
+
sendCommandResponse
|
|
515
|
+
};
|
|
516
|
+
//# sourceMappingURL=whatsapp-commands.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/hooks/whatsapp-commands.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * WhatsApp Inbound Command Processor\n * Process WhatsApp messages as commands\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { execFileSync } from 'child_process';\nimport { writeFileSecure, ensureSecureDir } from './secure-fs.js';\nimport { WhatsAppCommandsConfigSchema, parseConfigSafe } from './schemas.js';\nimport { executeActionSafe } from './sms-action-runner.js';\nimport {\n syncContext,\n getFrameDigestData,\n generateMobileDigest,\n loadSyncOptions,\n} from './whatsapp-sync.js';\nimport { sendNotification } from './sms-notify.js';\n\n// ReDoS protection: max execution time for regex test (ms)\nconst REGEX_TIMEOUT_MS = 100;\n// Max input length for regex matching to prevent catastrophic backtracking\nconst MAX_REGEX_INPUT_LENGTH = 200;\n\n/**\n * Safely test a regex pattern against input with ReDoS protection\n * Returns false if pattern is invalid, times out, or doesn't match\n */\nfunction safeRegexTest(pattern: string, input: string): boolean {\n // Truncate input to prevent catastrophic backtracking\n const safeInput = input.slice(0, MAX_REGEX_INPUT_LENGTH);\n\n try {\n const regex = new RegExp(pattern);\n\n // Use a simple timeout approach - run in try/catch with limited input\n // For true async timeout, we'd need Worker threads which adds complexity\n const startTime = Date.now();\n const result = regex.test(safeInput);\n const elapsed = Date.now() - startTime;\n\n // Log warning if regex took too long (could indicate ReDoS attempt)\n if (elapsed > REGEX_TIMEOUT_MS) {\n console.warn(\n `[whatsapp-commands] Slow regex detected: ${pattern} took ${elapsed}ms`\n );\n return false;\n }\n\n return result;\n } catch {\n // Invalid regex pattern\n console.warn(`[whatsapp-commands] Invalid regex pattern: ${pattern}`);\n return false;\n }\n}\n\nexport interface WhatsAppCommand {\n name: string;\n description: string;\n enabled: boolean;\n action?: string; // Safe action to execute\n requiresArg?: boolean;\n argPattern?: string; // Regex pattern for arg validation\n}\n\nexport interface CommandsConfig {\n enabled: boolean;\n commands: WhatsAppCommand[];\n}\n\nexport interface CommandResult {\n handled: boolean;\n response?: string;\n action?: string;\n error?: string;\n}\n\nconst CONFIG_PATH = join(homedir(), '.stackmemory', 'whatsapp-commands.json');\nconst REMOTE_SESSIONS_PATH = join(\n homedir(),\n '.stackmemory',\n 'remote-sessions.json'\n);\n\n/**\n * Remote session tracking\n */\nexport interface RemoteSession {\n id: string;\n url: string;\n prompt: string;\n createdAt: string;\n status: 'active' | 'completed' | 'failed';\n lastActivity?: string;\n}\n\ninterface RemoteSessionsStore {\n sessions: RemoteSession[];\n}\n\nfunction loadRemoteSessions(): RemoteSessionsStore {\n try {\n if (existsSync(REMOTE_SESSIONS_PATH)) {\n return JSON.parse(readFileSync(REMOTE_SESSIONS_PATH, 'utf8'));\n }\n } catch {\n // Use defaults\n }\n return { sessions: [] };\n}\n\nfunction saveRemoteSessions(store: RemoteSessionsStore): void {\n try {\n ensureSecureDir(join(homedir(), '.stackmemory'));\n writeFileSecure(REMOTE_SESSIONS_PATH, JSON.stringify(store, null, 2));\n } catch {\n // Silently fail\n }\n}\n\nfunction addRemoteSession(session: RemoteSession): void {\n const store = loadRemoteSessions();\n // Keep last 20 sessions\n store.sessions = [session, ...store.sessions.slice(0, 19)];\n saveRemoteSessions(store);\n}\n\nexport function getRemoteSessions(): RemoteSession[] {\n return loadRemoteSessions().sessions;\n}\n\nexport function getActiveRemoteSessions(): RemoteSession[] {\n return loadRemoteSessions().sessions.filter((s) => s.status === 'active');\n}\n\n// Default supported commands\nconst DEFAULT_COMMANDS: WhatsAppCommand[] = [\n {\n name: 'status',\n description: 'Get current task/frame status',\n enabled: true,\n // No action - handled specially in-process\n },\n {\n name: 'tasks',\n description: 'List active tasks',\n enabled: true,\n // No action - handled specially in-process\n },\n {\n name: 'context',\n description: 'Get latest context digest',\n enabled: true,\n // No action - handled specially\n },\n {\n name: 'approve',\n description: 'Approve a PR (requires PR number)',\n enabled: true,\n requiresArg: true,\n argPattern: '^\\\\d+$', // PR number must be numeric\n },\n {\n name: 'merge',\n description: 'Merge a PR (requires PR number)',\n enabled: true,\n requiresArg: true,\n argPattern: '^\\\\d+$',\n },\n {\n name: 'help',\n description: 'List available commands',\n enabled: true,\n // No action - handled specially\n },\n {\n name: 'sync',\n description: 'Push current context to WhatsApp',\n enabled: true,\n // No action - handled specially\n },\n {\n name: 'build',\n description: 'Run npm build',\n enabled: true,\n action: 'npm run build',\n },\n {\n name: 'test',\n description: 'Run tests',\n enabled: true,\n action: 'npm run test:run',\n },\n {\n name: 'lint',\n description: 'Run linter',\n enabled: true,\n action: 'npm run lint',\n },\n {\n name: 'log',\n description: 'Show recent git commits',\n enabled: true,\n action: 'git log --oneline -5',\n },\n {\n name: 'diff',\n description: 'Show git diff summary',\n enabled: true,\n action: 'git diff --stat',\n },\n {\n name: 'pr',\n description: 'List open PRs',\n enabled: true,\n action: 'gh pr list',\n },\n {\n name: 'branch',\n description: 'Show current branch',\n enabled: true,\n action: 'git branch --show-current',\n },\n {\n name: 'remote',\n description: 'Launch remote Claude session (requires task prompt)',\n enabled: true,\n requiresArg: true,\n // No action - handled specially to capture session URL\n },\n {\n name: 'sessions',\n description: 'List active remote sessions',\n enabled: true,\n // No action - handled specially\n },\n];\n\nconst DEFAULT_CONFIG: CommandsConfig = {\n enabled: true,\n commands: DEFAULT_COMMANDS,\n};\n\n/**\n * Load commands config\n */\nexport function loadCommandsConfig(): CommandsConfig {\n try {\n if (existsSync(CONFIG_PATH)) {\n const data = JSON.parse(readFileSync(CONFIG_PATH, 'utf8'));\n return parseConfigSafe(\n WhatsAppCommandsConfigSchema,\n { ...DEFAULT_CONFIG, ...data },\n DEFAULT_CONFIG,\n 'whatsapp-commands'\n );\n }\n } catch {\n // Use defaults\n }\n return { ...DEFAULT_CONFIG };\n}\n\n/**\n * Save commands config\n */\nexport function saveCommandsConfig(config: CommandsConfig): void {\n try {\n ensureSecureDir(join(homedir(), '.stackmemory'));\n writeFileSecure(CONFIG_PATH, JSON.stringify(config, null, 2));\n } catch {\n // Silently fail\n }\n}\n\n/**\n * Check if a message is a command\n */\nexport function isCommand(message: string): boolean {\n const trimmed = message.trim().toLowerCase();\n\n // Check if it's a single word command\n const config = loadCommandsConfig();\n if (!config.enabled) return false;\n\n const words = trimmed.split(/\\s+/);\n const firstWord = words[0];\n\n return config.commands.some(\n (cmd) => cmd.enabled && cmd.name.toLowerCase() === firstWord\n );\n}\n\n/**\n * Parse command from message\n */\nfunction parseCommand(message: string): { name: string; arg?: string } | null {\n const trimmed = message.trim();\n const words = trimmed.split(/\\s+/);\n\n if (words.length === 0) return null;\n\n const name = words[0].toLowerCase();\n const arg = words.slice(1).join(' ').trim() || undefined;\n\n return { name, arg };\n}\n\n/**\n * Generate help text for available commands\n */\nfunction generateHelpText(config: CommandsConfig): string {\n const lines: string[] = ['Available commands:'];\n\n config.commands\n .filter((cmd) => cmd.enabled)\n .forEach((cmd) => {\n const argHint = cmd.requiresArg ? ' <arg>' : '';\n lines.push(` ${cmd.name}${argHint} - ${cmd.description}`);\n });\n\n lines.push('');\n lines.push('Reply with command name to execute');\n\n return lines.join('\\n');\n}\n\n/**\n * Handle the 'context' command specially\n */\nasync function handleContextCommand(): Promise<string> {\n const data = await getFrameDigestData();\n\n if (!data) {\n return 'No context available. Start a task first.';\n }\n\n const options = loadSyncOptions();\n return generateMobileDigest(data, options);\n}\n\n/**\n * Handle the 'sync' command specially\n */\nasync function handleSyncCommand(): Promise<string> {\n const result = await syncContext();\n\n if (result.success) {\n return `Context synced (${result.digestLength} chars)`;\n } else {\n return `Sync failed: ${result.error}`;\n }\n}\n\n/**\n * Handle the 'status' command - get current frame/task status\n */\nasync function handleStatusCommand(): Promise<string> {\n try {\n const data = await getFrameDigestData();\n if (!data) {\n return 'No active session. Start with: claude-sm';\n }\n\n const lines: string[] = [];\n lines.push(`Frame: ${data.name || data.frameId}`);\n lines.push(`Status: ${data.status}`);\n lines.push(`Files: ${data.filesModified?.length || 0} modified`);\n lines.push(`Tools: ${data.toolCallCount || 0} calls`);\n if (data.errors?.length > 0) {\n const unresolved = data.errors.filter((e) => !e.resolved).length;\n if (unresolved > 0) lines.push(`Errors: ${unresolved} unresolved`);\n }\n lines.push(`Duration: ${Math.round(data.durationSeconds / 60)}min`);\n\n return lines.join('\\n');\n } catch {\n return 'Status unavailable';\n }\n}\n\n/**\n * Handle the 'tasks' command - list recent decisions/risks\n */\nasync function handleTasksCommand(): Promise<string> {\n try {\n const data = await getFrameDigestData();\n if (!data) {\n return 'No active tasks';\n }\n\n const lines: string[] = [];\n\n if (data.decisions?.length > 0) {\n lines.push('Recent decisions:');\n data.decisions.slice(0, 3).forEach((d, i) => {\n lines.push(\n `${i + 1}. ${d.substring(0, 50)}${d.length > 50 ? '...' : ''}`\n );\n });\n }\n\n if (data.risks?.length > 0) {\n lines.push('');\n lines.push('Risks:');\n data.risks.slice(0, 2).forEach((r) => {\n lines.push(`- ${r.substring(0, 50)}${r.length > 50 ? '...' : ''}`);\n });\n }\n\n if (lines.length === 0) {\n return 'No active tasks or decisions';\n }\n\n return lines.join('\\n');\n } catch {\n return 'Tasks unavailable';\n }\n}\n\n/**\n * Handle the 'remote' command - launch a remote Claude session\n */\nasync function handleRemoteCommand(prompt: string): Promise<string> {\n try {\n // Sanitize prompt - remove any shell-dangerous characters\n const sanitizedPrompt = prompt\n .replace(/[`$\\\\]/g, '')\n .replace(/[\"']/g, \"'\")\n .substring(0, 500);\n\n if (!sanitizedPrompt.trim()) {\n return 'Please provide a task prompt. Usage: remote <your task>';\n }\n\n console.log(\n `[whatsapp-commands] Launching remote session: ${sanitizedPrompt.substring(0, 50)}...`\n );\n\n // Execute claude --remote with the prompt\n const output = execFileSync('claude', ['--remote', sanitizedPrompt], {\n encoding: 'utf8',\n timeout: 30000,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n // Parse session URL from output\n // Expected format contains: https://claude.ai/code/session_...\n const urlMatch = output.match(\n /https:\\/\\/claude\\.ai\\/code\\/session_[a-zA-Z0-9]+/\n );\n\n if (urlMatch) {\n const sessionUrl = urlMatch[0];\n const sessionId = sessionUrl.split('/').pop() || 'unknown';\n\n // Store the session\n addRemoteSession({\n id: sessionId,\n url: sessionUrl,\n prompt: sanitizedPrompt,\n createdAt: new Date().toISOString(),\n status: 'active',\n });\n\n return `Remote session launched!\\n\\n${sessionUrl}\\n\\nTask: ${sanitizedPrompt.substring(0, 100)}`;\n }\n\n // No URL found - return raw output\n return `Session launched:\\n${output.substring(0, 300)}`;\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err);\n console.error(`[whatsapp-commands] Remote launch failed: ${error}`);\n return `Failed to launch remote session: ${error.substring(0, 100)}`;\n }\n}\n\n/**\n * Handle the 'sessions' command - list active remote sessions\n */\nfunction handleSessionsCommand(): string {\n const sessions = getActiveRemoteSessions();\n\n if (sessions.length === 0) {\n return 'No active remote sessions';\n }\n\n const lines: string[] = ['Active remote sessions:'];\n\n sessions.slice(0, 5).forEach((s, i) => {\n const age = Math.round(\n (Date.now() - new Date(s.createdAt).getTime()) / 60000\n );\n const ageStr = age < 60 ? `${age}m ago` : `${Math.round(age / 60)}h ago`;\n lines.push(`${i + 1}. ${s.prompt.substring(0, 40)}... (${ageStr})`);\n lines.push(` ${s.url}`);\n });\n\n return lines.join('\\n');\n}\n\n/**\n * Process an incoming WhatsApp command\n */\nexport async function processCommand(\n from: string,\n message: string\n): Promise<CommandResult> {\n const config = loadCommandsConfig();\n\n if (!config.enabled) {\n return { handled: false };\n }\n\n const parsed = parseCommand(message);\n if (!parsed) {\n return { handled: false };\n }\n\n const command = config.commands.find(\n (cmd) => cmd.enabled && cmd.name.toLowerCase() === parsed.name\n );\n\n if (!command) {\n return { handled: false };\n }\n\n // Handle special commands\n if (command.name === 'help') {\n const helpText = generateHelpText(config);\n return { handled: true, response: helpText };\n }\n\n if (command.name === 'context') {\n const contextText = await handleContextCommand();\n return { handled: true, response: contextText };\n }\n\n if (command.name === 'sync') {\n const syncText = await handleSyncCommand();\n return { handled: true, response: syncText };\n }\n\n if (command.name === 'status') {\n const statusText = await handleStatusCommand();\n return { handled: true, response: statusText };\n }\n\n if (command.name === 'tasks') {\n const tasksText = await handleTasksCommand();\n return { handled: true, response: tasksText };\n }\n\n if (command.name === 'remote') {\n if (!parsed.arg) {\n return {\n handled: true,\n response:\n 'Usage: remote <task prompt>\\nExample: remote Fix the login bug',\n error: 'Missing prompt',\n };\n }\n const remoteText = await handleRemoteCommand(parsed.arg);\n return { handled: true, response: remoteText };\n }\n\n if (command.name === 'sessions') {\n const sessionsText = handleSessionsCommand();\n return { handled: true, response: sessionsText };\n }\n\n // Check if argument is required\n if (command.requiresArg && !parsed.arg) {\n return {\n handled: true,\n response: `${command.name} requires an argument. Usage: ${command.name} <arg>`,\n error: 'Missing argument',\n };\n }\n\n // Validate argument pattern if specified (with ReDoS protection)\n if (command.argPattern && parsed.arg) {\n if (!safeRegexTest(command.argPattern, parsed.arg)) {\n return {\n handled: true,\n response: `Invalid argument format for ${command.name}`,\n error: 'Invalid argument format',\n };\n }\n }\n\n // Build the action command\n let action = command.action;\n\n if (action && parsed.arg) {\n // Special handling for PR commands\n if (command.name === 'approve') {\n action = `gh pr review ${parsed.arg} --approve`;\n } else if (command.name === 'merge') {\n action = `gh pr merge ${parsed.arg} --squash`;\n }\n }\n\n // Execute the action if defined\n if (action) {\n console.log(`[whatsapp-commands] Executing: ${action}`);\n\n const result = await executeActionSafe(action, message);\n\n if (result.success) {\n const output = result.output?.slice(0, 200) || 'Done';\n return {\n handled: true,\n response: `${command.name}: ${output}`,\n action,\n };\n } else {\n return {\n handled: true,\n response: `${command.name} failed: ${result.error?.slice(0, 100)}`,\n error: result.error,\n action,\n };\n }\n }\n\n return {\n handled: true,\n response: `Command ${command.name} acknowledged`,\n };\n}\n\n/**\n * Send command result back via WhatsApp\n */\nexport async function sendCommandResponse(\n response: string\n): Promise<{ success: boolean; error?: string }> {\n const result = await sendNotification({\n type: 'custom',\n title: 'Command Result',\n message: response,\n });\n\n return { success: result.success, error: result.error };\n}\n\n/**\n * Enable command processing\n */\nexport function enableCommands(): void {\n const config = loadCommandsConfig();\n config.enabled = true;\n saveCommandsConfig(config);\n}\n\n/**\n * Disable command processing\n */\nexport function disableCommands(): void {\n const config = loadCommandsConfig();\n config.enabled = false;\n saveCommandsConfig(config);\n}\n\n/**\n * Check if commands are enabled\n */\nexport function isCommandsEnabled(): boolean {\n const config = loadCommandsConfig();\n return config.enabled;\n}\n\n/**\n * Add a custom command\n */\nexport function addCommand(command: WhatsAppCommand): void {\n const config = loadCommandsConfig();\n\n // Check if command already exists\n const existingIndex = config.commands.findIndex(\n (c) => c.name.toLowerCase() === command.name.toLowerCase()\n );\n\n if (existingIndex >= 0) {\n config.commands[existingIndex] = command;\n } else {\n config.commands.push(command);\n }\n\n saveCommandsConfig(config);\n}\n\n/**\n * Remove a custom command\n */\nexport function removeCommand(name: string): boolean {\n const config = loadCommandsConfig();\n const initialLength = config.commands.length;\n\n config.commands = config.commands.filter(\n (c) => c.name.toLowerCase() !== name.toLowerCase()\n );\n\n if (config.commands.length < initialLength) {\n saveCommandsConfig(config);\n return true;\n }\n\n return false;\n}\n\n/**\n * Get list of available commands\n */\nexport function getAvailableCommands(): WhatsAppCommand[] {\n const config = loadCommandsConfig();\n return config.commands.filter((c) => c.enabled);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|