@a5c-ai/babysitter-github 0.1.1-staging.04cc300a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/plugin.json +25 -0
- package/AGENTS.md +41 -0
- package/README.md +606 -0
- package/bin/cli.js +116 -0
- package/bin/install-shared.js +701 -0
- package/bin/install.js +129 -0
- package/bin/uninstall.js +76 -0
- package/commands/assimilate.md +37 -0
- package/commands/call.md +7 -0
- package/commands/cleanup.md +20 -0
- package/commands/contrib.md +33 -0
- package/commands/doctor.md +426 -0
- package/commands/forever.md +7 -0
- package/commands/help.md +244 -0
- package/commands/observe.md +12 -0
- package/commands/plan.md +7 -0
- package/commands/plugins.md +255 -0
- package/commands/project-install.md +17 -0
- package/commands/resume.md +8 -0
- package/commands/retrospect.md +55 -0
- package/commands/user-install.md +17 -0
- package/commands/yolo.md +7 -0
- package/hooks/session-end.ps1 +68 -0
- package/hooks/session-end.sh +65 -0
- package/hooks/session-start.ps1 +110 -0
- package/hooks/session-start.sh +100 -0
- package/hooks/user-prompt-submitted.ps1 +51 -0
- package/hooks/user-prompt-submitted.sh +41 -0
- package/hooks.json +29 -0
- package/package.json +50 -0
- package/plugin.json +25 -0
- package/scripts/sync-command-surfaces.js +62 -0
- package/scripts/team-install.js +93 -0
- package/skills/assimilate/SKILL.md +38 -0
- package/skills/babysit/SKILL.md +77 -0
- package/skills/call/SKILL.md +8 -0
- package/skills/doctor/SKILL.md +427 -0
- package/skills/help/SKILL.md +245 -0
- package/skills/observe/SKILL.md +13 -0
- package/skills/plan/SKILL.md +8 -0
- package/skills/resume/SKILL.md +9 -0
- package/skills/retrospect/SKILL.md +56 -0
- package/skills/user-install/SKILL.md +18 -0
- package/versions.json +3 -0
|
@@ -0,0 +1,701 @@
|
|
|
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
|
+
const LEGACY_HOOK_SCRIPT_NAMES = [
|
|
11
|
+
'session-start.sh',
|
|
12
|
+
'stop-hook.sh',
|
|
13
|
+
'user-prompt-submit.sh',
|
|
14
|
+
];
|
|
15
|
+
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',
|
|
22
|
+
];
|
|
23
|
+
const DEFAULT_MARKETPLACE = {
|
|
24
|
+
name: 'local-plugins',
|
|
25
|
+
interface: {
|
|
26
|
+
displayName: 'Local Plugins',
|
|
27
|
+
},
|
|
28
|
+
plugins: [],
|
|
29
|
+
};
|
|
30
|
+
const PLUGIN_BUNDLE_ENTRIES = [
|
|
31
|
+
'plugin.json',
|
|
32
|
+
'hooks.json',
|
|
33
|
+
'hooks',
|
|
34
|
+
'skills',
|
|
35
|
+
'versions.json',
|
|
36
|
+
'AGENTS.md',
|
|
37
|
+
];
|
|
38
|
+
const CLOUD_AGENT_BUNDLE_ENTRIES = [
|
|
39
|
+
'.github',
|
|
40
|
+
'AGENTS.md',
|
|
41
|
+
'README.md',
|
|
42
|
+
'bin',
|
|
43
|
+
'commands',
|
|
44
|
+
'hooks',
|
|
45
|
+
'hooks.json',
|
|
46
|
+
'package.json',
|
|
47
|
+
'plugin.json',
|
|
48
|
+
'scripts',
|
|
49
|
+
'skills',
|
|
50
|
+
'versions.json',
|
|
51
|
+
];
|
|
52
|
+
const MANAGED_BLOCK_START = '<!-- BEGIN BABYSITTER GITHUB CLOUD AGENT -->';
|
|
53
|
+
const MANAGED_BLOCK_END = '<!-- END BABYSITTER GITHUB CLOUD AGENT -->';
|
|
54
|
+
|
|
55
|
+
function getCopilotHome() {
|
|
56
|
+
if (process.env.COPILOT_HOME) return path.resolve(process.env.COPILOT_HOME);
|
|
57
|
+
return path.join(os.homedir(), '.copilot');
|
|
58
|
+
}
|
|
59
|
+
|
|
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
|
+
function replaceManagedMarkdownBlock(existing, block) {
|
|
165
|
+
const normalized = String(existing || '').replace(/\r\n/g, '\n');
|
|
166
|
+
const managedBlock = `${MANAGED_BLOCK_START}\n${block.trim()}\n${MANAGED_BLOCK_END}`;
|
|
167
|
+
const escapedStart = MANAGED_BLOCK_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
168
|
+
const escapedEnd = MANAGED_BLOCK_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
169
|
+
const managedPattern = new RegExp(`${escapedStart}[\\s\\S]*?${escapedEnd}`, 'm');
|
|
170
|
+
|
|
171
|
+
if (managedPattern.test(normalized)) {
|
|
172
|
+
return normalized.replace(managedPattern, managedBlock).replace(/\n{3,}/g, '\n\n').trimEnd() + '\n';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (normalized.trim().length === 0) {
|
|
176
|
+
return `${managedBlock}\n`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return `${normalized.trimEnd()}\n\n${managedBlock}\n`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function writeManagedMarkdown(filePath, block) {
|
|
183
|
+
const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : '';
|
|
184
|
+
return writeFileIfChanged(filePath, replaceManagedMarkdownBlock(existing, block));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function readSdkVersion(packageRoot) {
|
|
188
|
+
const versionsPath = path.join(packageRoot, 'versions.json');
|
|
189
|
+
if (!fs.existsSync(versionsPath)) {
|
|
190
|
+
return 'latest';
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
const parsed = readJson(versionsPath);
|
|
194
|
+
return typeof parsed.sdkVersion === 'string' && parsed.sdkVersion.trim() !== ''
|
|
195
|
+
? parsed.sdkVersion.trim()
|
|
196
|
+
: 'latest';
|
|
197
|
+
} catch {
|
|
198
|
+
return 'latest';
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function toLowerHyphenName(name) {
|
|
203
|
+
return String(name)
|
|
204
|
+
.trim()
|
|
205
|
+
.toLowerCase()
|
|
206
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
207
|
+
.replace(/^-+|-+$/g, '');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function rewriteCloudSkill(skillId, contents) {
|
|
211
|
+
const normalized = String(contents).replace(/\r\n/g, '\n');
|
|
212
|
+
const prefixedName = `babysitter-${skillId}`;
|
|
213
|
+
let next = normalized;
|
|
214
|
+
|
|
215
|
+
if (next.startsWith('---\n')) {
|
|
216
|
+
next = next.replace(/^---\n([\s\S]*?)\n---\n?/, (_match, frontmatter) => {
|
|
217
|
+
const lines = String(frontmatter).split('\n');
|
|
218
|
+
let sawName = false;
|
|
219
|
+
const updatedLines = lines.map((line) => {
|
|
220
|
+
if (/^name:\s*/.test(line)) {
|
|
221
|
+
sawName = true;
|
|
222
|
+
return `name: ${prefixedName}`;
|
|
223
|
+
}
|
|
224
|
+
return line;
|
|
225
|
+
});
|
|
226
|
+
if (!sawName) {
|
|
227
|
+
updatedLines.push(`name: ${prefixedName}`);
|
|
228
|
+
}
|
|
229
|
+
return `---\n${updatedLines.join('\n')}\n---\n`;
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
next = next.replace(/^#\s+.+$/m, `# ${prefixedName}`);
|
|
234
|
+
return next.replace(/\n{3,}/g, '\n\n').trimEnd() + '\n';
|
|
235
|
+
}
|
|
236
|
+
|
|
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
|
+
/**
|
|
284
|
+
* Registers the plugin in ~/.copilot/config.json.
|
|
285
|
+
*/
|
|
286
|
+
function registerCopilotPlugin(pluginRoot) {
|
|
287
|
+
const copilotHome = getCopilotHome();
|
|
288
|
+
const configPath = path.join(copilotHome, 'config.json');
|
|
289
|
+
|
|
290
|
+
fs.mkdirSync(copilotHome, { recursive: true });
|
|
291
|
+
|
|
292
|
+
let config = {};
|
|
293
|
+
if (fs.existsSync(configPath)) {
|
|
294
|
+
try {
|
|
295
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
296
|
+
} catch {
|
|
297
|
+
config = {};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (!config.plugins) {
|
|
302
|
+
config.plugins = [];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const existing = config.plugins.findIndex(
|
|
306
|
+
(p) => (typeof p === 'string' ? p : p.path) === pluginRoot
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
if (existing === -1) {
|
|
310
|
+
config.plugins.push({
|
|
311
|
+
path: pluginRoot,
|
|
312
|
+
enabled: true,
|
|
313
|
+
});
|
|
314
|
+
} else {
|
|
315
|
+
config.plugins[existing] = {
|
|
316
|
+
path: pluginRoot,
|
|
317
|
+
enabled: true,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
writeFileIfChanged(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Removes the plugin entry from ~/.copilot/config.json.
|
|
326
|
+
*/
|
|
327
|
+
function deregisterCopilotPlugin(pluginRoot) {
|
|
328
|
+
const configPath = path.join(getCopilotHome(), 'config.json');
|
|
329
|
+
if (!fs.existsSync(configPath)) return;
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
333
|
+
if (Array.isArray(config.plugins)) {
|
|
334
|
+
config.plugins = config.plugins.filter(
|
|
335
|
+
(p) => (typeof p === 'string' ? p : p.path) !== pluginRoot
|
|
336
|
+
);
|
|
337
|
+
writeFileIfChanged(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
338
|
+
console.log(`[${PLUGIN_NAME}] Removed plugin entry from config.json`);
|
|
339
|
+
}
|
|
340
|
+
} catch (err) {
|
|
341
|
+
console.warn(`[${PLUGIN_NAME}] Warning: Could not update config.json: ${err.message}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function installManagedSkills(packageRoot, copilotHome) {
|
|
346
|
+
const sourceRoot = path.join(packageRoot, 'skills');
|
|
347
|
+
if (!fs.existsSync(sourceRoot)) return;
|
|
348
|
+
const targetRoot = path.join(copilotHome, 'skills');
|
|
349
|
+
fs.mkdirSync(targetRoot, { recursive: true });
|
|
350
|
+
|
|
351
|
+
for (const entry of fs.readdirSync(sourceRoot, { withFileTypes: true })) {
|
|
352
|
+
if (!entry.isDirectory()) continue;
|
|
353
|
+
copyRecursive(
|
|
354
|
+
path.join(sourceRoot, entry.name),
|
|
355
|
+
path.join(targetRoot, entry.name),
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function mergeManagedHooksConfig(packageRoot, copilotHome) {
|
|
361
|
+
const hooksJsonPath = path.join(packageRoot, 'hooks.json');
|
|
362
|
+
if (!fs.existsSync(hooksJsonPath)) return;
|
|
363
|
+
const managedConfig = readJson(hooksJsonPath);
|
|
364
|
+
const managedHooks = managedConfig.hooks || {};
|
|
365
|
+
const hooksConfigPath = path.join(copilotHome, 'hooks.json');
|
|
366
|
+
const existing = fs.existsSync(hooksConfigPath)
|
|
367
|
+
? readJson(hooksConfigPath)
|
|
368
|
+
: { version: 1, hooks: {} };
|
|
369
|
+
// Ensure version field is present per Copilot CLI spec
|
|
370
|
+
existing.version = existing.version || 1;
|
|
371
|
+
if (!existing.hooks || typeof existing.hooks !== 'object') {
|
|
372
|
+
existing.hooks = {};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Remove legacy PascalCase event entries that are no longer valid
|
|
376
|
+
for (const legacyEvent of ['SessionStart', 'UserPromptSubmit', 'Stop']) {
|
|
377
|
+
delete existing.hooks[legacyEvent];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const allScriptNames = [...LEGACY_HOOK_SCRIPT_NAMES, ...HOOK_SCRIPT_NAMES];
|
|
381
|
+
for (const [eventName, entries] of Object.entries(managedHooks)) {
|
|
382
|
+
const existingEntries = Array.isArray(existing.hooks[eventName]) ? existing.hooks[eventName] : [];
|
|
383
|
+
const filteredEntries = existingEntries
|
|
384
|
+
.filter((entry) => {
|
|
385
|
+
const bash = String(entry.bash || entry.command || '');
|
|
386
|
+
const ps = String(entry.powershell || '');
|
|
387
|
+
return !allScriptNames.some((name) => bash.includes(name) || ps.includes(name));
|
|
388
|
+
});
|
|
389
|
+
existing.hooks[eventName] = [...filteredEntries, ...entries];
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
writeJson(hooksConfigPath, existing);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function installManagedHooks(packageRoot, copilotHome) {
|
|
396
|
+
const sourceRoot = path.join(packageRoot, 'hooks');
|
|
397
|
+
if (!fs.existsSync(sourceRoot)) return;
|
|
398
|
+
const targetRoot = path.join(copilotHome, 'hooks');
|
|
399
|
+
fs.mkdirSync(targetRoot, { recursive: true });
|
|
400
|
+
|
|
401
|
+
for (const scriptName of HOOK_SCRIPT_NAMES) {
|
|
402
|
+
const sourcePath = path.join(sourceRoot, scriptName);
|
|
403
|
+
if (!fs.existsSync(sourcePath)) continue;
|
|
404
|
+
const targetPath = path.join(targetRoot, scriptName);
|
|
405
|
+
copyRecursive(sourcePath, targetPath);
|
|
406
|
+
ensureExecutable(targetPath);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
mergeManagedHooksConfig(packageRoot, copilotHome);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function removeLegacyHooks(copilotHome) {
|
|
413
|
+
for (const hookName of LEGACY_HOOK_SCRIPT_NAMES) {
|
|
414
|
+
fs.rmSync(path.join(copilotHome, 'hooks', hookName), { force: true });
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const hooksConfigPath = path.join(copilotHome, 'hooks.json');
|
|
418
|
+
if (!fs.existsSync(hooksConfigPath)) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
let hooksConfig;
|
|
422
|
+
try {
|
|
423
|
+
hooksConfig = readJson(hooksConfigPath);
|
|
424
|
+
} catch {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (!hooksConfig.hooks || typeof hooksConfig.hooks !== 'object') {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
for (const eventName of ['SessionStart', 'UserPromptSubmit', 'Stop', 'sessionStart', 'sessionEnd', 'userPromptSubmitted']) {
|
|
431
|
+
const eventHooks = Array.isArray(hooksConfig.hooks[eventName]) ? hooksConfig.hooks[eventName] : [];
|
|
432
|
+
const filteredMatchers = eventHooks
|
|
433
|
+
.map((matcher) => {
|
|
434
|
+
const hooks = Array.isArray(matcher.hooks) ? matcher.hooks : [];
|
|
435
|
+
const keptHooks = hooks.filter((hook) => {
|
|
436
|
+
const command = String(hook.command || '');
|
|
437
|
+
return !LEGACY_HOOK_SCRIPT_NAMES.some((name) => command.includes(name));
|
|
438
|
+
});
|
|
439
|
+
return keptHooks.length > 0 ? { ...matcher, hooks: keptHooks } : null;
|
|
440
|
+
})
|
|
441
|
+
.filter(Boolean);
|
|
442
|
+
if (filteredMatchers.length > 0) {
|
|
443
|
+
hooksConfig.hooks[eventName] = filteredMatchers;
|
|
444
|
+
} else {
|
|
445
|
+
delete hooksConfig.hooks[eventName];
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (Object.keys(hooksConfig.hooks).length === 0) {
|
|
449
|
+
fs.rmSync(hooksConfigPath, { force: true });
|
|
450
|
+
} else {
|
|
451
|
+
writeJson(hooksConfigPath, hooksConfig);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function installCopilotSurface(packageRoot, copilotHome) {
|
|
456
|
+
removeLegacyHooks(copilotHome);
|
|
457
|
+
installManagedSkills(packageRoot, copilotHome);
|
|
458
|
+
installManagedHooks(packageRoot, copilotHome);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function renderCloudAgentAgentsBlock() {
|
|
462
|
+
return [
|
|
463
|
+
'## Babysitter Cloud Agent',
|
|
464
|
+
'',
|
|
465
|
+
'This repository is configured to let GitHub Copilot cloud agent use Babysitter orchestration assets.',
|
|
466
|
+
'',
|
|
467
|
+
'- For complex multi-step work, use the Babysitter skills installed under `.github/skills/` when relevant.',
|
|
468
|
+
'- The cloud-agent setup workflow installs the Babysitter SDK before work begins.',
|
|
469
|
+
'- If you need the full orchestration instructions, run `babysitter instructions:babysit-skill --harness github-copilot --json`.',
|
|
470
|
+
'- Execute requested shell and Node tasks directly and report the results instead of describing what should be done.',
|
|
471
|
+
'- Do not revert unrelated user changes.',
|
|
472
|
+
].join('\n');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function renderCloudAgentCopilotInstructionsBlock() {
|
|
476
|
+
return [
|
|
477
|
+
'# Babysitter Copilot Cloud Agent Support',
|
|
478
|
+
'',
|
|
479
|
+
'This repository includes Babysitter support for GitHub Copilot cloud agent.',
|
|
480
|
+
'',
|
|
481
|
+
'- Read the nearest `AGENTS.md` instructions before making changes.',
|
|
482
|
+
'- Consider the Babysitter skills under `.github/skills/` when the task is a multi-step workflow, orchestration run, diagnosis, planning, or retrospective.',
|
|
483
|
+
'- The `copilot-setup-steps` workflow installs the Babysitter SDK and initializes the active process library before the agent starts working.',
|
|
484
|
+
'- Add repository-specific build, test, and validation commands below this managed section if they are not already documented elsewhere in the repo.',
|
|
485
|
+
].join('\n');
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function renderCloudAgentSetupWorkflow(packageRoot) {
|
|
489
|
+
const sdkVersion = readSdkVersion(packageRoot);
|
|
490
|
+
return [
|
|
491
|
+
'name: "Copilot Setup Steps"',
|
|
492
|
+
'',
|
|
493
|
+
'on:',
|
|
494
|
+
' workflow_dispatch:',
|
|
495
|
+
' push:',
|
|
496
|
+
' paths:',
|
|
497
|
+
' - .github/workflows/copilot-setup-steps.yml',
|
|
498
|
+
' - .github/copilot-instructions.md',
|
|
499
|
+
' - .github/skills/**',
|
|
500
|
+
' - AGENTS.md',
|
|
501
|
+
' pull_request:',
|
|
502
|
+
' paths:',
|
|
503
|
+
' - .github/workflows/copilot-setup-steps.yml',
|
|
504
|
+
' - .github/copilot-instructions.md',
|
|
505
|
+
' - .github/skills/**',
|
|
506
|
+
' - AGENTS.md',
|
|
507
|
+
'',
|
|
508
|
+
'jobs:',
|
|
509
|
+
' copilot-setup-steps:',
|
|
510
|
+
' runs-on: ubuntu-latest',
|
|
511
|
+
' permissions:',
|
|
512
|
+
' contents: read',
|
|
513
|
+
' steps:',
|
|
514
|
+
' - name: Check out repository',
|
|
515
|
+
' uses: actions/checkout@v4',
|
|
516
|
+
'',
|
|
517
|
+
' - name: Set up Node.js',
|
|
518
|
+
' uses: actions/setup-node@v4',
|
|
519
|
+
' with:',
|
|
520
|
+
' node-version: 22',
|
|
521
|
+
' cache: npm',
|
|
522
|
+
'',
|
|
523
|
+
' - name: Install Babysitter SDK',
|
|
524
|
+
` run: npm install -g @a5c-ai/babysitter-sdk@${sdkVersion}`,
|
|
525
|
+
'',
|
|
526
|
+
' - name: Initialize active process library',
|
|
527
|
+
' run: babysitter process-library:active --json',
|
|
528
|
+
].join('\n') + '\n';
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function installCloudAgentBundle(packageRoot, workspaceRoot) {
|
|
532
|
+
const bundleRoot = path.join(workspaceRoot, '.github', 'babysitter', 'github-plugin');
|
|
533
|
+
fs.rmSync(bundleRoot, { recursive: true, force: true });
|
|
534
|
+
fs.mkdirSync(bundleRoot, { recursive: true });
|
|
535
|
+
for (const entry of CLOUD_AGENT_BUNDLE_ENTRIES) {
|
|
536
|
+
const sourcePath = path.join(packageRoot, entry);
|
|
537
|
+
if (!fs.existsSync(sourcePath)) {
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
copyRecursive(sourcePath, path.join(bundleRoot, entry));
|
|
541
|
+
}
|
|
542
|
+
return bundleRoot;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function installCloudAgentSkills(packageRoot, workspaceRoot) {
|
|
546
|
+
const sourceRoot = path.join(packageRoot, 'skills');
|
|
547
|
+
if (!fs.existsSync(sourceRoot)) return [];
|
|
548
|
+
|
|
549
|
+
const targetRoot = path.join(workspaceRoot, '.github', 'skills');
|
|
550
|
+
fs.mkdirSync(targetRoot, { recursive: true });
|
|
551
|
+
const installed = [];
|
|
552
|
+
|
|
553
|
+
for (const entry of fs.readdirSync(sourceRoot, { withFileTypes: true })) {
|
|
554
|
+
if (!entry.isDirectory()) continue;
|
|
555
|
+
const sourceDir = path.join(sourceRoot, entry.name);
|
|
556
|
+
const skillId = toLowerHyphenName(entry.name);
|
|
557
|
+
const targetDir = path.join(targetRoot, `babysitter-${skillId}`);
|
|
558
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
559
|
+
copyRecursive(sourceDir, targetDir);
|
|
560
|
+
const skillPath = path.join(targetDir, 'SKILL.md');
|
|
561
|
+
if (fs.existsSync(skillPath)) {
|
|
562
|
+
const rewritten = rewriteCloudSkill(skillId, fs.readFileSync(skillPath, 'utf8'));
|
|
563
|
+
fs.writeFileSync(skillPath, rewritten, 'utf8');
|
|
564
|
+
}
|
|
565
|
+
installed.push(targetDir);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return installed;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function installCloudAgentInstructions(packageRoot, workspaceRoot) {
|
|
572
|
+
const agentsPath = path.join(workspaceRoot, 'AGENTS.md');
|
|
573
|
+
const copilotInstructionsPath = path.join(workspaceRoot, '.github', 'copilot-instructions.md');
|
|
574
|
+
|
|
575
|
+
writeManagedMarkdown(agentsPath, renderCloudAgentAgentsBlock(packageRoot));
|
|
576
|
+
writeManagedMarkdown(copilotInstructionsPath, renderCloudAgentCopilotInstructionsBlock(packageRoot));
|
|
577
|
+
|
|
578
|
+
return {
|
|
579
|
+
agentsPath,
|
|
580
|
+
copilotInstructionsPath,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function installCloudAgentSetupSteps(packageRoot, workspaceRoot) {
|
|
585
|
+
const workflowsDir = path.join(workspaceRoot, '.github', 'workflows');
|
|
586
|
+
const workflowPath = path.join(workflowsDir, 'copilot-setup-steps.yml');
|
|
587
|
+
const examplePath = path.join(workflowsDir, 'copilot-setup-steps.babysitter.generated.yml');
|
|
588
|
+
const contents = renderCloudAgentSetupWorkflow(packageRoot);
|
|
589
|
+
|
|
590
|
+
fs.mkdirSync(workflowsDir, { recursive: true });
|
|
591
|
+
|
|
592
|
+
if (!fs.existsSync(workflowPath)) {
|
|
593
|
+
writeFileIfChanged(workflowPath, contents);
|
|
594
|
+
fs.rmSync(examplePath, { force: true });
|
|
595
|
+
return { workflowPath, examplePath: null, managed: true, needsManualMerge: false };
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const existing = fs.readFileSync(workflowPath, 'utf8');
|
|
599
|
+
if (existing.includes('copilot-setup-steps') && existing.includes('@a5c-ai/babysitter-sdk')) {
|
|
600
|
+
writeFileIfChanged(workflowPath, contents);
|
|
601
|
+
fs.rmSync(examplePath, { force: true });
|
|
602
|
+
return { workflowPath, examplePath: null, managed: true, needsManualMerge: false };
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
writeFileIfChanged(examplePath, contents);
|
|
606
|
+
return { workflowPath, examplePath, managed: false, needsManualMerge: true };
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function installCloudAgentSurface(packageRoot, workspaceRoot) {
|
|
610
|
+
const bundleRoot = installCloudAgentBundle(packageRoot, workspaceRoot);
|
|
611
|
+
const skillDirs = installCloudAgentSkills(packageRoot, workspaceRoot);
|
|
612
|
+
const instructions = installCloudAgentInstructions(packageRoot, workspaceRoot);
|
|
613
|
+
const setupWorkflow = installCloudAgentSetupSteps(packageRoot, workspaceRoot);
|
|
614
|
+
return {
|
|
615
|
+
bundleRoot,
|
|
616
|
+
skillDirs,
|
|
617
|
+
...instructions,
|
|
618
|
+
setupWorkflow,
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
|
|
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
|
+
};
|
|
628
|
+
}
|
|
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
|
+
};
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
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 || {}),
|
|
655
|
+
},
|
|
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
|
+
);
|
|
664
|
+
}
|
|
665
|
+
return result.stdout;
|
|
666
|
+
}
|
|
667
|
+
|
|
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
|
+
);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function warnWindowsHooks() {
|
|
679
|
+
if (process.platform !== 'win32') {
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
console.warn(`[${PLUGIN_NAME}] Note: On Windows, Copilot CLI will use .ps1 PowerShell hooks.`);
|
|
683
|
+
console.warn(`[${PLUGIN_NAME}] Both bash (.sh) and PowerShell (.ps1) hook scripts are included.`);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
module.exports = {
|
|
687
|
+
copyPluginBundle,
|
|
688
|
+
deregisterCopilotPlugin,
|
|
689
|
+
ensureGlobalProcessLibrary,
|
|
690
|
+
ensureMarketplaceEntry,
|
|
691
|
+
getCopilotHome,
|
|
692
|
+
getHomeMarketplacePath,
|
|
693
|
+
getHomePluginRoot,
|
|
694
|
+
installCopilotSurface,
|
|
695
|
+
installCloudAgentSurface,
|
|
696
|
+
registerCopilotPlugin,
|
|
697
|
+
removeLegacyHooks,
|
|
698
|
+
removeMarketplaceEntry,
|
|
699
|
+
warnWindowsHooks,
|
|
700
|
+
writeJson,
|
|
701
|
+
};
|