@a5c-ai/babysitter-opencode 5.0.1-staging.e4f17eff → 5.0.1-staging.ef4e872c

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 (43) hide show
  1. package/README.md +2 -2
  2. package/bin/cli.cjs +1 -191
  3. package/bin/cli.js +90 -49
  4. package/bin/install-shared.cjs +1 -478
  5. package/bin/install-shared.js +615 -0
  6. package/bin/install.cjs +1 -143
  7. package/bin/install.js +18 -98
  8. package/bin/uninstall.cjs +1 -87
  9. package/bin/uninstall.js +12 -34
  10. package/commands/doctor.md +5 -5
  11. package/commands/help.md +245 -244
  12. package/commands/observe.md +12 -12
  13. package/hooks/babysitter-proxied-session-created.js +18 -212
  14. package/hooks/babysitter-proxied-session-created.sh +11 -0
  15. package/hooks/babysitter-proxied-session-idle.js +18 -167
  16. package/hooks/babysitter-proxied-session-idle.sh +3 -0
  17. package/hooks/babysitter-proxied-shell-env.js +21 -145
  18. package/hooks/babysitter-proxied-shell-env.sh +3 -0
  19. package/hooks/babysitter-proxied-tool-execute-after.js +20 -160
  20. package/hooks/babysitter-proxied-tool-execute-after.sh +3 -0
  21. package/hooks/babysitter-proxied-tool-execute-before.js +20 -162
  22. package/hooks/babysitter-proxied-tool-execute-before.sh +3 -0
  23. package/hooks/hooks.json +18 -18
  24. package/package.json +22 -18
  25. package/plugin.json +6 -4
  26. package/scripts/team-install.js +23 -0
  27. package/skills/contrib/SKILL.md +25 -25
  28. package/skills/doctor/SKILL.md +5 -5
  29. package/skills/help/SKILL.md +3 -2
  30. package/skills/observe/SKILL.md +1 -1
  31. package/skills/plugins/SKILL.md +243 -243
  32. package/skills/project-install/SKILL.md +3 -3
  33. package/skills/resume/SKILL.md +1 -1
  34. package/skills/retrospect/SKILL.md +48 -48
  35. package/skills/user-install/SKILL.md +3 -3
  36. package/versions.json +2 -2
  37. package/hooks/hooks.json.legacy +0 -46
  38. package/hooks/proxied-hooks.json +0 -47
  39. package/hooks/session-created.js +0 -182
  40. package/hooks/session-idle.js +0 -124
  41. package/hooks/shell-env.js +0 -88
  42. package/hooks/tool-execute-after.js +0 -107
  43. package/hooks/tool-execute-before.js +0 -109
@@ -1,480 +1,3 @@
1
- #!/usr/bin/env node
2
1
  'use strict';
3
2
 
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
- // Accomplish AI detection and paths
372
- // ---------------------------------------------------------------------------
373
-
374
- /**
375
- * Returns the Accomplish user data directory for the current platform.
376
- * If OPENCODE_CONFIG_DIR is set, returns its parent (the Accomplish data dir).
377
- * Otherwise falls back to platform-specific defaults.
378
- */
379
- function getAccomplishDataDir() {
380
- if (process.env.OPENCODE_CONFIG_DIR) {
381
- return path.resolve(process.env.OPENCODE_CONFIG_DIR, '..');
382
- }
383
- const home = getUserHome();
384
- switch (process.platform) {
385
- case 'darwin':
386
- return path.join(home, 'Library', 'Application Support', 'Accomplish');
387
- case 'win32': {
388
- const appData = process.env.APPDATA || process.env.LOCALAPPDATA;
389
- return appData
390
- ? path.join(appData, 'Accomplish')
391
- : path.join(home, 'AppData', 'Roaming', 'Accomplish');
392
- }
393
- default:
394
- return path.join(home, '.config', 'Accomplish');
395
- }
396
- }
397
-
398
- /**
399
- * Returns true if Accomplish AI appears to be installed or is running.
400
- * Checks for:
401
- * - ACCOMPLISH_TASK_ID env var (running inside Accomplish)
402
- * - Accomplish data dir with an opencode/ subdirectory on disk
403
- */
404
- function isAccomplishInstalled() {
405
- if (process.env.ACCOMPLISH_TASK_ID) return true;
406
- try {
407
- const dataDir = getAccomplishDataDir();
408
- const openCodeDir = path.join(dataDir, 'opencode');
409
- return fs.existsSync(openCodeDir);
410
- } catch {
411
- return false;
412
- }
413
- }
414
-
415
- /**
416
- * Returns the OpenCode home directory inside the Accomplish data dir.
417
- */
418
- function getAccomplishOpenCodeHome() {
419
- return path.join(getAccomplishDataDir(), 'opencode');
420
- }
421
-
422
- /**
423
- * Install the babysitter plugin into Accomplish's OpenCode directory.
424
- * Mirrors the standard OpenCode install: copies bundle, writes index.js,
425
- * installs skills and hooks.
426
- */
427
- function installAccomplishSurface(packageRoot, accomplishOpenCodeHome) {
428
- const pluginRoot = path.join(accomplishOpenCodeHome, 'plugins', PLUGIN_NAME);
429
-
430
- // Copy plugin bundle
431
- copyPluginBundle(packageRoot, pluginRoot);
432
-
433
- // Create index.js entry point
434
- writeIndexJs(pluginRoot);
435
-
436
- // Install skills and hooks config
437
- installOpenCodeSurface(packageRoot, accomplishOpenCodeHome);
438
- }
439
-
440
- // ---------------------------------------------------------------------------
441
- // OpenCode surface installation
442
- // ---------------------------------------------------------------------------
443
-
444
- function installOpenCodeSurface(packageRoot, openCodeHome) {
445
- // Copy skills into .opencode/skills/
446
- const sourceSkills = path.join(packageRoot, 'skills');
447
- if (fs.existsSync(sourceSkills)) {
448
- const targetSkills = path.join(openCodeHome, 'skills');
449
- fs.mkdirSync(targetSkills, { recursive: true });
450
- for (const entry of fs.readdirSync(sourceSkills, { withFileTypes: true })) {
451
- if (!entry.isDirectory()) continue;
452
- copyRecursive(
453
- path.join(sourceSkills, entry.name),
454
- path.join(targetSkills, entry.name),
455
- );
456
- }
457
- }
458
-
459
- // Merge hooks config
460
- mergeHooksConfig(packageRoot, openCodeHome);
461
- }
462
-
463
- module.exports = {
464
- copyPluginBundle,
465
- copyRecursive,
466
- ensureGlobalProcessLibrary,
467
- ensureMarketplaceEntry,
468
- getAccomplishDataDir,
469
- getAccomplishOpenCodeHome,
470
- getHomeMarketplacePath,
471
- getHomePluginRoot,
472
- getOpenCodeHome,
473
- installAccomplishSurface,
474
- installOpenCodeSurface,
475
- isAccomplishInstalled,
476
- removeManagedHooks,
477
- removeMarketplaceEntry,
478
- writeIndexJs,
479
- writeJson,
480
- };
3
+ module.exports = require('./install-shared.js');