@a5c-ai/babysitter-opencode 0.1.1-staging.0dc03363

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.
Files changed (51) hide show
  1. package/README.md +169 -0
  2. package/bin/cli.cjs +194 -0
  3. package/bin/cli.js +55 -0
  4. package/bin/install-shared.cjs +406 -0
  5. package/bin/install.cjs +97 -0
  6. package/bin/install.js +110 -0
  7. package/bin/uninstall.cjs +90 -0
  8. package/bin/uninstall.js +46 -0
  9. package/commands/assimilate.md +37 -0
  10. package/commands/call.md +7 -0
  11. package/commands/cleanup.md +20 -0
  12. package/commands/contrib.md +33 -0
  13. package/commands/doctor.md +426 -0
  14. package/commands/forever.md +7 -0
  15. package/commands/help.md +244 -0
  16. package/commands/observe.md +12 -0
  17. package/commands/plan.md +7 -0
  18. package/commands/plugins.md +255 -0
  19. package/commands/project-install.md +17 -0
  20. package/commands/resume.md +8 -0
  21. package/commands/retrospect.md +55 -0
  22. package/commands/status.md +8 -0
  23. package/commands/user-install.md +17 -0
  24. package/commands/yolo.md +7 -0
  25. package/hooks/hooks.json +46 -0
  26. package/hooks/session-created.js +180 -0
  27. package/hooks/session-idle.js +122 -0
  28. package/hooks/shell-env.js +86 -0
  29. package/hooks/tool-execute-after.js +105 -0
  30. package/hooks/tool-execute-before.js +107 -0
  31. package/package.json +46 -0
  32. package/plugin.json +25 -0
  33. package/scripts/sync-command-docs.cjs +105 -0
  34. package/scripts/sync-command-surfaces.js +52 -0
  35. package/skills/assimilate/SKILL.md +38 -0
  36. package/skills/babysit/SKILL.md +35 -0
  37. package/skills/call/SKILL.md +8 -0
  38. package/skills/cleanup/SKILL.md +21 -0
  39. package/skills/contrib/SKILL.md +34 -0
  40. package/skills/doctor/SKILL.md +427 -0
  41. package/skills/forever/SKILL.md +8 -0
  42. package/skills/help/SKILL.md +245 -0
  43. package/skills/observe/SKILL.md +13 -0
  44. package/skills/plan/SKILL.md +8 -0
  45. package/skills/plugins/SKILL.md +257 -0
  46. package/skills/project-install/SKILL.md +18 -0
  47. package/skills/resume/SKILL.md +9 -0
  48. package/skills/retrospect/SKILL.md +56 -0
  49. package/skills/user-install/SKILL.md +18 -0
  50. package/skills/yolo/SKILL.md +8 -0
  51. package/versions.json +4 -0
