@a5c-ai/babysitter-codex 0.1.6-staging.05b1f6af
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/.app.json +3 -0
- package/.codex-plugin/plugin.json +47 -0
- package/README.md +92 -0
- package/assets/icon.svg +7 -0
- package/assets/logo.svg +8 -0
- package/babysitter.lock.json +18 -0
- package/bin/cli.js +104 -0
- package/bin/install-shared.js +509 -0
- package/bin/install.js +48 -0
- package/bin/uninstall.js +40 -0
- package/hooks/babysitter-session-start.sh +37 -0
- package/hooks/babysitter-stop-hook.sh +37 -0
- package/hooks/user-prompt-submit.sh +39 -0
- package/hooks.json +37 -0
- package/package.json +48 -0
- package/scripts/team-install.js +86 -0
- package/skills/assimilate/SKILL.md +17 -0
- package/skills/babysit/SKILL.md +876 -0
- package/skills/call/SKILL.md +17 -0
- package/skills/doctor/SKILL.md +16 -0
- package/skills/forever/SKILL.md +15 -0
- package/skills/help/SKILL.md +15 -0
- package/skills/issue/SKILL.md +16 -0
- package/skills/model/SKILL.md +15 -0
- package/skills/observe/SKILL.md +15 -0
- package/skills/plan/SKILL.md +16 -0
- package/skills/project-install/SKILL.md +15 -0
- package/skills/resume/SKILL.md +15 -0
- package/skills/retrospect/SKILL.md +15 -0
- package/skills/team-install/SKILL.md +15 -0
- package/skills/user-install/SKILL.md +15 -0
- package/skills/yolo/SKILL.md +19 -0
|
@@ -0,0 +1,509 @@
|
|
|
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-codex';
|
|
9
|
+
const PLUGIN_CATEGORY = 'Coding';
|
|
10
|
+
const LEGACY_SKILL_NAMES = [
|
|
11
|
+
'babysit',
|
|
12
|
+
'babysitter-codex',
|
|
13
|
+
'assimilate',
|
|
14
|
+
'call',
|
|
15
|
+
'doctor',
|
|
16
|
+
'forever',
|
|
17
|
+
'help',
|
|
18
|
+
'issue',
|
|
19
|
+
'model',
|
|
20
|
+
'observe',
|
|
21
|
+
'plan',
|
|
22
|
+
'project-install',
|
|
23
|
+
'resume',
|
|
24
|
+
'retrospect',
|
|
25
|
+
'team-install',
|
|
26
|
+
'user-install',
|
|
27
|
+
'yolo',
|
|
28
|
+
];
|
|
29
|
+
const LEGACY_PROMPT_NAMES = [
|
|
30
|
+
'assimilate.md',
|
|
31
|
+
'call.md',
|
|
32
|
+
'doctor.md',
|
|
33
|
+
'forever.md',
|
|
34
|
+
'help.md',
|
|
35
|
+
'issue.md',
|
|
36
|
+
'model.md',
|
|
37
|
+
'observe.md',
|
|
38
|
+
'plan.md',
|
|
39
|
+
'project-install.md',
|
|
40
|
+
'resume.md',
|
|
41
|
+
'retrospect.md',
|
|
42
|
+
'team-install.md',
|
|
43
|
+
'user-install.md',
|
|
44
|
+
'yolo.md',
|
|
45
|
+
'babysit.md',
|
|
46
|
+
];
|
|
47
|
+
const LEGACY_HOOK_SCRIPT_NAMES = [
|
|
48
|
+
'babysitter-session-start.sh',
|
|
49
|
+
'babysitter-stop-hook.sh',
|
|
50
|
+
'user-prompt-submit.sh',
|
|
51
|
+
];
|
|
52
|
+
const DEFAULT_MARKETPLACE = {
|
|
53
|
+
name: 'local-plugins',
|
|
54
|
+
interface: {
|
|
55
|
+
displayName: 'Local Plugins',
|
|
56
|
+
},
|
|
57
|
+
plugins: [],
|
|
58
|
+
};
|
|
59
|
+
const PLUGIN_BUNDLE_ENTRIES = [
|
|
60
|
+
'.codex-plugin',
|
|
61
|
+
'.app.json',
|
|
62
|
+
'assets',
|
|
63
|
+
'hooks',
|
|
64
|
+
'hooks.json',
|
|
65
|
+
'skills',
|
|
66
|
+
'babysitter.lock.json',
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
function getCodexHome() {
|
|
70
|
+
if (process.env.CODEX_HOME) return path.resolve(process.env.CODEX_HOME);
|
|
71
|
+
return path.join(os.homedir(), '.codex');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getUserHome() {
|
|
75
|
+
if (process.env.USERPROFILE) return path.resolve(process.env.USERPROFILE);
|
|
76
|
+
if (process.env.HOME) return path.resolve(process.env.HOME);
|
|
77
|
+
return os.homedir();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getGlobalStateDir() {
|
|
81
|
+
if (process.env.BABYSITTER_GLOBAL_STATE_DIR) {
|
|
82
|
+
return path.resolve(process.env.BABYSITTER_GLOBAL_STATE_DIR);
|
|
83
|
+
}
|
|
84
|
+
return path.join(getUserHome(), '.a5c');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getHomePluginRoot() {
|
|
88
|
+
if (process.env.BABYSITTER_CODEX_PLUGIN_DIR) {
|
|
89
|
+
return path.resolve(process.env.BABYSITTER_CODEX_PLUGIN_DIR, PLUGIN_NAME);
|
|
90
|
+
}
|
|
91
|
+
return path.join(getCodexHome(), 'plugins', PLUGIN_NAME);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getHomeMarketplacePath() {
|
|
95
|
+
if (process.env.BABYSITTER_CODEX_MARKETPLACE_PATH) {
|
|
96
|
+
return path.resolve(process.env.BABYSITTER_CODEX_MARKETPLACE_PATH);
|
|
97
|
+
}
|
|
98
|
+
return path.join(getUserHome(), '.agents', 'plugins', 'marketplace.json');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function renderCodexConfigToml() {
|
|
102
|
+
return [
|
|
103
|
+
'approval_policy = "on-request"',
|
|
104
|
+
'sandbox_mode = "workspace-write"',
|
|
105
|
+
'project_doc_max_bytes = 65536',
|
|
106
|
+
'',
|
|
107
|
+
'[sandbox_workspace_write]',
|
|
108
|
+
'writable_roots = [".a5c", ".codex"]',
|
|
109
|
+
'',
|
|
110
|
+
'[features]',
|
|
111
|
+
'codex_hooks = true',
|
|
112
|
+
'multi_agent = true',
|
|
113
|
+
'',
|
|
114
|
+
'[agents]',
|
|
115
|
+
'max_depth = 3',
|
|
116
|
+
'max_threads = 4',
|
|
117
|
+
'',
|
|
118
|
+
].join('\n');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function writeFileIfChanged(filePath, contents) {
|
|
122
|
+
if (fs.existsSync(filePath)) {
|
|
123
|
+
const current = fs.readFileSync(filePath, 'utf8');
|
|
124
|
+
if (current === contents) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
129
|
+
fs.writeFileSync(filePath, contents, 'utf8');
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function copyRecursive(src, dest) {
|
|
134
|
+
const stat = fs.statSync(src);
|
|
135
|
+
if (stat.isDirectory()) {
|
|
136
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
137
|
+
for (const entry of fs.readdirSync(src)) {
|
|
138
|
+
if (['node_modules', '.git', 'test', '.a5c'].includes(entry)) continue;
|
|
139
|
+
copyRecursive(path.join(src, entry), path.join(dest, entry));
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (path.basename(src) === 'SKILL.md') {
|
|
145
|
+
const file = fs.readFileSync(src);
|
|
146
|
+
const hasBom = file.length >= 3 && file[0] === 0xef && file[1] === 0xbb && file[2] === 0xbf;
|
|
147
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
148
|
+
fs.writeFileSync(dest, hasBom ? file.subarray(3) : file);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
153
|
+
fs.copyFileSync(src, dest);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function copyPluginBundle(packageRoot, pluginRoot) {
|
|
157
|
+
if (path.resolve(packageRoot) === path.resolve(pluginRoot)) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
fs.rmSync(pluginRoot, { recursive: true, force: true });
|
|
161
|
+
fs.mkdirSync(pluginRoot, { recursive: true });
|
|
162
|
+
for (const entry of PLUGIN_BUNDLE_ENTRIES) {
|
|
163
|
+
copyRecursive(path.join(packageRoot, entry), path.join(pluginRoot, entry));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function insertRootKey(content, key, line) {
|
|
168
|
+
const keyPattern = new RegExp(`^\\s*${key}\\s*=`, 'm');
|
|
169
|
+
if (keyPattern.test(content)) {
|
|
170
|
+
return content;
|
|
171
|
+
}
|
|
172
|
+
const sectionMatch = content.match(/^\[[^\]]+\]\s*$/m);
|
|
173
|
+
if (!sectionMatch || sectionMatch.index === undefined) {
|
|
174
|
+
return content.trim() ? `${content.trimEnd()}\n${line}\n` : `${line}\n`;
|
|
175
|
+
}
|
|
176
|
+
const before = content.slice(0, sectionMatch.index).trimEnd();
|
|
177
|
+
const after = content.slice(sectionMatch.index);
|
|
178
|
+
return before ? `${before}\n${line}\n\n${after}` : `${line}\n\n${after}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function ensureSectionLine(content, sectionName, lineKey, line) {
|
|
182
|
+
const keyPattern = new RegExp(`^\\s*${lineKey}\\s*=`, 'm');
|
|
183
|
+
if (keyPattern.test(content)) {
|
|
184
|
+
return content;
|
|
185
|
+
}
|
|
186
|
+
const sectionHeader = `[${sectionName}]`;
|
|
187
|
+
const escapedSection = sectionName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
188
|
+
const sectionPattern = new RegExp(`^\\[${escapedSection}\\]\\s*$`, 'm');
|
|
189
|
+
if (sectionPattern.test(content)) {
|
|
190
|
+
return content.replace(sectionPattern, `${sectionHeader}\n${line}`);
|
|
191
|
+
}
|
|
192
|
+
return content.trim()
|
|
193
|
+
? `${content.trimEnd()}\n\n${sectionHeader}\n${line}\n`
|
|
194
|
+
: `${sectionHeader}\n${line}\n`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function ensureWritableRoots(content) {
|
|
198
|
+
const sectionPattern = /^\[sandbox_workspace_write\]\s*$/m;
|
|
199
|
+
const rootsPattern = /^writable_roots\s*=\s*\[(.*?)\]\s*$/m;
|
|
200
|
+
const requiredRoots = ['.a5c', '.codex'];
|
|
201
|
+
|
|
202
|
+
if (!sectionPattern.test(content)) {
|
|
203
|
+
return content.trim()
|
|
204
|
+
? `${content.trimEnd()}\n\n[sandbox_workspace_write]\nwritable_roots = [".a5c", ".codex"]\n`
|
|
205
|
+
: '[sandbox_workspace_write]\nwritable_roots = [".a5c", ".codex"]\n';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!rootsPattern.test(content)) {
|
|
209
|
+
return content.replace(
|
|
210
|
+
sectionPattern,
|
|
211
|
+
'[sandbox_workspace_write]\nwritable_roots = [".a5c", ".codex"]',
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return content.replace(rootsPattern, (_match, inner) => {
|
|
216
|
+
const values = inner
|
|
217
|
+
.split(',')
|
|
218
|
+
.map((part) => part.trim())
|
|
219
|
+
.filter(Boolean)
|
|
220
|
+
.map((part) => part.replace(/^"(.*)"$/, '$1'));
|
|
221
|
+
const merged = [...new Set([...values, ...requiredRoots])];
|
|
222
|
+
return `writable_roots = [${merged.map((value) => `"${value}"`).join(', ')}]`;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function mergeCodexConfig(existing) {
|
|
227
|
+
let content = existing.trim() ? existing : '';
|
|
228
|
+
content = insertRootKey(content, 'approval_policy', 'approval_policy = "on-request"');
|
|
229
|
+
content = insertRootKey(content, 'sandbox_mode', 'sandbox_mode = "workspace-write"');
|
|
230
|
+
content = insertRootKey(content, 'project_doc_max_bytes', 'project_doc_max_bytes = 65536');
|
|
231
|
+
content = ensureWritableRoots(content);
|
|
232
|
+
content = ensureSectionLine(content, 'features', 'codex_hooks', 'codex_hooks = true');
|
|
233
|
+
content = ensureSectionLine(content, 'features', 'multi_agent', 'multi_agent = true');
|
|
234
|
+
content = ensureSectionLine(content, 'agents', 'max_depth', 'max_depth = 3');
|
|
235
|
+
content = ensureSectionLine(content, 'agents', 'max_threads', 'max_threads = 4');
|
|
236
|
+
return `${content.trimEnd()}\n`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function mergeCodexConfigFile(configPath) {
|
|
240
|
+
const current = fs.existsSync(configPath)
|
|
241
|
+
? fs.readFileSync(configPath, 'utf8')
|
|
242
|
+
: renderCodexConfigToml();
|
|
243
|
+
writeFileIfChanged(configPath, mergeCodexConfig(current));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function resolveBabysitterCommand(packageRoot) {
|
|
247
|
+
if (process.env.BABYSITTER_SDK_CLI) {
|
|
248
|
+
return {
|
|
249
|
+
command: process.execPath,
|
|
250
|
+
argsPrefix: [path.resolve(process.env.BABYSITTER_SDK_CLI)],
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
return {
|
|
255
|
+
command: process.execPath,
|
|
256
|
+
argsPrefix: [
|
|
257
|
+
require.resolve('@a5c-ai/babysitter-sdk/dist/cli/main.js', {
|
|
258
|
+
paths: [packageRoot],
|
|
259
|
+
}),
|
|
260
|
+
],
|
|
261
|
+
};
|
|
262
|
+
} catch {
|
|
263
|
+
return {
|
|
264
|
+
command: 'babysitter',
|
|
265
|
+
argsPrefix: [],
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function runBabysitterCli(packageRoot, cliArgs, options = {}) {
|
|
271
|
+
const resolved = resolveBabysitterCommand(packageRoot);
|
|
272
|
+
const result = spawnSync(resolved.command, [...resolved.argsPrefix, ...cliArgs], {
|
|
273
|
+
cwd: options.cwd || process.cwd(),
|
|
274
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
275
|
+
encoding: 'utf8',
|
|
276
|
+
env: {
|
|
277
|
+
...process.env,
|
|
278
|
+
...(options.env || {}),
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
if (result.status !== 0) {
|
|
282
|
+
const stderr = (result.stderr || '').trim();
|
|
283
|
+
const stdout = (result.stdout || '').trim();
|
|
284
|
+
throw new Error(
|
|
285
|
+
`babysitter ${cliArgs.join(' ')} failed` +
|
|
286
|
+
(stderr ? `: ${stderr}` : stdout ? `: ${stdout}` : ''),
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
return result.stdout;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function ensureGlobalProcessLibrary(packageRoot) {
|
|
293
|
+
return JSON.parse(
|
|
294
|
+
runBabysitterCli(
|
|
295
|
+
packageRoot,
|
|
296
|
+
['process-library:active', '--state-dir', getGlobalStateDir(), '--json'],
|
|
297
|
+
{ cwd: packageRoot },
|
|
298
|
+
),
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function readJson(filePath) {
|
|
303
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function writeJson(filePath, value) {
|
|
307
|
+
writeFileIfChanged(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function ensureExecutable(filePath) {
|
|
311
|
+
try {
|
|
312
|
+
fs.chmodSync(filePath, 0o755);
|
|
313
|
+
} catch {
|
|
314
|
+
// Best-effort only. Windows and some filesystems may ignore mode changes.
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function normalizeMarketplaceSourcePath(marketplacePath, pluginSourcePath) {
|
|
319
|
+
let next = pluginSourcePath;
|
|
320
|
+
if (path.isAbsolute(next)) {
|
|
321
|
+
next = path.relative(path.dirname(marketplacePath), next);
|
|
322
|
+
}
|
|
323
|
+
next = String(next || '').replace(/\\/g, '/');
|
|
324
|
+
if (!next.startsWith('./') && !next.startsWith('../')) {
|
|
325
|
+
next = `./${next}`;
|
|
326
|
+
}
|
|
327
|
+
return next;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function ensureMarketplaceEntry(marketplacePath, pluginSourcePath) {
|
|
331
|
+
const marketplace = fs.existsSync(marketplacePath)
|
|
332
|
+
? readJson(marketplacePath)
|
|
333
|
+
: { ...DEFAULT_MARKETPLACE, plugins: [] };
|
|
334
|
+
marketplace.name = marketplace.name || DEFAULT_MARKETPLACE.name;
|
|
335
|
+
marketplace.interface = marketplace.interface || {};
|
|
336
|
+
marketplace.interface.displayName =
|
|
337
|
+
marketplace.interface.displayName || DEFAULT_MARKETPLACE.interface.displayName;
|
|
338
|
+
const nextEntry = {
|
|
339
|
+
name: PLUGIN_NAME,
|
|
340
|
+
source: {
|
|
341
|
+
source: 'local',
|
|
342
|
+
path: normalizeMarketplaceSourcePath(marketplacePath, pluginSourcePath),
|
|
343
|
+
},
|
|
344
|
+
policy: {
|
|
345
|
+
installation: 'AVAILABLE',
|
|
346
|
+
authentication: 'ON_INSTALL',
|
|
347
|
+
},
|
|
348
|
+
category: PLUGIN_CATEGORY,
|
|
349
|
+
};
|
|
350
|
+
const existingIndex = Array.isArray(marketplace.plugins)
|
|
351
|
+
? marketplace.plugins.findIndex((entry) => entry && entry.name === PLUGIN_NAME)
|
|
352
|
+
: -1;
|
|
353
|
+
if (!Array.isArray(marketplace.plugins)) {
|
|
354
|
+
marketplace.plugins = [nextEntry];
|
|
355
|
+
} else if (existingIndex >= 0) {
|
|
356
|
+
marketplace.plugins[existingIndex] = nextEntry;
|
|
357
|
+
} else {
|
|
358
|
+
marketplace.plugins.push(nextEntry);
|
|
359
|
+
}
|
|
360
|
+
writeJson(marketplacePath, marketplace);
|
|
361
|
+
return nextEntry;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function removeMarketplaceEntry(marketplacePath) {
|
|
365
|
+
if (!fs.existsSync(marketplacePath)) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const marketplace = readJson(marketplacePath);
|
|
369
|
+
if (!Array.isArray(marketplace.plugins)) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
marketplace.plugins = marketplace.plugins.filter((entry) => entry && entry.name !== PLUGIN_NAME);
|
|
373
|
+
writeJson(marketplacePath, marketplace);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function removeLegacyCodexSurface(codexHome) {
|
|
377
|
+
for (const skillName of LEGACY_SKILL_NAMES) {
|
|
378
|
+
fs.rmSync(path.join(codexHome, 'skills', skillName), { recursive: true, force: true });
|
|
379
|
+
}
|
|
380
|
+
for (const promptName of LEGACY_PROMPT_NAMES) {
|
|
381
|
+
fs.rmSync(path.join(codexHome, 'prompts', promptName), { force: true });
|
|
382
|
+
}
|
|
383
|
+
for (const hookName of LEGACY_HOOK_SCRIPT_NAMES) {
|
|
384
|
+
fs.rmSync(path.join(codexHome, 'hooks', hookName), { force: true });
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const hooksConfigPath = path.join(codexHome, 'hooks.json');
|
|
388
|
+
if (!fs.existsSync(hooksConfigPath)) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
let hooksConfig;
|
|
392
|
+
try {
|
|
393
|
+
hooksConfig = readJson(hooksConfigPath);
|
|
394
|
+
} catch {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (!hooksConfig.hooks || typeof hooksConfig.hooks !== 'object') {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
for (const eventName of ['SessionStart', 'UserPromptSubmit', 'Stop']) {
|
|
401
|
+
const eventHooks = Array.isArray(hooksConfig.hooks[eventName]) ? hooksConfig.hooks[eventName] : [];
|
|
402
|
+
const filteredMatchers = eventHooks
|
|
403
|
+
.map((matcher) => {
|
|
404
|
+
const hooks = Array.isArray(matcher.hooks) ? matcher.hooks : [];
|
|
405
|
+
const keptHooks = hooks.filter((hook) => {
|
|
406
|
+
const command = String(hook.command || '');
|
|
407
|
+
return !LEGACY_HOOK_SCRIPT_NAMES.some((name) => command.includes(name));
|
|
408
|
+
});
|
|
409
|
+
return keptHooks.length > 0 ? { ...matcher, hooks: keptHooks } : null;
|
|
410
|
+
})
|
|
411
|
+
.filter(Boolean);
|
|
412
|
+
if (filteredMatchers.length > 0) {
|
|
413
|
+
hooksConfig.hooks[eventName] = filteredMatchers;
|
|
414
|
+
} else {
|
|
415
|
+
delete hooksConfig.hooks[eventName];
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (Object.keys(hooksConfig.hooks).length === 0) {
|
|
419
|
+
fs.rmSync(hooksConfigPath, { force: true });
|
|
420
|
+
} else {
|
|
421
|
+
writeJson(hooksConfigPath, hooksConfig);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function installManagedSkills(packageRoot, codexHome) {
|
|
426
|
+
const sourceRoot = path.join(packageRoot, 'skills');
|
|
427
|
+
const targetRoot = path.join(codexHome, 'skills');
|
|
428
|
+
fs.mkdirSync(targetRoot, { recursive: true });
|
|
429
|
+
|
|
430
|
+
for (const entry of fs.readdirSync(sourceRoot, { withFileTypes: true })) {
|
|
431
|
+
if (!entry.isDirectory()) continue;
|
|
432
|
+
copyRecursive(
|
|
433
|
+
path.join(sourceRoot, entry.name),
|
|
434
|
+
path.join(targetRoot, entry.name),
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function mergeManagedHooksConfig(packageRoot, codexHome) {
|
|
440
|
+
const managedHooks = readJson(path.join(packageRoot, 'hooks.json')).hooks || {};
|
|
441
|
+
const hooksConfigPath = path.join(codexHome, 'hooks.json');
|
|
442
|
+
const existing = fs.existsSync(hooksConfigPath)
|
|
443
|
+
? readJson(hooksConfigPath)
|
|
444
|
+
: { hooks: {} };
|
|
445
|
+
if (!existing.hooks || typeof existing.hooks !== 'object') {
|
|
446
|
+
existing.hooks = {};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
for (const [eventName, matchers] of Object.entries(managedHooks)) {
|
|
450
|
+
const existingMatchers = Array.isArray(existing.hooks[eventName]) ? existing.hooks[eventName] : [];
|
|
451
|
+
const filteredMatchers = existingMatchers
|
|
452
|
+
.map((matcher) => {
|
|
453
|
+
const hooks = Array.isArray(matcher.hooks) ? matcher.hooks : [];
|
|
454
|
+
const keptHooks = hooks.filter((hook) => {
|
|
455
|
+
const command = String(hook.command || '');
|
|
456
|
+
return !LEGACY_HOOK_SCRIPT_NAMES.some((name) => command.includes(name));
|
|
457
|
+
});
|
|
458
|
+
return keptHooks.length > 0 ? { ...matcher, hooks: keptHooks } : null;
|
|
459
|
+
})
|
|
460
|
+
.filter(Boolean);
|
|
461
|
+
existing.hooks[eventName] = [...filteredMatchers, ...matchers];
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
writeJson(hooksConfigPath, existing);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function installManagedHooks(packageRoot, codexHome) {
|
|
468
|
+
const sourceRoot = path.join(packageRoot, 'hooks');
|
|
469
|
+
const targetRoot = path.join(codexHome, 'hooks');
|
|
470
|
+
fs.mkdirSync(targetRoot, { recursive: true });
|
|
471
|
+
|
|
472
|
+
for (const scriptName of LEGACY_HOOK_SCRIPT_NAMES) {
|
|
473
|
+
const sourcePath = path.join(sourceRoot, scriptName);
|
|
474
|
+
const targetPath = path.join(targetRoot, scriptName);
|
|
475
|
+
copyRecursive(sourcePath, targetPath);
|
|
476
|
+
ensureExecutable(targetPath);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
mergeManagedHooksConfig(packageRoot, codexHome);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function installCodexSurface(packageRoot, codexHome) {
|
|
483
|
+
removeLegacyCodexSurface(codexHome);
|
|
484
|
+
installManagedSkills(packageRoot, codexHome);
|
|
485
|
+
installManagedHooks(packageRoot, codexHome);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function warnWindowsHooks() {
|
|
489
|
+
if (process.platform !== 'win32') {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
console.warn('[babysitter-codex] Warning: Codex hooks are currently disabled on native Windows.');
|
|
493
|
+
console.warn('[babysitter-codex] The plugin will install correctly, but SessionStart/UserPromptSubmit/Stop hooks will not fire until Codex enables Windows hook execution.');
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
module.exports = {
|
|
497
|
+
copyPluginBundle,
|
|
498
|
+
ensureGlobalProcessLibrary,
|
|
499
|
+
ensureMarketplaceEntry,
|
|
500
|
+
getCodexHome,
|
|
501
|
+
getHomeMarketplacePath,
|
|
502
|
+
getHomePluginRoot,
|
|
503
|
+
installCodexSurface,
|
|
504
|
+
mergeCodexConfigFile,
|
|
505
|
+
removeLegacyCodexSurface,
|
|
506
|
+
removeMarketplaceEntry,
|
|
507
|
+
warnWindowsHooks,
|
|
508
|
+
writeJson,
|
|
509
|
+
};
|
package/bin/install.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const {
|
|
6
|
+
copyPluginBundle,
|
|
7
|
+
ensureGlobalProcessLibrary,
|
|
8
|
+
ensureMarketplaceEntry,
|
|
9
|
+
getCodexHome,
|
|
10
|
+
getHomeMarketplacePath,
|
|
11
|
+
getHomePluginRoot,
|
|
12
|
+
installCodexSurface,
|
|
13
|
+
mergeCodexConfigFile,
|
|
14
|
+
warnWindowsHooks,
|
|
15
|
+
} = require('./install-shared');
|
|
16
|
+
|
|
17
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
18
|
+
|
|
19
|
+
function main() {
|
|
20
|
+
const codexHome = getCodexHome();
|
|
21
|
+
const pluginRoot = getHomePluginRoot();
|
|
22
|
+
const marketplacePath = getHomeMarketplacePath();
|
|
23
|
+
|
|
24
|
+
console.log(`[babysitter-codex] Installing plugin to ${pluginRoot}`);
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
copyPluginBundle(PACKAGE_ROOT, pluginRoot);
|
|
28
|
+
ensureMarketplaceEntry(marketplacePath, pluginRoot);
|
|
29
|
+
mergeCodexConfigFile(path.join(codexHome, 'config.toml'));
|
|
30
|
+
installCodexSurface(PACKAGE_ROOT, codexHome);
|
|
31
|
+
|
|
32
|
+
const active = ensureGlobalProcessLibrary(PACKAGE_ROOT);
|
|
33
|
+
console.log(`[babysitter-codex] marketplace: ${marketplacePath}`);
|
|
34
|
+
console.log(`[babysitter-codex] process library: ${active.binding?.dir}`);
|
|
35
|
+
if (active.defaultSpec?.cloneDir) {
|
|
36
|
+
console.log(`[babysitter-codex] process library clone: ${active.defaultSpec.cloneDir}`);
|
|
37
|
+
}
|
|
38
|
+
console.log(`[babysitter-codex] process library state: ${active.stateFile}`);
|
|
39
|
+
warnWindowsHooks();
|
|
40
|
+
console.log('[babysitter-codex] Installation complete!');
|
|
41
|
+
console.log('[babysitter-codex] Restart Codex to pick up the installed plugin and config changes.');
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error(`[babysitter-codex] Failed to install plugin: ${err.message}`);
|
|
44
|
+
process.exitCode = 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
main();
|
package/bin/uninstall.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const {
|
|
6
|
+
getCodexHome,
|
|
7
|
+
getHomeMarketplacePath,
|
|
8
|
+
getHomePluginRoot,
|
|
9
|
+
removeLegacyCodexSurface,
|
|
10
|
+
removeMarketplaceEntry,
|
|
11
|
+
} = require('./install-shared');
|
|
12
|
+
|
|
13
|
+
function main() {
|
|
14
|
+
const codexHome = getCodexHome();
|
|
15
|
+
const pluginRoot = getHomePluginRoot();
|
|
16
|
+
const marketplacePath = getHomeMarketplacePath();
|
|
17
|
+
let removedPlugin = false;
|
|
18
|
+
|
|
19
|
+
if (fs.existsSync(pluginRoot)) {
|
|
20
|
+
try {
|
|
21
|
+
fs.rmSync(pluginRoot, { recursive: true, force: true });
|
|
22
|
+
console.log(`[babysitter-codex] Removed ${pluginRoot}`);
|
|
23
|
+
removedPlugin = true;
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.warn(`[babysitter-codex] Warning: Could not remove plugin directory ${pluginRoot}: ${err.message}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
removeMarketplaceEntry(marketplacePath);
|
|
30
|
+
removeLegacyCodexSurface(codexHome);
|
|
31
|
+
|
|
32
|
+
if (!removedPlugin) {
|
|
33
|
+
console.log('[babysitter-codex] Plugin directory not found, legacy Codex surface cleaned if present.');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log('[babysitter-codex] Restart Codex to complete uninstallation.');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
main();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
6
|
+
STATE_DIR="${BABYSITTER_STATE_DIR:-${PWD}/.a5c}"
|
|
7
|
+
LOG_DIR="${BABYSITTER_LOG_DIR:-$PLUGIN_ROOT/.a5c/logs}"
|
|
8
|
+
LOG_FILE="$LOG_DIR/babysitter-session-start-hook.log"
|
|
9
|
+
|
|
10
|
+
export CODEX_PLUGIN_ROOT="${CODEX_PLUGIN_ROOT:-${PLUGIN_ROOT}}"
|
|
11
|
+
export BABYSITTER_STATE_DIR="${STATE_DIR}"
|
|
12
|
+
|
|
13
|
+
mkdir -p "$LOG_DIR" 2>/dev/null
|
|
14
|
+
{
|
|
15
|
+
echo "[INFO] $(date -u +%Y-%m-%dT%H:%M:%SZ) Hook script invoked"
|
|
16
|
+
echo "[INFO] $(date -u +%Y-%m-%dT%H:%M:%SZ) PLUGIN_ROOT=$PLUGIN_ROOT"
|
|
17
|
+
echo "[INFO] $(date -u +%Y-%m-%dT%H:%M:%SZ) STATE_DIR=$STATE_DIR"
|
|
18
|
+
} >> "$LOG_FILE" 2>/dev/null
|
|
19
|
+
|
|
20
|
+
INPUT_FILE=$(mktemp 2>/dev/null || echo "/tmp/codex-session-start-hook-$$.json")
|
|
21
|
+
cat > "$INPUT_FILE"
|
|
22
|
+
|
|
23
|
+
echo "[INFO] $(date -u +%Y-%m-%dT%H:%M:%SZ) Hook input received ($(wc -c < "$INPUT_FILE") bytes)" >> "$LOG_FILE" 2>/dev/null
|
|
24
|
+
|
|
25
|
+
RESULT=$(babysitter hook:run \
|
|
26
|
+
--hook-type session-start \
|
|
27
|
+
--harness codex \
|
|
28
|
+
--plugin-root "${CODEX_PLUGIN_ROOT}" \
|
|
29
|
+
--state-dir "${BABYSITTER_STATE_DIR}" \
|
|
30
|
+
< "$INPUT_FILE" 2>"$LOG_DIR/babysitter-session-start-hook-stderr.log")
|
|
31
|
+
EXIT_CODE=$?
|
|
32
|
+
|
|
33
|
+
echo "[INFO] $(date -u +%Y-%m-%dT%H:%M:%SZ) CLI exit code=$EXIT_CODE" >> "$LOG_FILE" 2>/dev/null
|
|
34
|
+
|
|
35
|
+
rm -f "$INPUT_FILE" 2>/dev/null
|
|
36
|
+
printf '%s\n' "$RESULT"
|
|
37
|
+
exit $EXIT_CODE
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
6
|
+
STATE_DIR="${BABYSITTER_STATE_DIR:-${PWD}/.a5c}"
|
|
7
|
+
LOG_DIR="${BABYSITTER_LOG_DIR:-$PLUGIN_ROOT/.a5c/logs}"
|
|
8
|
+
LOG_FILE="$LOG_DIR/babysitter-stop-hook.log"
|
|
9
|
+
|
|
10
|
+
export CODEX_PLUGIN_ROOT="${CODEX_PLUGIN_ROOT:-${PLUGIN_ROOT}}"
|
|
11
|
+
export BABYSITTER_STATE_DIR="${STATE_DIR}"
|
|
12
|
+
|
|
13
|
+
mkdir -p "$LOG_DIR" 2>/dev/null
|
|
14
|
+
{
|
|
15
|
+
echo "[INFO] $(date -u +%Y-%m-%dT%H:%M:%SZ) Hook script invoked"
|
|
16
|
+
echo "[INFO] $(date -u +%Y-%m-%dT%H:%M:%SZ) PLUGIN_ROOT=$PLUGIN_ROOT"
|
|
17
|
+
echo "[INFO] $(date -u +%Y-%m-%dT%H:%M:%SZ) STATE_DIR=$STATE_DIR"
|
|
18
|
+
} >> "$LOG_FILE" 2>/dev/null
|
|
19
|
+
|
|
20
|
+
INPUT_FILE=$(mktemp 2>/dev/null || echo "/tmp/codex-stop-hook-$$.json")
|
|
21
|
+
cat > "$INPUT_FILE"
|
|
22
|
+
|
|
23
|
+
echo "[INFO] $(date -u +%Y-%m-%dT%H:%M:%SZ) Hook input received ($(wc -c < "$INPUT_FILE") bytes)" >> "$LOG_FILE" 2>/dev/null
|
|
24
|
+
|
|
25
|
+
RESULT=$(babysitter hook:run \
|
|
26
|
+
--hook-type stop \
|
|
27
|
+
--harness codex \
|
|
28
|
+
--plugin-root "${CODEX_PLUGIN_ROOT}" \
|
|
29
|
+
--state-dir "${BABYSITTER_STATE_DIR}" \
|
|
30
|
+
< "$INPUT_FILE" 2>"$LOG_DIR/babysitter-stop-hook-stderr.log")
|
|
31
|
+
EXIT_CODE=$?
|
|
32
|
+
|
|
33
|
+
echo "[INFO] $(date -u +%Y-%m-%dT%H:%M:%SZ) CLI exit code=$EXIT_CODE" >> "$LOG_FILE" 2>/dev/null
|
|
34
|
+
|
|
35
|
+
rm -f "$INPUT_FILE" 2>/dev/null
|
|
36
|
+
printf '%s\n' "$RESULT"
|
|
37
|
+
exit $EXIT_CODE
|