codiedev 0.9.68 → 0.9.69
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/dist/codexHookTrust.d.ts +10 -1
- package/dist/codexHookTrust.js +78 -4
- package/dist/packs/install.js +3 -1
- package/package.json +1 -1
package/dist/codexHookTrust.d.ts
CHANGED
|
@@ -17,7 +17,13 @@ export interface CodexHookTrustEntry {
|
|
|
17
17
|
eventName: string;
|
|
18
18
|
command: string;
|
|
19
19
|
currentHash: string;
|
|
20
|
+
/** Plugin hooks only: Codex also pins approval to the bundle's contents. */
|
|
21
|
+
contentHash?: string;
|
|
20
22
|
}
|
|
23
|
+
/** Codex's fingerprint of an installed plugin bundle, byte-for-byte the
|
|
24
|
+
* algorithm in codex-rs hooks/src/engine/plugin_fingerprint.rs. Codex skips a
|
|
25
|
+
* plugin hook unless `trusted_content_hash` equals this (Codex 0.163+). */
|
|
26
|
+
export declare function codexPluginContentHash(root: string): string;
|
|
21
27
|
/** Codex hashes a canonical, normalized hook identity before it will execute a
|
|
22
28
|
* user hook. Keep this compatible with codex_config::version_for_toml. */
|
|
23
29
|
export declare function codexCommandHookHash(eventName: string, group: MatcherGroup, handler: CommandHandler): string;
|
|
@@ -32,7 +38,10 @@ export declare function inspectCodexHookTrust(configPath: string, hooksPath: str
|
|
|
32
38
|
trusted: number;
|
|
33
39
|
};
|
|
34
40
|
/** Trust entries for every command handler in a pack's hooks.json. */
|
|
35
|
-
export declare function packCodexHookTrustEntries(hooksPath: string, hooksFile: unknown
|
|
41
|
+
export declare function packCodexHookTrustEntries(hooksPath: string, hooksFile: unknown, plugin?: {
|
|
42
|
+
id: string;
|
|
43
|
+
root: string;
|
|
44
|
+
}): CodexHookTrustEntry[];
|
|
36
45
|
/** Regenerate the pack trust block from scratch with exactly `entries`.
|
|
37
46
|
* Entries for packs that were removed or whose hooks changed disappear; the
|
|
38
47
|
* `codiedev-hook-trust` block and all other config are left untouched. */
|
package/dist/codexHookTrust.js
CHANGED
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.codexPluginContentHash = codexPluginContentHash;
|
|
36
37
|
exports.codexCommandHookHash = codexCommandHookHash;
|
|
37
38
|
exports.codiedevCodexHookTrustEntries = codiedevCodexHookTrustEntries;
|
|
38
39
|
exports.writeCodexHookTrust = writeCodexHookTrust;
|
|
@@ -69,6 +70,46 @@ const MATCHER_EVENTS = new Set([
|
|
|
69
70
|
"SubagentStart",
|
|
70
71
|
"SubagentStop",
|
|
71
72
|
]);
|
|
73
|
+
/** Codex's fingerprint of an installed plugin bundle, byte-for-byte the
|
|
74
|
+
* algorithm in codex-rs hooks/src/engine/plugin_fingerprint.rs. Codex skips a
|
|
75
|
+
* plugin hook unless `trusted_content_hash` equals this (Codex 0.163+). */
|
|
76
|
+
function codexPluginContentHash(root) {
|
|
77
|
+
const hash = crypto.createHash("sha256");
|
|
78
|
+
hash.update("codex-plugin-bundle-v1");
|
|
79
|
+
const u64 = (n) => {
|
|
80
|
+
const b = Buffer.alloc(8);
|
|
81
|
+
b.writeBigUInt64LE(BigInt(n));
|
|
82
|
+
return b;
|
|
83
|
+
};
|
|
84
|
+
const pending = [root];
|
|
85
|
+
while (pending.length) {
|
|
86
|
+
const current = pending.pop();
|
|
87
|
+
const relative = path.relative(root, current).split(path.sep).join("/");
|
|
88
|
+
const name = path.posix.basename(relative);
|
|
89
|
+
if (name === "__pycache__" || name === ".venv" || name.endsWith(".pyc") || name.endsWith(".egg-info"))
|
|
90
|
+
continue;
|
|
91
|
+
const nameBytes = Buffer.from(relative, "utf8");
|
|
92
|
+
hash.update(u64(nameBytes.length));
|
|
93
|
+
hash.update(nameBytes);
|
|
94
|
+
const stat = fs.lstatSync(current);
|
|
95
|
+
if (stat.isDirectory()) {
|
|
96
|
+
hash.update("directory");
|
|
97
|
+
// Rust sorts by OsString, i.e. bytewise; push reversed so the smallest pops first.
|
|
98
|
+
const entries = fs.readdirSync(current).sort((a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b)));
|
|
99
|
+
for (const entry of entries.reverse())
|
|
100
|
+
pending.push(path.join(current, entry));
|
|
101
|
+
}
|
|
102
|
+
else if (stat.isFile()) {
|
|
103
|
+
hash.update("file");
|
|
104
|
+
hash.update(u64(stat.size));
|
|
105
|
+
hash.update(fs.readFileSync(current));
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
throw new Error(`unsupported plugin entry type: ${current}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return `sha256:${hash.digest("hex")}`;
|
|
112
|
+
}
|
|
72
113
|
function canonicalize(value) {
|
|
73
114
|
if (Array.isArray(value))
|
|
74
115
|
return value.map(canonicalize);
|
|
@@ -157,6 +198,32 @@ function codiedevCodexHookTrustEntries(hooksPath, hooksFile) {
|
|
|
157
198
|
}
|
|
158
199
|
return entries;
|
|
159
200
|
}
|
|
201
|
+
/** Remove our marker block, keeping any table that isn't a hook-trust entry.
|
|
202
|
+
* Codex appends new tables (e.g. `[marketplaces.codiedev-packs]`,
|
|
203
|
+
* `[plugins."<id>"]`) at the end of config.toml, which lands them before our
|
|
204
|
+
* closing marker when our block is last. Dropping them with the block silently
|
|
205
|
+
* unregistered the pack from Codex on every reinstall. */
|
|
206
|
+
function stripBlockKeepingForeign(text, begin, end) {
|
|
207
|
+
const foreign = [];
|
|
208
|
+
const stripped = text.replace(new RegExp(`\\n?${begin}\\n([\\s\\S]*?)${end}\\n?`, "g"), (_match, body) => {
|
|
209
|
+
let table = null;
|
|
210
|
+
const flush = () => {
|
|
211
|
+
if (table && table.join("").trim())
|
|
212
|
+
foreign.push(table.join("\n").trimEnd());
|
|
213
|
+
};
|
|
214
|
+
for (const line of body.split("\n")) {
|
|
215
|
+
if (/^\s*\[/.test(line)) {
|
|
216
|
+
flush();
|
|
217
|
+
table = /^\s*\[hooks\.state\./.test(line) ? null : [];
|
|
218
|
+
}
|
|
219
|
+
if (table)
|
|
220
|
+
table.push(line);
|
|
221
|
+
}
|
|
222
|
+
flush();
|
|
223
|
+
return "\n";
|
|
224
|
+
});
|
|
225
|
+
return foreign.length ? `${stripped.trimEnd()}\n\n${foreign.join("\n\n")}\n` : stripped;
|
|
226
|
+
}
|
|
160
227
|
function tableHeader(key) {
|
|
161
228
|
return `[hooks.state.${JSON.stringify(key)}]`;
|
|
162
229
|
}
|
|
@@ -181,7 +248,7 @@ function removeTable(lines, header) {
|
|
|
181
248
|
function writeCodexHookTrust(configPath, hooksPath, hooksFile) {
|
|
182
249
|
const entries = codiedevCodexHookTrustEntries(hooksPath, hooksFile);
|
|
183
250
|
let existing = fs.existsSync(configPath) ? fs.readFileSync(configPath, "utf8") : "";
|
|
184
|
-
existing = existing
|
|
251
|
+
existing = stripBlockKeepingForeign(existing, TRUST_BEGIN, TRUST_END);
|
|
185
252
|
let lines = existing.split("\n");
|
|
186
253
|
for (const entry of entries)
|
|
187
254
|
lines = removeTable(lines, tableHeader(entry.key));
|
|
@@ -237,11 +304,16 @@ function inspectCodexHookTrust(configPath, hooksPath, hooksFile, requiredSubcomm
|
|
|
237
304
|
const PACK_TRUST_BEGIN = "# codiedev-pack-trust:begin";
|
|
238
305
|
const PACK_TRUST_END = "# codiedev-pack-trust:end";
|
|
239
306
|
/** Trust entries for every command handler in a pack's hooks.json. */
|
|
240
|
-
function packCodexHookTrustEntries(hooksPath, hooksFile) {
|
|
307
|
+
function packCodexHookTrustEntries(hooksPath, hooksFile, plugin) {
|
|
241
308
|
const events = hooksFile?.hooks;
|
|
242
309
|
if (!events || typeof events !== "object")
|
|
243
310
|
return [];
|
|
244
|
-
|
|
311
|
+
// Codex keys a plugin's hooks by "<plugin id>:<path inside the plugin>",
|
|
312
|
+
// not by the file's absolute path (codex-rs declarations::plugin_hook_key_source).
|
|
313
|
+
const source = plugin
|
|
314
|
+
? `${plugin.id}:${path.relative(plugin.root, hooksPath).split(path.sep).join("/")}`
|
|
315
|
+
: normalizedHooksPath(hooksPath);
|
|
316
|
+
const contentHash = plugin ? codexPluginContentHash(plugin.root) : undefined;
|
|
245
317
|
const entries = [];
|
|
246
318
|
for (const [eventName, groupsValue] of Object.entries(events)) {
|
|
247
319
|
const eventLabel = EVENT_LABELS[eventName];
|
|
@@ -260,6 +332,7 @@ function packCodexHookTrustEntries(hooksPath, hooksFile) {
|
|
|
260
332
|
eventName,
|
|
261
333
|
command: handler.command,
|
|
262
334
|
currentHash: codexCommandHookHash(eventName, group, handler),
|
|
335
|
+
...(contentHash ? { contentHash } : {}),
|
|
263
336
|
});
|
|
264
337
|
});
|
|
265
338
|
});
|
|
@@ -271,7 +344,7 @@ function packCodexHookTrustEntries(hooksPath, hooksFile) {
|
|
|
271
344
|
* `codiedev-hook-trust` block and all other config are left untouched. */
|
|
272
345
|
function writeCodexPackHookTrust(configPath, entries) {
|
|
273
346
|
let existing = fs.existsSync(configPath) ? fs.readFileSync(configPath, "utf8") : "";
|
|
274
|
-
existing = existing
|
|
347
|
+
existing = stripBlockKeepingForeign(existing, PACK_TRUST_BEGIN, PACK_TRUST_END);
|
|
275
348
|
let lines = existing.split("\n");
|
|
276
349
|
// A stray table for the same key outside our block would make the TOML
|
|
277
350
|
// invalid (duplicate table) — drop it so ours is authoritative.
|
|
@@ -291,6 +364,7 @@ function writeCodexPackHookTrust(configPath, entries) {
|
|
|
291
364
|
tableHeader(entry.key),
|
|
292
365
|
"enabled = true",
|
|
293
366
|
`trusted_hash = ${JSON.stringify(entry.currentHash)}`,
|
|
367
|
+
...(entry.contentHash ? [`trusted_content_hash = ${JSON.stringify(entry.contentHash)}`] : []),
|
|
294
368
|
"",
|
|
295
369
|
]).join("\n");
|
|
296
370
|
const next = `${base}${base ? "\n\n" : ""}${PACK_TRUST_BEGIN}\n${trustBlock}${PACK_TRUST_END}\n`;
|
package/dist/packs/install.js
CHANGED
|
@@ -94,7 +94,9 @@ function codexPackTrustEntries(registry, codexDir) {
|
|
|
94
94
|
if (!hooksPath)
|
|
95
95
|
continue;
|
|
96
96
|
try {
|
|
97
|
-
|
|
97
|
+
// hooks/hooks.json sits at the root of the installed plugin bundle.
|
|
98
|
+
const plugin = { id: `${slug}@${paths_1.MARKETPLACE_NAME}`, root: path.dirname(path.dirname(hooksPath)) };
|
|
99
|
+
bySlug.set(slug, (0, codexHookTrust_1.packCodexHookTrustEntries)(hooksPath, JSON.parse(fs.readFileSync(hooksPath, "utf8")), plugin));
|
|
98
100
|
}
|
|
99
101
|
catch {
|
|
100
102
|
// unreadable hooks → not trusted
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codiedev",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.69",
|
|
4
4
|
"description": "Connect Claude Code, Codex, Cursor, OpenCode, or VS Code Copilot to Codiedev for team session capture and artifact collaboration — and run the governed Codie harness",
|
|
5
5
|
"bin": {
|
|
6
6
|
"codiedev": "dist/cli.js",
|