@a5c-ai/babysitter-github 5.0.1-staging.936d11e1 → 5.0.1-staging.ae07dd8d

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 (38) hide show
  1. package/bin/cli.js +14 -26
  2. package/bin/install-shared.js +449 -211
  3. package/bin/install.js +49 -89
  4. package/bin/uninstall.js +30 -60
  5. package/commands/doctor.md +5 -5
  6. package/commands/help.md +245 -244
  7. package/commands/observe.md +12 -12
  8. package/hooks/babysitter-proxied-session-end.ps1 +10 -114
  9. package/hooks/babysitter-proxied-session-end.sh +2 -111
  10. package/hooks/babysitter-proxied-session-start.ps1 +10 -187
  11. package/hooks/babysitter-proxied-session-start.sh +6 -168
  12. package/hooks/babysitter-proxied-user-prompt-submitted.ps1 +10 -90
  13. package/hooks/babysitter-proxied-user-prompt-submitted.sh +2 -86
  14. package/hooks.json +10 -10
  15. package/package.json +18 -20
  16. package/plugin.json +7 -6
  17. package/scripts/team-install.js +14 -84
  18. package/skills/cleanup/SKILL.md +21 -0
  19. package/skills/contrib/SKILL.md +34 -0
  20. package/skills/doctor/SKILL.md +5 -5
  21. package/skills/forever/SKILL.md +8 -0
  22. package/skills/help/SKILL.md +3 -2
  23. package/skills/observe/SKILL.md +1 -1
  24. package/skills/plugins/SKILL.md +257 -0
  25. package/skills/project-install/SKILL.md +18 -0
  26. package/skills/resume/SKILL.md +1 -1
  27. package/skills/retrospect/SKILL.md +48 -48
  28. package/skills/user-install/SKILL.md +3 -3
  29. package/skills/yolo/SKILL.md +8 -0
  30. package/versions.json +1 -1
  31. package/.github/plugin.json +0 -25
  32. package/hooks/proxied-hooks.json +0 -29
  33. package/hooks/session-end.ps1 +0 -69
  34. package/hooks/session-end.sh +0 -54
  35. package/hooks/session-start.ps1 +0 -111
  36. package/hooks/session-start.sh +0 -101
  37. package/hooks/user-prompt-submitted.ps1 +0 -52
  38. package/hooks/user-prompt-submitted.sh +0 -31
@@ -5,20 +5,213 @@ const os = require('os');
5
5
  const path = require('path');
6
6
  const { spawnSync } = require('child_process');
7
7
 
8
- const PLUGIN_NAME = 'babysitter';
8
+ const PLUGIN_NAME = "babysitter";
9
9
  const PLUGIN_CATEGORY = 'Coding';
