@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
@@ -0,0 +1,615 @@
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";
9
+ const PLUGIN_CATEGORY = 'Coding';
10
+
11
+ function getUserHome() {
12
+ return os.homedir();
13
+ }
14
+
15
+ function getHarnessHome() {
16
+ return path.join(os.homedir(), ".opencode");
17
+ }
18
+
19
+ function getHomePluginRoot(scope) {
20
+ if (scope === 'workspace') return path.join(process.cwd(), '.a5c', 'plugins', PLUGIN_NAME);
21
+ return path.join(path.join(getHarnessHome(), 'plugins'), PLUGIN_NAME);
22
+ }
23
+
24
+ function getHomeMarketplacePath() {
25
+ return path.join(getHarnessHome(), 'plugins', 'marketplace.json');
26
+ }
27
+
28
+ function writeFileIfChanged(filePath, contents) {
29
+ try {
30
+ const existing = fs.readFileSync(filePath, 'utf8');
31
+ if (existing === contents) return false;
32
+ } catch {}
33
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
34
+ fs.writeFileSync(filePath, contents);
35
+ return true;
36
+ }
37
+
38
+ function copyRecursive(src, dest) {
39
+ fs.mkdirSync(dest, { recursive: true });
40
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
41
+ if (entry.name === 'node_modules' || entry.name === '.git') continue;
42
+ const s = path.join(src, entry.name);
43
+ const d = path.join(dest, entry.name);
44
+ if (entry.isDirectory()) {
45
+ copyRecursive(s, d);
46
+ } else {
47
+ fs.copyFileSync(s, d);
48
+ }
49
+ }
50
+ }
51
+
52
+ function copyPluginBundle(packageRoot, pluginRoot) {
53
+ const bundleEntries = fs.readdirSync(packageRoot).filter(
54
+ e => !['node_modules', '.git', 'test', 'dist'].includes(e)
55
+ );
56
+ fs.mkdirSync(pluginRoot, { recursive: true });
57
+ for (const entry of bundleEntries) {
58
+ const src = path.join(packageRoot, entry);
59
+ const dest = path.join(pluginRoot, entry);
60
+ const stat = fs.statSync(src);
61
+ if (stat.isDirectory()) {
62
+ copyRecursive(src, dest);
63
+ } else {
64
+ fs.copyFileSync(src, dest);
65
+ }
66
+ }
67
+ }
68
+
69
+ function readJson(filePath) {
70
+ try {
71
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+
77
+ function writeJson(filePath, value) {
78
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
79
+ fs.writeFileSync(filePath, JSON.stringify(value, null, 2) + '\n');
80
+ }
81
+
82
+ function ensureExecutable(filePath) {
83
+ try {
84
+ fs.chmodSync(filePath, 0o755);
85
+ } catch {}
86
+ }
87
+
88
+ function normalizeMarketplaceSourcePath(source, marketplacePath) {
89
+ if (typeof source === 'string') {
90
+ return path.relative(path.dirname(marketplacePath), source).replace(/\\/g, '/');
91
+ }
92
+ return source;
93
+ }
94
+
95
+ function ensureMarketplaceEntry(marketplacePath, pluginRoot) {
96
+ let marketplace = readJson(marketplacePath) || {
97
+ name: "a5c.ai",
98
+ plugins: [],
99
+ };
100
+ if (!Array.isArray(marketplace.plugins)) marketplace.plugins = [];
101
+ const idx = marketplace.plugins.findIndex(p => p.name === PLUGIN_NAME);
102
+ const relSource = './' + normalizeMarketplaceSourcePath(pluginRoot, marketplacePath);
103
+ const entry = {
104
+ name: PLUGIN_NAME,
105
+ source: relSource,
106
+ description: "Orchestrate complex, multi-step workflows with event-sourced state management, hook-based extensibility, and human-in-the-loop approval",
107
+ version: "5.0.0",
108
+ author: { name: "a5c.ai" },
109
+ };
110
+ if (idx >= 0) marketplace.plugins[idx] = entry;
111
+ else marketplace.plugins.push(entry);
112
+ writeJson(marketplacePath, marketplace);
113
+ }
114
+
115
+ function removeMarketplaceEntry(marketplacePath) {
116
+ const marketplace = readJson(marketplacePath);
117
+ if (!marketplace || !Array.isArray(marketplace.plugins)) return;
118
+ marketplace.plugins = marketplace.plugins.filter(p => p.name !== PLUGIN_NAME);
119
+ writeJson(marketplacePath, marketplace);
120
+ }
121
+
122
+ function warnWindowsHooks() {
123
+ if (process.platform === 'win32') {
124
+ console.warn('[' + PLUGIN_NAME + '] Windows detected — shell hooks (.sh) require Git Bash or WSL.');
125
+ }
126
+ }
127
+
128
+ function runPostInstall(pluginRoot) {
129
+ const postInstall = path.join(pluginRoot, 'scripts', 'post-install.js');
130
+ if (fs.existsSync(postInstall)) {
131
+ spawnSync(process.execPath, [postInstall], {
132
+ cwd: pluginRoot, stdio: 'inherit',
133
+ env: { ...process.env, PLUGIN_ROOT: pluginRoot },
134
+ });
135
+ }
136
+ }
137
+
138
+ function getGlobalStateDir() {
139
+ return process.env.BABYSITTER_GLOBAL_STATE_DIR || path.join(getUserHome(), '.a5c');
140
+ }
141
+
142
+ function resolveCliCommand(packageRoot) {
143
+ try {
144
+ const result = spawnSync('babysitter', ['--version'], { stdio: 'pipe', timeout: 10000 });
145
+ if (result.status === 0) return 'babysitter';
146
+ } catch {}
147
+ const versionsPath = path.join(packageRoot, 'versions.json');
148
+ const versions = readJson(versionsPath) || {};
149
+ const ver = versions.sdkVersion || 'latest';
150
+ return `npx -y @a5c-ai/babysitter-sdk@${ver}`;
151
+ }
152
+
153
+ function runCli(packageRoot, cliArgs, options = {}) {
154
+ const cmd = resolveCliCommand(packageRoot);
155
+ const parts = cmd.split(' ');
156
+ const result = spawnSync(parts[0], [...parts.slice(1), ...cliArgs], {
157
+ stdio: options.stdio || 'inherit',
158
+ timeout: options.timeout || 120000,
159
+ cwd: options.cwd || process.cwd(),
160
+ env: { ...process.env, ...options.env },
161
+ });
162
+ return result;
163
+ }
164
+
165
+ function ensureGlobalProcessLibrary(packageRoot) {
166
+ const stateDir = getGlobalStateDir();
167
+ const activeFile = path.join(stateDir, 'active', 'process-library.json');
168
+ let active = readJson(activeFile);
169
+ if (active && active.binding && active.binding.dir) {
170
+ return active;
171
+ }
172
+ const defaultSpec = readJson(path.join(stateDir, 'process-library-defaults.json'));
173
+ const cloneDir = defaultSpec && defaultSpec.cloneDir
174
+ ? defaultSpec.cloneDir
175
+ : path.join(stateDir, 'process-library', PLUGIN_NAME + '-repo');
176
+ runCli(packageRoot, [
177
+ 'process-library:clone',
178
+ '--dir', cloneDir,
179
+ '--state-dir', stateDir,
180
+ '--json',
181
+ ], { stdio: 'pipe' });
182
+ runCli(packageRoot, [
183
+ 'process-library:use',
184
+ '--dir', cloneDir,
185
+ '--state-dir', stateDir,
186
+ '--json',
187
+ ], { stdio: 'pipe' });
188
+ active = readJson(activeFile);
189
+ return {
190
+ binding: active && active.binding ? active.binding : { dir: cloneDir },
191
+ defaultSpec: defaultSpec || { cloneDir },
192
+ stateFile: activeFile,
193
+ };
194
+ }
195
+
196
+
197
+ // ---------------------------------------------------------------------------
198
+ // Opencode harness-specific surface
199
+ //
200
+ // This file is appended by the unified plugin compiler after the generic
201
+ // install-shared base and the SDK surface. It may reference any identifier
202
+ // already declared in those layers (PLUGIN_NAME, getUserHome, readJson,
203
+ // writeJson, writeFileIfChanged, getGlobalStateDir, resolveCliCommand,
204
+ // runCli, ensureGlobalProcessLibrary, etc.) and may re-declare functions
205
+ // to override the base implementation.
206
+ // ---------------------------------------------------------------------------
207
+
208
+ const PLUGIN_BUNDLE_ENTRIES = [
209
+ 'plugin.json',
210
+ 'versions.json',
211
+ 'hooks',
212
+ 'skills',
213
+ 'commands',
214
+ ];
215
+
216
+ const HOOK_SCRIPT_NAMES = [
217
+ 'session-created.js',
218
+ 'session-idle.js',
219
+ 'shell-env.js',
220
+ 'tool-execute-before.js',
221
+ 'tool-execute-after.js',
222
+ ];
223
+
224
+ const DEFAULT_MARKETPLACE = {
225
+ name: 'local-plugins',
226
+ interface: {
227
+ displayName: 'Local Plugins',
228
+ },
229
+ plugins: [],
230
+ };
231
+
232
+ // ---------------------------------------------------------------------------
233
+ // Path helpers (override base)
234
+ // ---------------------------------------------------------------------------
235
+
236
+ /**
237
+ * Resolve the OpenCode config root.
238
+ * OpenCode uses `.opencode/` in the workspace directory by default.
239
+ * Respect OPENCODE_HOME env var if set.
240
+ */
241
+ function getOpenCodeHome(workspace) {
242
+ if (process.env.OPENCODE_HOME) return path.resolve(process.env.OPENCODE_HOME);
243
+ return path.join(workspace || process.cwd(), '.opencode');
244
+ }
245
+
246
+ function getHomePluginRoot(workspace) {
247
+ if (process.env.BABYSITTER_OPENCODE_PLUGIN_DIR) {
248
+ return path.resolve(process.env.BABYSITTER_OPENCODE_PLUGIN_DIR, PLUGIN_NAME);
249
+ }
250
+ return path.join(getOpenCodeHome(workspace), 'plugins', PLUGIN_NAME);
251
+ }
252
+
253
+ function getHomeMarketplacePath(workspace) {
254
+ if (process.env.BABYSITTER_OPENCODE_MARKETPLACE_PATH) {
255
+ return path.resolve(process.env.BABYSITTER_OPENCODE_MARKETPLACE_PATH);
256
+ }
257
+ return path.join(getUserHome(), '.agents', 'plugins', 'marketplace.json');
258
+ }
259
+
260
+ // ---------------------------------------------------------------------------
261
+ // File utilities (override base — adds BOM stripping for SKILL.md)
262
+ // ---------------------------------------------------------------------------
263
+
264
+ function copyRecursive(src, dest) {
265
+ const stat = fs.statSync(src);
266
+ if (stat.isDirectory()) {
267
+ fs.mkdirSync(dest, { recursive: true });
268
+ for (const entry of fs.readdirSync(src)) {
269
+ if (['node_modules', '.git', 'test', '.a5c'].includes(entry)) continue;
270
+ copyRecursive(path.join(src, entry), path.join(dest, entry));
271
+ }
272
+ return;
273
+ }
274
+
275
+ // Strip BOM from SKILL.md files
276
+ if (path.basename(src) === 'SKILL.md') {
277
+ const file = fs.readFileSync(src);
278
+ const hasBom = file.length >= 3 && file[0] === 0xef && file[1] === 0xbb && file[2] === 0xbf;
279
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
280
+ fs.writeFileSync(dest, hasBom ? file.subarray(3) : file);
281
+ return;
282
+ }
283
+
284
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
285
+ fs.copyFileSync(src, dest);
286
+ }
287
+
288
+ // ---------------------------------------------------------------------------
289
+ // Plugin bundle (override base — uses PLUGIN_BUNDLE_ENTRIES allowlist)
290
+ // ---------------------------------------------------------------------------
291
+
292
+ function copyPluginBundle(packageRoot, pluginRoot) {
293
+ if (path.resolve(packageRoot) === path.resolve(pluginRoot)) {
294
+ return;
295
+ }
296
+ fs.rmSync(pluginRoot, { recursive: true, force: true });
297
+ fs.mkdirSync(pluginRoot, { recursive: true });
298
+ for (const entry of PLUGIN_BUNDLE_ENTRIES) {
299
+ const src = path.join(packageRoot, entry);
300
+ if (fs.existsSync(src)) {
301
+ copyRecursive(src, path.join(pluginRoot, entry));
302
+ }
303
+ }
304
+ }
305
+
306
+ // ---------------------------------------------------------------------------
307
+ // OpenCode index.js entry point generation
308
+ // ---------------------------------------------------------------------------
309
+
310
+ function writeIndexJs(pluginRoot) {
311
+ const indexContent = `#!/usr/bin/env node
312
+ /**
313
+ * Babysitter plugin entry point for OpenCode.
314
+ *
315
+ * OpenCode discovers plugins by looking for JS/TS modules in
316
+ * .opencode/plugins/. This file registers the babysitter hooks
317
+ * with the OpenCode plugin system.
318
+ */
319
+
320
+ "use strict";
321
+
322
+ const path = require("path");
323
+
324
+ const PLUGIN_DIR = __dirname;
325
+
326
+ module.exports = {
327
+ name: "babysitter",
328
+ version: require(path.join(PLUGIN_DIR, "plugin.json")).version,
329
+
330
+ hooks: {
331
+ "session.created": require(path.join(PLUGIN_DIR, "hooks", "session-created.js")),
332
+ "session.idle": require(path.join(PLUGIN_DIR, "hooks", "session-idle.js")),
333
+ "shell.env": require(path.join(PLUGIN_DIR, "hooks", "shell-env.js")),
334
+ "tool.execute.before": require(path.join(PLUGIN_DIR, "hooks", "tool-execute-before.js")),
335
+ "tool.execute.after": require(path.join(PLUGIN_DIR, "hooks", "tool-execute-after.js")),
336
+ },
337
+ };
338
+ `;
339
+ fs.writeFileSync(path.join(pluginRoot, 'index.js'), indexContent, 'utf8');
340
+ }
341
+
342
+ // ---------------------------------------------------------------------------
343
+ // OpenCode hooks.json config registration
344
+ // ---------------------------------------------------------------------------
345
+
346
+ function mergeHooksConfig(packageRoot, openCodeHome) {
347
+ const hooksJsonPath = path.join(packageRoot, 'hooks', 'hooks.json');
348
+ if (!fs.existsSync(hooksJsonPath)) return;
349
+ const managedConfig = readJson(hooksJsonPath);
350
+ const managedHooks = managedConfig.hooks || {};
351
+ const hooksConfigPath = path.join(openCodeHome, 'hooks.json');
352
+ const existing = fs.existsSync(hooksConfigPath)
353
+ ? readJson(hooksConfigPath)
354
+ : { version: 1, hooks: {} };
355
+ existing.version = existing.version || 1;
356
+ if (!existing.hooks || typeof existing.hooks !== 'object') {
357
+ existing.hooks = {};
358
+ }
359
+
360
+ for (const [eventName, entries] of Object.entries(managedHooks)) {
361
+ const existingEntries = Array.isArray(existing.hooks[eventName]) ? existing.hooks[eventName] : [];
362
+ const filteredEntries = existingEntries.filter((entry) => {
363
+ const script = String(entry.script || entry.command || entry.bash || '');
364
+ return !HOOK_SCRIPT_NAMES.some((name) => script.includes(name));
365
+ });
366
+ const installedEntries = entries.map((entry) => {
367
+ const relativeScript = String(entry.script || '').trim();
368
+ if (relativeScript) {
369
+ const normalizedScript = relativeScript.replace(/\\/g, '/').replace(/^\.\//, '');
370
+ return {
371
+ ...entry,
372
+ script: `npx -y -p @a5c-ai/hooks-mux-cli -c "a5c-hooks-mux invoke --adapter opencode --handler 'node ./plugins/${PLUGIN_NAME}/${normalizedScript}' --json"`,
373
+ };
374
+ }
375
+ if (entry.command) {
376
+ return {
377
+ ...entry,
378
+ script: entry.command,
379
+ };
380
+ }
381
+ return entry;
382
+ });
383
+ existing.hooks[eventName] = [...filteredEntries, ...installedEntries];
384
+ }
385
+
386
+ writeJson(hooksConfigPath, existing);
387
+ }
388
+
389
+ function removeManagedHooks(openCodeHome) {
390
+ const hooksConfigPath = path.join(openCodeHome, 'hooks.json');
391
+ if (!fs.existsSync(hooksConfigPath)) return;
392
+
393
+ let hooksConfig;
394
+ try {
395
+ hooksConfig = readJson(hooksConfigPath);
396
+ } catch {
397
+ return;
398
+ }
399
+ if (!hooksConfig.hooks || typeof hooksConfig.hooks !== 'object') return;
400
+
401
+ for (const eventName of Object.keys(hooksConfig.hooks)) {
402
+ const eventHooks = Array.isArray(hooksConfig.hooks[eventName]) ? hooksConfig.hooks[eventName] : [];
403
+ const filtered = eventHooks.filter((entry) => {
404
+ const script = String(entry.script || entry.command || entry.bash || '');
405
+ return !HOOK_SCRIPT_NAMES.some((name) => script.includes(name));
406
+ });
407
+ if (filtered.length > 0) {
408
+ hooksConfig.hooks[eventName] = filtered;
409
+ } else {
410
+ delete hooksConfig.hooks[eventName];
411
+ }
412
+ }
413
+ if (Object.keys(hooksConfig.hooks).length === 0) {
414
+ fs.rmSync(hooksConfigPath, { force: true });
415
+ } else {
416
+ writeJson(hooksConfigPath, hooksConfig);
417
+ }
418
+ }
419
+
420
+ // ---------------------------------------------------------------------------
421
+ // Marketplace (override base — opencode format with normalizeMarketplaceName)
422
+ // ---------------------------------------------------------------------------
423
+
424
+ function normalizeMarketplaceName(name) {
425
+ const raw = String(name || '').trim();
426
+ const sanitized = raw
427
+ .replace(/[^A-Za-z0-9_-]+/g, '-')
428
+ .replace(/^-+|-+$/g, '');
429
+ return sanitized || DEFAULT_MARKETPLACE.name;
430
+ }
431
+
432
+ function normalizeMarketplaceSourcePath(marketplacePath, pluginSourcePath) {
433
+ let next = pluginSourcePath;
434
+ if (path.isAbsolute(next)) {
435
+ next = path.relative(path.dirname(marketplacePath), next);
436
+ }
437
+ next = String(next || '').replace(/\\/g, '/');
438
+ if (!next.startsWith('./') && !next.startsWith('../')) {
439
+ next = `./${next}`;
440
+ }
441
+ return next;
442
+ }
443
+
444
+ function ensureMarketplaceEntry(marketplacePath, pluginSourcePath) {
445
+ const marketplace = fs.existsSync(marketplacePath)
446
+ ? readJson(marketplacePath)
447
+ : { ...DEFAULT_MARKETPLACE, plugins: [] };
448
+ marketplace.name = normalizeMarketplaceName(marketplace.name);
449
+ marketplace.interface = marketplace.interface || {};
450
+ marketplace.interface.displayName =
451
+ marketplace.interface.displayName || DEFAULT_MARKETPLACE.interface.displayName;
452
+ const nextEntry = {
453
+ name: PLUGIN_NAME,
454
+ source: {
455
+ source: 'local',
456
+ path: normalizeMarketplaceSourcePath(marketplacePath, pluginSourcePath),
457
+ },
458
+ policy: {
459
+ installation: 'AVAILABLE',
460
+ authentication: 'ON_INSTALL',
461
+ },
462
+ category: 'Coding',
463
+ };
464
+ const existingIndex = Array.isArray(marketplace.plugins)
465
+ ? marketplace.plugins.findIndex((entry) => entry && entry.name === PLUGIN_NAME)
466
+ : -1;
467
+ if (!Array.isArray(marketplace.plugins)) {
468
+ marketplace.plugins = [nextEntry];
469
+ } else if (existingIndex >= 0) {
470
+ marketplace.plugins[existingIndex] = nextEntry;
471
+ } else {
472
+ marketplace.plugins.push(nextEntry);
473
+ }
474
+ writeJson(marketplacePath, marketplace);
475
+ return nextEntry;
476
+ }
477
+
478
+ function removeMarketplaceEntry(marketplacePath) {
479
+ if (!fs.existsSync(marketplacePath)) return;
480
+ const marketplace = readJson(marketplacePath);
481
+ if (!Array.isArray(marketplace.plugins)) return;
482
+ marketplace.plugins = marketplace.plugins.filter((entry) => entry && entry.name !== PLUGIN_NAME);
483
+ writeJson(marketplacePath, marketplace);
484
+ }
485
+
486
+ // ---------------------------------------------------------------------------
487
+ // Accomplish AI detection and paths
488
+ // ---------------------------------------------------------------------------
489
+
490
+ /**
491
+ * Returns the Accomplish user data directory for the current platform.
492
+ * If OPENCODE_CONFIG_DIR is set, returns its parent (the Accomplish data dir).
493
+ * Otherwise falls back to platform-specific defaults.
494
+ */
495
+ function getAccomplishDataDir() {
496
+ if (process.env.OPENCODE_CONFIG_DIR) {
497
+ return path.resolve(process.env.OPENCODE_CONFIG_DIR, '..');
498
+ }
499
+ const home = getUserHome();
500
+ switch (process.platform) {
501
+ case 'darwin':
502
+ return path.join(home, 'Library', 'Application Support', 'Accomplish');
503
+ case 'win32': {
504
+ const appData = process.env.APPDATA || process.env.LOCALAPPDATA;
505
+ return appData
506
+ ? path.join(appData, 'Accomplish')
507
+ : path.join(home, 'AppData', 'Roaming', 'Accomplish');
508
+ }
509
+ default:
510
+ return path.join(home, '.config', 'Accomplish');
511
+ }
512
+ }
513
+
514
+ /**
515
+ * Returns true if Accomplish AI appears to be installed or is running.
516
+ * Checks for:
517
+ * - ACCOMPLISH_TASK_ID env var (running inside Accomplish)
518
+ * - Accomplish data dir with an opencode/ subdirectory on disk
519
+ */
520
+ function isAccomplishInstalled() {
521
+ if (process.env.ACCOMPLISH_TASK_ID) return true;
522
+ try {
523
+ const dataDir = getAccomplishDataDir();
524
+ const openCodeDir = path.join(dataDir, 'opencode');
525
+ return fs.existsSync(openCodeDir);
526
+ } catch {
527
+ return false;
528
+ }
529
+ }
530
+
531
+ /**
532
+ * Returns the OpenCode home directory inside the Accomplish data dir.
533
+ */
534
+ function getAccomplishOpenCodeHome() {
535
+ return path.join(getAccomplishDataDir(), 'opencode');
536
+ }
537
+
538
+ /**
539
+ * Install the babysitter plugin into Accomplish's OpenCode directory.
540
+ * Mirrors the standard OpenCode install: copies bundle, writes index.js,
541
+ * installs skills and hooks.
542
+ */
543
+ function installAccomplishSurface(packageRoot, accomplishOpenCodeHome) {
544
+ const pluginRoot = path.join(accomplishOpenCodeHome, 'plugins', PLUGIN_NAME);
545
+
546
+ // Copy plugin bundle
547
+ copyPluginBundle(packageRoot, pluginRoot);
548
+
549
+ // Create index.js entry point
550
+ writeIndexJs(pluginRoot);
551
+
552
+ // Install skills and hooks config
553
+ installOpenCodeSurface(packageRoot, accomplishOpenCodeHome);
554
+ }
555
+
556
+ // ---------------------------------------------------------------------------
557
+ // OpenCode surface installation
558
+ // ---------------------------------------------------------------------------
559
+
560
+ function installOpenCodeSurface(packageRoot, openCodeHome) {
561
+ // Copy skills into .opencode/skills/
562
+ const sourceSkills = path.join(packageRoot, 'skills');
563
+ if (fs.existsSync(sourceSkills)) {
564
+ const targetSkills = path.join(openCodeHome, 'skills');
565
+ fs.mkdirSync(targetSkills, { recursive: true });
566
+ for (const entry of fs.readdirSync(sourceSkills, { withFileTypes: true })) {
567
+ if (!entry.isDirectory()) continue;
568
+ copyRecursive(
569
+ path.join(sourceSkills, entry.name),
570
+ path.join(targetSkills, entry.name),
571
+ );
572
+ }
573
+ }
574
+
575
+ // Merge hooks config
576
+ mergeHooksConfig(packageRoot, openCodeHome);
577
+ }
578
+
579
+
580
+ module.exports = {
581
+ PLUGIN_NAME,
582
+ PLUGIN_CATEGORY,
583
+ getUserHome,
584
+ getHarnessHome,
585
+ writeFileIfChanged,
586
+ readJson,
587
+ writeJson,
588
+ ensureExecutable,
589
+ warnWindowsHooks,
590
+ runPostInstall,
591
+ getGlobalStateDir,
592
+ resolveCliCommand,
593
+ runCli,
594
+ ensureGlobalProcessLibrary,
595
+ PLUGIN_BUNDLE_ENTRIES,
596
+ copyRecursive,
597
+ copyPluginBundle,
598
+ DEFAULT_MARKETPLACE,
599
+ normalizeMarketplaceSourcePath,
600
+ normalizeMarketplaceName,
601
+ ensureMarketplaceEntry,
602
+ removeMarketplaceEntry,
603
+ HOOK_SCRIPT_NAMES,
604
+ getOpenCodeHome,
605
+ getHomePluginRoot,
606
+ getHomeMarketplacePath,
607
+ writeIndexJs,
608
+ mergeHooksConfig,
609
+ removeManagedHooks,
610
+ getAccomplishDataDir,
611
+ isAccomplishInstalled,
612
+ getAccomplishOpenCodeHome,
613
+ installAccomplishSurface,
614
+ installOpenCodeSurface,
615
+ };