@@ -0,0 +1,406 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Shared installation utilities for babysitter-opencode plugin.
6
+ *
7
+ * Handles plugin bundle copying, OpenCode config registration,
8
+ * marketplace entry management, and process library bootstrapping.
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const os = require('os');
13
+ const path = require('path');
14
+ const { spawnSync } = require('child_process');
15
+
16
+ const PLUGIN_NAME = 'babysitter';
17
+
18
+ const PLUGIN_BUNDLE_ENTRIES = [
19
+ 'plugin.json',
20
+ 'versions.json',
21
+ 'hooks',
22
+ 'skills',
23
+ 'commands',
24
+ ];
25
+
26
+ const HOOK_SCRIPT_NAMES = [
27
+ 'session-created.js',
28
+ 'session-idle.js',
29
+ 'shell-env.js',
30
+ 'tool-execute-before.js',
31
+ 'tool-execute-after.js',
32
+ ];
33
+
34
+ const DEFAULT_MARKETPLACE = {
35
+ name: 'local-plugins',
36
+ interface: {
37
+ displayName: 'Local Plugins',
38
+ },
39
+ plugins: [],
40
+ };
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Path helpers
44
+ // ---------------------------------------------------------------------------
45
+
46
+ function getUserHome() {
47
+ if (process.env.USERPROFILE) return path.resolve(process.env.USERPROFILE);
48
+ if (process.env.HOME) return path.resolve(process.env.HOME);
49
+ return os.homedir();
50
+ }
51
+
52
+ function getGlobalStateDir() {
53
+ if (process.env.BABYSITTER_GLOBAL_STATE_DIR) {
54
+ return path.resolve(process.env.BABYSITTER_GLOBAL_STATE_DIR);
55
+ }
56
+ return path.join(getUserHome(), '.a5c');
57
+ }
58
+
59
+ /**
60
+ * Resolve the OpenCode config root.
61
+ * OpenCode uses `.opencode/` in the workspace directory by default.
62
+ * Respect OPENCODE_HOME env var if set.
63
+ */
64
+ function getOpenCodeHome(workspace) {
65
+ if (process.env.OPENCODE_HOME) return path.resolve(process.env.OPENCODE_HOME);
66
+ return path.join(workspace || process.cwd(), '.opencode');
67
+ }
68
+
69
+ function getHomePluginRoot(workspace) {
70
+ if (process.env.BABYSITTER_OPENCODE_PLUGIN_DIR) {
71
+ return path.resolve(process.env.BABYSITTER_OPENCODE_PLUGIN_DIR, PLUGIN_NAME);
72
+ }
73
+ return path.join(getOpenCodeHome(workspace), 'plugins', PLUGIN_NAME);
74
+ }
75
+
76
+ function getHomeMarketplacePath(workspace) {
77
+ if (process.env.BABYSITTER_OPENCODE_MARKETPLACE_PATH) {
78
+ return path.resolve(process.env.BABYSITTER_OPENCODE_MARKETPLACE_PATH);
79
+ }
80
+ return path.join(getUserHome(), '.agents', 'plugins', 'marketplace.json');
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // File utilities
85
+ // ---------------------------------------------------------------------------
86
+
87
+ function writeFileIfChanged(filePath, contents) {
88
+ if (fs.existsSync(filePath)) {
89
+ const current = fs.readFileSync(filePath, 'utf8');
90
+ if (current === contents) return false;
91
+ }
92
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
93
+ fs.writeFileSync(filePath, contents, 'utf8');
94
+ return true;
95
+ }
96
+
97
+ function readJson(filePath) {
98
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
99
+ }
100
+
101
+ function writeJson(filePath, value) {
102
+ writeFileIfChanged(filePath, `${JSON.stringify(value, null, 2)}\n`);
103
+ }
104
+
105
+ function copyRecursive(src, dest) {
106
+ const stat = fs.statSync(src);
107
+ if (stat.isDirectory()) {
108
+ fs.mkdirSync(dest, { recursive: true });
109
+ for (const entry of fs.readdirSync(src)) {
110
+ if (['node_modules', '.git', 'test', '.a5c'].includes(entry)) continue;
111
+ copyRecursive(path.join(src, entry), path.join(dest, entry));
112
+ }
113
+ return;
114
+ }
115
+
116
+ // Strip BOM from SKILL.md files
117
+ if (path.basename(src) === 'SKILL.md') {
118
+ const file = fs.readFileSync(src);
119
+ const hasBom = file.length >= 3 && file[0] === 0xef && file[1] === 0xbb && file[2] === 0xbf;
120
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
121
+ fs.writeFileSync(dest, hasBom ? file.subarray(3) : file);
122
+ return;
123
+ }
124
+
125
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
126
+ fs.copyFileSync(src, dest);
127
+ }
128
+
129
+ // ---------------------------------------------------------------------------
130
+ // Plugin bundle
131
+ // ---------------------------------------------------------------------------
132
+
133
+ function copyPluginBundle(packageRoot, pluginRoot) {
134
+ if (path.resolve(packageRoot) === path.resolve(pluginRoot)) {
135
+ return;
136
+ }
137
+ fs.rmSync(pluginRoot, { recursive: true, force: true });
138
+ fs.mkdirSync(pluginRoot, { recursive: true });
139
+ for (const entry of PLUGIN_BUNDLE_ENTRIES) {
140
+ const src = path.join(packageRoot, entry);
141
+ if (fs.existsSync(src)) {
142
+ copyRecursive(src, path.join(pluginRoot, entry));
143
+ }
144
+ }
145
+ }
146
+
147
+ // ---------------------------------------------------------------------------
148
+ // OpenCode index.js entry point generation
149
+ // ---------------------------------------------------------------------------
150
+
151
+ function writeIndexJs(pluginRoot) {
152
+ const indexContent = `#!/usr/bin/env node
153
+ /**
154
+ * Babysitter plugin entry point for OpenCode.
155
+ *
156
+ * OpenCode discovers plugins by looking for JS/TS modules in
157
+ * .opencode/plugins/. This file registers the babysitter hooks
158
+ * with the OpenCode plugin system.
159
+ */
160
+
161
+ "use strict";
162
+
163
+ const path = require("path");
164
+
165
+ const PLUGIN_DIR = __dirname;
166
+
167
+ module.exports = {
168
+ name: "babysitter",
169
+ version: require(path.join(PLUGIN_DIR, "plugin.json")).version,
170
+
171
+ hooks: {
172
+ "session.created": require(path.join(PLUGIN_DIR, "hooks", "session-created.js")),
173
+ "session.idle": require(path.join(PLUGIN_DIR, "hooks", "session-idle.js")),
174
+ "shell.env": require(path.join(PLUGIN_DIR, "hooks", "shell-env.js")),
175
+ "tool.execute.before": require(path.join(PLUGIN_DIR, "hooks", "tool-execute-before.js")),
176
+ "tool.execute.after": require(path.join(PLUGIN_DIR, "hooks", "tool-execute-after.js")),
177
+ },
178
+ };
179
+ `;
180
+ fs.writeFileSync(path.join(pluginRoot, 'index.js'), indexContent, 'utf8');
181
+ }
182
+
183
+ // ---------------------------------------------------------------------------
184
+ // OpenCode hooks.json config registration
185
+ // ---------------------------------------------------------------------------
186
+
187
+ function mergeHooksConfig(packageRoot, openCodeHome) {
188
+ const hooksJsonPath = path.join(packageRoot, 'hooks', 'hooks.json');
189
+ if (!fs.existsSync(hooksJsonPath)) return;
190
+ const managedConfig = readJson(hooksJsonPath);
191
+ const managedHooks = managedConfig.hooks || {};
192
+ const hooksConfigPath = path.join(openCodeHome, 'hooks.json');
193
+ const existing = fs.existsSync(hooksConfigPath)
194
+ ? readJson(hooksConfigPath)
195
+ : { version: 1, hooks: {} };
196
+ existing.version = existing.version || 1;
197
+ if (!existing.hooks || typeof existing.hooks !== 'object') {
198
+ existing.hooks = {};
199
+ }
200
+
201
+ for (const [eventName, entries] of Object.entries(managedHooks)) {
202
+ const existingEntries = Array.isArray(existing.hooks[eventName]) ? existing.hooks[eventName] : [];
203
+ const filteredEntries = existingEntries.filter((entry) => {
204
+ const script = String(entry.script || entry.command || entry.bash || '');
205
+ return !HOOK_SCRIPT_NAMES.some((name) => script.includes(name));
206
+ });
207
+ existing.hooks[eventName] = [...filteredEntries, ...entries];
208
+ }
209
+
210
+ writeJson(hooksConfigPath, existing);
211
+ }
212
+
213
+ function removeManagedHooks(openCodeHome) {
214
+ const hooksConfigPath = path.join(openCodeHome, 'hooks.json');
215
+ if (!fs.existsSync(hooksConfigPath)) return;
216
+
217
+ let hooksConfig;
218
+ try {
219
+ hooksConfig = readJson(hooksConfigPath);
220
+ } catch {
221
+ return;
222
+ }
223
+ if (!hooksConfig.hooks || typeof hooksConfig.hooks !== 'object') return;
224
+
225
+ for (const eventName of Object.keys(hooksConfig.hooks)) {
226
+ const eventHooks = Array.isArray(hooksConfig.hooks[eventName]) ? hooksConfig.hooks[eventName] : [];
227
+ const filtered = eventHooks.filter((entry) => {
228
+ const script = String(entry.script || entry.command || entry.bash || '');
229
+ return !HOOK_SCRIPT_NAMES.some((name) => script.includes(name));
230
+ });
231
+ if (filtered.length > 0) {
232
+ hooksConfig.hooks[eventName] = filtered;
233
+ } else {
234
+ delete hooksConfig.hooks[eventName];
235
+ }
236
+ }
237
+ if (Object.keys(hooksConfig.hooks).length === 0) {
238
+ fs.rmSync(hooksConfigPath, { force: true });
239
+ } else {
240
+ writeJson(hooksConfigPath, hooksConfig);
241
+ }
242
+ }
243
+
244
+ // ---------------------------------------------------------------------------
245
+ // Marketplace
246
+ // ---------------------------------------------------------------------------
247
+
248
+ function normalizeMarketplaceName(name) {
249
+ const raw = String(name || '').trim();
250
+ const sanitized = raw
251
+ .replace(/[^A-Za-z0-9_-]+/g, '-')
252
+ .replace(/^-+|-+$/g, '');
253
+ return sanitized || DEFAULT_MARKETPLACE.name;
254
+ }
255
+
256
+ function normalizeMarketplaceSourcePath(marketplacePath, pluginSourcePath) {
257
+ let next = pluginSourcePath;
258
+ if (path.isAbsolute(next)) {
259
+ next = path.relative(path.dirname(marketplacePath), next);
260
+ }
261
+ next = String(next || '').replace(/\\/g, '/');
262
+ if (!next.startsWith('./') && !next.startsWith('../')) {
263
+ next = `./${next}`;
264
+ }
265
+ return next;
266
+ }
267
+
268
+ function ensureMarketplaceEntry(marketplacePath, pluginSourcePath) {
269
+ const marketplace = fs.existsSync(marketplacePath)
270
+ ? readJson(marketplacePath)
271
+ : { ...DEFAULT_MARKETPLACE, plugins: [] };
272
+ marketplace.name = normalizeMarketplaceName(marketplace.name);
273
+ marketplace.interface = marketplace.interface || {};
274
+ marketplace.interface.displayName =
275
+ marketplace.interface.displayName || DEFAULT_MARKETPLACE.interface.displayName;
276
+ const nextEntry = {
277
+ name: PLUGIN_NAME,
278
+ source: {
279
+ source: 'local',
280
+ path: normalizeMarketplaceSourcePath(marketplacePath, pluginSourcePath),
281
+ },
282
+ policy: {
283
+ installation: 'AVAILABLE',
284
+ authentication: 'ON_INSTALL',
285
+ },
286
+ category: 'Coding',
287
+ };
288
+ const existingIndex = Array.isArray(marketplace.plugins)
289
+ ? marketplace.plugins.findIndex((entry) => entry && entry.name === PLUGIN_NAME)
290
+ : -1;
291
+ if (!Array.isArray(marketplace.plugins)) {
292
+ marketplace.plugins = [nextEntry];
293
+ } else if (existingIndex >= 0) {
294
+ marketplace.plugins[existingIndex] = nextEntry;
295
+ } else {
296
+ marketplace.plugins.push(nextEntry);
297
+ }
298
+ writeJson(marketplacePath, marketplace);
299
+ return nextEntry;
300
+ }
301
+
302
+ function removeMarketplaceEntry(marketplacePath) {
303
+ if (!fs.existsSync(marketplacePath)) return;
304
+ const marketplace = readJson(marketplacePath);
305
+ if (!Array.isArray(marketplace.plugins)) return;
306
+ marketplace.plugins = marketplace.plugins.filter((entry) => entry && entry.name !== PLUGIN_NAME);
307
+ writeJson(marketplacePath, marketplace);
308
+ }
309
+
310
+ // ---------------------------------------------------------------------------
311
+ // Babysitter SDK CLI helpers
312
+ // ---------------------------------------------------------------------------
313
+
314
+ function resolveBabysitterCommand(packageRoot) {
315
+ if (process.env.BABYSITTER_SDK_CLI) {
316
+ return {
317
+ command: process.execPath,
318
+ argsPrefix: [path.resolve(process.env.BABYSITTER_SDK_CLI)],
319
+ };
320
+ }
321
+ try {
322
+ return {
323
+ command: process.execPath,
324
+ argsPrefix: [
325
+ require.resolve('@a5c-ai/babysitter-sdk/dist/cli/main.js', {
326
+ paths: [packageRoot],
327
+ }),
328
+ ],
329
+ };
330
+ } catch {
331
+ return {
332
+ command: 'babysitter',
333
+ argsPrefix: [],
334
+ };
335
+ }
336
+ }
337
+
338
+ function runBabysitterCli(packageRoot, cliArgs, options = {}) {
339
+ const resolved = resolveBabysitterCommand(packageRoot);
340
+ const result = spawnSync(resolved.command, [...resolved.argsPrefix, ...cliArgs], {
341
+ cwd: options.cwd || process.cwd(),
342
+ stdio: ['ignore', 'pipe', 'pipe'],
343
+ encoding: 'utf8',
344
+ env: {
345
+ ...process.env,
346
+ ...(options.env || {}),
347
+ },
348
+ });
349
+ if (result.status !== 0) {
350
+ const stderr = (result.stderr || '').trim();
351
+ const stdout = (result.stdout || '').trim();
352
+ throw new Error(
353
+ `babysitter ${cliArgs.join(' ')} failed` +
354
+ (stderr ? `: ${stderr}` : stdout ? `: ${stdout}` : ''),
355
+ );
356
+ }
357
+ return result.stdout;
358
+ }
359
+
360
+ function ensureGlobalProcessLibrary(packageRoot) {
361
+ return JSON.parse(
362
+ runBabysitterCli(
363
+ packageRoot,
364
+ ['process-library:active', '--state-dir', getGlobalStateDir(), '--json'],
365
+ { cwd: packageRoot },
366
+ ),
367
+ );
368
+ }
369
+
370
+ // ---------------------------------------------------------------------------
371
+ // OpenCode surface installation
372
+ // ---------------------------------------------------------------------------
373
+
374
+ function installOpenCodeSurface(packageRoot, openCodeHome) {
375
+ // Copy skills into .opencode/skills/
376
+ const sourceSkills = path.join(packageRoot, 'skills');
377
+ if (fs.existsSync(sourceSkills)) {
378
+ const targetSkills = path.join(openCodeHome, 'skills');
379
+ fs.mkdirSync(targetSkills, { recursive: true });
380
+ for (const entry of fs.readdirSync(sourceSkills, { withFileTypes: true })) {
381
+ if (!entry.isDirectory()) continue;
382
+ copyRecursive(
383
+ path.join(sourceSkills, entry.name),
384
+ path.join(targetSkills, entry.name),
385
+ );
386
+ }
387
+ }
388
+
389
+ // Merge hooks config
390
+ mergeHooksConfig(packageRoot, openCodeHome);
391
+ }
392
+
393
+ module.exports = {
394
+ copyPluginBundle,
395
+ copyRecursive,
396
+ ensureGlobalProcessLibrary,
397
+ ensureMarketplaceEntry,
398
+ getHomeMarketplacePath,
399
+ getHomePluginRoot,
400
+ getOpenCodeHome,
401
+ installOpenCodeSurface,
402
+ removeManagedHooks,
403
+ removeMarketplaceEntry,
404
+ writeIndexJs,
405
+ writeJson,
406
+ };
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Babysitter OpenCode Plugin Installer
6
+ *
7
+ * Copies the babysitter plugin bundle into the OpenCode plugins directory:
8
+ * <workspace>/.opencode/plugins/babysitter/
9
+ *
10
+ * Registers hooks in OpenCode config, creates the index.js entry point
11
+ * for plugin discovery, and bootstraps the global process library.
12
+ *
13
+ * Usage:
14
+ * node install.cjs # Install into cwd workspace
15
+ * node install.cjs --workspace /path # Install into specified workspace
16
+ * node install.cjs --global # Global install (user home)
17
+ */
18
+
19
+ const path = require('path');
20
+ const {
21
+ copyPluginBundle,
22
+ ensureGlobalProcessLibrary,
23
+ ensureMarketplaceEntry,
24
+ getHomeMarketplacePath,
25
+ getHomePluginRoot,
26
+ getOpenCodeHome,
27
+ installOpenCodeSurface,
28
+ writeIndexJs,
29
+ } = require('./install-shared.cjs');
30
+
31
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
32
+
33
+ function parseArgs(argv) {
34
+ let workspace = process.env.OPENCODE_WORKSPACE || process.cwd();
35
+ for (let i = 2; i < argv.length; i += 1) {
36
+ const arg = argv[i];
37
+ if (arg === '--workspace') {
38
+ const next = argv[i + 1];
39
+ workspace = next && !next.startsWith('-') ? path.resolve(argv[++i]) : process.cwd();
40
+ continue;
41
+ }
42
+ if (arg === '--global') {
43
+ workspace = null;
44
+ continue;
45
+ }
46
+ throw new Error(`unknown argument: ${arg}`);
47
+ }
48
+ return { workspace };
49
+ }
50
+
51
+ function main() {
52
+ const { workspace } = parseArgs(process.argv);
53
+ const openCodeHome = getOpenCodeHome(workspace);
54
+ const pluginRoot = getHomePluginRoot(workspace);
55
+ const marketplacePath = getHomeMarketplacePath(workspace);
56
+
57
+ console.log(`[babysitter] Installing OpenCode plugin to ${pluginRoot}`);
58
+
59
+ try {
60
+ // 1. Copy plugin bundle
61
+ copyPluginBundle(PACKAGE_ROOT, pluginRoot);
62
+ console.log('[babysitter] Copied plugin bundle');
63
+
64
+ // 2. Write index.js entry point for OpenCode plugin discovery
65
+ writeIndexJs(pluginRoot);
66
+ console.log('[babysitter] Created index.js entry point');
67
+
68
+ // 3. Register in marketplace
69
+ ensureMarketplaceEntry(marketplacePath, pluginRoot);
70
+ console.log(`[babysitter] Marketplace: ${marketplacePath}`);
71
+
72
+ // 4. Install OpenCode surfaces (skills, hooks config)
73
+ installOpenCodeSurface(PACKAGE_ROOT, openCodeHome);
74
+ console.log('[babysitter] Installed hooks and skills');
75
+
76
+ // 5. Bootstrap global process library
77
+ try {
78
+ const active = ensureGlobalProcessLibrary(PACKAGE_ROOT);
79
+ console.log(`[babysitter] Process library: ${active.binding?.dir || '(default)'}`);
80
+ if (active.defaultSpec?.cloneDir) {
81
+ console.log(`[babysitter] Process library clone: ${active.defaultSpec.cloneDir}`);
82
+ }
83
+ console.log(`[babysitter] Process library state: ${active.stateFile}`);
84
+ } catch (err) {
85
+ console.warn(`[babysitter] Warning: Could not bootstrap process library: ${err.message}`);
86
+ console.warn('[babysitter] Run "babysitter process-library:clone" manually if needed.');
87
+ }
88
+
89
+ console.log('[babysitter] Installation complete!');
90
+ console.log('[babysitter] Restart OpenCode to pick up the installed plugin.');
91
+ } catch (err) {
92
+ console.error(`[babysitter] Failed to install plugin: ${err.message}`);
93
+ process.exitCode = 1;
94
+ }
95
+ }
96
+
97
+ main();
package/bin/install.js ADDED
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Babysitter OpenCode Plugin Installer
4
+ *
5
+ * Copies plugin files into the OpenCode plugins directory:
6
+ * .opencode/plugins/babysitter/
7
+ *
8
+ * OpenCode discovers plugins as JS/TS modules in .opencode/plugins/.
9
+ * This installer creates the necessary directory structure and copies
10
+ * hook scripts, skills, and an index.js entry point.
11
+ */
12
+
13
+ "use strict";
14
+
15
+ const fs = require("fs");
16
+ const path = require("path");
17
+
18
+ const PLUGIN_ROOT = path.resolve(__dirname, "..");
19
+ const WORKSPACE = process.env.OPENCODE_WORKSPACE || process.cwd();
20
+ const TARGET_DIR = path.join(WORKSPACE, ".opencode", "plugins", "babysitter");
21
+
22
+ function copyRecursive(src, dest) {
23
+ const stat = fs.statSync(src);
24
+ if (stat.isDirectory()) {
25
+ fs.mkdirSync(dest, { recursive: true });
26
+ for (const entry of fs.readdirSync(src)) {
27
+ copyRecursive(path.join(src, entry), path.join(dest, entry));
28
+ }
29
+ } else {
30
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
31
+ fs.copyFileSync(src, dest);
32
+ }
33
+ }
34
+
35
+ function main() {
36
+ console.log(`Installing babysitter plugin for OpenCode...`);
37
+ console.log(` Source: ${PLUGIN_ROOT}`);
38
+ console.log(` Target: ${TARGET_DIR}`);
39
+
40
+ // Create target directory
41
+ fs.mkdirSync(TARGET_DIR, { recursive: true });
42
+
43
+ // Copy hooks
44
+ const hooksDir = path.join(PLUGIN_ROOT, "hooks");
45
+ if (fs.existsSync(hooksDir)) {
46
+ copyRecursive(hooksDir, path.join(TARGET_DIR, "hooks"));
47
+ console.log(" Copied hooks/");
48
+ }
49
+
50
+ // Copy skills
51
+ const skillsDir = path.join(PLUGIN_ROOT, "skills");
52
+ if (fs.existsSync(skillsDir)) {
53
+ copyRecursive(skillsDir, path.join(TARGET_DIR, "skills"));
54
+ console.log(" Copied skills/");
55
+ }
56
+
57
+ // Copy commands
58
+ const commandsDir = path.join(PLUGIN_ROOT, "commands");
59
+ if (fs.existsSync(commandsDir)) {
60
+ copyRecursive(commandsDir, path.join(TARGET_DIR, "commands"));
61
+ console.log(" Copied commands/");
62
+ }
63
+
64
+ // Copy plugin.json and versions.json
65
+ for (const file of ["plugin.json", "versions.json"]) {
66
+ const src = path.join(PLUGIN_ROOT, file);
67
+ if (fs.existsSync(src)) {
68
+ fs.copyFileSync(src, path.join(TARGET_DIR, file));
69
+ console.log(` Copied ${file}`);
70
+ }
71
+ }
72
+
73
+ // Create index.js entry point for OpenCode plugin discovery
74
+ const indexContent = `#!/usr/bin/env node
75
+ /**
76
+ * Babysitter plugin entry point for OpenCode.
77
+ *
78
+ * OpenCode discovers plugins by looking for JS/TS modules in
79
+ * .opencode/plugins/. This file registers the babysitter hooks
80
+ * with the OpenCode plugin system.
81
+ */
82
+
83
+ "use strict";
84
+
85
+ const path = require("path");
86
+
87
+ const PLUGIN_DIR = __dirname;
88
+
89
+ module.exports = {
90
+ name: "babysitter",
91
+ version: require(path.join(PLUGIN_DIR, "plugin.json")).version,
92
+
93
+ hooks: {
94
+ "session.created": require(path.join(PLUGIN_DIR, "hooks", "session-created.js")),
95
+ "session.idle": require(path.join(PLUGIN_DIR, "hooks", "session-idle.js")),
96
+ "shell.env": require(path.join(PLUGIN_DIR, "hooks", "shell-env.js")),
97
+ "tool.execute.before": require(path.join(PLUGIN_DIR, "hooks", "tool-execute-before.js")),
98
+ "tool.execute.after": require(path.join(PLUGIN_DIR, "hooks", "tool-execute-after.js")),
99
+ },
100
+ };
101
+ `;
102
+
103
+ fs.writeFileSync(path.join(TARGET_DIR, "index.js"), indexContent);
104
+ console.log(" Created index.js");
105
+
106
+ console.log(`\nBabysitter plugin installed to ${TARGET_DIR}`);
107
+ console.log("Restart OpenCode to activate the plugin.");
108
+ }
109
+
110
+ main();
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Babysitter OpenCode Plugin Uninstaller
6
+ *
7
+ * Removes the babysitter plugin from the OpenCode plugins directory
8
+ * and cleans up hooks config and marketplace entries.
9
+ *
10
+ * Usage:
11
+ * node uninstall.cjs # Uninstall from cwd workspace
12
+ * node uninstall.cjs --workspace /path # Uninstall from specified workspace
13
+ * node uninstall.cjs --global # Global uninstall
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const {
19
+ getHomeMarketplacePath,
20
+ getHomePluginRoot,
21
+ getOpenCodeHome,
22
+ removeManagedHooks,
23
+ removeMarketplaceEntry,
24
+ } = require('./install-shared.cjs');
25
+
26
+ function parseArgs(argv) {
27
+ let workspace = process.env.OPENCODE_WORKSPACE || process.cwd();
28
+ for (let i = 2; i < argv.length; i += 1) {
29
+ const arg = argv[i];
30
+ if (arg === '--workspace') {
31
+ const next = argv[i + 1];
32
+ workspace = next && !next.startsWith('-') ? path.resolve(argv[++i]) : process.cwd();
33
+ continue;
34
+ }
35
+ if (arg === '--global') {
36
+ workspace = null;
37
+ continue;
38
+ }
39
+ throw new Error(`unknown argument: ${arg}`);
40
+ }
41
+ return { workspace };
42
+ }
43
+
44
+ function main() {
45
+ const { workspace } = parseArgs(process.argv);
46
+ const openCodeHome = getOpenCodeHome(workspace);
47
+ const pluginRoot = getHomePluginRoot(workspace);
48
+ const marketplacePath = getHomeMarketplacePath(workspace);
49
+ let removedPlugin = false;
50
+
51
+ console.log(`[babysitter] Uninstalling OpenCode plugin from ${pluginRoot}`);
52
+
53
+ // 1. Remove plugin directory
54
+ if (fs.existsSync(pluginRoot)) {
55
+ try {
56
+ fs.rmSync(pluginRoot, { recursive: true, force: true });
57
+ console.log(`[babysitter] Removed ${pluginRoot}`);
58
+ removedPlugin = true;
59
+ } catch (err) {
60
+ console.warn(`[babysitter] Warning: Could not remove plugin directory: ${err.message}`);
61
+ }
62
+ }
63
+
64
+ // 2. Remove marketplace entry
65
+ removeMarketplaceEntry(marketplacePath);
66
+ console.log('[babysitter] Cleaned marketplace entry');
67
+
68
+ // 3. Remove managed hooks from OpenCode config
69
+ removeManagedHooks(openCodeHome);
70
+ console.log('[babysitter] Cleaned hooks config');
71
+
72
+ // 4. Clean up empty parent directories
73
+ const pluginsDir = path.dirname(pluginRoot);
74
+ try {
75
+ const remaining = fs.readdirSync(pluginsDir);
76
+ if (remaining.length === 0) {
77
+ fs.rmdirSync(pluginsDir);
78
+ console.log('[babysitter] Removed empty plugins/ directory');
79
+ }
80
+ } catch { /* best-effort */ }
81
+
82
+ if (!removedPlugin) {
83
+ console.log('[babysitter] Plugin directory not found; config and hooks cleaned if present.');
84
+ return;
85
+ }
86
+
87
+ console.log('[babysitter] Uninstallation complete. Restart OpenCode to finish removal.');
88
+ }
89
+
90
+ main();