10
+
11
+ function getUserHome() {
12
+ return os.homedir();
13
+ }
14
+
15
+ function getHarnessHome() {
16
+ return path.join(os.homedir(), '.copilot');
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
+ // Per-harness surface for github-copilot.
198
+ // Contains only harness-specific constants, functions unique to GitHub Copilot,
199
+ // and overrides of base/SDK-surface functions.
200
+ // Generic infrastructure (file utils, marketplace base, SDK CLI resolution) is
201
+ // provided by the compiler base and SDK surface layers.
202
+
10
203
  const LEGACY_HOOK_SCRIPT_NAMES = [
11
204
  'session-start.sh',
12
205
  'stop-hook.sh',
13
206
  'user-prompt-submit.sh',
14
207
  ];
15
208
  const HOOK_SCRIPT_NAMES = [
16
- 'session-start.sh',
17
- 'session-start.ps1',
18
- 'session-end.sh',
19
- 'session-end.ps1',
20
- 'user-prompt-submitted.sh',
21
- 'user-prompt-submitted.ps1',
209
+ 'babysitter-proxied-session-start.sh',
210
+ 'babysitter-proxied-session-start.ps1',
211
+ 'babysitter-proxied-session-end.sh',
212
+ 'babysitter-proxied-session-end.ps1',
213
+ 'babysitter-proxied-user-prompt-submitted.sh',
214
+ 'babysitter-proxied-user-prompt-submitted.ps1',
22
215
  ];
23
216
  const DEFAULT_MARKETPLACE = {
24
217
  name: 'local-plugins',
@@ -52,115 +245,13 @@ const CLOUD_AGENT_BUNDLE_ENTRIES = [
52
245
  const MANAGED_BLOCK_START = '<!-- BEGIN BABYSITTER GITHUB CLOUD AGENT -->';
53
246
  const MANAGED_BLOCK_END = '<!-- END BABYSITTER GITHUB CLOUD AGENT -->';
54
247
 
248
+ // --- Harness-specific functions ---
249
+
55
250
  function getCopilotHome() {
56
251
  if (process.env.COPILOT_HOME) return path.resolve(process.env.COPILOT_HOME);
57
252
  return path.join(os.homedir(), '.copilot');
58
253
  }
59
254
 
60
- function getUserHome() {
61
- if (process.env.USERPROFILE) return path.resolve(process.env.USERPROFILE);
62
- if (process.env.HOME) return path.resolve(process.env.HOME);
63
- return os.homedir();
64
- }
65
-
66
- function getGlobalStateDir() {
67
- if (process.env.BABYSITTER_GLOBAL_STATE_DIR) {
68
- return path.resolve(process.env.BABYSITTER_GLOBAL_STATE_DIR);
69
- }
70
- return path.join(getUserHome(), '.a5c');
71
- }
72
-
73
- function getHomePluginRoot() {
74
- if (process.env.BABYSITTER_GITHUB_PLUGIN_DIR) {
75
- return path.resolve(process.env.BABYSITTER_GITHUB_PLUGIN_DIR, PLUGIN_NAME);
76
- }
77
- return path.join(getCopilotHome(), 'plugins', PLUGIN_NAME);
78
- }
79
-
80
- function getHomeMarketplacePath() {
81
- if (process.env.BABYSITTER_GITHUB_MARKETPLACE_PATH) {
82
- return path.resolve(process.env.BABYSITTER_GITHUB_MARKETPLACE_PATH);
83
- }
84
- return path.join(getUserHome(), '.agents', 'plugins', 'marketplace.json');
85
- }
86
-
87
- function writeFileIfChanged(filePath, contents) {
88
- if (fs.existsSync(filePath)) {
89
- const current = fs.readFileSync(filePath, 'utf8');
90
- if (current === contents) {
91
- return false;
92
- }
93
- }
94
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
95
- fs.writeFileSync(filePath, contents, 'utf8');
96
- return true;
97
- }
98
-
99
- function copyRecursive(src, dest) {
100
- const stat = fs.statSync(src);
101
- if (stat.isDirectory()) {
102
- fs.mkdirSync(dest, { recursive: true });
103
- for (const entry of fs.readdirSync(src)) {
104
- if (['node_modules', '.git', 'test', '.a5c'].includes(entry)) continue;
105
- copyRecursive(path.join(src, entry), path.join(dest, entry));
106
- }
107
- return;
108
- }
109
-
110
- if (path.basename(src) === 'SKILL.md') {
111
- const file = fs.readFileSync(src);
112
- const hasBom = file.length >= 3 && file[0] === 0xef && file[1] === 0xbb && file[2] === 0xbf;
113
- fs.mkdirSync(path.dirname(dest), { recursive: true });
114
- fs.writeFileSync(dest, hasBom ? file.subarray(3) : file);
115
- return;
116
- }
117
-
118
- fs.mkdirSync(path.dirname(dest), { recursive: true });
119
- fs.copyFileSync(src, dest);
120
- }
121
-
122
- function copyPluginBundle(packageRoot, pluginRoot) {
123
- if (path.resolve(packageRoot) === path.resolve(pluginRoot)) {
124
- return;
125
- }
126
- fs.rmSync(pluginRoot, { recursive: true, force: true });
127
- fs.mkdirSync(pluginRoot, { recursive: true });
128
- for (const entry of PLUGIN_BUNDLE_ENTRIES) {
129
- const src = path.join(packageRoot, entry);
130
- if (fs.existsSync(src)) {
131
- copyRecursive(src, path.join(pluginRoot, entry));
132
- }
133
- }
134
- }
135
-
136
- function ensureExecutable(filePath) {
137
- try {
138
- fs.chmodSync(filePath, 0o755);
139
- } catch {
140
- // Best-effort only. Windows and some filesystems may ignore mode changes.
141
- }
142
- }
143
-
144
- function normalizeMarketplaceSourcePath(marketplacePath, pluginSourcePath) {
145
- let next = pluginSourcePath;
146
- if (path.isAbsolute(next)) {
147
- next = path.relative(path.dirname(marketplacePath), next);
148
- }
149
- next = String(next || '').replace(/\\/g, '/');
150
- if (!next.startsWith('./') && !next.startsWith('../')) {
151
- next = `./${next}`;
152
- }
153
- return next;
154
- }
155
-
156
- function readJson(filePath) {
157
- return JSON.parse(fs.readFileSync(filePath, 'utf8'));
158
- }
159
-
160
- function writeJson(filePath, value) {
161
- writeFileIfChanged(filePath, `${JSON.stringify(value, null, 2)}\n`);
162
- }
163
-
164
255
  function replaceManagedMarkdownBlock(existing, block) {
165
256
  const normalized = String(existing || '').replace(/\r\n/g, '\n');
166
257
  const managedBlock = `${MANAGED_BLOCK_START}\n${block.trim()}\n${MANAGED_BLOCK_END}`;
@@ -234,52 +325,6 @@ function rewriteCloudSkill(skillId, contents) {
234
325
  return next.replace(/\n{3,}/g, '\n\n').trimEnd() + '\n';
235
326
  }
236
327
 
237
- function ensureMarketplaceEntry(marketplacePath, pluginSourcePath) {
238
- const marketplace = fs.existsSync(marketplacePath)
239
- ? readJson(marketplacePath)
240
- : { ...DEFAULT_MARKETPLACE, plugins: [] };
241
- marketplace.name = marketplace.name || DEFAULT_MARKETPLACE.name;
242
- marketplace.interface = marketplace.interface || {};
243
- marketplace.interface.displayName =
244
- marketplace.interface.displayName || DEFAULT_MARKETPLACE.interface.displayName;
245
- const nextEntry = {
246
- name: PLUGIN_NAME,
247
- source: {
248
- source: 'local',
249
- path: normalizeMarketplaceSourcePath(marketplacePath, pluginSourcePath),
250
- },
251
- policy: {
252
- installation: 'AVAILABLE',
253
- authentication: 'ON_INSTALL',
254
- },
255
- category: PLUGIN_CATEGORY,
256
- };
257
- const existingIndex = Array.isArray(marketplace.plugins)
258
- ? marketplace.plugins.findIndex((entry) => entry && entry.name === PLUGIN_NAME)
259
- : -1;
260
- if (!Array.isArray(marketplace.plugins)) {
261
- marketplace.plugins = [nextEntry];
262
- } else if (existingIndex >= 0) {
263
- marketplace.plugins[existingIndex] = nextEntry;
264
- } else {
265
- marketplace.plugins.push(nextEntry);
266
- }
267
- writeJson(marketplacePath, marketplace);
268
- return nextEntry;
269
- }
270
-
271
- function removeMarketplaceEntry(marketplacePath) {
272
- if (!fs.existsSync(marketplacePath)) {
273
- return;
274
- }
275
- const marketplace = readJson(marketplacePath);
276
- if (!Array.isArray(marketplace.plugins)) {
277
- return;
278
- }
279
- marketplace.plugins = marketplace.plugins.filter((entry) => entry && entry.name !== PLUGIN_NAME);
280
- writeJson(marketplacePath, marketplace);
281
- }
282
-
283
328
  /**
284
329
  * Registers the plugin in ~/.copilot/config.json.
285
330
  */
@@ -409,6 +454,70 @@ function installManagedHooks(packageRoot, copilotHome) {
409
454
  mergeManagedHooksConfig(packageRoot, copilotHome);
410
455
  }
411
456
 
457
+ function filterManagedHookEntries(eventHooks) {
458
+ if (!Array.isArray(eventHooks)) {
459
+ return [];
460
+ }
461
+ const allScriptNames = [...LEGACY_HOOK_SCRIPT_NAMES, ...HOOK_SCRIPT_NAMES];
462
+
463
+ return eventHooks
464
+ .map((entry) => {
465
+ if (!entry || typeof entry !== 'object') {
466
+ return null;
467
+ }
468
+
469
+ if (Array.isArray(entry.hooks)) {
470
+ const keptHooks = entry.hooks.filter((hook) => {
471
+ const command = String(hook && hook.command || '');
472
+ return !allScriptNames.some((name) => command.includes(name));
473
+ });
474
+ return keptHooks.length > 0 ? { ...entry, hooks: keptHooks } : null;
475
+ }
476
+
477
+ const bash = String(entry.bash || entry.command || '');
478
+ const ps = String(entry.powershell || '');
479
+ return allScriptNames.some((name) => bash.includes(name) || ps.includes(name))
480
+ ? null
481
+ : entry;
482
+ })
483
+ .filter(Boolean);
484
+ }
485
+
486
+ function removeManagedHooks(copilotHome) {
487
+ for (const hookName of [...LEGACY_HOOK_SCRIPT_NAMES, ...HOOK_SCRIPT_NAMES]) {
488
+ fs.rmSync(path.join(copilotHome, 'hooks', hookName), { force: true });
489
+ }
490
+
491
+ const hooksConfigPath = path.join(copilotHome, 'hooks.json');
492
+ if (!fs.existsSync(hooksConfigPath)) {
493
+ return;
494
+ }
495
+ let hooksConfig;
496
+ try {
497
+ hooksConfig = readJson(hooksConfigPath);
498
+ } catch {
499
+ return;
500
+ }
501
+ if (!hooksConfig.hooks || typeof hooksConfig.hooks !== 'object') {
502
+ return;
503
+ }
504
+
505
+ for (const eventName of ['SessionStart', 'UserPromptSubmit', 'Stop', 'sessionStart', 'sessionEnd', 'userPromptSubmitted']) {
506
+ const filteredEntries = filterManagedHookEntries(hooksConfig.hooks[eventName]);
507
+ if (filteredEntries.length > 0) {
508
+ hooksConfig.hooks[eventName] = filteredEntries;
509
+ } else {
510
+ delete hooksConfig.hooks[eventName];
511
+ }
512
+ }
513
+
514
+ if (Object.keys(hooksConfig.hooks).length === 0) {
515
+ fs.rmSync(hooksConfigPath, { force: true });
516
+ } else {
517
+ writeJson(hooksConfigPath, hooksConfig);
518
+ }
519
+ }
520
+
412
521
  function removeLegacyHooks(copilotHome) {
413
522
  for (const hookName of LEGACY_HOOK_SCRIPT_NAMES) {
414
523
  fs.rmSync(path.join(copilotHome, 'hooks', hookName), { force: true });
@@ -453,7 +562,7 @@ function removeLegacyHooks(copilotHome) {
453
562
  }
454
563
 
455
564
  function installCopilotSurface(packageRoot, copilotHome) {
456
- removeLegacyHooks(copilotHome);
565
+ removeManagedHooks(copilotHome);
457
566
  installManagedSkills(packageRoot, copilotHome);
458
567
  installManagedHooks(packageRoot, copilotHome);
459
568
  }
@@ -619,60 +728,82 @@ function installCloudAgentSurface(packageRoot, workspaceRoot) {
619
728
  };
620
729
  }
621
730
 
622
- function resolveBabysitterCommand(packageRoot) {
623
- if (process.env.BABYSITTER_SDK_CLI) {
624
- return {
625
- command: process.execPath,
626
- argsPrefix: [path.resolve(process.env.BABYSITTER_SDK_CLI)],
627
- };
731
+ // --- Overrides of base functions ---
732
+
733
+ function writeJson(filePath, value) {
734
+ writeFileIfChanged(filePath, `${JSON.stringify(value, null, 2)}\n`);
735
+ }
736
+
737
+ function normalizeMarketplaceSourcePath(marketplacePath, pluginSourcePath) {
738
+ let next = pluginSourcePath;
739
+ if (path.isAbsolute(next)) {
740
+ next = path.relative(path.dirname(marketplacePath), next);
628
741
  }
629
- try {
630
- return {
631
- command: process.execPath,
632
- argsPrefix: [
633
- require.resolve('@a5c-ai/babysitter-sdk/dist/cli/main.js', {
634
- paths: [packageRoot],
635
- }),
636
- ],
637
- };
638
- } catch {
639
- return {
640
- command: 'babysitter',
641
- argsPrefix: [],
642
- };
742
+ next = String(next || '').replace(/\\/g, '/');
743
+ if (!next.startsWith('./') && !next.startsWith('../')) {
744
+ next = `./${next}`;
643
745
  }
746
+ return next;
644
747
  }
645
748
 
646
- function runBabysitterCli(packageRoot, cliArgs, options = {}) {
647
- const resolved = resolveBabysitterCommand(packageRoot);
648
- const result = spawnSync(resolved.command, [...resolved.argsPrefix, ...cliArgs], {
649
- cwd: options.cwd || process.cwd(),
650
- stdio: ['ignore', 'pipe', 'pipe'],
651
- encoding: 'utf8',
652
- env: {
653
- ...process.env,
654
- ...(options.env || {}),
749
+ function getHomePluginRoot() {
750
+ if (process.env.BABYSITTER_GITHUB_PLUGIN_DIR) {
751
+ return path.resolve(process.env.BABYSITTER_GITHUB_PLUGIN_DIR, PLUGIN_NAME);
752
+ }
753
+ return path.join(getCopilotHome(), 'plugins', PLUGIN_NAME);
754
+ }
755
+
756
+ function getHomeMarketplacePath() {
757
+ if (process.env.BABYSITTER_GITHUB_MARKETPLACE_PATH) {
758
+ return path.resolve(process.env.BABYSITTER_GITHUB_MARKETPLACE_PATH);
759
+ }
760
+ return path.join(getUserHome(), '.agents', 'plugins', 'marketplace.json');
761
+ }
762
+
763
+ function ensureMarketplaceEntry(marketplacePath, pluginSourcePath) {
764
+ const marketplace = fs.existsSync(marketplacePath)
765
+ ? readJson(marketplacePath)
766
+ : { ...DEFAULT_MARKETPLACE, plugins: [] };
767
+ marketplace.name = marketplace.name || DEFAULT_MARKETPLACE.name;
768
+ marketplace.interface = marketplace.interface || {};
769
+ marketplace.interface.displayName =
770
+ marketplace.interface.displayName || DEFAULT_MARKETPLACE.interface.displayName;
771
+ const nextEntry = {
772
+ name: PLUGIN_NAME,
773
+ source: {
774
+ source: 'local',
775
+ path: normalizeMarketplaceSourcePath(marketplacePath, pluginSourcePath),
655
776
  },
656
- });
657
- if (result.status !== 0) {
658
- const stderr = (result.stderr || '').trim();
659
- const stdout = (result.stdout || '').trim();
660
- throw new Error(
661
- `babysitter ${cliArgs.join(' ')} failed` +
662
- (stderr ? `: ${stderr}` : stdout ? `: ${stdout}` : ''),
663
- );
777
+ policy: {
778
+ installation: 'AVAILABLE',
779
+ authentication: 'ON_INSTALL',
780
+ },
781
+ category: PLUGIN_CATEGORY,
782
+ };
783
+ const existingIndex = Array.isArray(marketplace.plugins)
784
+ ? marketplace.plugins.findIndex((entry) => entry && entry.name === PLUGIN_NAME)
785
+ : -1;
786
+ if (!Array.isArray(marketplace.plugins)) {
787
+ marketplace.plugins = [nextEntry];
788
+ } else if (existingIndex >= 0) {
789
+ marketplace.plugins[existingIndex] = nextEntry;
790
+ } else {
791
+ marketplace.plugins.push(nextEntry);
664
792
  }
665
- return result.stdout;
793
+ writeJson(marketplacePath, marketplace);
794
+ return nextEntry;
666
795
  }
667
796
 
668
- function ensureGlobalProcessLibrary(packageRoot) {
669
- return JSON.parse(
670
- runBabysitterCli(
671
- packageRoot,
672
- ['process-library:active', '--state-dir', getGlobalStateDir(), '--json'],
673
- { cwd: packageRoot },
674
- ),
675
- );
797
+ function removeMarketplaceEntry(marketplacePath) {
798
+ if (!fs.existsSync(marketplacePath)) {
799
+ return;
800
+ }
801
+ const marketplace = readJson(marketplacePath);
802
+ if (!Array.isArray(marketplace.plugins)) {
803
+ return;
804
+ }
805
+ marketplace.plugins = marketplace.plugins.filter((entry) => entry && entry.name !== PLUGIN_NAME);
806
+ writeJson(marketplacePath, marketplace);
676
807
  }
677
808
 
678
809
  function warnWindowsHooks() {
@@ -683,19 +814,126 @@ function warnWindowsHooks() {
683
814
  console.warn(`[${PLUGIN_NAME}] Both bash (.sh) and PowerShell (.ps1) hook scripts are included.`);
684
815
  }
685
816
 
817
+ function copyPluginBundle(packageRoot, pluginRoot) {
818
+ if (path.resolve(packageRoot) === path.resolve(pluginRoot)) {
819
+ return;
820
+ }
821
+ fs.rmSync(pluginRoot, { recursive: true, force: true });
822
+ fs.mkdirSync(pluginRoot, { recursive: true });
823
+ for (const entry of PLUGIN_BUNDLE_ENTRIES) {
824
+ const src = path.join(packageRoot, entry);
825
+ if (fs.existsSync(src)) {
826
+ copyRecursive(src, path.join(pluginRoot, entry));
827
+ }
828
+ }
829
+ }
830
+
831
+ function copyRecursive(src, dest) {
832
+ const stat = fs.statSync(src);
833
+ if (stat.isDirectory()) {
834
+ fs.mkdirSync(dest, { recursive: true });
835
+ for (const entry of fs.readdirSync(src)) {
836
+ if (['node_modules', '.git', 'test', '.a5c'].includes(entry)) continue;
837
+ copyRecursive(path.join(src, entry), path.join(dest, entry));
838
+ }
839
+ return;
840
+ }
841
+
842
+ if (path.basename(src) === 'SKILL.md') {
843
+ const file = fs.readFileSync(src);
844
+ const hasBom = file.length >= 3 && file[0] === 0xef && file[1] === 0xbb && file[2] === 0xbf;
845
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
846
+ fs.writeFileSync(dest, hasBom ? file.subarray(3) : file);
847
+ return;
848
+ }
849
+
850
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
851
+ fs.copyFileSync(src, dest);
852
+ }
853
+
854
+ function harnessCliRoute(argv, packageRoot, runNodeScript) {
855
+ if (argv.includes('--cloud-agent')) {
856
+ const args = argv.filter(a => a !== '--cloud-agent');
857
+ args.push('--cloud-agent');
858
+ runNodeScript(path.join(packageRoot, 'bin', 'install.js'), args);
859
+ return true;
860
+ }
861
+ return false;
862
+ }
863
+
864
+ function harnessInstall(packageRoot, _pluginRoot) {
865
+ const argv = process.argv.slice(2);
866
+ if (!argv.includes('--cloud-agent')) return;
867
+ const workspaceIdx = argv.indexOf('--workspace');
868
+ const workspaceRoot = (workspaceIdx >= 0 && argv[workspaceIdx + 1])
869
+ ? path.resolve(argv[workspaceIdx + 1])
870
+ : process.cwd();
871
+ console.log(`[${PLUGIN_NAME}] Installing cloud-agent support into ${workspaceRoot}`);
872
+ const activeProcessLibrary = runCli(packageRoot, [
873
+ 'process-library:active',
874
+ '--json',
875
+ ], { stdio: 'pipe' });
876
+ if (activeProcessLibrary.status !== 0) {
877
+ ensureGlobalProcessLibrary(packageRoot);
878
+ }
879
+ installCloudAgentSurface(packageRoot, workspaceRoot);
880
+ console.log(`[${PLUGIN_NAME}] Cloud-agent installation complete!`);
881
+ process.exit(0);
882
+ }
883
+
884
+
686
885
  module.exports = {
687
- copyPluginBundle,
688
- deregisterCopilotPlugin,
886
+ PLUGIN_NAME,
887
+ PLUGIN_CATEGORY,
888
+ getUserHome,
889
+ getHarnessHome,
890
+ writeFileIfChanged,
891
+ readJson,
892
+ ensureExecutable,
893
+ runPostInstall,
894
+ getGlobalStateDir,
895
+ resolveCliCommand,
896
+ runCli,
689
897
  ensureGlobalProcessLibrary,
690
- ensureMarketplaceEntry,
898
+ LEGACY_HOOK_SCRIPT_NAMES,
899
+ HOOK_SCRIPT_NAMES,
900
+ DEFAULT_MARKETPLACE,
901
+ PLUGIN_BUNDLE_ENTRIES,
902
+ CLOUD_AGENT_BUNDLE_ENTRIES,
903
+ MANAGED_BLOCK_START,
904
+ MANAGED_BLOCK_END,
691
905
  getCopilotHome,
692
- getHomeMarketplacePath,
693
906
  getHomePluginRoot,
694
- installCopilotSurface,
695
- installCloudAgentSurface,
907
+ getHomeMarketplacePath,
908
+ writeJson,
909
+ replaceManagedMarkdownBlock,
910
+ writeManagedMarkdown,
911
+ readSdkVersion,
912
+ toLowerHyphenName,
913
+ rewriteCloudSkill,
696
914
  registerCopilotPlugin,
915
+ deregisterCopilotPlugin,
916
+ installManagedSkills,
917
+ mergeManagedHooksConfig,
918
+ installManagedHooks,
919
+ filterManagedHookEntries,
920
+ removeManagedHooks,
697
921
  removeLegacyHooks,
922
+ installCopilotSurface,
923
+ renderCloudAgentAgentsBlock,
924
+ renderCloudAgentCopilotInstructionsBlock,
925
+ renderCloudAgentSetupWorkflow,
926
+ installCloudAgentBundle,
927
+ installCloudAgentSkills,
928
+ installCloudAgentInstructions,
929
+ installCloudAgentSetupSteps,
930
+ installCloudAgentSurface,
931
+ normalizeMarketplaceSourcePath,
932
+ ensureMarketplaceEntry,
698
933
  removeMarketplaceEntry,
699
934
  warnWindowsHooks,
700
- writeJson,
935
+ copyPluginBundle,
936
+ copyRecursive,
937
+ harnessCliRoute,
938
+ harnessInstall,
701
939
  };