slyplan-mcp 1.7.2 → 1.7.4
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/cli.js +16 -169
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -10,175 +10,23 @@ import { createClient } from '@supabase/supabase-js';
|
|
|
10
10
|
const SUPABASE_URL = 'https://omfzpkwtuzucwwxmyuqt.supabase.co';
|
|
11
11
|
const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9tZnpwa3d0dXp1Y3d3eG15dXF0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzA5MjMwNDIsImV4cCI6MjA4NjQ5OTA0Mn0.KXGoUez7M45RtFM9qR7mjzGX6UhlaRE-gggAJxSkIHY';
|
|
12
12
|
const SLYPLAN_ORIGIN = 'https://slyplan.com';
|
|
13
|
-
//
|
|
14
|
-
// Optimized: uses string search instead of parsing every JSON line.
|
|
13
|
+
// Simple reminder hook: always fires, Claude decides relevance via CLAUDE.md instructions.
|
|
15
14
|
const PRE_HOOK_FILE_CONTENT = `#!/usr/bin/env node
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
let input = '';
|
|
21
|
-
process.stdin.setEncoding('utf8');
|
|
22
|
-
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
23
|
-
process.stdin.on('end', () => {
|
|
24
|
-
try {
|
|
25
|
-
const data = JSON.parse(input);
|
|
26
|
-
|
|
27
|
-
if (!data.transcript_path) {
|
|
28
|
-
process.stdout.write(JSON.stringify({
|
|
29
|
-
hookSpecificOutput: {
|
|
30
|
-
hookEventName: "PreToolCall",
|
|
31
|
-
additionalContext: "BLOCKED: Call list_projects + set_project before doing any work. This is not optional."
|
|
32
|
-
}
|
|
33
|
-
}));
|
|
34
|
-
process.exit(0);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const transcriptPath = data.transcript_path.replace(/^~/, process.env.HOME || process.env.USERPROFILE || '');
|
|
38
|
-
if (!fs.existsSync(transcriptPath)) { process.exit(0); }
|
|
39
|
-
|
|
40
|
-
// Fast: read file as string and use indexOf instead of parsing every JSON line
|
|
41
|
-
const content = fs.readFileSync(transcriptPath, 'utf8');
|
|
42
|
-
|
|
43
|
-
if (!content.includes('set_project')) {
|
|
44
|
-
process.stdout.write(JSON.stringify({
|
|
45
|
-
hookSpecificOutput: {
|
|
46
|
-
hookEventName: "PreToolCall",
|
|
47
|
-
additionalContext: "BLOCKED: Call list_projects + set_project before doing any work. This is not optional."
|
|
48
|
-
}
|
|
49
|
-
}));
|
|
50
|
-
process.exit(0);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// For work mode, we need to check the LAST occurrence wins (add vs remove)
|
|
54
|
-
const lastAdd = content.lastIndexOf('add_to_work_mode');
|
|
55
|
-
const lastRemove = content.lastIndexOf('remove_from_work_mode');
|
|
56
|
-
if (lastAdd < 0 || (lastRemove > lastAdd)) {
|
|
57
|
-
process.stdout.write(JSON.stringify({
|
|
58
|
-
hookSpecificOutput: {
|
|
59
|
-
hookEventName: "PreToolCall",
|
|
60
|
-
additionalContext: "BLOCKED: No node in work mode. Call search + add_to_work_mode before doing any work. This is not optional."
|
|
61
|
-
}
|
|
62
|
-
}));
|
|
63
|
-
process.exit(0);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Check for SlyPlan update (non-blocking, just informational)
|
|
67
|
-
const cacheFile = path.join(os.homedir(), '.claude', 'cache', 'slyplan-update-check.json');
|
|
68
|
-
try {
|
|
69
|
-
if (fs.existsSync(cacheFile)) {
|
|
70
|
-
const cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
|
|
71
|
-
if (cache.update_available) {
|
|
72
|
-
process.stdout.write(JSON.stringify({
|
|
73
|
-
hookSpecificOutput: {
|
|
74
|
-
hookEventName: "PreToolCall",
|
|
75
|
-
additionalContext: "SlyPlan update available: " + cache.installed + " -> " + cache.latest + ". Run /sly:update to update."
|
|
76
|
-
}
|
|
77
|
-
}));
|
|
78
|
-
process.exit(0);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
} catch {}
|
|
82
|
-
|
|
83
|
-
process.exit(0);
|
|
84
|
-
} catch (e) {
|
|
85
|
-
process.exit(0);
|
|
15
|
+
process.stdout.write(JSON.stringify({
|
|
16
|
+
hookSpecificOutput: {
|
|
17
|
+
hookEventName: "PreToolCall",
|
|
18
|
+
additionalContext: "BLOCKED: You MUST call list_projects + set_project + add_to_work_mode before doing any work. Ignore if already done this session."
|
|
86
19
|
}
|
|
87
|
-
});
|
|
20
|
+
}));
|
|
88
21
|
`;
|
|
89
|
-
//
|
|
90
|
-
// Optimized: reads only the tail of the transcript to find recent activity.
|
|
22
|
+
// Simple reminder hook: always fires, Claude decides relevance via CLAUDE.md instructions.
|
|
91
23
|
const POST_HOOK_FILE_CONTENT = `#!/usr/bin/env node
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
let input = '';
|
|
98
|
-
process.stdin.setEncoding('utf8');
|
|
99
|
-
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
100
|
-
process.stdin.on('end', () => {
|
|
101
|
-
try {
|
|
102
|
-
const data = JSON.parse(input);
|
|
103
|
-
|
|
104
|
-
if (!data.transcript_path) { process.exit(0); }
|
|
105
|
-
|
|
106
|
-
const transcriptPath = data.transcript_path.replace(/^~/, process.env.HOME || process.env.USERPROFILE || '');
|
|
107
|
-
if (!fs.existsSync(transcriptPath)) { process.exit(0); }
|
|
108
|
-
|
|
109
|
-
// Read only the tail of the file for performance
|
|
110
|
-
const stat = fs.statSync(transcriptPath);
|
|
111
|
-
const fd = fs.openSync(transcriptPath, 'r');
|
|
112
|
-
const readStart = Math.max(0, stat.size - TAIL_BYTES);
|
|
113
|
-
const buf = Buffer.alloc(Math.min(stat.size, TAIL_BYTES));
|
|
114
|
-
fs.readSync(fd, buf, 0, buf.length, readStart);
|
|
115
|
-
fs.closeSync(fd);
|
|
116
|
-
const tail = buf.toString('utf8');
|
|
117
|
-
|
|
118
|
-
// If we're reading a partial file, skip the first (possibly incomplete) line
|
|
119
|
-
const lines = tail.split('\\n').filter(Boolean);
|
|
120
|
-
if (readStart > 0 && lines.length > 0) lines.shift();
|
|
121
|
-
|
|
122
|
-
let lastSyncIdx = -1;
|
|
123
|
-
let fileChanges = 0;
|
|
124
|
-
let hasCommit = false;
|
|
125
|
-
|
|
126
|
-
for (let i = 0; i < lines.length; i++) {
|
|
127
|
-
// Fast string checks before expensive JSON.parse
|
|
128
|
-
const line = lines[i];
|
|
129
|
-
if (!line.includes('tool_use')) continue;
|
|
130
|
-
|
|
131
|
-
try {
|
|
132
|
-
const entry = JSON.parse(line);
|
|
133
|
-
if (entry.type !== 'assistant' || !entry.message || !Array.isArray(entry.message.content)) continue;
|
|
134
|
-
for (const block of entry.message.content) {
|
|
135
|
-
if (block.type !== 'tool_use') continue;
|
|
136
|
-
const name = block.name || '';
|
|
137
|
-
|
|
138
|
-
if (name.includes('update_node') || name.includes('add_node') || name.includes('add_to_work_mode')) {
|
|
139
|
-
lastSyncIdx = i;
|
|
140
|
-
fileChanges = 0;
|
|
141
|
-
hasCommit = false;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if ((name === 'Write' || name === 'Edit' || name === 'NotebookEdit') && i > lastSyncIdx) {
|
|
145
|
-
fileChanges++;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (name === 'Bash' && line.includes('git commit') && i > lastSyncIdx) {
|
|
149
|
-
hasCommit = true;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
} catch {}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (fileChanges === 0 && !hasCommit) { process.exit(0); }
|
|
156
|
-
|
|
157
|
-
if (hasCommit) {
|
|
158
|
-
process.stdout.write(JSON.stringify({
|
|
159
|
-
hookSpecificOutput: {
|
|
160
|
-
hookEventName: "PostToolUse",
|
|
161
|
-
additionalContext: "SYNC NOW: Git commit detected. Call update_node with current progress."
|
|
162
|
-
}
|
|
163
|
-
}));
|
|
164
|
-
process.exit(0);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (fileChanges >= BATCH_THRESHOLD) {
|
|
168
|
-
process.stdout.write(JSON.stringify({
|
|
169
|
-
hookSpecificOutput: {
|
|
170
|
-
hookEventName: "PostToolUse",
|
|
171
|
-
additionalContext: "SYNC NOW: " + fileChanges + " file change(s) since last sync. Call update_node with current progress."
|
|
172
|
-
}
|
|
173
|
-
}));
|
|
174
|
-
process.exit(0);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
process.exit(0);
|
|
178
|
-
} catch (e) {
|
|
179
|
-
process.exit(0);
|
|
24
|
+
process.stdout.write(JSON.stringify({
|
|
25
|
+
hookSpecificOutput: {
|
|
26
|
+
hookEventName: "PostToolUse",
|
|
27
|
+
additionalContext: "SYNC NOW: Call update_node with current progress. Ignore if recently synced or no meaningful changes since last sync."
|
|
180
28
|
}
|
|
181
|
-
});
|
|
29
|
+
}));
|
|
182
30
|
`;
|
|
183
31
|
// SessionStart hook: checks npm for new slyplan-mcp version in background
|
|
184
32
|
const SESSION_START_HOOK_CONTENT = `#!/usr/bin/env node
|
|
@@ -499,11 +347,10 @@ function mergeMcpJson(existing, auth) {
|
|
|
499
347
|
else if (auth.refreshToken) {
|
|
500
348
|
env.SLYPLAN_REFRESH_TOKEN = auth.refreshToken;
|
|
501
349
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
args: ['-y', 'slyplan-mcp@latest'],
|
|
505
|
-
env
|
|
506
|
-
};
|
|
350
|
+
const isWindows = process.platform === 'win32';
|
|
351
|
+
result.mcpServers.slyplan = isWindows
|
|
352
|
+
? { command: 'cmd', args: ['/c', 'npx', '-y', 'slyplan-mcp@latest'], env }
|
|
353
|
+
: { command: 'npx', args: ['-y', 'slyplan-mcp@latest'], env };
|
|
507
354
|
return result;
|
|
508
355
|
}
|
|
509
356
|
// Hooks go in .claude/settings.json
|