@a5c-ai/babysitter-cursor 0.1.1-staging.d0170171
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/.cursor-plugin/plugin.json +24 -0
- package/.cursorrules +55 -0
- package/README.md +481 -0
- package/bin/cli.js +104 -0
- package/bin/install-shared.js +369 -0
- package/bin/install.js +46 -0
- package/bin/uninstall.js +40 -0
- package/hooks/session-start.ps1 +115 -0
- package/hooks/session-start.sh +105 -0
- package/hooks/stop-hook.ps1 +72 -0
- package/hooks/stop-hook.sh +64 -0
- package/hooks.json +21 -0
- package/package.json +47 -0
- package/plugin.json +24 -0
- package/scripts/team-install.js +81 -0
- package/skills/assimilate/SKILL.md +17 -0
- package/skills/babysit/SKILL.md +81 -0
- package/skills/call/SKILL.md +17 -0
- package/skills/doctor/SKILL.md +16 -0
- package/skills/help/SKILL.md +15 -0
- package/skills/observe/SKILL.md +15 -0
- package/skills/plan/SKILL.md +16 -0
- package/skills/resume/SKILL.md +15 -0
- package/skills/retrospect/SKILL.md +55 -0
- package/skills/user-install/SKILL.md +15 -0
- package/versions.json +3 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { spawnSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
const PLUGIN_NAME = 'babysitter-cursor';
|
|
9
|
+
const PLUGIN_CATEGORY = 'Coding';
|
|
10
|
+
const HOOK_SCRIPT_NAMES = [
|
|
11
|
+
'session-start.sh',
|
|
12
|
+
'session-start.ps1',
|
|
13
|
+
'stop-hook.sh',
|
|
14
|
+
'stop-hook.ps1',
|
|
15
|
+
];
|
|
16
|
+
const DEFAULT_MARKETPLACE = {
|
|
17
|
+
name: 'local-plugins',
|
|
18
|
+
interface: {
|
|
19
|
+
displayName: 'Local Plugins',
|
|
20
|
+
},
|
|
21
|
+
plugins: [],
|
|
22
|
+
};
|
|
23
|
+
const PLUGIN_BUNDLE_ENTRIES = [
|
|
24
|
+
'.cursor-plugin',
|
|
25
|
+
'plugin.json',
|
|
26
|
+
'hooks.json',
|
|
27
|
+
'hooks',
|
|
28
|
+
'skills',
|
|
29
|
+
'versions.json',
|
|
30
|
+
'.cursorrules',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
function getCursorHome() {
|
|
34
|
+
if (process.env.CURSOR_HOME) return path.resolve(process.env.CURSOR_HOME);
|
|
35
|
+
return path.join(os.homedir(), '.cursor');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getUserHome() {
|
|
39
|
+
if (process.env.USERPROFILE) return path.resolve(process.env.USERPROFILE);
|
|
40
|
+
if (process.env.HOME) return path.resolve(process.env.HOME);
|
|
41
|
+
return os.homedir();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getGlobalStateDir() {
|
|
45
|
+
if (process.env.BABYSITTER_GLOBAL_STATE_DIR) {
|
|
46
|
+
return path.resolve(process.env.BABYSITTER_GLOBAL_STATE_DIR);
|
|
47
|
+
}
|
|
48
|
+
return path.join(getUserHome(), '.a5c');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getHomePluginRoot() {
|
|
52
|
+
if (process.env.BABYSITTER_CURSOR_PLUGIN_DIR) {
|
|
53
|
+
return path.resolve(process.env.BABYSITTER_CURSOR_PLUGIN_DIR, PLUGIN_NAME);
|
|
54
|
+
}
|
|
55
|
+
return path.join(getCursorHome(), 'plugins', PLUGIN_NAME);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getHomeMarketplacePath() {
|
|
59
|
+
if (process.env.BABYSITTER_CURSOR_MARKETPLACE_PATH) {
|
|
60
|
+
return path.resolve(process.env.BABYSITTER_CURSOR_MARKETPLACE_PATH);
|
|
61
|
+
}
|
|
62
|
+
return path.join(getUserHome(), '.agents', 'plugins', 'marketplace.json');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function writeFileIfChanged(filePath, contents) {
|
|
66
|
+
if (fs.existsSync(filePath)) {
|
|
67
|
+
const current = fs.readFileSync(filePath, 'utf8');
|
|
68
|
+
if (current === contents) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
73
|
+
fs.writeFileSync(filePath, contents, 'utf8');
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function copyRecursive(src, dest) {
|
|
78
|
+
const stat = fs.statSync(src);
|
|
79
|
+
if (stat.isDirectory()) {
|
|
80
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
81
|
+
for (const entry of fs.readdirSync(src)) {
|
|
82
|
+
if (['node_modules', '.git', 'test', '.a5c'].includes(entry)) continue;
|
|
83
|
+
copyRecursive(path.join(src, entry), path.join(dest, entry));
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (path.basename(src) === 'SKILL.md') {
|
|
89
|
+
const file = fs.readFileSync(src);
|
|
90
|
+
const hasBom = file.length >= 3 && file[0] === 0xef && file[1] === 0xbb && file[2] === 0xbf;
|
|
91
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
92
|
+
fs.writeFileSync(dest, hasBom ? file.subarray(3) : file);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
97
|
+
fs.copyFileSync(src, dest);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function copyPluginBundle(packageRoot, pluginRoot) {
|
|
101
|
+
if (path.resolve(packageRoot) === path.resolve(pluginRoot)) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
fs.rmSync(pluginRoot, { recursive: true, force: true });
|
|
105
|
+
fs.mkdirSync(pluginRoot, { recursive: true });
|
|
106
|
+
for (const entry of PLUGIN_BUNDLE_ENTRIES) {
|
|
107
|
+
const src = path.join(packageRoot, entry);
|
|
108
|
+
if (fs.existsSync(src)) {
|
|
109
|
+
copyRecursive(src, path.join(pluginRoot, entry));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function ensureExecutable(filePath) {
|
|
115
|
+
try {
|
|
116
|
+
fs.chmodSync(filePath, 0o755);
|
|
117
|
+
} catch {
|
|
118
|
+
// Best-effort only. Windows and some filesystems may ignore mode changes.
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function normalizeMarketplaceSourcePath(marketplacePath, pluginSourcePath) {
|
|
123
|
+
let next = pluginSourcePath;
|
|
124
|
+
if (path.isAbsolute(next)) {
|
|
125
|
+
next = path.relative(path.dirname(marketplacePath), next);
|
|
126
|
+
}
|
|
127
|
+
next = String(next || '').replace(/\\/g, '/');
|
|
128
|
+
if (!next.startsWith('./') && !next.startsWith('../')) {
|
|
129
|
+
next = `./${next}`;
|
|
130
|
+
}
|
|
131
|
+
return next;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function readJson(filePath) {
|
|
135
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function writeJson(filePath, value) {
|
|
139
|
+
writeFileIfChanged(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function ensureMarketplaceEntry(marketplacePath, pluginSourcePath) {
|
|
143
|
+
const marketplace = fs.existsSync(marketplacePath)
|
|
144
|
+
? readJson(marketplacePath)
|
|
145
|
+
: { ...DEFAULT_MARKETPLACE, plugins: [] };
|
|
146
|
+
marketplace.name = marketplace.name || DEFAULT_MARKETPLACE.name;
|
|
147
|
+
marketplace.interface = marketplace.interface || {};
|
|
148
|
+
marketplace.interface.displayName =
|
|
149
|
+
marketplace.interface.displayName || DEFAULT_MARKETPLACE.interface.displayName;
|
|
150
|
+
const nextEntry = {
|
|
151
|
+
name: PLUGIN_NAME,
|
|
152
|
+
source: {
|
|
153
|
+
source: 'local',
|
|
154
|
+
path: normalizeMarketplaceSourcePath(marketplacePath, pluginSourcePath),
|
|
155
|
+
},
|
|
156
|
+
policy: {
|
|
157
|
+
installation: 'AVAILABLE',
|
|
158
|
+
authentication: 'ON_INSTALL',
|
|
159
|
+
},
|
|
160
|
+
category: PLUGIN_CATEGORY,
|
|
161
|
+
};
|
|
162
|
+
const existingIndex = Array.isArray(marketplace.plugins)
|
|
163
|
+
? marketplace.plugins.findIndex((entry) => entry && entry.name === PLUGIN_NAME)
|
|
164
|
+
: -1;
|
|
165
|
+
if (!Array.isArray(marketplace.plugins)) {
|
|
166
|
+
marketplace.plugins = [nextEntry];
|
|
167
|
+
} else if (existingIndex >= 0) {
|
|
168
|
+
marketplace.plugins[existingIndex] = nextEntry;
|
|
169
|
+
} else {
|
|
170
|
+
marketplace.plugins.push(nextEntry);
|
|
171
|
+
}
|
|
172
|
+
writeJson(marketplacePath, marketplace);
|
|
173
|
+
return nextEntry;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function removeMarketplaceEntry(marketplacePath) {
|
|
177
|
+
if (!fs.existsSync(marketplacePath)) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const marketplace = readJson(marketplacePath);
|
|
181
|
+
if (!Array.isArray(marketplace.plugins)) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
marketplace.plugins = marketplace.plugins.filter((entry) => entry && entry.name !== PLUGIN_NAME);
|
|
185
|
+
writeJson(marketplacePath, marketplace);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function mergeManagedHooksConfig(packageRoot, cursorHome) {
|
|
189
|
+
const hooksJsonPath = path.join(packageRoot, 'hooks.json');
|
|
190
|
+
if (!fs.existsSync(hooksJsonPath)) return;
|
|
191
|
+
const managedConfig = readJson(hooksJsonPath);
|
|
192
|
+
const managedHooks = managedConfig.hooks || {};
|
|
193
|
+
const hooksConfigPath = path.join(cursorHome, 'hooks.json');
|
|
194
|
+
const existing = fs.existsSync(hooksConfigPath)
|
|
195
|
+
? readJson(hooksConfigPath)
|
|
196
|
+
: { version: 1, hooks: {} };
|
|
197
|
+
existing.version = existing.version || 1;
|
|
198
|
+
if (!existing.hooks || typeof existing.hooks !== 'object') {
|
|
199
|
+
existing.hooks = {};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
for (const [eventName, entries] of Object.entries(managedHooks)) {
|
|
203
|
+
const existingEntries = Array.isArray(existing.hooks[eventName]) ? existing.hooks[eventName] : [];
|
|
204
|
+
const filteredEntries = existingEntries
|
|
205
|
+
.filter((entry) => {
|
|
206
|
+
const bash = String(entry.bash || entry.command || '');
|
|
207
|
+
const ps = String(entry.powershell || '');
|
|
208
|
+
return !HOOK_SCRIPT_NAMES.some((name) => bash.includes(name) || ps.includes(name));
|
|
209
|
+
});
|
|
210
|
+
existing.hooks[eventName] = [...filteredEntries, ...entries];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
writeJson(hooksConfigPath, existing);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function removeManagedHooks(cursorHome) {
|
|
217
|
+
for (const hookName of HOOK_SCRIPT_NAMES) {
|
|
218
|
+
fs.rmSync(path.join(cursorHome, 'hooks', hookName), { force: true });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const hooksConfigPath = path.join(cursorHome, 'hooks.json');
|
|
222
|
+
if (!fs.existsSync(hooksConfigPath)) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
let hooksConfig;
|
|
226
|
+
try {
|
|
227
|
+
hooksConfig = readJson(hooksConfigPath);
|
|
228
|
+
} catch {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (!hooksConfig.hooks || typeof hooksConfig.hooks !== 'object') {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
for (const eventName of Object.keys(hooksConfig.hooks)) {
|
|
235
|
+
const eventHooks = Array.isArray(hooksConfig.hooks[eventName]) ? hooksConfig.hooks[eventName] : [];
|
|
236
|
+
const filtered = eventHooks.filter((entry) => {
|
|
237
|
+
const bash = String(entry.bash || entry.command || '');
|
|
238
|
+
const ps = String(entry.powershell || '');
|
|
239
|
+
return !HOOK_SCRIPT_NAMES.some((name) => bash.includes(name) || ps.includes(name));
|
|
240
|
+
});
|
|
241
|
+
if (filtered.length > 0) {
|
|
242
|
+
hooksConfig.hooks[eventName] = filtered;
|
|
243
|
+
} else {
|
|
244
|
+
delete hooksConfig.hooks[eventName];
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (Object.keys(hooksConfig.hooks).length === 0) {
|
|
248
|
+
fs.rmSync(hooksConfigPath, { force: true });
|
|
249
|
+
} else {
|
|
250
|
+
writeJson(hooksConfigPath, hooksConfig);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function installCursorSurface(packageRoot, cursorHome) {
|
|
255
|
+
// Install skills
|
|
256
|
+
const sourceSkills = path.join(packageRoot, 'skills');
|
|
257
|
+
if (fs.existsSync(sourceSkills)) {
|
|
258
|
+
const targetSkills = path.join(cursorHome, 'skills');
|
|
259
|
+
fs.mkdirSync(targetSkills, { recursive: true });
|
|
260
|
+
for (const entry of fs.readdirSync(sourceSkills, { withFileTypes: true })) {
|
|
261
|
+
if (!entry.isDirectory()) continue;
|
|
262
|
+
copyRecursive(
|
|
263
|
+
path.join(sourceSkills, entry.name),
|
|
264
|
+
path.join(targetSkills, entry.name),
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Install hooks
|
|
270
|
+
const sourceHooks = path.join(packageRoot, 'hooks');
|
|
271
|
+
if (fs.existsSync(sourceHooks)) {
|
|
272
|
+
const targetHooks = path.join(cursorHome, 'hooks');
|
|
273
|
+
fs.mkdirSync(targetHooks, { recursive: true });
|
|
274
|
+
for (const scriptName of HOOK_SCRIPT_NAMES) {
|
|
275
|
+
const sourcePath = path.join(sourceHooks, scriptName);
|
|
276
|
+
if (!fs.existsSync(sourcePath)) continue;
|
|
277
|
+
const targetPath = path.join(targetHooks, scriptName);
|
|
278
|
+
copyRecursive(sourcePath, targetPath);
|
|
279
|
+
ensureExecutable(targetPath);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Merge hooks.json config
|
|
284
|
+
mergeManagedHooksConfig(packageRoot, cursorHome);
|
|
285
|
+
|
|
286
|
+
// Install .cursorrules
|
|
287
|
+
const sourceRules = path.join(packageRoot, '.cursorrules');
|
|
288
|
+
if (fs.existsSync(sourceRules)) {
|
|
289
|
+
copyRecursive(sourceRules, path.join(cursorHome, '.cursorrules'));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function resolveBabysitterCommand(packageRoot) {
|
|
294
|
+
if (process.env.BABYSITTER_SDK_CLI) {
|
|
295
|
+
return {
|
|
296
|
+
command: process.execPath,
|
|
297
|
+
argsPrefix: [path.resolve(process.env.BABYSITTER_SDK_CLI)],
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
return {
|
|
302
|
+
command: process.execPath,
|
|
303
|
+
argsPrefix: [
|
|
304
|
+
require.resolve('@a5c-ai/babysitter-sdk/dist/cli/main.js', {
|
|
305
|
+
paths: [packageRoot],
|
|
306
|
+
}),
|
|
307
|
+
],
|
|
308
|
+
};
|
|
309
|
+
} catch {
|
|
310
|
+
return {
|
|
311
|
+
command: 'babysitter',
|
|
312
|
+
argsPrefix: [],
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function runBabysitterCli(packageRoot, cliArgs, options = {}) {
|
|
318
|
+
const resolved = resolveBabysitterCommand(packageRoot);
|
|
319
|
+
const result = spawnSync(resolved.command, [...resolved.argsPrefix, ...cliArgs], {
|
|
320
|
+
cwd: options.cwd || process.cwd(),
|
|
321
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
322
|
+
encoding: 'utf8',
|
|
323
|
+
env: {
|
|
324
|
+
...process.env,
|
|
325
|
+
...(options.env || {}),
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
if (result.status !== 0) {
|
|
329
|
+
const stderr = (result.stderr || '').trim();
|
|
330
|
+
const stdout = (result.stdout || '').trim();
|
|
331
|
+
throw new Error(
|
|
332
|
+
`babysitter ${cliArgs.join(' ')} failed` +
|
|
333
|
+
(stderr ? `: ${stderr}` : stdout ? `: ${stdout}` : ''),
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
return result.stdout;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function ensureGlobalProcessLibrary(packageRoot) {
|
|
340
|
+
return JSON.parse(
|
|
341
|
+
runBabysitterCli(
|
|
342
|
+
packageRoot,
|
|
343
|
+
['process-library:active', '--state-dir', getGlobalStateDir(), '--json'],
|
|
344
|
+
{ cwd: packageRoot },
|
|
345
|
+
),
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function warnWindowsHooks() {
|
|
350
|
+
if (process.platform !== 'win32') {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
console.warn(`[${PLUGIN_NAME}] Note: On Windows, Cursor will use .ps1 PowerShell hooks.`);
|
|
354
|
+
console.warn(`[${PLUGIN_NAME}] Both bash (.sh) and PowerShell (.ps1) hook scripts are included.`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
module.exports = {
|
|
358
|
+
copyPluginBundle,
|
|
359
|
+
ensureGlobalProcessLibrary,
|
|
360
|
+
ensureMarketplaceEntry,
|
|
361
|
+
getCursorHome,
|
|
362
|
+
getHomeMarketplacePath,
|
|
363
|
+
getHomePluginRoot,
|
|
364
|
+
installCursorSurface,
|
|
365
|
+
removeManagedHooks,
|
|
366
|
+
removeMarketplaceEntry,
|
|
367
|
+
warnWindowsHooks,
|
|
368
|
+
writeJson,
|
|
369
|
+
};
|
package/bin/install.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const {
|
|
6
|
+
copyPluginBundle,
|
|
7
|
+
ensureGlobalProcessLibrary,
|
|
8
|
+
ensureMarketplaceEntry,
|
|
9
|
+
getCursorHome,
|
|
10
|
+
getHomeMarketplacePath,
|
|
11
|
+
getHomePluginRoot,
|
|
12
|
+
installCursorSurface,
|
|
13
|
+
warnWindowsHooks,
|
|
14
|
+
} = require('./install-shared');
|
|
15
|
+
|
|
16
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
17
|
+
|
|
18
|
+
function main() {
|
|
19
|
+
const cursorHome = getCursorHome();
|
|
20
|
+
const pluginRoot = getHomePluginRoot();
|
|
21
|
+
const marketplacePath = getHomeMarketplacePath();
|
|
22
|
+
|
|
23
|
+
console.log(`[babysitter-cursor] Installing plugin to ${pluginRoot}`);
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
copyPluginBundle(PACKAGE_ROOT, pluginRoot);
|
|
27
|
+
ensureMarketplaceEntry(marketplacePath, pluginRoot);
|
|
28
|
+
installCursorSurface(PACKAGE_ROOT, cursorHome);
|
|
29
|
+
|
|
30
|
+
const active = ensureGlobalProcessLibrary(PACKAGE_ROOT);
|
|
31
|
+
console.log(`[babysitter-cursor] marketplace: ${marketplacePath}`);
|
|
32
|
+
console.log(`[babysitter-cursor] process library: ${active.binding?.dir}`);
|
|
33
|
+
if (active.defaultSpec?.cloneDir) {
|
|
34
|
+
console.log(`[babysitter-cursor] process library clone: ${active.defaultSpec.cloneDir}`);
|
|
35
|
+
}
|
|
36
|
+
console.log(`[babysitter-cursor] process library state: ${active.stateFile}`);
|
|
37
|
+
warnWindowsHooks();
|
|
38
|
+
console.log('[babysitter-cursor] Installation complete!');
|
|
39
|
+
console.log('[babysitter-cursor] Restart Cursor to pick up the installed plugin and config changes.');
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error(`[babysitter-cursor] Failed to install plugin: ${err.message}`);
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
main();
|
package/bin/uninstall.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const {
|
|
6
|
+
getCursorHome,
|
|
7
|
+
getHomeMarketplacePath,
|
|
8
|
+
getHomePluginRoot,
|
|
9
|
+
removeManagedHooks,
|
|
10
|
+
removeMarketplaceEntry,
|
|
11
|
+
} = require('./install-shared');
|
|
12
|
+
|
|
13
|
+
function main() {
|
|
14
|
+
const cursorHome = getCursorHome();
|
|
15
|
+
const pluginRoot = getHomePluginRoot();
|
|
16
|
+
const marketplacePath = getHomeMarketplacePath();
|
|
17
|
+
let removedPlugin = false;
|
|
18
|
+
|
|
19
|
+
if (fs.existsSync(pluginRoot)) {
|
|
20
|
+
try {
|
|
21
|
+
fs.rmSync(pluginRoot, { recursive: true, force: true });
|
|
22
|
+
console.log(`[babysitter-cursor] Removed ${pluginRoot}`);
|
|
23
|
+
removedPlugin = true;
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.warn(`[babysitter-cursor] Warning: Could not remove plugin directory ${pluginRoot}: ${err.message}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
removeMarketplaceEntry(marketplacePath);
|
|
30
|
+
removeManagedHooks(cursorHome);
|
|
31
|
+
|
|
32
|
+
if (!removedPlugin) {
|
|
33
|
+
console.log('[babysitter-cursor] Plugin directory not found, config and hooks cleaned if present.');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log('[babysitter-cursor] Restart Cursor to complete uninstallation.');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
main();
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Babysitter Session Start Hook for Cursor IDE/CLI (PowerShell)
|
|
2
|
+
# Ensures the babysitter SDK CLI is installed (from versions.json sdkVersion),
|
|
3
|
+
# then delegates to the SDK hook handler.
|
|
4
|
+
#
|
|
5
|
+
# Protocol:
|
|
6
|
+
# Input: JSON via stdin (contains session_id, cwd, etc.)
|
|
7
|
+
# Output: JSON via stdout ({} on success)
|
|
8
|
+
# Stderr: debug/log output only
|
|
9
|
+
# Exit 0: success
|
|
10
|
+
# Exit 2: block (fatal error)
|
|
11
|
+
|
|
12
|
+
$ErrorActionPreference = "Stop"
|
|
13
|
+
|
|
14
|
+
$PluginRoot = if ($env:CURSOR_PLUGIN_ROOT) { $env:CURSOR_PLUGIN_ROOT } else { Split-Path -Parent $PSScriptRoot }
|
|
15
|
+
$StateDir = if ($env:BABYSITTER_STATE_DIR) { $env:BABYSITTER_STATE_DIR } else { Join-Path $PWD ".a5c" }
|
|
16
|
+
$MarkerFile = Join-Path $PluginRoot ".babysitter-install-attempted"
|
|
17
|
+
|
|
18
|
+
$env:CURSOR_PLUGIN_ROOT = $PluginRoot
|
|
19
|
+
$env:BABYSITTER_STATE_DIR = $StateDir
|
|
20
|
+
|
|
21
|
+
$LogDir = if ($env:BABYSITTER_LOG_DIR) { $env:BABYSITTER_LOG_DIR } else { Join-Path $PluginRoot ".a5c\logs" }
|
|
22
|
+
$LogFile = Join-Path $LogDir "babysitter-session-start-hook.log"
|
|
23
|
+
New-Item -ItemType Directory -Path $LogDir -Force -ErrorAction SilentlyContinue | Out-Null
|
|
24
|
+
|
|
25
|
+
function Write-Blog {
|
|
26
|
+
param([string]$Message)
|
|
27
|
+
$ts = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
|
|
28
|
+
Add-Content -Path $LogFile -Value "[INFO] $ts $Message" -ErrorAction SilentlyContinue
|
|
29
|
+
if (Get-Command babysitter -ErrorAction SilentlyContinue) {
|
|
30
|
+
& babysitter log --type hook --label "hook:session-start" --message $Message --source shell-hook 2>$null
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Write-Blog "Hook script invoked"
|
|
35
|
+
Write-Blog "PLUGIN_ROOT=$PluginRoot"
|
|
36
|
+
Write-Blog "STATE_DIR=$StateDir"
|
|
37
|
+
|
|
38
|
+
# Get required SDK version from versions.json
|
|
39
|
+
$versionsFile = Join-Path $PluginRoot "versions.json"
|
|
40
|
+
try {
|
|
41
|
+
$SdkVersion = (Get-Content $versionsFile -Raw | ConvertFrom-Json).sdkVersion
|
|
42
|
+
if (-not $SdkVersion) { $SdkVersion = "latest" }
|
|
43
|
+
} catch {
|
|
44
|
+
$SdkVersion = "latest"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function Install-Sdk {
|
|
48
|
+
param([string]$TargetVersion)
|
|
49
|
+
try {
|
|
50
|
+
& npm i -g "@a5c-ai/babysitter-sdk@$TargetVersion" --loglevel=error 2>$null
|
|
51
|
+
if ($LASTEXITCODE -eq 0) {
|
|
52
|
+
Write-Blog "Installed SDK globally ($TargetVersion)"
|
|
53
|
+
return $true
|
|
54
|
+
}
|
|
55
|
+
} catch {}
|
|
56
|
+
try {
|
|
57
|
+
$prefix = Join-Path $env:USERPROFILE ".local"
|
|
58
|
+
& npm i -g "@a5c-ai/babysitter-sdk@$TargetVersion" --prefix $prefix --loglevel=error 2>$null
|
|
59
|
+
if ($LASTEXITCODE -eq 0) {
|
|
60
|
+
$env:PATH = "$prefix\bin;$env:PATH"
|
|
61
|
+
Write-Blog "Installed SDK to user prefix ($TargetVersion)"
|
|
62
|
+
return $true
|
|
63
|
+
}
|
|
64
|
+
} catch {}
|
|
65
|
+
return $false
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# Check if babysitter CLI exists and if version matches
|
|
69
|
+
$NeedsInstall = $false
|
|
70
|
+
if (Get-Command babysitter -ErrorAction SilentlyContinue) {
|
|
71
|
+
$CurrentVersion = & babysitter --version 2>$null
|
|
72
|
+
if ($CurrentVersion -ne $SdkVersion) {
|
|
73
|
+
Write-Blog "SDK version mismatch: installed=$CurrentVersion, required=$SdkVersion"
|
|
74
|
+
$NeedsInstall = $true
|
|
75
|
+
} else {
|
|
76
|
+
Write-Blog "SDK version OK: $CurrentVersion"
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
Write-Blog "SDK CLI not found, will install"
|
|
80
|
+
$NeedsInstall = $true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Install/upgrade if needed (only attempt once per plugin version)
|
|
84
|
+
if ($NeedsInstall -and -not (Test-Path $MarkerFile)) {
|
|
85
|
+
Install-Sdk $SdkVersion | Out-Null
|
|
86
|
+
Set-Content -Path $MarkerFile -Value $SdkVersion -ErrorAction SilentlyContinue
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# If still not available after install attempt, try npx as last resort
|
|
90
|
+
$useFallback = $false
|
|
91
|
+
if (-not (Get-Command babysitter -ErrorAction SilentlyContinue)) {
|
|
92
|
+
Write-Blog "CLI not found after install, using npx fallback"
|
|
93
|
+
$useFallback = $true
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Capture stdin
|
|
97
|
+
$InputFile = [System.IO.Path]::GetTempFileName()
|
|
98
|
+
$input | Out-File -FilePath $InputFile -Encoding utf8
|
|
99
|
+
|
|
100
|
+
Write-Blog "Hook input received"
|
|
101
|
+
|
|
102
|
+
$stderrLog = Join-Path $LogDir "babysitter-session-start-hook-stderr.log"
|
|
103
|
+
|
|
104
|
+
if ($useFallback) {
|
|
105
|
+
$Result = Get-Content $InputFile | & npx -y "@a5c-ai/babysitter-sdk@$SdkVersion" hook:run --hook-type session-start --harness cursor --plugin-root $PluginRoot --state-dir $StateDir --json 2>$stderrLog
|
|
106
|
+
} else {
|
|
107
|
+
$Result = Get-Content $InputFile | & babysitter hook:run --hook-type session-start --harness cursor --plugin-root $PluginRoot --state-dir $StateDir --json 2>$stderrLog
|
|
108
|
+
}
|
|
109
|
+
$ExitCode = $LASTEXITCODE
|
|
110
|
+
|
|
111
|
+
Write-Blog "CLI exit code=$ExitCode"
|
|
112
|
+
|
|
113
|
+
Remove-Item $InputFile -Force -ErrorAction SilentlyContinue
|
|
114
|
+
Write-Output $Result
|
|
115
|
+
exit $ExitCode
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Babysitter Session Start Hook for Cursor IDE/CLI
|
|
3
|
+
# Ensures the babysitter SDK CLI is installed (from versions.json sdkVersion),
|
|
4
|
+
# then delegates to the SDK hook handler.
|
|
5
|
+
#
|
|
6
|
+
# Protocol:
|
|
7
|
+
# Input: JSON via stdin (contains session_id, cwd, etc.)
|
|
8
|
+
# Output: JSON via stdout ({} on success)
|
|
9
|
+
# Stderr: debug/log output only
|
|
10
|
+
# Exit 0: success
|
|
11
|
+
# Exit 2: block (fatal error)
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
|
+
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
17
|
+
STATE_DIR="${BABYSITTER_STATE_DIR:-${PWD}/.a5c}"
|
|
18
|
+
LOG_DIR="${BABYSITTER_LOG_DIR:-$PLUGIN_ROOT/.a5c/logs}"
|
|
19
|
+
LOG_FILE="$LOG_DIR/babysitter-session-start-hook.log"
|
|
20
|
+
|
|
21
|
+
export CURSOR_PLUGIN_ROOT="${CURSOR_PLUGIN_ROOT:-${PLUGIN_ROOT}}"
|
|
22
|
+
export BABYSITTER_STATE_DIR="${STATE_DIR}"
|
|
23
|
+
|
|
24
|
+
mkdir -p "$LOG_DIR" 2>/dev/null
|
|
25
|
+
|
|
26
|
+
blog() {
|
|
27
|
+
local msg="$1"
|
|
28
|
+
local ts
|
|
29
|
+
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
30
|
+
echo "[INFO] $ts $msg" >> "$LOG_FILE" 2>/dev/null
|
|
31
|
+
babysitter log --type hook --label "hook:session-start" --message "$msg" --source shell-hook 2>/dev/null || true
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
blog "Hook script invoked"
|
|
35
|
+
blog "PLUGIN_ROOT=$PLUGIN_ROOT"
|
|
36
|
+
blog "STATE_DIR=$STATE_DIR"
|
|
37
|
+
|
|
38
|
+
# Get required SDK version from versions.json
|
|
39
|
+
SDK_VERSION=$(node -e "try{console.log(JSON.parse(require('fs').readFileSync('${PLUGIN_ROOT}/versions.json','utf8')).sdkVersion||'latest')}catch{console.log('latest')}" 2>/dev/null || echo "latest")
|
|
40
|
+
|
|
41
|
+
# Function to install/upgrade SDK
|
|
42
|
+
install_sdk() {
|
|
43
|
+
local target_version="$1"
|
|
44
|
+
if npm i -g "@a5c-ai/babysitter-sdk@${target_version}" --loglevel=error 2>/dev/null; then
|
|
45
|
+
blog "Installed SDK globally (${target_version})"
|
|
46
|
+
return 0
|
|
47
|
+
else
|
|
48
|
+
if npm i -g "@a5c-ai/babysitter-sdk@${target_version}" --prefix "$HOME/.local" --loglevel=error 2>/dev/null; then
|
|
49
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
50
|
+
blog "Installed SDK to user prefix (${target_version})"
|
|
51
|
+
return 0
|
|
52
|
+
fi
|
|
53
|
+
fi
|
|
54
|
+
return 1
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Check if babysitter CLI exists and if version matches
|
|
58
|
+
NEEDS_INSTALL=false
|
|
59
|
+
if command -v babysitter &>/dev/null; then
|
|
60
|
+
CURRENT_VERSION=$(babysitter --version 2>/dev/null || echo "unknown")
|
|
61
|
+
if [ "$CURRENT_VERSION" != "$SDK_VERSION" ]; then
|
|
62
|
+
blog "SDK version mismatch: installed=${CURRENT_VERSION}, required=${SDK_VERSION}"
|
|
63
|
+
NEEDS_INSTALL=true
|
|
64
|
+
else
|
|
65
|
+
blog "SDK version OK: ${CURRENT_VERSION}"
|
|
66
|
+
fi
|
|
67
|
+
else
|
|
68
|
+
blog "SDK CLI not found, will install"
|
|
69
|
+
NEEDS_INSTALL=true
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
MARKER_FILE="${PLUGIN_ROOT}/.babysitter-install-attempted"
|
|
73
|
+
|
|
74
|
+
# Install/upgrade if needed (only attempt once per plugin version)
|
|
75
|
+
if [ "$NEEDS_INSTALL" = true ] && [ ! -f "$MARKER_FILE" ]; then
|
|
76
|
+
install_sdk "$SDK_VERSION"
|
|
77
|
+
echo "$SDK_VERSION" > "$MARKER_FILE" 2>/dev/null
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# If still not available after install attempt, try npx as last resort
|
|
81
|
+
if ! command -v babysitter &>/dev/null; then
|
|
82
|
+
blog "CLI not found after install, using npx fallback"
|
|
83
|
+
babysitter() { npx -y "@a5c-ai/babysitter-sdk@${SDK_VERSION}" "$@"; }
|
|
84
|
+
export -f babysitter
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# Capture stdin to a temp file so the CLI receives a clean EOF
|
|
88
|
+
INPUT_FILE=$(mktemp 2>/dev/null || echo "/tmp/cursor-session-start-hook-$$.json")
|
|
89
|
+
cat > "$INPUT_FILE"
|
|
90
|
+
|
|
91
|
+
blog "Hook input received ($(wc -c < "$INPUT_FILE") bytes)"
|
|
92
|
+
|
|
93
|
+
RESULT=$(babysitter hook:run \
|
|
94
|
+
--hook-type session-start \
|
|
95
|
+
--harness cursor \
|
|
96
|
+
--plugin-root "$PLUGIN_ROOT" \
|
|
97
|
+
--state-dir "${BABYSITTER_STATE_DIR}" \
|
|
98
|
+
--json < "$INPUT_FILE" 2>"$LOG_DIR/babysitter-session-start-hook-stderr.log")
|
|
99
|
+
EXIT_CODE=$?
|
|
100
|
+
|
|
101
|
+
blog "CLI exit code=$EXIT_CODE"
|
|
102
|
+
|
|
103
|
+
rm -f "$INPUT_FILE" 2>/dev/null
|
|
104
|
+
printf '%s\n' "$RESULT"
|
|
105
|
+
exit $EXIT_CODE
|