codiedev 0.9.67 → 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/cli.js +32 -0
- package/dist/codexHookTrust.d.ts +15 -0
- package/dist/codexHookTrust.js +148 -1
- package/dist/codie.js +21 -1
- package/dist/commands/pack.d.ts +54 -0
- package/dist/commands/pack.js +602 -0
- package/dist/hook.js +15 -1
- package/dist/packs/codie.d.ts +45 -0
- package/dist/packs/codie.js +197 -0
- package/dist/packs/harnesses.d.ts +71 -0
- package/dist/packs/harnesses.js +407 -0
- package/dist/packs/hookGate.d.ts +53 -0
- package/dist/packs/hookGate.js +289 -0
- package/dist/packs/install.d.ts +46 -0
- package/dist/packs/install.js +306 -0
- package/dist/packs/manifest.d.ts +52 -0
- package/dist/packs/manifest.js +140 -0
- package/dist/packs/paths.d.ts +17 -0
- package/dist/packs/paths.js +89 -0
- package/dist/packs/shim.d.ts +26 -0
- package/dist/packs/shim.js +222 -0
- package/dist/packs/store.d.ts +42 -0
- package/dist/packs/store.js +167 -0
- package/dist/packs/sync.d.ts +40 -0
- package/dist/packs/sync.js +190 -0
- package/package.json +1 -1
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.MAX_GATED_COMMAND_BYTES = void 0;
|
|
37
|
+
exports.skillsFromPayload = skillsFromPayload;
|
|
38
|
+
exports.encodeGatedCommand = encodeGatedCommand;
|
|
39
|
+
exports.decodeGatedCommand = decodeGatedCommand;
|
|
40
|
+
exports.parseGateArgs = parseGateArgs;
|
|
41
|
+
exports.gateMatches = gateMatches;
|
|
42
|
+
exports.splitShellOverride = splitShellOverride;
|
|
43
|
+
exports.planShellCommand = planShellCommand;
|
|
44
|
+
exports.planGateCommand = planGateCommand;
|
|
45
|
+
exports.runHookGate = runHookGate;
|
|
46
|
+
exports.runHookGateCli = runHookGateCli;
|
|
47
|
+
// `codiedev hook-gate --skills a,b --cmd-b64 <base64url>`: run a pack hook only
|
|
48
|
+
// while one of the named skills is in use. Reads the harness hook payload on
|
|
49
|
+
// stdin, passes it through to the command unchanged, and exits with its status
|
|
50
|
+
// (including 2 = block). No match / unreadable payload → exit 0, no output.
|
|
51
|
+
// Also accepted: `-- <argv…>` (exec'd directly) and the legacy
|
|
52
|
+
// `-- sh -c '<cmd>'` form saved by earlier pack editors.
|
|
53
|
+
const fs = __importStar(require("fs"));
|
|
54
|
+
const path = __importStar(require("path"));
|
|
55
|
+
const child_process_1 = require("child_process");
|
|
56
|
+
function str(value) {
|
|
57
|
+
return typeof value === "string" && value ? value : undefined;
|
|
58
|
+
}
|
|
59
|
+
function parseMaybeJson(value) {
|
|
60
|
+
if (typeof value !== "string")
|
|
61
|
+
return value;
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(value);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const SKILL_PATH = /[\\/]skills[\\/]([^\\/\s'"`;|&<>()]+)[\\/]SKILL\.md\b/g;
|
|
70
|
+
function skillsInPath(text) {
|
|
71
|
+
return [...text.matchAll(SKILL_PATH)].map((m) => m[1]);
|
|
72
|
+
}
|
|
73
|
+
function stripPlugin(name) {
|
|
74
|
+
const index = name.lastIndexOf(":");
|
|
75
|
+
return index >= 0 ? name.slice(index + 1) : name;
|
|
76
|
+
}
|
|
77
|
+
/** Skill names a hook payload shows as running, across the five tools:
|
|
78
|
+
* - Claude: tool_name "Skill", tool_input.skill "<plugin>:<name>"
|
|
79
|
+
* - Copilot: tool_name "skill", tool_input.skill "<name>" (also camelCase toolName/toolArgs)
|
|
80
|
+
* - Codie: tool_name "skill", tool_input.name "<name>"
|
|
81
|
+
* - Cursor: tool_name "Read", tool_input.file_path ".../skills/<name>/SKILL.md"
|
|
82
|
+
* - Codex: tool_name "Bash", tool_input.command containing ".../skills/<name>/SKILL.md" */
|
|
83
|
+
function skillsFromPayload(payload) {
|
|
84
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
85
|
+
return [];
|
|
86
|
+
const p = payload;
|
|
87
|
+
const toolName = str(p.tool_name) ?? str(p.toolName) ?? "";
|
|
88
|
+
const rawInput = parseMaybeJson(p.tool_input ?? p.toolInput ?? p.toolArgs ?? p.input);
|
|
89
|
+
const input = rawInput && typeof rawInput === "object" && !Array.isArray(rawInput)
|
|
90
|
+
? rawInput
|
|
91
|
+
: {};
|
|
92
|
+
const tool = toolName.toLowerCase();
|
|
93
|
+
if (tool === "skill") {
|
|
94
|
+
const name = str(input.skill) ?? str(input.name) ?? str(input.skill_name);
|
|
95
|
+
return name ? [stripPlugin(name.trim())] : [];
|
|
96
|
+
}
|
|
97
|
+
if (tool === "read" || tool === "view" || tool === "read_file") {
|
|
98
|
+
const file = str(input.file_path) ?? str(input.path) ?? str(input.filePath) ?? "";
|
|
99
|
+
return skillsInPath(file);
|
|
100
|
+
}
|
|
101
|
+
if (tool === "bash" || tool === "shell" || tool === "exec_command" || tool === "local_shell") {
|
|
102
|
+
const command = Array.isArray(input.command) ? input.command.join(" ") : str(input.command) ?? str(input.cmd) ?? "";
|
|
103
|
+
return skillsInPath(command);
|
|
104
|
+
}
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
/** Largest decoded `--cmd-b64` command we run. */
|
|
108
|
+
exports.MAX_GATED_COMMAND_BYTES = 16 * 1024;
|
|
109
|
+
/** Canonical form (what the portal writes): base64url of the UTF-8 command, no
|
|
110
|
+
* padding. Only [A-Za-z0-9_-], so no shell on any platform expands or
|
|
111
|
+
* re-quotes it — a `$(…)` in the gated command can't run before the gate. */
|
|
112
|
+
function encodeGatedCommand(command) {
|
|
113
|
+
return Buffer.from(command, "utf8").toString("base64url");
|
|
114
|
+
}
|
|
115
|
+
/** Strict decode: canonical base64url only, ≤16KB, valid UTF-8, no NUL. */
|
|
116
|
+
function decodeGatedCommand(value) {
|
|
117
|
+
if (!/^[A-Za-z0-9_-]+$/.test(value) || value.length % 4 === 1)
|
|
118
|
+
throw new Error("--cmd-b64 is not base64url");
|
|
119
|
+
if (value.length > Math.ceil((exports.MAX_GATED_COMMAND_BYTES * 4) / 3) + 4)
|
|
120
|
+
throw new Error("--cmd-b64 command is over 16KB");
|
|
121
|
+
const bytes = Buffer.from(value, "base64url");
|
|
122
|
+
if (bytes.toString("base64url") !== value)
|
|
123
|
+
throw new Error("--cmd-b64 is not canonical base64url");
|
|
124
|
+
if (bytes.length > exports.MAX_GATED_COMMAND_BYTES)
|
|
125
|
+
throw new Error("--cmd-b64 command is over 16KB");
|
|
126
|
+
let text;
|
|
127
|
+
try {
|
|
128
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
throw new Error("--cmd-b64 command is not UTF-8");
|
|
132
|
+
}
|
|
133
|
+
if (text.includes("\u0000"))
|
|
134
|
+
throw new Error("--cmd-b64 command contains NUL");
|
|
135
|
+
if (!text.trim())
|
|
136
|
+
throw new Error("--cmd-b64 command is empty");
|
|
137
|
+
return text;
|
|
138
|
+
}
|
|
139
|
+
function parseGateArgs(argv) {
|
|
140
|
+
const sep = argv.indexOf("--");
|
|
141
|
+
const flags = sep >= 0 ? argv.slice(0, sep) : argv;
|
|
142
|
+
const command = sep >= 0 ? argv.slice(sep + 1) : [];
|
|
143
|
+
let skills = [];
|
|
144
|
+
let cmdB64;
|
|
145
|
+
for (let i = 0; i < flags.length; i++) {
|
|
146
|
+
const a = flags[i];
|
|
147
|
+
if (a === "--cmd-b64" || a.startsWith("--cmd-b64=")) {
|
|
148
|
+
cmdB64 = a === "--cmd-b64" ? flags[++i] ?? "" : a.slice("--cmd-b64=".length);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const value = a === "--skills" || a === "--skill" ? flags[++i] : a.startsWith("--skills=") ? a.slice(9) : undefined;
|
|
152
|
+
if (value === undefined)
|
|
153
|
+
throw new Error(`Unknown hook-gate argument: ${a}`);
|
|
154
|
+
skills.push(...value.split(",").map((s) => stripPlugin(s.trim())).filter(Boolean));
|
|
155
|
+
}
|
|
156
|
+
skills = [...new Set(skills)];
|
|
157
|
+
if (skills.length === 0)
|
|
158
|
+
throw new Error("hook-gate needs --skills a,b");
|
|
159
|
+
if (cmdB64 !== undefined && command.length)
|
|
160
|
+
throw new Error("hook-gate takes --cmd-b64 or -- <command>, not both");
|
|
161
|
+
if (cmdB64 === undefined && command.length === 0)
|
|
162
|
+
throw new Error("hook-gate needs --cmd-b64 <base64url> or -- <command>");
|
|
163
|
+
return { skills, command, ...(cmdB64 !== undefined ? { cmdB64 } : {}) };
|
|
164
|
+
}
|
|
165
|
+
function gateMatches(skills, payload) {
|
|
166
|
+
const running = skillsFromPayload(payload);
|
|
167
|
+
return running.some((name) => skills.includes(name));
|
|
168
|
+
}
|
|
169
|
+
/** Split a CODIEDEV_HOOK_SHELL value ("pwsh -NoProfile -Command") into argv,
|
|
170
|
+
* honouring simple "double" or 'single' quotes. */
|
|
171
|
+
function splitShellOverride(value) {
|
|
172
|
+
const out = [];
|
|
173
|
+
for (const m of value.matchAll(/"([^"]*)"|'([^']*)'|(\S+)/g))
|
|
174
|
+
out.push(m[1] ?? m[2] ?? m[3]);
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
function shellFor(line, platform, env) {
|
|
178
|
+
const override = env.CODIEDEV_HOOK_SHELL?.trim();
|
|
179
|
+
if (override) {
|
|
180
|
+
const [file, ...args] = splitShellOverride(override);
|
|
181
|
+
if (file)
|
|
182
|
+
return { file, args: [...args, line] };
|
|
183
|
+
}
|
|
184
|
+
if (platform === "win32") {
|
|
185
|
+
// Same invocation Node uses for shell:true on Windows.
|
|
186
|
+
return { file: env.ComSpec || env.COMSPEC || "cmd.exe", args: ["/d", "/s", "/c", `"${line}"`], verbatim: true };
|
|
187
|
+
}
|
|
188
|
+
return { file: "/bin/sh", args: ["-c", line] };
|
|
189
|
+
}
|
|
190
|
+
/** The decoded `--cmd-b64` command line, run by the platform shell
|
|
191
|
+
* (/bin/sh -c on POSIX, cmd.exe /d /s /c on Windows, or CODIEDEV_HOOK_SHELL). */
|
|
192
|
+
function planShellCommand(line, options = {}) {
|
|
193
|
+
return shellFor(line, options.platform ?? process.platform, options.env ?? process.env);
|
|
194
|
+
}
|
|
195
|
+
/** `-- <argv…>`: exec'd directly, no shell. Legacy `sh -c '<cmd>'` (packs saved
|
|
196
|
+
* before --cmd-b64) runs as given, except on Windows without sh, where <cmd>
|
|
197
|
+
* goes to cmd.exe. */
|
|
198
|
+
function planGateCommand(command, options = {}) {
|
|
199
|
+
const platform = options.platform ?? process.platform;
|
|
200
|
+
const env = options.env ?? process.env;
|
|
201
|
+
const legacy = command.length === 3 && /^(?:\/bin\/)?sh(?:\.exe)?$/i.test(command[0]) && command[1] === "-c";
|
|
202
|
+
if (legacy && platform === "win32" && !(options.hasSh ?? (() => onPathSimple("sh", env, platform)))()) {
|
|
203
|
+
return { ...shellFor(command[2], platform, env), note: "codiedev hook-gate: sh isn't available; running the hook with cmd.exe instead\n" };
|
|
204
|
+
}
|
|
205
|
+
const [file, ...args] = command;
|
|
206
|
+
return { file, args };
|
|
207
|
+
}
|
|
208
|
+
function onPathSimple(bin, env, platform) {
|
|
209
|
+
const sep = platform === "win32" ? ";" : ":";
|
|
210
|
+
const exts = platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
|
|
211
|
+
for (const dir of (env.PATH ?? env.Path ?? "").split(sep).filter(Boolean)) {
|
|
212
|
+
for (const ext of exts) {
|
|
213
|
+
try {
|
|
214
|
+
if (fs.statSync(path.join(dir, bin + ext)).isFile())
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// keep looking
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
/** Returns the exit status to use. */
|
|
225
|
+
function runHookGate(argv, stdin, env = process.env, options = {}) {
|
|
226
|
+
let args;
|
|
227
|
+
try {
|
|
228
|
+
args = parseGateArgs(argv);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
// A misconfigured gate must not block the agent; say why on stderr.
|
|
232
|
+
process.stderr.write(`codiedev hook-gate: ${error.message}\n`);
|
|
233
|
+
return 0;
|
|
234
|
+
}
|
|
235
|
+
let payload;
|
|
236
|
+
try {
|
|
237
|
+
payload = JSON.parse(stdin);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return 0;
|
|
241
|
+
}
|
|
242
|
+
// Nothing is decoded or run unless one of the skills is in use.
|
|
243
|
+
if (!gateMatches(args.skills, payload))
|
|
244
|
+
return 0;
|
|
245
|
+
let plan;
|
|
246
|
+
if (args.cmdB64 !== undefined) {
|
|
247
|
+
let line;
|
|
248
|
+
try {
|
|
249
|
+
line = decodeGatedCommand(args.cmdB64);
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
// Never block the agent on a malformed gate.
|
|
253
|
+
process.stderr.write(`codiedev hook-gate: ${error.message}; hook skipped\n`);
|
|
254
|
+
return 0;
|
|
255
|
+
}
|
|
256
|
+
plan = planShellCommand(line, { ...options, env });
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
plan = planGateCommand(args.command, { ...options, env });
|
|
260
|
+
}
|
|
261
|
+
if (plan.note)
|
|
262
|
+
process.stderr.write(plan.note);
|
|
263
|
+
const result = (options.spawn ?? child_process_1.spawnSync)(plan.file, plan.args, {
|
|
264
|
+
input: stdin,
|
|
265
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
266
|
+
env,
|
|
267
|
+
windowsHide: true,
|
|
268
|
+
...(plan.verbatim ? { windowsVerbatimArguments: true } : {}),
|
|
269
|
+
});
|
|
270
|
+
// A command that exits without reading its stdin (`exit 2`, `touch x`)
|
|
271
|
+
// closes the pipe before the payload is written; spawnSync then reports
|
|
272
|
+
// EPIPE next to the command's real exit status, which is the answer.
|
|
273
|
+
const ignoredStdin = result.error?.code === "EPIPE" && result.status !== null;
|
|
274
|
+
if (result.error && !ignoredStdin) {
|
|
275
|
+
process.stderr.write(`codiedev hook-gate: ${result.error.message}\n`);
|
|
276
|
+
return 1;
|
|
277
|
+
}
|
|
278
|
+
return result.status ?? 1;
|
|
279
|
+
}
|
|
280
|
+
function runHookGateCli(argv) {
|
|
281
|
+
let stdin = "";
|
|
282
|
+
try {
|
|
283
|
+
stdin = fs.readFileSync(0, "utf8");
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
stdin = "";
|
|
287
|
+
}
|
|
288
|
+
process.exitCode = runHookGate(argv, stdin);
|
|
289
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { CodexHookTrustEntry } from "../codexHookTrust";
|
|
2
|
+
import { AdapterResult, HarnessName, Runner } from "./harnesses";
|
|
3
|
+
import { PackManifest } from "./manifest";
|
|
4
|
+
import { Registry } from "./store";
|
|
5
|
+
export interface InstallOptions {
|
|
6
|
+
manifests: PackManifest[];
|
|
7
|
+
harnesses?: HarnessName[];
|
|
8
|
+
dryRun?: boolean;
|
|
9
|
+
env?: NodeJS.ProcessEnv;
|
|
10
|
+
home?: string;
|
|
11
|
+
run?: Runner;
|
|
12
|
+
now?: () => number;
|
|
13
|
+
}
|
|
14
|
+
export interface PackInstallResult {
|
|
15
|
+
slug: string;
|
|
16
|
+
channel?: string;
|
|
17
|
+
version: number;
|
|
18
|
+
contentHash: string;
|
|
19
|
+
dir: string;
|
|
20
|
+
files: string[];
|
|
21
|
+
results: AdapterResult[];
|
|
22
|
+
harnesses: string[];
|
|
23
|
+
}
|
|
24
|
+
/** Where Codex unpacked this pack's hooks.json after `codex plugin add`. */
|
|
25
|
+
export declare function codexPackHooksPath(codexDir: string, slug: string, version: number): string | null;
|
|
26
|
+
/** Trust entries for every registry pack installed into Codex. */
|
|
27
|
+
export declare function codexPackTrustEntries(registry: Registry, codexDir: string): Map<string, CodexHookTrustEntry[]>;
|
|
28
|
+
export declare function installPacks(options: InstallOptions): PackInstallResult[];
|
|
29
|
+
export interface UninstallOptions {
|
|
30
|
+
slugs: string[];
|
|
31
|
+
env?: NodeJS.ProcessEnv;
|
|
32
|
+
home?: string;
|
|
33
|
+
run?: Runner;
|
|
34
|
+
}
|
|
35
|
+
export interface PackUninstallResult {
|
|
36
|
+
slug: string;
|
|
37
|
+
results: AdapterResult[];
|
|
38
|
+
}
|
|
39
|
+
/** Remove packs that no longer apply (unassigned or archived): run each
|
|
40
|
+
* tool's uninstall, drop the plugin dir, the marketplace entry, the registry
|
|
41
|
+
* entry, its Codex trust entries and its shim hooks. Unknown slugs are a
|
|
42
|
+
* no-op. A tool failing to uninstall never keeps the pack registered — the
|
|
43
|
+
* pack is no longer authorized, so local state must stop advertising it. */
|
|
44
|
+
export declare function uninstallPacks(options: UninstallOptions): PackUninstallResult[];
|
|
45
|
+
export declare function formatUninstallLines(results: PackUninstallResult[]): string[];
|
|
46
|
+
export declare function formatResultLines(results: PackInstallResult[]): string[];
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.codexPackHooksPath = codexPackHooksPath;
|
|
37
|
+
exports.codexPackTrustEntries = codexPackTrustEntries;
|
|
38
|
+
exports.installPacks = installPacks;
|
|
39
|
+
exports.uninstallPacks = uninstallPacks;
|
|
40
|
+
exports.formatUninstallLines = formatUninstallLines;
|
|
41
|
+
exports.formatResultLines = formatResultLines;
|
|
42
|
+
// Orchestrates `codiedev pack install`: write the plugin dir + marketplace,
|
|
43
|
+
// run every detected tool's adapter, record installed.json, then regenerate
|
|
44
|
+
// the Codex pack trust block and the Codie hook shim from the registry.
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
const os = __importStar(require("os"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const harnessDirs_1 = require("../harnessDirs");
|
|
49
|
+
const codexHookTrust_1 = require("../codexHookTrust");
|
|
50
|
+
const harnesses_1 = require("./harnesses");
|
|
51
|
+
const manifest_1 = require("./manifest");
|
|
52
|
+
const paths_1 = require("./paths");
|
|
53
|
+
const shim_1 = require("./shim");
|
|
54
|
+
const store_1 = require("./store");
|
|
55
|
+
function marketplaceEntries(registry) {
|
|
56
|
+
return Object.entries(registry.packs).map(([slug, entry]) => ({
|
|
57
|
+
slug,
|
|
58
|
+
version: entry.version,
|
|
59
|
+
description: entry.description,
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
/** Where Codex unpacked this pack's hooks.json after `codex plugin add`. */
|
|
63
|
+
function codexPackHooksPath(codexDir, slug, version) {
|
|
64
|
+
const base = path.join(codexDir, "plugins", "cache", paths_1.MARKETPLACE_NAME, slug);
|
|
65
|
+
const exact = path.join(base, (0, store_1.pluginVersion)(version), "hooks", "hooks.json");
|
|
66
|
+
if (fs.existsSync(exact))
|
|
67
|
+
return exact;
|
|
68
|
+
let best = null;
|
|
69
|
+
try {
|
|
70
|
+
for (const entry of fs.readdirSync(base)) {
|
|
71
|
+
const file = path.join(base, entry, "hooks", "hooks.json");
|
|
72
|
+
try {
|
|
73
|
+
const mtime = fs.statSync(file).mtimeMs;
|
|
74
|
+
if (!best || mtime > best.mtime)
|
|
75
|
+
best = { file, mtime };
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// no hooks in this version dir
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return best?.file ?? null;
|
|
86
|
+
}
|
|
87
|
+
/** Trust entries for every registry pack installed into Codex. */
|
|
88
|
+
function codexPackTrustEntries(registry, codexDir) {
|
|
89
|
+
const bySlug = new Map();
|
|
90
|
+
for (const [slug, entry] of Object.entries(registry.packs).sort(([a], [b]) => a.localeCompare(b))) {
|
|
91
|
+
if (!entry.harnesses?.includes("codex"))
|
|
92
|
+
continue;
|
|
93
|
+
const hooksPath = codexPackHooksPath(codexDir, slug, entry.version);
|
|
94
|
+
if (!hooksPath)
|
|
95
|
+
continue;
|
|
96
|
+
try {
|
|
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));
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// unreadable hooks → not trusted
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return bySlug;
|
|
106
|
+
}
|
|
107
|
+
function shimInputs(registry) {
|
|
108
|
+
const packs = [];
|
|
109
|
+
for (const [slug, entry] of Object.entries(registry.packs).sort(([a], [b]) => a.localeCompare(b))) {
|
|
110
|
+
if (!entry.harnesses?.includes("codie"))
|
|
111
|
+
continue;
|
|
112
|
+
try {
|
|
113
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(entry.dir, "hooks", "hooks.json"), "utf8"));
|
|
114
|
+
if (parsed?.hooks && typeof parsed.hooks === "object")
|
|
115
|
+
packs.push({ slug, root: entry.dir, hooks: parsed.hooks });
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// no hooks for this pack
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return packs;
|
|
122
|
+
}
|
|
123
|
+
function installPacks(options) {
|
|
124
|
+
const env = options.env ?? process.env;
|
|
125
|
+
const home = options.home ?? os.homedir();
|
|
126
|
+
const run = options.run ?? (0, harnesses_1.defaultRunner)(env);
|
|
127
|
+
const now = options.now ?? Date.now;
|
|
128
|
+
const root = (0, paths_1.packsRoot)(env, home);
|
|
129
|
+
const selected = new Set(options.harnesses ?? harnesses_1.ADAPTERS.map((a) => a.name));
|
|
130
|
+
const adapters = harnesses_1.ADAPTERS.filter((a) => selected.has(a.name));
|
|
131
|
+
const registry = (0, store_1.readRegistry)(root);
|
|
132
|
+
const detected = new Map();
|
|
133
|
+
for (const adapter of adapters)
|
|
134
|
+
detected.set(adapter.name, adapter.detect({ root, env, home, run }));
|
|
135
|
+
const output = [];
|
|
136
|
+
for (const manifest of options.manifests) {
|
|
137
|
+
const dir = (0, paths_1.pluginDir)(root, manifest.slug);
|
|
138
|
+
const previous = registry.packs[manifest.slug];
|
|
139
|
+
const files = Object.keys((0, store_1.pluginFiles)(manifest)).sort();
|
|
140
|
+
const results = [];
|
|
141
|
+
if (options.dryRun) {
|
|
142
|
+
for (const adapter of adapters) {
|
|
143
|
+
const ctx = { root, env, home, run, manifest, pluginDir: dir, portablePluginDir: (0, paths_1.pluginDir)((0, paths_1.portableRoot)(root), manifest.slug), previous };
|
|
144
|
+
results.push(detected.get(adapter.name)
|
|
145
|
+
? { harness: adapter.name, status: "dry-run", detail: `would run: ${adapter.plan(ctx)}` }
|
|
146
|
+
: { harness: adapter.name, status: "skipped", detail: "not detected" });
|
|
147
|
+
}
|
|
148
|
+
output.push({ slug: manifest.slug, version: manifest.version, contentHash: manifest.contentHash, dir, files, results, harnesses: [] });
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
(0, store_1.writePluginDir)(root, manifest);
|
|
152
|
+
// Same plugin with agent model levels removed, for Codex/Copilot/Cursor.
|
|
153
|
+
(0, store_1.writePluginDir)((0, paths_1.portableRoot)(root), manifest, { portable: true });
|
|
154
|
+
// Harnesses read the marketplace during install, so it must list this pack first.
|
|
155
|
+
const pending = {
|
|
156
|
+
packs: {
|
|
157
|
+
...registry.packs,
|
|
158
|
+
[manifest.slug]: {
|
|
159
|
+
version: manifest.version,
|
|
160
|
+
contentHash: manifest.contentHash,
|
|
161
|
+
dir,
|
|
162
|
+
installedAt: previous?.installedAt ?? now(),
|
|
163
|
+
harnesses: previous?.harnesses ?? [],
|
|
164
|
+
name: manifest.name,
|
|
165
|
+
description: manifest.description,
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
(0, store_1.writeMarketplace)(root, marketplaceEntries(pending));
|
|
170
|
+
(0, store_1.writeMarketplace)((0, paths_1.portableRoot)(root), marketplaceEntries(pending));
|
|
171
|
+
for (const adapter of adapters) {
|
|
172
|
+
if (!detected.get(adapter.name)) {
|
|
173
|
+
results.push({ harness: adapter.name, status: "skipped", detail: "not detected" });
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
const detail = adapter.install({ root, env, home, run, manifest, pluginDir: dir, portablePluginDir: (0, paths_1.pluginDir)((0, paths_1.portableRoot)(root), manifest.slug), previous });
|
|
178
|
+
results.push({ harness: adapter.name, status: "ok", detail });
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
results.push({ harness: adapter.name, status: "failed", detail: error instanceof Error ? error.message : String(error) });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Tools not selected this run keep their previous state; selected ones
|
|
185
|
+
// reflect this run's outcome.
|
|
186
|
+
const kept = (previous?.harnesses ?? []).filter((h) => !selected.has(h));
|
|
187
|
+
const succeeded = results.filter((r) => r.status === "ok").map((r) => r.harness);
|
|
188
|
+
const harnesses = [...new Set([...kept, ...succeeded])].sort();
|
|
189
|
+
registry.packs[manifest.slug] = {
|
|
190
|
+
version: manifest.version,
|
|
191
|
+
contentHash: manifest.contentHash,
|
|
192
|
+
dir,
|
|
193
|
+
installedAt: now(),
|
|
194
|
+
harnesses,
|
|
195
|
+
name: manifest.name,
|
|
196
|
+
description: manifest.description,
|
|
197
|
+
...(manifest.channel ? { channel: manifest.channel } : {}),
|
|
198
|
+
...(manifest.icon ? { icon: manifest.icon } : {}),
|
|
199
|
+
};
|
|
200
|
+
(0, store_1.writeRegistry)(root, registry);
|
|
201
|
+
output.push({ slug: manifest.slug, ...(manifest.channel ? { channel: manifest.channel } : {}), version: manifest.version, contentHash: manifest.contentHash, dir, files, results, harnesses });
|
|
202
|
+
}
|
|
203
|
+
if (options.dryRun || options.manifests.length === 0)
|
|
204
|
+
return output;
|
|
205
|
+
if (selected.has("codex") && detected.get("codex")) {
|
|
206
|
+
const bySlug = regenerateCodexTrust(registry, env, home);
|
|
207
|
+
for (const pack of output) {
|
|
208
|
+
const codex = pack.results.find((r) => r.harness === "codex" && r.status === "ok");
|
|
209
|
+
const count = bySlug.get(pack.slug)?.length ?? 0;
|
|
210
|
+
if (codex && count)
|
|
211
|
+
codex.detail += `; ${count} hook${count === 1 ? "" : "s"} trusted`;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (selected.has("codie")) {
|
|
215
|
+
(0, shim_1.writeShim)((0, paths_1.shimPath)(root), shimInputs(registry));
|
|
216
|
+
const skipped = (0, shim_1.unsupportedShimEvents)(shimInputs(registry));
|
|
217
|
+
for (const pack of output) {
|
|
218
|
+
const codie = pack.results.find((r) => r.harness === "codie" && r.status === "ok");
|
|
219
|
+
const events = skipped.get(pack.slug);
|
|
220
|
+
if (codie && events?.length)
|
|
221
|
+
codie.notes = [`hooks Codie can't run were skipped: ${events.join(", ")}`];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return output;
|
|
225
|
+
}
|
|
226
|
+
function regenerateCodexTrust(registry, env, home) {
|
|
227
|
+
const codexDir = (0, harnessDirs_1.codexHome)(env, home);
|
|
228
|
+
const bySlug = codexPackTrustEntries(registry, codexDir);
|
|
229
|
+
(0, codexHookTrust_1.writeCodexPackHookTrust)(path.join(codexDir, "config.toml"), [...bySlug.values()].flat());
|
|
230
|
+
return bySlug;
|
|
231
|
+
}
|
|
232
|
+
/** Remove packs that no longer apply (unassigned or archived): run each
|
|
233
|
+
* tool's uninstall, drop the plugin dir, the marketplace entry, the registry
|
|
234
|
+
* entry, its Codex trust entries and its shim hooks. Unknown slugs are a
|
|
235
|
+
* no-op. A tool failing to uninstall never keeps the pack registered — the
|
|
236
|
+
* pack is no longer authorized, so local state must stop advertising it. */
|
|
237
|
+
function uninstallPacks(options) {
|
|
238
|
+
const env = options.env ?? process.env;
|
|
239
|
+
const home = options.home ?? os.homedir();
|
|
240
|
+
const run = options.run ?? (0, harnesses_1.defaultRunner)(env);
|
|
241
|
+
const root = (0, paths_1.packsRoot)(env, home);
|
|
242
|
+
const registry = (0, store_1.readRegistry)(root);
|
|
243
|
+
const output = [];
|
|
244
|
+
for (const slug of options.slugs) {
|
|
245
|
+
const entry = registry.packs[slug];
|
|
246
|
+
if (!entry || !(0, manifest_1.isSafeSegment)(slug))
|
|
247
|
+
continue;
|
|
248
|
+
const results = [];
|
|
249
|
+
for (const adapter of harnesses_1.ADAPTERS) {
|
|
250
|
+
if (!entry.harnesses?.includes(adapter.name))
|
|
251
|
+
continue;
|
|
252
|
+
if (!adapter.detect({ root, env, home, run })) {
|
|
253
|
+
results.push({ harness: adapter.name, status: "skipped", detail: "not detected" });
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
results.push({ harness: adapter.name, status: "ok", detail: adapter.uninstall({ root, env, home, run, slug }) });
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
results.push({ harness: adapter.name, status: "failed", detail: error instanceof Error ? error.message : String(error) });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
delete registry.packs[slug];
|
|
264
|
+
fs.rmSync((0, paths_1.pluginDir)(root, slug), { recursive: true, force: true });
|
|
265
|
+
fs.rmSync((0, paths_1.pluginDir)((0, paths_1.portableRoot)(root), slug), { recursive: true, force: true });
|
|
266
|
+
output.push({ slug, results });
|
|
267
|
+
}
|
|
268
|
+
if (output.length === 0)
|
|
269
|
+
return output;
|
|
270
|
+
(0, store_1.writeRegistry)(root, registry);
|
|
271
|
+
(0, store_1.writeMarketplace)(root, marketplaceEntries(registry));
|
|
272
|
+
(0, store_1.writeMarketplace)((0, paths_1.portableRoot)(root), marketplaceEntries(registry));
|
|
273
|
+
// Regenerate even when Codex isn't on PATH any more: a stale trust entry
|
|
274
|
+
// for a removed pack must not survive in config.toml.
|
|
275
|
+
const codexConfig = path.join((0, harnessDirs_1.codexHome)(env, home), "config.toml");
|
|
276
|
+
if (fs.existsSync(codexConfig))
|
|
277
|
+
regenerateCodexTrust(registry, env, home);
|
|
278
|
+
(0, shim_1.writeShim)((0, paths_1.shimPath)(root), shimInputs(registry));
|
|
279
|
+
return output;
|
|
280
|
+
}
|
|
281
|
+
function formatUninstallLines(results) {
|
|
282
|
+
const lines = [];
|
|
283
|
+
for (const pack of results) {
|
|
284
|
+
lines.push(`${pack.slug} removed (no longer assigned to your team)`);
|
|
285
|
+
for (const r of pack.results) {
|
|
286
|
+
const mark = r.status === "ok" ? "✓" : r.status === "failed" ? "✗" : "-";
|
|
287
|
+
lines.push(` ${mark} ${r.harness}: ${r.status === "failed" ? "failed: " : r.status === "skipped" ? "skipped: " : ""}${r.detail}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return lines;
|
|
291
|
+
}
|
|
292
|
+
function formatResultLines(results) {
|
|
293
|
+
const lines = [];
|
|
294
|
+
for (const pack of results) {
|
|
295
|
+
lines.push(`${pack.slug} v${pack.version}${pack.channel ? ` (${pack.channel})` : ""} → ${pack.dir}`);
|
|
296
|
+
for (const r of pack.results) {
|
|
297
|
+
const mark = r.status === "ok" ? "✓" : r.status === "failed" ? "✗" : r.status === "dry-run" ? "·" : "-";
|
|
298
|
+
const word = r.status === "ok" ? "" : r.status === "failed" ? "failed: " : r.status === "skipped" ? "skipped: " : "";
|
|
299
|
+
lines.push(` ${mark} ${r.harness}: ${word}${r.detail}`);
|
|
300
|
+
if (process.env.CODIEDEV_DEBUG)
|
|
301
|
+
for (const note of r.notes ?? [])
|
|
302
|
+
lines.push(` note: ${note}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return lines;
|
|
306
|
+
}
|