@astrosheep/square 0.3.5 → 0.3.7
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/codex-plugin/.codex-plugin/plugin.json +3 -2
- package/codex-plugin/hooks/hooks.json +3 -14
- package/dist/activity-feed.js +26 -18
- package/dist/activity.js +10 -10
- package/dist/artifact.js +138 -203
- package/dist/boundary-presentation.js +77 -0
- package/dist/claude-hook.js +4 -94
- package/dist/cli/context.js +7 -7
- package/dist/cli/maintenance-commands.js +10 -26
- package/dist/cli/meta-commands.js +3 -6
- package/dist/cli/observation-commands.js +48 -53
- package/dist/cli/program.js +4 -4
- package/dist/cli/registry.js +5 -5
- package/dist/cli/square-commands.js +27 -20
- package/dist/cmd/notify-once.js +23 -21
- package/dist/codex-hook.js +22 -0
- package/dist/compact.js +1 -1
- package/dist/decisions.js +61 -88
- package/dist/delivery-health.js +104 -210
- package/dist/delivery.js +68 -18
- package/dist/doctor.js +9 -8
- package/dist/harness-claude.js +38 -245
- package/dist/harness-codex.js +82 -616
- package/dist/harness-stage.js +36 -0
- package/dist/harness.js +3 -5
- package/dist/help.js +43 -35
- package/dist/inbox.js +12 -11
- package/dist/index.js +10 -121
- package/dist/list.js +1 -1
- package/dist/model.js +0 -6
- package/dist/notification-failures.js +54 -0
- package/dist/notifications.js +47 -62
- package/dist/paseo-delivery.js +160 -0
- package/dist/paseo-state.js +31 -0
- package/dist/paseo-timeline.js +58 -188
- package/dist/presentation.js +57 -64
- package/dist/presented.js +9 -8
- package/dist/registry.js +55 -45
- package/dist/runtime.js +27 -84
- package/dist/square-application.js +135 -130
- package/dist/square-core.js +3 -11
- package/dist/stream.js +27 -126
- package/dist/wake-sink.js +3 -214
- package/dist/watch.js +65 -122
- package/extensions/square-opencode.js +8 -73
- package/extensions/square-pi.js +8 -132
- package/guides/architect.md +3 -3
- package/guides/participant.md +25 -16
- package/package.json +2 -2
- package/skills/brainstorm/SKILL.md +25 -32
- package/skills/square/.claude-plugin/plugin.json +1 -1
- package/skills/square/SKILL.md +39 -107
- package/skills/square/hooks/hooks.json +2 -13
- package/skills/square-feedback/SKILL.md +4 -4
- package/dist/harness-lifecycle.js +0 -102
- package/dist/square-store.js +0 -111
- package/dist/terminal.js +0 -125
package/dist/harness-codex.js
CHANGED
|
@@ -1,653 +1,119 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { SQUARE_IDENTITY } from './identity.js';
|
|
6
|
-
import {
|
|
6
|
+
import { stageReplacement } from './harness-stage.js';
|
|
7
7
|
export const CODEX_HOOK_COMMAND = SQUARE_IDENTITY.hookCommand;
|
|
8
8
|
export const SQUARE_CODEX_MARKER = SQUARE_IDENTITY.hookMarker;
|
|
9
9
|
export const CODEX_PLUGIN_ID = SQUARE_IDENTITY.pluginId;
|
|
10
10
|
export const CODEX_MARKETPLACE_NAME = SQUARE_IDENTITY.marketplaceName;
|
|
11
|
+
const LEGACY_MARKETPLACES = ['astrosheep-square'];
|
|
11
12
|
export function codexMarketplaceRoot(homeDir) {
|
|
12
13
|
return path.join(homeDir, '.square', 'codex', 'marketplaces', CODEX_MARKETPLACE_NAME);
|
|
13
14
|
}
|
|
14
|
-
export function codexPluginRoot(homeDir) {
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
export function
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
function codexHome(homeDir) {
|
|
21
|
-
return process.env.CODEX_HOME?.trim() || path.join(homeDir, '.codex');
|
|
22
|
-
}
|
|
23
|
-
export function codexHomeHooksPath(homeDir) {
|
|
24
|
-
return path.join(codexHome(homeDir), 'hooks.json');
|
|
25
|
-
}
|
|
26
|
-
export function codexConfigPath(homeDir) {
|
|
27
|
-
return path.join(codexHome(homeDir), 'config.toml');
|
|
28
|
-
}
|
|
29
|
-
function managedHooksPath(homeDir) {
|
|
30
|
-
return path.join(homeDir, '.square', 'codex', 'hooks.json');
|
|
31
|
-
}
|
|
32
|
-
function sectionRange(lines, section) {
|
|
33
|
-
const escaped = section.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
34
|
-
const header = new RegExp(`^\\s*\\[${escaped}\\]\\s*(?:#.*)?$`);
|
|
35
|
-
const start = lines.findIndex((line) => header.test(line));
|
|
36
|
-
if (start < 0)
|
|
37
|
-
return undefined;
|
|
38
|
-
const next = lines.findIndex((line, index) => index > start && /^\s*\[[^\]]+\]\s*(?:#.*)?$/.test(line));
|
|
39
|
-
return { start, end: next < 0 ? lines.length : next };
|
|
40
|
-
}
|
|
41
|
-
export function upsertTomlSectionKey(text, section, key, value) {
|
|
42
|
-
const lines = text === '' ? [] : text.replace(/\n$/, '').split('\n');
|
|
43
|
-
const range = sectionRange(lines, section);
|
|
44
|
-
const assignment = `${key} = ${value}`;
|
|
45
|
-
if (range === undefined) {
|
|
46
|
-
if (lines.length > 0 && lines.at(-1) !== '')
|
|
47
|
-
lines.push('');
|
|
48
|
-
lines.push(`[${section}]`, assignment);
|
|
49
|
-
return `${lines.join('\n')}\n`;
|
|
50
|
-
}
|
|
51
|
-
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
52
|
-
const keyPattern = new RegExp(`^\\s*${escaped}\\s*=`);
|
|
53
|
-
const existing = lines.findIndex((line, index) => index > range.start && index < range.end && keyPattern.test(line));
|
|
54
|
-
if (existing < 0)
|
|
55
|
-
lines.splice(range.end, 0, assignment);
|
|
56
|
-
else
|
|
57
|
-
lines[existing] = assignment;
|
|
58
|
-
return `${lines.join('\n')}\n`;
|
|
59
|
-
}
|
|
60
|
-
function writeAtomic(filePath, contents) {
|
|
61
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
62
|
-
const temporary = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
63
|
-
fs.writeFileSync(temporary, contents, { mode: 0o600 });
|
|
64
|
-
fs.renameSync(temporary, filePath);
|
|
65
|
-
}
|
|
66
|
-
function writeJsonAtomic(filePath, value) {
|
|
67
|
-
writeAtomic(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
68
|
-
}
|
|
69
|
-
function backup(filePath) {
|
|
70
|
-
const backupPath = `${filePath}.square-bak`;
|
|
71
|
-
if (fs.existsSync(filePath) && !fs.existsSync(backupPath))
|
|
72
|
-
fs.copyFileSync(filePath, backupPath);
|
|
73
|
-
}
|
|
74
|
-
function requireSuccess(result, operation) {
|
|
75
|
-
if (result.status === 0)
|
|
76
|
-
return;
|
|
77
|
-
const detail = result.stderr.trim() || result.stdout.trim() || `exit ${result.status}`;
|
|
78
|
-
throw new Error(`Codex ${operation} failed: ${detail}`);
|
|
79
|
-
}
|
|
15
|
+
export function codexPluginRoot(homeDir) { return path.join(codexMarketplaceRoot(homeDir), 'plugins', SQUARE_IDENTITY.pluginName); }
|
|
16
|
+
export function codexPluginHooksPath(homeDir) { return path.join(codexPluginRoot(homeDir), 'hooks', 'hooks.json'); }
|
|
17
|
+
function codexHome(homeDir) { return path.join(homeDir, '.codex'); }
|
|
18
|
+
export function codexHomeHooksPath(homeDir) { return path.join(codexHome(homeDir), 'hooks.json'); }
|
|
19
|
+
export function codexConfigPath(homeDir) { return path.join(codexHome(homeDir), 'config.toml'); }
|
|
80
20
|
function runCodex(homeDir, args) {
|
|
81
21
|
const result = spawnSync(process.env.SQUARE_CODEX_BIN || 'codex', args, {
|
|
82
|
-
encoding: 'utf8',
|
|
83
|
-
env: {
|
|
84
|
-
...process.env,
|
|
85
|
-
HOME: homeDir,
|
|
86
|
-
CODEX_HOME: codexHome(homeDir),
|
|
87
|
-
},
|
|
88
|
-
maxBuffer: 4 * 1024 * 1024,
|
|
89
|
-
timeout: 30_000,
|
|
22
|
+
encoding: 'utf8', env: { ...process.env, HOME: homeDir, CODEX_HOME: codexHome(homeDir) }, timeout: 30_000,
|
|
90
23
|
});
|
|
91
24
|
if (result.error)
|
|
92
25
|
throw result.error;
|
|
93
|
-
return {
|
|
94
|
-
status: result.status ?? 1,
|
|
95
|
-
stdout: result.stdout || '',
|
|
96
|
-
stderr: result.stderr || '',
|
|
97
|
-
};
|
|
26
|
+
return { status: result.status ?? 1, stdout: result.stdout || '', stderr: result.stderr || '' };
|
|
98
27
|
}
|
|
99
|
-
function
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (group === null || typeof group !== 'object')
|
|
104
|
-
return false;
|
|
105
|
-
const hooks = group.hooks;
|
|
106
|
-
if (!Array.isArray(hooks))
|
|
107
|
-
return false;
|
|
108
|
-
return hooks.some((hook) => {
|
|
109
|
-
if (hook === null || typeof hook !== 'object')
|
|
110
|
-
return false;
|
|
111
|
-
const command = hook.command;
|
|
112
|
-
const statusMessage = hook.statusMessage;
|
|
113
|
-
return command === CODEX_HOOK_COMMAND || statusMessage === SQUARE_CODEX_MARKER;
|
|
114
|
-
});
|
|
28
|
+
function requireSuccess(result, operation, allowMissing = false) {
|
|
29
|
+
if (result.status === 0 || (allowMissing && /not configured|not installed|not found/i.test(result.stderr)))
|
|
30
|
+
return;
|
|
31
|
+
throw new Error(`Codex ${operation} failed: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.status}`}`);
|
|
115
32
|
}
|
|
116
|
-
export function
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
if (
|
|
121
|
-
return
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (remaining.length === groups.length)
|
|
129
|
-
continue;
|
|
130
|
-
changed = true;
|
|
131
|
-
if (remaining.length === 0)
|
|
132
|
-
delete root.hooks[event];
|
|
133
|
-
else
|
|
134
|
-
root.hooks[event] = remaining;
|
|
135
|
-
}
|
|
136
|
-
if (!changed)
|
|
137
|
-
return false;
|
|
138
|
-
if (Object.keys(root.hooks).length === 0)
|
|
139
|
-
fs.rmSync(filePath, { force: true });
|
|
33
|
+
export function upsertTomlSectionKey(text, section, key, value) {
|
|
34
|
+
const lines = text.replace(/\n$/, '').split('\n').filter((line, i) => line !== '' || i < text.length);
|
|
35
|
+
const header = `[${section}]`;
|
|
36
|
+
const start = lines.findIndex((line) => line.trim() === header);
|
|
37
|
+
if (start < 0)
|
|
38
|
+
return `${text.trimEnd()}${text.trim() ? '\n\n' : ''}${header}\n${key} = ${value}\n`;
|
|
39
|
+
const end = lines.findIndex((line, i) => i > start && /^\s*\[[^\]]+\]/.test(line));
|
|
40
|
+
const stop = end < 0 ? lines.length : end;
|
|
41
|
+
const match = new RegExp(`^\\s*${key.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&')}\\s*=`);
|
|
42
|
+
const existing = lines.findIndex((line, i) => i > start && i < stop && match.test(line));
|
|
43
|
+
if (existing < 0)
|
|
44
|
+
lines.splice(stop, 0, `${key} = ${value}`);
|
|
140
45
|
else
|
|
141
|
-
|
|
142
|
-
return
|
|
46
|
+
lines[existing] = `${key} = ${value}`;
|
|
47
|
+
return `${lines.join('\n')}\n`;
|
|
143
48
|
}
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
{
|
|
150
|
-
name: SQUARE_IDENTITY.pluginName,
|
|
151
|
-
source: { source: 'local', path: './plugins/square' },
|
|
152
|
-
policy: { installation: 'AVAILABLE', authentication: 'ON_USE' },
|
|
153
|
-
category: 'Developer Tools',
|
|
154
|
-
},
|
|
155
|
-
],
|
|
156
|
-
};
|
|
49
|
+
function writeAtomic(file, text) {
|
|
50
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
51
|
+
const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
52
|
+
fs.writeFileSync(temp, text, { mode: 0o600 });
|
|
53
|
+
fs.renameSync(temp, file);
|
|
157
54
|
}
|
|
158
|
-
function
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const skill = path.join(pluginRoot, 'skills', 'square', 'SKILL.md');
|
|
55
|
+
export async function installCodexPlugin(homeDir, run = runCodex) {
|
|
56
|
+
const root = codexMarketplaceRoot(homeDir);
|
|
57
|
+
const config = codexConfigPath(homeDir);
|
|
58
|
+
const current = fs.existsSync(config) ? fs.readFileSync(config, 'utf8') : '';
|
|
59
|
+
const staged = stageReplacement(root, (stage) => {
|
|
60
|
+
const plugin = path.join(stage, 'plugins', SQUARE_IDENTITY.pluginName);
|
|
61
|
+
fs.cpSync(fileURLToPath(new URL('../codex-plugin/', import.meta.url)), plugin, { recursive: true });
|
|
62
|
+
const skill = path.join(plugin, 'skills', 'square', 'SKILL.md');
|
|
167
63
|
fs.mkdirSync(path.dirname(skill), { recursive: true });
|
|
168
|
-
fs.copyFileSync(path.join(
|
|
169
|
-
|
|
170
|
-
if (fs.existsSync(marketplaceRoot))
|
|
171
|
-
fs.renameSync(marketplaceRoot, backupPath);
|
|
172
|
-
fs.renameSync(stage, marketplaceRoot);
|
|
173
|
-
}
|
|
174
|
-
catch (error) {
|
|
175
|
-
try {
|
|
176
|
-
fs.rmSync(stage, { recursive: true, force: true });
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
// Keep the source error; staging cleanup is best effort.
|
|
180
|
-
}
|
|
181
|
-
if (fs.existsSync(backupPath) && !fs.existsSync(marketplaceRoot)) {
|
|
182
|
-
fs.renameSync(backupPath, marketplaceRoot);
|
|
183
|
-
}
|
|
184
|
-
throw error;
|
|
185
|
-
}
|
|
186
|
-
return {
|
|
187
|
-
desired: {
|
|
188
|
-
marketplaceName: CODEX_MARKETPLACE_NAME,
|
|
189
|
-
marketplaceRoot,
|
|
190
|
-
pluginId: CODEX_PLUGIN_ID,
|
|
191
|
-
},
|
|
192
|
-
rollback() {
|
|
193
|
-
fs.rmSync(marketplaceRoot, { recursive: true, force: true });
|
|
194
|
-
if (fs.existsSync(backupPath))
|
|
195
|
-
fs.renameSync(backupPath, marketplaceRoot);
|
|
196
|
-
},
|
|
197
|
-
finalize() {
|
|
198
|
-
fs.rmSync(backupPath, { recursive: true, force: true });
|
|
199
|
-
},
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
function parseInventory(stdout) {
|
|
203
|
-
let payload;
|
|
204
|
-
try {
|
|
205
|
-
payload = JSON.parse(stdout);
|
|
206
|
-
}
|
|
207
|
-
catch {
|
|
208
|
-
throw new Error('Codex marketplace inventory returned invalid JSON.');
|
|
209
|
-
}
|
|
210
|
-
if (payload === null || typeof payload !== 'object' || !Array.isArray(payload.marketplaces)) {
|
|
211
|
-
throw new Error('Codex marketplace inventory is malformed.');
|
|
212
|
-
}
|
|
213
|
-
const marketplaces = payload.marketplaces.map(parseMarketplaceRegistration);
|
|
214
|
-
return { marketplaces };
|
|
215
|
-
}
|
|
216
|
-
function parseMarketplaceRegistration(value) {
|
|
217
|
-
if (value === null || typeof value !== 'object') {
|
|
218
|
-
throw new Error('Codex marketplace inventory entry is invalid.');
|
|
219
|
-
}
|
|
220
|
-
const entry = value;
|
|
221
|
-
if (typeof entry.name !== 'string' || typeof entry.root !== 'string') {
|
|
222
|
-
throw new Error('Codex marketplace inventory entry is malformed.');
|
|
223
|
-
}
|
|
224
|
-
const source = entry.marketplaceSource;
|
|
225
|
-
if (source === undefined)
|
|
226
|
-
return { name: entry.name, source: entry.root, local: false };
|
|
227
|
-
if (source === null || typeof source !== 'object')
|
|
228
|
-
throw new Error('Codex marketplace source is malformed.');
|
|
229
|
-
const marketplaceSource = source;
|
|
230
|
-
if (typeof marketplaceSource.sourceType !== 'string') {
|
|
231
|
-
throw new Error('Codex marketplace source is malformed.');
|
|
232
|
-
}
|
|
233
|
-
if (marketplaceSource.sourceType !== 'local') {
|
|
234
|
-
const stableSource = typeof marketplaceSource.source === 'string'
|
|
235
|
-
? marketplaceSource.source
|
|
236
|
-
: entry.root;
|
|
237
|
-
return {
|
|
238
|
-
name: entry.name,
|
|
239
|
-
source: `${marketplaceSource.sourceType}:${stableSource}`,
|
|
240
|
-
local: false,
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
if (typeof marketplaceSource.source !== 'string') {
|
|
244
|
-
throw new Error('Codex local marketplace inventory entry is malformed.');
|
|
245
|
-
}
|
|
246
|
-
return { name: entry.name, source: marketplaceSource.source, local: true };
|
|
247
|
-
}
|
|
248
|
-
function pluginHooksFromList(result) {
|
|
249
|
-
const entries = result?.data;
|
|
250
|
-
if (!Array.isArray(entries))
|
|
251
|
-
throw new Error('Codex hooks/list returned invalid data.');
|
|
252
|
-
const hooks = [];
|
|
253
|
-
for (const entry of entries) {
|
|
254
|
-
if (!Array.isArray(entry.hooks))
|
|
255
|
-
continue;
|
|
256
|
-
for (const value of entry.hooks) {
|
|
257
|
-
if (value === null || typeof value !== 'object')
|
|
258
|
-
continue;
|
|
259
|
-
const hook = value;
|
|
260
|
-
if (hook.pluginId !== CODEX_PLUGIN_ID)
|
|
261
|
-
continue;
|
|
262
|
-
if (typeof hook.key !== 'string' ||
|
|
263
|
-
typeof hook.eventName !== 'string' ||
|
|
264
|
-
typeof hook.enabled !== 'boolean' ||
|
|
265
|
-
typeof hook.currentHash !== 'string' ||
|
|
266
|
-
typeof hook.trustStatus !== 'string') {
|
|
267
|
-
throw new Error('Codex hooks/list returned an invalid Square hook.');
|
|
268
|
-
}
|
|
269
|
-
hooks.push({
|
|
270
|
-
key: hook.key,
|
|
271
|
-
eventName: hook.eventName,
|
|
272
|
-
pluginId: CODEX_PLUGIN_ID,
|
|
273
|
-
enabled: hook.enabled,
|
|
274
|
-
currentHash: hook.currentHash,
|
|
275
|
-
trustStatus: hook.trustStatus,
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
return hooks;
|
|
280
|
-
}
|
|
281
|
-
function expectedHooks(hooks) {
|
|
282
|
-
if (hooks.length !== 2 || hooks.some((hook) => !hook.enabled))
|
|
283
|
-
return false;
|
|
284
|
-
const events = hooks.map((hook) => hook.eventName).sort();
|
|
285
|
-
return events[0] === 'stop' && events[1] === 'userPromptSubmit';
|
|
286
|
-
}
|
|
287
|
-
function trustUpdate(hooks) {
|
|
288
|
-
return Object.fromEntries(hooks.map((hook) => [hook.key, { trusted_hash: hook.currentHash }]));
|
|
289
|
-
}
|
|
290
|
-
export function inspectCodexPluginHooks(homeDir, trust) {
|
|
291
|
-
return new Promise((resolve, reject) => {
|
|
292
|
-
const child = spawn(process.env.SQUARE_CODEX_BIN || 'codex', ['app-server', '--stdio'], {
|
|
293
|
-
env: {
|
|
294
|
-
...process.env,
|
|
295
|
-
HOME: homeDir,
|
|
296
|
-
CODEX_HOME: codexHome(homeDir),
|
|
297
|
-
},
|
|
298
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
299
|
-
});
|
|
300
|
-
let stdout = '';
|
|
301
|
-
let stderr = '';
|
|
302
|
-
let settled = false;
|
|
303
|
-
const finish = (error, hooks) => {
|
|
304
|
-
if (settled)
|
|
305
|
-
return;
|
|
306
|
-
settled = true;
|
|
307
|
-
clearTimeout(timer);
|
|
308
|
-
child.kill('SIGTERM');
|
|
309
|
-
if (error !== undefined)
|
|
310
|
-
reject(error);
|
|
311
|
-
else
|
|
312
|
-
resolve(hooks ?? []);
|
|
313
|
-
};
|
|
314
|
-
const timer = setTimeout(() => finish(new Error(`Codex hooks API timed out${stderr.trim() ? `: ${stderr.trim()}` : ''}`)), 30_000);
|
|
315
|
-
const send = (message) => child.stdin.write(`${JSON.stringify(message)}\n`);
|
|
316
|
-
const requestHooks = (id) => {
|
|
317
|
-
send({ id, method: 'hooks/list', params: { cwds: [process.cwd()] } });
|
|
318
|
-
};
|
|
319
|
-
const sendTrust = (hooks) => {
|
|
320
|
-
send({
|
|
321
|
-
id: 2,
|
|
322
|
-
method: 'config/batchWrite',
|
|
323
|
-
params: {
|
|
324
|
-
edits: [{ keyPath: 'hooks.state', value: trustUpdate(hooks), mergeStrategy: 'upsert' }],
|
|
325
|
-
reloadUserConfig: true,
|
|
326
|
-
},
|
|
327
|
-
});
|
|
328
|
-
};
|
|
329
|
-
const failForMessage = (message) => {
|
|
330
|
-
if (message.error === undefined)
|
|
331
|
-
return false;
|
|
332
|
-
const detail = typeof message.error.message === 'string'
|
|
333
|
-
? message.error.message
|
|
334
|
-
: JSON.stringify(message.error);
|
|
335
|
-
finish(new Error(`Codex hooks API failed: ${detail}`));
|
|
336
|
-
return true;
|
|
337
|
-
};
|
|
338
|
-
const handleMessage = (message) => {
|
|
339
|
-
if (failForMessage(message))
|
|
340
|
-
return;
|
|
341
|
-
if (message.id === 0) {
|
|
342
|
-
send({ method: 'initialized', params: {} });
|
|
343
|
-
requestHooks(1);
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
if (message.id === 1) {
|
|
347
|
-
const hooks = pluginHooksFromList(message.result);
|
|
348
|
-
if (!trust) {
|
|
349
|
-
finish(undefined, hooks);
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
if (!expectedHooks(hooks)) {
|
|
353
|
-
finish(new Error(`Codex discovered an unexpected hook set for ${CODEX_PLUGIN_ID}.`));
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
sendTrust(hooks);
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
if (message.id === 2) {
|
|
360
|
-
requestHooks(3);
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
if (message.id === 3)
|
|
364
|
-
finish(undefined, pluginHooksFromList(message.result));
|
|
365
|
-
};
|
|
366
|
-
const consumeStdout = (chunk) => {
|
|
367
|
-
stdout += String(chunk);
|
|
368
|
-
while (stdout.includes('\n')) {
|
|
369
|
-
const newline = stdout.indexOf('\n');
|
|
370
|
-
const line = stdout.slice(0, newline);
|
|
371
|
-
stdout = stdout.slice(newline + 1);
|
|
372
|
-
if (!line.trim())
|
|
373
|
-
continue;
|
|
374
|
-
try {
|
|
375
|
-
handleMessage(JSON.parse(line));
|
|
376
|
-
}
|
|
377
|
-
catch (error) {
|
|
378
|
-
const diagnostic = error instanceof Error ? error : new Error(String(error));
|
|
379
|
-
if (diagnostic instanceof SyntaxError) {
|
|
380
|
-
finish(new Error(`Codex hooks API returned invalid JSON: ${line}`));
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
finish(diagnostic);
|
|
384
|
-
}
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
};
|
|
389
|
-
child.on('error', (error) => finish(error));
|
|
390
|
-
child.stdin.on('error', (error) => finish(error));
|
|
391
|
-
child.stderr.on('data', (chunk) => {
|
|
392
|
-
stderr += String(chunk);
|
|
393
|
-
});
|
|
394
|
-
child.stdout.on('data', consumeStdout);
|
|
395
|
-
child.on('exit', (code) => {
|
|
396
|
-
if (!settled) {
|
|
397
|
-
const suffix = stderr.trim() ? `: ${stderr.trim()}` : '';
|
|
398
|
-
finish(new Error(`Codex hooks API exited ${code ?? 'without status'}${suffix}`));
|
|
399
|
-
}
|
|
400
|
-
});
|
|
401
|
-
send({
|
|
402
|
-
id: 0,
|
|
403
|
-
method: 'initialize',
|
|
404
|
-
params: {
|
|
405
|
-
clientInfo: {
|
|
406
|
-
name: SQUARE_IDENTITY.clientName,
|
|
407
|
-
title: SQUARE_IDENTITY.productName,
|
|
408
|
-
version: SQUARE_IDENTITY.packageVersion,
|
|
409
|
-
},
|
|
410
|
-
},
|
|
411
|
-
});
|
|
64
|
+
fs.copyFileSync(path.join(fileURLToPath(new URL('../skills/square/', import.meta.url)), 'SKILL.md'), skill);
|
|
65
|
+
writeAtomic(path.join(stage, '.agents', 'plugins', 'marketplace.json'), `${JSON.stringify({ name: CODEX_MARKETPLACE_NAME, plugins: [{ name: SQUARE_IDENTITY.pluginName, source: { source: 'local', path: './plugins/square' } }] }, null, 2)}\n`);
|
|
412
66
|
});
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const inspectInventory = (homeDir) => {
|
|
416
|
-
const result = run(homeDir, ['plugin', 'marketplace', 'list', '--json']);
|
|
417
|
-
requireSuccess(result, 'marketplace inventory');
|
|
418
|
-
return parseInventory(result.stdout);
|
|
419
|
-
};
|
|
420
|
-
const stageBundle = (homeDir, marketplaceRoot) => {
|
|
421
|
-
const config = codexConfigPath(homeDir);
|
|
422
|
-
const hooks = codexHomeHooksPath(homeDir);
|
|
423
|
-
backup(config);
|
|
424
|
-
backup(hooks);
|
|
425
|
-
const current = fs.existsSync(config) ? fs.readFileSync(config, 'utf8') : '';
|
|
67
|
+
const notes = [];
|
|
68
|
+
try {
|
|
426
69
|
writeAtomic(config, upsertTomlSectionKey(current, 'features', 'hooks', 'true'));
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
requireSuccess(result, 'marketplace install');
|
|
432
|
-
};
|
|
433
|
-
const installOrUpdate = (homeDir, pluginId) => {
|
|
434
|
-
const result = run(homeDir, ['plugin', 'add', pluginId, '--json']);
|
|
435
|
-
requireSuccess(result, 'plugin install');
|
|
70
|
+
requireSuccess(run(homeDir, ['plugin', 'marketplace', 'add', root, '--json']), 'marketplace install', true);
|
|
71
|
+
const installed = run(homeDir, ['plugin', 'add', CODEX_PLUGIN_ID, '--json']);
|
|
72
|
+
requireSuccess(installed, 'plugin install');
|
|
73
|
+
let installedPath;
|
|
436
74
|
try {
|
|
437
|
-
const
|
|
438
|
-
if (typeof
|
|
439
|
-
|
|
75
|
+
const value = JSON.parse(installed.stdout).installedPath;
|
|
76
|
+
if (typeof value === 'string')
|
|
77
|
+
installedPath = value;
|
|
440
78
|
}
|
|
441
79
|
catch {
|
|
442
|
-
notes.push('Codex
|
|
80
|
+
notes.push('Codex returned non-JSON installation output.');
|
|
443
81
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
}
|
|
452
|
-
catch {
|
|
453
|
-
throw new Error('Codex plugin inventory returned invalid JSON.');
|
|
454
|
-
}
|
|
455
|
-
const installedAndEnabled = Array.isArray(entries) && entries.some((entry) => {
|
|
456
|
-
if (entry === null || typeof entry !== 'object')
|
|
457
|
-
return false;
|
|
458
|
-
const plugin = entry;
|
|
459
|
-
return plugin.pluginId === pluginId && plugin.installed === true && plugin.enabled === true;
|
|
460
|
-
});
|
|
461
|
-
if (!installedAndEnabled) {
|
|
462
|
-
throw new Error(`Codex did not verify ${pluginId} as installed and enabled.`);
|
|
463
|
-
}
|
|
464
|
-
const hooks = await hooksRuntime(homeDir, true);
|
|
465
|
-
if (!expectedHooks(hooks)) {
|
|
466
|
-
throw new Error(`Codex did not discover both Square plugin hooks for ${pluginId}.`);
|
|
467
|
-
}
|
|
468
|
-
const untrusted = hooks.filter((hook) => hook.trustStatus !== 'trusted');
|
|
469
|
-
if (untrusted.length > 0) {
|
|
470
|
-
throw new Error(`Codex did not trust Square plugin hooks: ${untrusted.map((hook) => hook.key).join(', ')}`);
|
|
471
|
-
}
|
|
472
|
-
};
|
|
473
|
-
const removePlugin = (homeDir, pluginId) => {
|
|
474
|
-
const result = run(homeDir, ['plugin', 'remove', pluginId, '--json']);
|
|
475
|
-
if (result.status !== 0 && !result.stderr.includes('is not configured or installed')) {
|
|
476
|
-
requireSuccess(result, 'plugin removal');
|
|
477
|
-
}
|
|
478
|
-
};
|
|
479
|
-
const removeMarketplace = (homeDir, marketplaceName) => {
|
|
480
|
-
const result = run(homeDir, ['plugin', 'marketplace', 'remove', marketplaceName, '--json']);
|
|
481
|
-
if (result.status !== 0 && !result.stderr.includes('is not configured or installed')) {
|
|
482
|
-
requireSuccess(result, 'marketplace removal');
|
|
483
|
-
}
|
|
484
|
-
};
|
|
485
|
-
const retireDirectDelivery = (homeDir) => {
|
|
486
|
-
const hooks = codexHomeHooksPath(homeDir);
|
|
487
|
-
try {
|
|
488
|
-
if (stripSquareCodexHooks(hooks))
|
|
489
|
-
notes.push(`Removed legacy Square handlers from ${hooks}.`);
|
|
490
|
-
}
|
|
491
|
-
catch (error) {
|
|
492
|
-
notes.push(`Legacy hooks were left untouched: ${error instanceof Error ? error.message : String(error)}`);
|
|
82
|
+
for (const marketplace of LEGACY_MARKETPLACES) {
|
|
83
|
+
const pluginId = `${SQUARE_IDENTITY.pluginName}@${marketplace}`;
|
|
84
|
+
if (!current.includes(`[marketplaces.${marketplace}]`) && !current.includes(`[plugins."${pluginId}"]`))
|
|
85
|
+
continue;
|
|
86
|
+
requireSuccess(run(homeDir, ['plugin', 'remove', pluginId, '--json']), 'legacy plugin removal', true);
|
|
87
|
+
requireSuccess(run(homeDir, ['plugin', 'marketplace', 'remove', marketplace, '--json']), 'legacy marketplace removal', true);
|
|
88
|
+
notes.push(`retired ${pluginId}`);
|
|
493
89
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
return {
|
|
497
|
-
host: 'codex',
|
|
498
|
-
marketplaceName: CODEX_MARKETPLACE_NAME,
|
|
499
|
-
pluginId: CODEX_PLUGIN_ID,
|
|
500
|
-
managedRoot: (homeDir) => path.join(homeDir, '.square', 'codex'),
|
|
501
|
-
stageBundle,
|
|
502
|
-
inspectInventory,
|
|
503
|
-
registerMarketplace(homeDir, desired) {
|
|
504
|
-
registerMarketplace(homeDir, desired.marketplaceRoot);
|
|
505
|
-
},
|
|
506
|
-
installOrUpdate(homeDir, desired) {
|
|
507
|
-
installOrUpdate(homeDir, desired.pluginId);
|
|
508
|
-
},
|
|
509
|
-
verifyPluginAndHooks(homeDir, desired) {
|
|
510
|
-
return verifyPluginAndHooks(homeDir, desired.marketplaceName, desired.pluginId);
|
|
511
|
-
},
|
|
512
|
-
removePlugin(homeDir, pluginId) {
|
|
513
|
-
removePlugin(homeDir, pluginId);
|
|
514
|
-
},
|
|
515
|
-
removeMarketplace(homeDir, marketplaceName) {
|
|
516
|
-
removeMarketplace(homeDir, marketplaceName);
|
|
517
|
-
},
|
|
518
|
-
removeManagedSource(source) {
|
|
519
|
-
fs.rmSync(source, { recursive: true, force: true });
|
|
520
|
-
},
|
|
521
|
-
retireDirectDelivery,
|
|
522
|
-
removeManagedRoot(homeDir) {
|
|
523
|
-
fs.rmSync(path.join(homeDir, '.square', 'codex'), { recursive: true, force: true });
|
|
524
|
-
},
|
|
525
|
-
};
|
|
526
|
-
}
|
|
527
|
-
function isManagedMarketplace(homeDir, registration) {
|
|
528
|
-
if (!registration.local)
|
|
529
|
-
return false;
|
|
530
|
-
const root = path.join(homeDir, '.square', 'codex');
|
|
531
|
-
const relative = path.relative(path.resolve(root), path.resolve(registration.source));
|
|
532
|
-
return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
|
|
533
|
-
}
|
|
534
|
-
function marketplaceRootFromInventory(homeDir, inventory) {
|
|
535
|
-
return inventory.marketplaces.find((entry) => entry.name === CODEX_MARKETPLACE_NAME && isManagedMarketplace(homeDir, entry))?.source ?? codexMarketplaceRoot(homeDir);
|
|
536
|
-
}
|
|
537
|
-
export async function installCodexPlugin(homeDir, run = runCodex, hooksRuntime = inspectCodexPluginHooks) {
|
|
538
|
-
const notes = [];
|
|
539
|
-
const installed = {};
|
|
540
|
-
const protocol = codexProtocol(run, hooksRuntime, notes, installed);
|
|
541
|
-
const inventory = await reconcileInstall(homeDir, protocol);
|
|
542
|
-
const marketplaceRoot = marketplaceRootFromInventory(homeDir, inventory);
|
|
543
|
-
return {
|
|
544
|
-
configPath: codexConfigPath(homeDir),
|
|
545
|
-
marketplaceRoot,
|
|
546
|
-
pluginRoot: path.join(marketplaceRoot, 'plugins', SQUARE_IDENTITY.pluginName),
|
|
547
|
-
...(installed.path === undefined ? {} : { installedPath: installed.path }),
|
|
548
|
-
notes,
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
export async function uninstallCodexPlugin(homeDir, run = runCodex) {
|
|
552
|
-
const notes = [];
|
|
553
|
-
const base = codexProtocol(run, async () => [], notes, {});
|
|
554
|
-
const protocol = {
|
|
555
|
-
...base,
|
|
556
|
-
async inspectInventory(currentHome) {
|
|
557
|
-
const inventory = await base.inspectInventory(currentHome);
|
|
558
|
-
if (inventory.marketplaces.some((entry) => isManagedMarketplace(currentHome, entry))) {
|
|
559
|
-
return inventory;
|
|
560
|
-
}
|
|
561
|
-
return {
|
|
562
|
-
marketplaces: [
|
|
563
|
-
...inventory.marketplaces,
|
|
564
|
-
{
|
|
565
|
-
name: CODEX_MARKETPLACE_NAME,
|
|
566
|
-
source: codexMarketplaceRoot(currentHome),
|
|
567
|
-
local: true,
|
|
568
|
-
pluginIds: [CODEX_PLUGIN_ID],
|
|
569
|
-
},
|
|
570
|
-
],
|
|
571
|
-
};
|
|
572
|
-
},
|
|
573
|
-
};
|
|
574
|
-
await reconcileUninstall(homeDir, protocol);
|
|
575
|
-
const root = codexMarketplaceRoot(homeDir);
|
|
576
|
-
fs.rmSync(root, { recursive: true, force: true });
|
|
577
|
-
fs.rmSync(managedHooksPath(homeDir), { force: true });
|
|
578
|
-
return { paths: [root, codexHomeHooksPath(homeDir), managedHooksPath(homeDir)], notes };
|
|
579
|
-
}
|
|
580
|
-
function pluginHooksInstalled(filePath) {
|
|
581
|
-
try {
|
|
582
|
-
const contents = fs.readFileSync(filePath, 'utf8');
|
|
583
|
-
const hooks = JSON.parse(contents);
|
|
584
|
-
return Array.isArray(hooks.hooks?.UserPromptSubmit) && Array.isArray(hooks.hooks?.Stop);
|
|
585
|
-
}
|
|
586
|
-
catch {
|
|
587
|
-
return false;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
function codexPluginStatus(homeDir, run) {
|
|
591
|
-
const listed = run(homeDir, ['plugin', 'list', '--marketplace', CODEX_MARKETPLACE_NAME, '--json']);
|
|
592
|
-
if (listed.status !== 0)
|
|
593
|
-
return `○ ${CODEX_PLUGIN_ID} unavailable`;
|
|
594
|
-
try {
|
|
595
|
-
const payload = JSON.parse(listed.stdout);
|
|
596
|
-
const plugin = payload.installed?.find((item) => item.pluginId === CODEX_PLUGIN_ID);
|
|
597
|
-
return plugin?.installed === true && plugin.enabled === true
|
|
598
|
-
? `✓ ${CODEX_PLUGIN_ID} installed and enabled`
|
|
599
|
-
: `○ ${CODEX_PLUGIN_ID} is not installed and enabled`;
|
|
90
|
+
staged.finalize();
|
|
91
|
+
return { configPath: config, marketplaceRoot: root, pluginRoot: codexPluginRoot(homeDir), ...(installedPath ? { installedPath } : {}), notes };
|
|
600
92
|
}
|
|
601
|
-
catch {
|
|
602
|
-
|
|
93
|
+
catch (error) {
|
|
94
|
+
staged.rollback();
|
|
95
|
+
throw error;
|
|
603
96
|
}
|
|
604
97
|
}
|
|
605
|
-
async function
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
: `✕ Codex runtime did not discover both ${CODEX_PLUGIN_ID} hooks`;
|
|
612
|
-
}
|
|
613
|
-
catch (error) {
|
|
614
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
615
|
-
return `✕ Codex hooks runtime unavailable (${detail})`;
|
|
98
|
+
export async function uninstallCodexPlugin(homeDir, run = runCodex) {
|
|
99
|
+
requireSuccess(run(homeDir, ['plugin', 'remove', CODEX_PLUGIN_ID, '--json']), 'plugin removal', true);
|
|
100
|
+
requireSuccess(run(homeDir, ['plugin', 'marketplace', 'remove', CODEX_MARKETPLACE_NAME, '--json']), 'marketplace removal', true);
|
|
101
|
+
for (const marketplace of LEGACY_MARKETPLACES) {
|
|
102
|
+
requireSuccess(run(homeDir, ['plugin', 'remove', `${SQUARE_IDENTITY.pluginName}@${marketplace}`, '--json']), 'legacy plugin removal', true);
|
|
103
|
+
requireSuccess(run(homeDir, ['plugin', 'marketplace', 'remove', marketplace, '--json']), 'legacy marketplace removal', true);
|
|
616
104
|
}
|
|
105
|
+
const root = codexMarketplaceRoot(homeDir);
|
|
106
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
107
|
+
fs.rmSync(codexHomeHooksPath(homeDir), { force: true });
|
|
108
|
+
return { paths: [root, codexConfigPath(homeDir), codexHomeHooksPath(homeDir)], notes: [] };
|
|
617
109
|
}
|
|
618
|
-
export async function doctorCodexPlugin(homeDir, run = runCodex
|
|
619
|
-
const
|
|
620
|
-
const inventory = await protocol.inspectInventory(homeDir);
|
|
621
|
-
const marketplaceRoot = marketplaceRootFromInventory(homeDir, inventory);
|
|
622
|
-
const stale = staleManagedRegistrations(inventory, protocol.managedRoot(homeDir), {
|
|
623
|
-
marketplaceName: CODEX_MARKETPLACE_NAME,
|
|
624
|
-
marketplaceRoot,
|
|
625
|
-
pluginId: CODEX_PLUGIN_ID,
|
|
626
|
-
});
|
|
627
|
-
const current = inventory.marketplaces.some((entry) => entry.name === CODEX_MARKETPLACE_NAME && isManagedMarketplace(homeDir, entry));
|
|
110
|
+
export async function doctorCodexPlugin(homeDir, run = runCodex) {
|
|
111
|
+
const root = codexMarketplaceRoot(homeDir);
|
|
628
112
|
const config = codexConfigPath(homeDir);
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
? `✓ features.hooks=true in ${config}`
|
|
635
|
-
: `○ features.hooks missing in ${config}`,
|
|
636
|
-
pluginHooksInstalled(bundleHooks)
|
|
637
|
-
? `✓ Square plugin hooks ${bundleHooks}`
|
|
638
|
-
: `○ Square plugin bundle missing ${bundleRoot}`,
|
|
639
|
-
current
|
|
640
|
-
? `✓ ${CODEX_PLUGIN_ID} marketplace registered`
|
|
641
|
-
: `○ ${CODEX_PLUGIN_ID} marketplace is not registered`,
|
|
642
|
-
codexPluginStatus(homeDir, run),
|
|
643
|
-
await codexHookStatus(homeDir, hooksRuntime),
|
|
113
|
+
const listed = run(homeDir, ['plugin', 'list', '--json']);
|
|
114
|
+
return [
|
|
115
|
+
/^hooks\s*=\s*true$/m.test(fs.existsSync(config) ? fs.readFileSync(config, 'utf8') : '') ? `✓ features.hooks=true in ${config}` : `○ features.hooks missing in ${config}`,
|
|
116
|
+
fs.existsSync(codexPluginHooksPath(homeDir)) ? `✓ Square plugin hooks ${codexPluginHooksPath(homeDir)}` : `○ Square plugin bundle missing ${root}`,
|
|
117
|
+
listed.status === 0 && listed.stdout.includes(CODEX_PLUGIN_ID) ? `✓ ${CODEX_PLUGIN_ID} installed` : `○ ${CODEX_PLUGIN_ID} unavailable`,
|
|
644
118
|
];
|
|
645
|
-
if (stale.length > 0)
|
|
646
|
-
lines.push(`✕ ${stale.length} stale Square marketplace registration(s)`);
|
|
647
|
-
return lines;
|
|
648
119
|
}
|
|
649
|
-
export const codexHarness = Object.freeze({
|
|
650
|
-
install: installCodexPlugin,
|
|
651
|
-
uninstall: uninstallCodexPlugin,
|
|
652
|
-
doctor: doctorCodexPlugin,
|
|
653
|
-
});
|