slyplan-mcp 1.7.0 → 1.7.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/cli.js +61 -90
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ 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
13
|
// Smart transcript-aware hook: checks if set_project + add_to_work_mode have been called.
|
|
14
|
-
//
|
|
14
|
+
// Optimized: uses string search instead of parsing every JSON line.
|
|
15
15
|
const PRE_HOOK_FILE_CONTENT = `#!/usr/bin/env node
|
|
16
16
|
const fs = require('fs');
|
|
17
17
|
const path = require('path');
|
|
@@ -25,63 +25,41 @@ process.stdin.on('end', () => {
|
|
|
25
25
|
const data = JSON.parse(input);
|
|
26
26
|
|
|
27
27
|
if (!data.transcript_path) {
|
|
28
|
-
|
|
29
|
-
suppressOutput: true,
|
|
28
|
+
process.stdout.write(JSON.stringify({
|
|
30
29
|
hookSpecificOutput: {
|
|
31
30
|
hookEventName: "PreToolCall",
|
|
32
31
|
additionalContext: "BLOCKED: Call list_projects + set_project before doing any work. This is not optional."
|
|
33
32
|
}
|
|
34
|
-
});
|
|
35
|
-
process.stdout.write(output);
|
|
33
|
+
}));
|
|
36
34
|
process.exit(0);
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
const transcriptPath = data.transcript_path.replace(/^~/, process.env.HOME || process.env.USERPROFILE || '');
|
|
40
38
|
if (!fs.existsSync(transcriptPath)) { process.exit(0); }
|
|
41
39
|
|
|
40
|
+
// Fast: read file as string and use indexOf instead of parsing every JSON line
|
|
42
41
|
const content = fs.readFileSync(transcriptPath, 'utf8');
|
|
43
|
-
const lines = content.split('\\n').filter(Boolean);
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
for (const line of lines) {
|
|
49
|
-
try {
|
|
50
|
-
const entry = JSON.parse(line);
|
|
51
|
-
if (entry.type === 'assistant' && entry.message && Array.isArray(entry.message.content)) {
|
|
52
|
-
for (const block of entry.message.content) {
|
|
53
|
-
if (block.type !== 'tool_use') continue;
|
|
54
|
-
const name = block.name || '';
|
|
55
|
-
if (name.includes('set_project')) hasSetProject = true;
|
|
56
|
-
// Track add/remove \\u2014 last action wins
|
|
57
|
-
if (name.includes('add_to_work_mode')) hasWorkModeNode = true;
|
|
58
|
-
if (name.includes('remove_from_work_mode')) hasWorkModeNode = false;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
} catch {}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (!hasSetProject) {
|
|
65
|
-
const output = JSON.stringify({
|
|
66
|
-
suppressOutput: true,
|
|
43
|
+
if (!content.includes('set_project')) {
|
|
44
|
+
process.stdout.write(JSON.stringify({
|
|
67
45
|
hookSpecificOutput: {
|
|
68
46
|
hookEventName: "PreToolCall",
|
|
69
47
|
additionalContext: "BLOCKED: Call list_projects + set_project before doing any work. This is not optional."
|
|
70
48
|
}
|
|
71
|
-
});
|
|
72
|
-
process.stdout.write(output);
|
|
49
|
+
}));
|
|
73
50
|
process.exit(0);
|
|
74
51
|
}
|
|
75
52
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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({
|
|
79
58
|
hookSpecificOutput: {
|
|
80
59
|
hookEventName: "PreToolCall",
|
|
81
60
|
additionalContext: "BLOCKED: No node in work mode. Call search + add_to_work_mode before doing any work. This is not optional."
|
|
82
61
|
}
|
|
83
|
-
});
|
|
84
|
-
process.stdout.write(output);
|
|
62
|
+
}));
|
|
85
63
|
process.exit(0);
|
|
86
64
|
}
|
|
87
65
|
|
|
@@ -91,14 +69,12 @@ process.stdin.on('end', () => {
|
|
|
91
69
|
if (fs.existsSync(cacheFile)) {
|
|
92
70
|
const cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
|
|
93
71
|
if (cache.update_available) {
|
|
94
|
-
|
|
95
|
-
suppressOutput: true,
|
|
72
|
+
process.stdout.write(JSON.stringify({
|
|
96
73
|
hookSpecificOutput: {
|
|
97
74
|
hookEventName: "PreToolCall",
|
|
98
|
-
additionalContext: "
|
|
75
|
+
additionalContext: "SlyPlan update available: " + cache.installed + " -> " + cache.latest + ". Run /sly:update to update."
|
|
99
76
|
}
|
|
100
|
-
});
|
|
101
|
-
process.stdout.write(output);
|
|
77
|
+
}));
|
|
102
78
|
process.exit(0);
|
|
103
79
|
}
|
|
104
80
|
}
|
|
@@ -111,11 +87,12 @@ process.stdin.on('end', () => {
|
|
|
111
87
|
});
|
|
112
88
|
`;
|
|
113
89
|
// Smart transcript-aware hook: counts file changes SINCE last SlyPlan sync.
|
|
114
|
-
//
|
|
90
|
+
// Optimized: reads only the tail of the transcript to find recent activity.
|
|
115
91
|
const POST_HOOK_FILE_CONTENT = `#!/usr/bin/env node
|
|
116
92
|
const fs = require('fs');
|
|
117
93
|
|
|
118
94
|
const BATCH_THRESHOLD = 3;
|
|
95
|
+
const TAIL_BYTES = 32768; // Read last 32KB — enough for recent tool calls
|
|
119
96
|
|
|
120
97
|
let input = '';
|
|
121
98
|
process.stdin.setEncoding('utf8');
|
|
@@ -129,80 +106,74 @@ process.stdin.on('end', () => {
|
|
|
129
106
|
const transcriptPath = data.transcript_path.replace(/^~/, process.env.HOME || process.env.USERPROFILE || '');
|
|
130
107
|
if (!fs.existsSync(transcriptPath)) { process.exit(0); }
|
|
131
108
|
|
|
132
|
-
|
|
133
|
-
const
|
|
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');
|
|
134
117
|
|
|
135
|
-
|
|
136
|
-
const
|
|
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();
|
|
137
121
|
|
|
138
|
-
let
|
|
139
|
-
let
|
|
140
|
-
let
|
|
122
|
+
let lastSyncIdx = -1;
|
|
123
|
+
let fileChanges = 0;
|
|
124
|
+
let hasCommit = false;
|
|
141
125
|
|
|
142
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
|
+
|
|
143
131
|
try {
|
|
144
|
-
const entry = JSON.parse(
|
|
145
|
-
if (entry.type
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
hasGitCommitSinceSync = false;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
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
|
+
}
|
|
158
143
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
144
|
+
if ((name === 'Write' || name === 'Edit' || name === 'NotebookEdit') && i > lastSyncIdx) {
|
|
145
|
+
fileChanges++;
|
|
146
|
+
}
|
|
163
147
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const cmd = block.input.command;
|
|
167
|
-
if (cmd.includes('git commit') && i > lastSlyplanSyncIdx) {
|
|
168
|
-
hasGitCommitSinceSync = true;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
148
|
+
if (name === 'Bash' && line.includes('git commit') && i > lastSyncIdx) {
|
|
149
|
+
hasCommit = true;
|
|
171
150
|
}
|
|
172
151
|
}
|
|
173
152
|
} catch {}
|
|
174
153
|
}
|
|
175
154
|
|
|
176
|
-
|
|
177
|
-
if (fileChangesSinceSync === 0 && !hasGitCommitSinceSync) { process.exit(0); }
|
|
155
|
+
if (fileChanges === 0 && !hasCommit) { process.exit(0); }
|
|
178
156
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const output = JSON.stringify({
|
|
182
|
-
suppressOutput: true,
|
|
157
|
+
if (hasCommit) {
|
|
158
|
+
process.stdout.write(JSON.stringify({
|
|
183
159
|
hookSpecificOutput: {
|
|
184
160
|
hookEventName: "PostToolUse",
|
|
185
|
-
additionalContext: "SYNC NOW: Git commit detected
|
|
161
|
+
additionalContext: "SYNC NOW: Git commit detected. Call update_node with current progress."
|
|
186
162
|
}
|
|
187
|
-
});
|
|
188
|
-
process.stdout.write(output);
|
|
163
|
+
}));
|
|
189
164
|
process.exit(0);
|
|
190
165
|
}
|
|
191
166
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const output = JSON.stringify({
|
|
195
|
-
suppressOutput: true,
|
|
167
|
+
if (fileChanges >= BATCH_THRESHOLD) {
|
|
168
|
+
process.stdout.write(JSON.stringify({
|
|
196
169
|
hookSpecificOutput: {
|
|
197
170
|
hookEventName: "PostToolUse",
|
|
198
|
-
additionalContext: "SYNC NOW: " +
|
|
171
|
+
additionalContext: "SYNC NOW: " + fileChanges + " file change(s) since last sync. Call update_node with current progress."
|
|
199
172
|
}
|
|
200
|
-
});
|
|
201
|
-
process.stdout.write(output);
|
|
173
|
+
}));
|
|
202
174
|
process.exit(0);
|
|
203
175
|
}
|
|
204
176
|
|
|
205
|
-
// Under threshold — stay silent
|
|
206
177
|
process.exit(0);
|
|
207
178
|
} catch (e) {
|
|
208
179
|
process.exit(0);
|