codiedev 0.9.67 → 0.9.68
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 +6 -0
- package/dist/codexHookTrust.js +73 -0
- 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 +304 -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,407 @@
|
|
|
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.ADAPTERS = exports.codieAdapter = exports.cursorAdapter = exports.CURSOR_MARKER = exports.codexAdapter = exports.copilotAdapter = exports.claudeAdapter = exports.HARNESSES = void 0;
|
|
37
|
+
exports.defaultRunner = defaultRunner;
|
|
38
|
+
exports.onPath = onPath;
|
|
39
|
+
exports.copyCursorSkills = copyCursorSkills;
|
|
40
|
+
exports.removeCursorSkills = removeCursorSkills;
|
|
41
|
+
exports.cursorLocalPluginsDir = cursorLocalPluginsDir;
|
|
42
|
+
exports.cursorPluginDir = cursorPluginDir;
|
|
43
|
+
exports.installCursorPlugin = installCursorPlugin;
|
|
44
|
+
exports.removeCursorPlugin = removeCursorPlugin;
|
|
45
|
+
exports.parseHarnessList = parseHarnessList;
|
|
46
|
+
// Per-tool adapters. Each registers the pack's plugin dir (via the shared
|
|
47
|
+
// local marketplace) with one coding tool. Adapters only run for tools that
|
|
48
|
+
// are detected, and one failing never stops the others.
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
const child_process_1 = require("child_process");
|
|
52
|
+
const harnessDirs_1 = require("../harnessDirs");
|
|
53
|
+
const paths_1 = require("./paths");
|
|
54
|
+
exports.HARNESSES = ["claude", "copilot", "codex", "cursor", "codie"];
|
|
55
|
+
function defaultRunner(env) {
|
|
56
|
+
return (command, args, options = {}) => {
|
|
57
|
+
const result = (0, child_process_1.spawnSync)(command, args, {
|
|
58
|
+
encoding: "utf8",
|
|
59
|
+
env,
|
|
60
|
+
cwd: options.cwd,
|
|
61
|
+
timeout: 180_000,
|
|
62
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
status: result.status,
|
|
66
|
+
stdout: result.stdout ?? "",
|
|
67
|
+
stderr: result.stderr ?? "",
|
|
68
|
+
...(result.error ? { error: result.error.message } : {}),
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** True when `bin` is an executable file on `env.PATH`. */
|
|
73
|
+
function onPath(bin, env) {
|
|
74
|
+
const dirs = (env.PATH ?? "").split(path.delimiter).filter(Boolean);
|
|
75
|
+
const exts = process.platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
|
|
76
|
+
for (const dir of dirs) {
|
|
77
|
+
for (const ext of exts) {
|
|
78
|
+
const candidate = path.join(dir, bin + ext);
|
|
79
|
+
try {
|
|
80
|
+
const stat = fs.statSync(candidate);
|
|
81
|
+
if (stat.isFile()) {
|
|
82
|
+
if (process.platform === "win32")
|
|
83
|
+
return true;
|
|
84
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// keep looking
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
function uninstallRef(slug) {
|
|
96
|
+
return `${slug}@${paths_1.MARKETPLACE_NAME}`;
|
|
97
|
+
}
|
|
98
|
+
function uninstallWith(ctx, bin, verb) {
|
|
99
|
+
const result = ctx.run(bin, ["plugin", verb, uninstallRef(ctx.slug)]);
|
|
100
|
+
if (result.status === 0)
|
|
101
|
+
return "uninstalled";
|
|
102
|
+
if (/not (found|installed)/i.test(`${result.stdout}\n${result.stderr}`))
|
|
103
|
+
return "already removed";
|
|
104
|
+
throw new Error(`\`${bin} plugin ${verb} ${uninstallRef(ctx.slug)}\` failed (${describe(result)})`);
|
|
105
|
+
}
|
|
106
|
+
function describe(result) {
|
|
107
|
+
if (result.error)
|
|
108
|
+
return result.error;
|
|
109
|
+
const text = (result.stderr || result.stdout || "").trim().split("\n").filter(Boolean).pop();
|
|
110
|
+
return text ? `exit ${result.status}: ${text.slice(0, 200)}` : `exit ${result.status}`;
|
|
111
|
+
}
|
|
112
|
+
function must(ctx, bin, args, options) {
|
|
113
|
+
const result = ctx.run(bin, args, options);
|
|
114
|
+
if (result.status !== 0)
|
|
115
|
+
throw new Error(`\`${bin} ${args.join(" ")}\` failed (${describe(result)})`);
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
function alreadyExists(result) {
|
|
119
|
+
return /already|exists/i.test(`${result.stdout}\n${result.stderr}`);
|
|
120
|
+
}
|
|
121
|
+
function wasInstalled(ctx, harness) {
|
|
122
|
+
return Boolean(ctx.previous?.harnesses?.includes(harness));
|
|
123
|
+
}
|
|
124
|
+
/** A same-version reinstall whose content changed: harness caches are keyed
|
|
125
|
+
* by version, so the old copy must be removed before installing again. */
|
|
126
|
+
function contentChangedInPlace(ctx) {
|
|
127
|
+
const prev = ctx.previous;
|
|
128
|
+
return Boolean(prev && prev.version === ctx.manifest.version && prev.contentHash !== ctx.manifest.contentHash);
|
|
129
|
+
}
|
|
130
|
+
function pluginRef(ctx) {
|
|
131
|
+
return `${ctx.manifest.slug}@${paths_1.MARKETPLACE_NAME}`;
|
|
132
|
+
}
|
|
133
|
+
function claudeMarketplaceKnown(env, home) {
|
|
134
|
+
try {
|
|
135
|
+
const known = JSON.parse(fs.readFileSync(path.join((0, harnessDirs_1.claudeConfigDir)(env, home), "plugins", "known_marketplaces.json"), "utf8"));
|
|
136
|
+
return Boolean(known && typeof known === "object" && paths_1.MARKETPLACE_NAME in known);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** Shared flow for tools that install Claude-format plugins from a local
|
|
143
|
+
* marketplace (Claude Code, Copilot CLI). */
|
|
144
|
+
/** Codex/Copilot register the portable root. Older CLIs registered the main
|
|
145
|
+
* root under the same name, so repoint once (remove, then add) per tool. */
|
|
146
|
+
function ensurePortableMarketplace(ctx, bin) {
|
|
147
|
+
const root = (0, paths_1.portableRoot)(ctx.root);
|
|
148
|
+
const stamp = path.join(root, `.registered-${bin}`);
|
|
149
|
+
if (!fs.existsSync(stamp))
|
|
150
|
+
ctx.run(bin, ["plugin", "marketplace", "remove", paths_1.MARKETPLACE_NAME]);
|
|
151
|
+
const added = ctx.run(bin, ["plugin", "marketplace", "add", root]);
|
|
152
|
+
if (added.status !== 0 && !alreadyExists(added)) {
|
|
153
|
+
throw new Error(`\`${bin} plugin marketplace add\` failed (${describe(added)})`);
|
|
154
|
+
}
|
|
155
|
+
fs.writeFileSync(stamp, `${root}\n`);
|
|
156
|
+
}
|
|
157
|
+
function marketplaceInstall(ctx, bin, harness, verbs, marketplaceKnown, portable = false) {
|
|
158
|
+
if (portable) {
|
|
159
|
+
ensurePortableMarketplace(ctx, bin);
|
|
160
|
+
}
|
|
161
|
+
else if (marketplaceKnown) {
|
|
162
|
+
// Directory marketplaces are re-read on update; best-effort.
|
|
163
|
+
ctx.run(bin, ["plugin", "marketplace", "update", paths_1.MARKETPLACE_NAME]);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
const added = ctx.run(bin, ["plugin", "marketplace", "add", ctx.root]);
|
|
167
|
+
if (added.status !== 0 && !alreadyExists(added)) {
|
|
168
|
+
throw new Error(`\`${bin} plugin marketplace add\` failed (${describe(added)})`);
|
|
169
|
+
}
|
|
170
|
+
if (added.status !== 0)
|
|
171
|
+
ctx.run(bin, ["plugin", "marketplace", "update", paths_1.MARKETPLACE_NAME]);
|
|
172
|
+
}
|
|
173
|
+
const ref = pluginRef(ctx);
|
|
174
|
+
if (wasInstalled(ctx, harness)) {
|
|
175
|
+
if (contentChangedInPlace(ctx)) {
|
|
176
|
+
ctx.run(bin, ["plugin", verbs.uninstall, ref]);
|
|
177
|
+
must(ctx, bin, ["plugin", verbs.install, ref]);
|
|
178
|
+
return "reinstalled";
|
|
179
|
+
}
|
|
180
|
+
const updated = ctx.run(bin, ["plugin", verbs.update, ref]);
|
|
181
|
+
if (updated.status === 0)
|
|
182
|
+
return "updated";
|
|
183
|
+
}
|
|
184
|
+
const installed = ctx.run(bin, ["plugin", verbs.install, ref]);
|
|
185
|
+
if (installed.status === 0)
|
|
186
|
+
return "installed";
|
|
187
|
+
if (alreadyExists(installed)) {
|
|
188
|
+
must(ctx, bin, ["plugin", verbs.update, ref]);
|
|
189
|
+
return "updated";
|
|
190
|
+
}
|
|
191
|
+
throw new Error(`\`${bin} plugin ${verbs.install} ${ref}\` failed (${describe(installed)})`);
|
|
192
|
+
}
|
|
193
|
+
exports.claudeAdapter = {
|
|
194
|
+
name: "claude",
|
|
195
|
+
detect: (ctx) => onPath("claude", ctx.env),
|
|
196
|
+
install: (ctx) => marketplaceInstall(ctx, "claude", "claude", { install: "install", update: "update", uninstall: "uninstall" }, claudeMarketplaceKnown(ctx.env, ctx.home)),
|
|
197
|
+
plan: (ctx) => `claude plugin marketplace add ${ctx.root}; claude plugin install ${pluginRef(ctx)}`,
|
|
198
|
+
uninstall: (ctx) => uninstallWith(ctx, "claude", "uninstall"),
|
|
199
|
+
};
|
|
200
|
+
exports.copilotAdapter = {
|
|
201
|
+
name: "copilot",
|
|
202
|
+
detect: (ctx) => onPath("copilot", ctx.env),
|
|
203
|
+
install: (ctx) => marketplaceInstall(ctx, "copilot", "copilot", { install: "install", update: "update", uninstall: "uninstall" }, false, true),
|
|
204
|
+
plan: (ctx) => `copilot plugin marketplace add ${(0, paths_1.portableRoot)(ctx.root)}; copilot plugin install ${pluginRef(ctx)}`,
|
|
205
|
+
uninstall: (ctx) => uninstallWith(ctx, "copilot", "uninstall"),
|
|
206
|
+
};
|
|
207
|
+
exports.codexAdapter = {
|
|
208
|
+
name: "codex",
|
|
209
|
+
detect: (ctx) => onPath("codex", ctx.env),
|
|
210
|
+
install: (ctx) => {
|
|
211
|
+
ensurePortableMarketplace(ctx, "codex");
|
|
212
|
+
const ref = pluginRef(ctx);
|
|
213
|
+
// Codex caches by version under plugins/cache/<mkt>/<p>/<version>; remove
|
|
214
|
+
// first so a reinstall always picks up the current content.
|
|
215
|
+
const reinstall = wasInstalled(ctx, "codex");
|
|
216
|
+
if (reinstall)
|
|
217
|
+
ctx.run("codex", ["plugin", "remove", ref]);
|
|
218
|
+
must(ctx, "codex", ["plugin", "add", ref]);
|
|
219
|
+
return reinstall ? "reinstalled" : "installed";
|
|
220
|
+
},
|
|
221
|
+
plan: (ctx) => `codex plugin marketplace add ${(0, paths_1.portableRoot)(ctx.root)}; codex plugin add ${pluginRef(ctx)}; trust pack hooks in config.toml`,
|
|
222
|
+
uninstall: (ctx) => uninstallWith(ctx, "codex", "remove"),
|
|
223
|
+
};
|
|
224
|
+
exports.CURSOR_MARKER = ".codiedev-pack";
|
|
225
|
+
function cursorSkillsDir(home) {
|
|
226
|
+
return path.join(home, ".cursor", "skills");
|
|
227
|
+
}
|
|
228
|
+
/** Fallback: copy skills to ~/.cursor/skills/<name>/, never overwriting a
|
|
229
|
+
* skill dir Codiedev didn't write, and pruning ones this pack no longer has. */
|
|
230
|
+
function copyCursorSkills(home, manifest, pluginDirPath) {
|
|
231
|
+
const base = cursorSkillsDir(home);
|
|
232
|
+
fs.mkdirSync(base, { recursive: true });
|
|
233
|
+
const notes = [];
|
|
234
|
+
const wanted = new Set(manifest.skills.map((s) => s.name));
|
|
235
|
+
for (const entry of fs.readdirSync(base)) {
|
|
236
|
+
const marker = path.join(base, entry, exports.CURSOR_MARKER);
|
|
237
|
+
try {
|
|
238
|
+
if (fs.readFileSync(marker, "utf8").trim() === manifest.slug && !wanted.has(entry)) {
|
|
239
|
+
fs.rmSync(path.join(base, entry), { recursive: true, force: true });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
// not ours
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
for (const skill of manifest.skills) {
|
|
247
|
+
const dest = path.join(base, skill.name);
|
|
248
|
+
if (fs.existsSync(dest)) {
|
|
249
|
+
let owner = "";
|
|
250
|
+
try {
|
|
251
|
+
owner = fs.readFileSync(path.join(dest, exports.CURSOR_MARKER), "utf8").trim();
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
owner = "";
|
|
255
|
+
}
|
|
256
|
+
if (owner !== manifest.slug) {
|
|
257
|
+
notes.push(`kept existing ~/.cursor/skills/${skill.name}`);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
261
|
+
}
|
|
262
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
263
|
+
fs.copyFileSync(path.join(pluginDirPath, "skills", skill.name, "SKILL.md"), path.join(dest, "SKILL.md"));
|
|
264
|
+
fs.writeFileSync(path.join(dest, exports.CURSOR_MARKER), `${manifest.slug}\n`);
|
|
265
|
+
}
|
|
266
|
+
return notes;
|
|
267
|
+
}
|
|
268
|
+
/** Remove every ~/.cursor/skills dir this pack's fallback copy wrote. */
|
|
269
|
+
function removeCursorSkills(home, slug) {
|
|
270
|
+
const base = cursorSkillsDir(home);
|
|
271
|
+
let removed = 0;
|
|
272
|
+
let entries = [];
|
|
273
|
+
try {
|
|
274
|
+
entries = fs.readdirSync(base);
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return 0;
|
|
278
|
+
}
|
|
279
|
+
for (const entry of entries) {
|
|
280
|
+
try {
|
|
281
|
+
if (fs.readFileSync(path.join(base, entry, exports.CURSOR_MARKER), "utf8").trim() === slug) {
|
|
282
|
+
fs.rmSync(path.join(base, entry), { recursive: true, force: true });
|
|
283
|
+
removed += 1;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
// not ours
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return removed;
|
|
291
|
+
}
|
|
292
|
+
// Cursor loads every Claude-format plugin dir in ~/.cursor/plugins/local/<name>/
|
|
293
|
+
// (skills, agents and hooks). Its `plugin marketplace add` only takes remote
|
|
294
|
+
// https git, and symlinks pointing outside plugins/local are rejected, so the
|
|
295
|
+
// pack is a real copy. A Cursor team admin can disable "user local plugin
|
|
296
|
+
// imports", in which case Cursor ignores the dir.
|
|
297
|
+
function cursorLocalPluginsDir(home) {
|
|
298
|
+
return path.join(home, ".cursor", "plugins", "local");
|
|
299
|
+
}
|
|
300
|
+
function cursorPluginDir(home, slug) {
|
|
301
|
+
return path.join(cursorLocalPluginsDir(home), `codiedev-${slug}`);
|
|
302
|
+
}
|
|
303
|
+
function markerOwner(dir) {
|
|
304
|
+
try {
|
|
305
|
+
return fs.readFileSync(path.join(dir, exports.CURSOR_MARKER), "utf8").trim();
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/** Recursive copy that always writes real files/dirs: symlinks are followed
|
|
312
|
+
* (Cursor rejects links whose target is outside plugins/local). fs.cpSync's
|
|
313
|
+
* `dereference` doesn't apply below the top level, so walk it ourselves. */
|
|
314
|
+
function copyTreeNoSymlinks(source, dest, depth = 0) {
|
|
315
|
+
if (depth > 32)
|
|
316
|
+
throw new Error(`plugin tree too deep at ${source}`);
|
|
317
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
318
|
+
for (const entry of fs.readdirSync(source)) {
|
|
319
|
+
const from = path.join(source, entry);
|
|
320
|
+
const to = path.join(dest, entry);
|
|
321
|
+
const stat = fs.statSync(from); // follows links
|
|
322
|
+
if (stat.isDirectory())
|
|
323
|
+
copyTreeNoSymlinks(from, to, depth + 1);
|
|
324
|
+
else if (stat.isFile())
|
|
325
|
+
fs.writeFileSync(to, fs.readFileSync(from));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/** Copy the plugin dir into place via a sibling temp dir + rename. Refuses to
|
|
329
|
+
* replace a dir that doesn't carry our marker for this pack. */
|
|
330
|
+
function installCursorPlugin(home, slug, sourceDir) {
|
|
331
|
+
const parent = cursorLocalPluginsDir(home);
|
|
332
|
+
const target = cursorPluginDir(home, slug);
|
|
333
|
+
if (fs.existsSync(target) && markerOwner(target) !== slug) {
|
|
334
|
+
throw new Error(`${target} exists and wasn't written by Codiedev; leaving it alone`);
|
|
335
|
+
}
|
|
336
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
337
|
+
const stamp = `${process.pid}-${Date.now()}`;
|
|
338
|
+
const tmp = path.join(parent, `.codiedev-${slug}.tmp-${stamp}`);
|
|
339
|
+
const old = path.join(parent, `.codiedev-${slug}.old-${stamp}`);
|
|
340
|
+
try {
|
|
341
|
+
copyTreeNoSymlinks(sourceDir, tmp);
|
|
342
|
+
fs.writeFileSync(path.join(tmp, exports.CURSOR_MARKER), `${slug}\n`);
|
|
343
|
+
if (fs.existsSync(target))
|
|
344
|
+
fs.renameSync(target, old);
|
|
345
|
+
fs.renameSync(tmp, target);
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
349
|
+
if (!fs.existsSync(target) && fs.existsSync(old))
|
|
350
|
+
fs.renameSync(old, target);
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
353
|
+
fs.rmSync(old, { recursive: true, force: true });
|
|
354
|
+
return target;
|
|
355
|
+
}
|
|
356
|
+
/** Remove our copy (only when it carries our marker). */
|
|
357
|
+
function removeCursorPlugin(home, slug) {
|
|
358
|
+
const target = cursorPluginDir(home, slug);
|
|
359
|
+
if (markerOwner(target) !== slug)
|
|
360
|
+
return false;
|
|
361
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
exports.cursorAdapter = {
|
|
365
|
+
name: "cursor",
|
|
366
|
+
detect: (ctx) => onPath("cursor-agent", ctx.env) || fs.existsSync(path.join(ctx.home, ".cursor")),
|
|
367
|
+
install: (ctx) => {
|
|
368
|
+
try {
|
|
369
|
+
installCursorPlugin(ctx.home, ctx.manifest.slug, ctx.portablePluginDir);
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
// Last resort: skills only, where Cursor also looks.
|
|
373
|
+
const notes = copyCursorSkills(ctx.home, ctx.manifest, ctx.portablePluginDir);
|
|
374
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
375
|
+
return [`skills only (couldn't write ~/.cursor/plugins/local: ${reason})`, ...notes].join("; ");
|
|
376
|
+
}
|
|
377
|
+
// Migrate: the plugin now carries the skills; drop old fallback copies.
|
|
378
|
+
const migrated = removeCursorSkills(ctx.home, ctx.manifest.slug);
|
|
379
|
+
return migrated ? `installed (removed ${migrated} old ~/.cursor/skills cop${migrated === 1 ? "y" : "ies"})` : "installed";
|
|
380
|
+
},
|
|
381
|
+
plan: (ctx) => `copy plugin to ${cursorPluginDir(ctx.home, ctx.manifest.slug)}`,
|
|
382
|
+
uninstall: (ctx) => {
|
|
383
|
+
const plugin = removeCursorPlugin(ctx.home, ctx.slug);
|
|
384
|
+
const skills = removeCursorSkills(ctx.home, ctx.slug);
|
|
385
|
+
if (!plugin && !skills)
|
|
386
|
+
return "already removed";
|
|
387
|
+
return skills ? `uninstalled (${skills} copied skill${skills === 1 ? "" : "s"} removed)` : "uninstalled";
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
exports.codieAdapter = {
|
|
391
|
+
name: "codie",
|
|
392
|
+
// `codie` ships in this package, so it is always available.
|
|
393
|
+
detect: () => true,
|
|
394
|
+
install: () => "loads on next `codie` launch",
|
|
395
|
+
plan: () => "inject skills/agents/hook shim at next codie launch",
|
|
396
|
+
uninstall: () => "removed from next `codie` launch",
|
|
397
|
+
};
|
|
398
|
+
exports.ADAPTERS = [exports.claudeAdapter, exports.copilotAdapter, exports.codexAdapter, exports.cursorAdapter, exports.codieAdapter];
|
|
399
|
+
function parseHarnessList(value) {
|
|
400
|
+
if (value === undefined)
|
|
401
|
+
return undefined;
|
|
402
|
+
const names = value.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
403
|
+
const unknown = names.filter((n) => !exports.HARNESSES.includes(n));
|
|
404
|
+
if (unknown.length)
|
|
405
|
+
throw new Error(`Unknown harness: ${unknown.join(", ")} (valid: ${exports.HARNESSES.join(", ")})`);
|
|
406
|
+
return [...new Set(names)];
|
|
407
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { spawnSync } from "child_process";
|
|
2
|
+
/** Skill names a hook payload shows as running, across the five tools:
|
|
3
|
+
* - Claude: tool_name "Skill", tool_input.skill "<plugin>:<name>"
|
|
4
|
+
* - Copilot: tool_name "skill", tool_input.skill "<name>" (also camelCase toolName/toolArgs)
|
|
5
|
+
* - Codie: tool_name "skill", tool_input.name "<name>"
|
|
6
|
+
* - Cursor: tool_name "Read", tool_input.file_path ".../skills/<name>/SKILL.md"
|
|
7
|
+
* - Codex: tool_name "Bash", tool_input.command containing ".../skills/<name>/SKILL.md" */
|
|
8
|
+
export declare function skillsFromPayload(payload: unknown): string[];
|
|
9
|
+
export interface GateArgs {
|
|
10
|
+
skills: string[];
|
|
11
|
+
/** `-- <argv…>`: exec'd directly (legacy `sh -c '<cmd>'` included). */
|
|
12
|
+
command: string[];
|
|
13
|
+
/** `--cmd-b64 <base64url>`: a command line for the platform shell. */
|
|
14
|
+
cmdB64?: string;
|
|
15
|
+
}
|
|
16
|
+
/** Largest decoded `--cmd-b64` command we run. */
|
|
17
|
+
export declare const MAX_GATED_COMMAND_BYTES: number;
|
|
18
|
+
/** Canonical form (what the portal writes): base64url of the UTF-8 command, no
|
|
19
|
+
* padding. Only [A-Za-z0-9_-], so no shell on any platform expands or
|
|
20
|
+
* re-quotes it — a `$(…)` in the gated command can't run before the gate. */
|
|
21
|
+
export declare function encodeGatedCommand(command: string): string;
|
|
22
|
+
/** Strict decode: canonical base64url only, ≤16KB, valid UTF-8, no NUL. */
|
|
23
|
+
export declare function decodeGatedCommand(value: string): string;
|
|
24
|
+
export declare function parseGateArgs(argv: string[]): GateArgs;
|
|
25
|
+
export declare function gateMatches(skills: string[], payload: unknown): boolean;
|
|
26
|
+
export interface GatePlan {
|
|
27
|
+
file: string;
|
|
28
|
+
args: string[];
|
|
29
|
+
/** cmd.exe needs its /c line passed verbatim (Node would re-quote it). */
|
|
30
|
+
verbatim?: boolean;
|
|
31
|
+
note?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface PlanOptions {
|
|
34
|
+
platform?: NodeJS.Platform;
|
|
35
|
+
env?: NodeJS.ProcessEnv;
|
|
36
|
+
/** Whether `sh` is runnable (only consulted for the legacy form on Windows). */
|
|
37
|
+
hasSh?: () => boolean;
|
|
38
|
+
}
|
|
39
|
+
/** Split a CODIEDEV_HOOK_SHELL value ("pwsh -NoProfile -Command") into argv,
|
|
40
|
+
* honouring simple "double" or 'single' quotes. */
|
|
41
|
+
export declare function splitShellOverride(value: string): string[];
|
|
42
|
+
/** The decoded `--cmd-b64` command line, run by the platform shell
|
|
43
|
+
* (/bin/sh -c on POSIX, cmd.exe /d /s /c on Windows, or CODIEDEV_HOOK_SHELL). */
|
|
44
|
+
export declare function planShellCommand(line: string, options?: PlanOptions): GatePlan;
|
|
45
|
+
/** `-- <argv…>`: exec'd directly, no shell. Legacy `sh -c '<cmd>'` (packs saved
|
|
46
|
+
* before --cmd-b64) runs as given, except on Windows without sh, where <cmd>
|
|
47
|
+
* goes to cmd.exe. */
|
|
48
|
+
export declare function planGateCommand(command: string[], options?: PlanOptions): GatePlan;
|
|
49
|
+
/** Returns the exit status to use. */
|
|
50
|
+
export declare function runHookGate(argv: string[], stdin: string, env?: NodeJS.ProcessEnv, options?: Omit<PlanOptions, "env"> & {
|
|
51
|
+
spawn?: typeof spawnSync;
|
|
52
|
+
}): number;
|
|
53
|
+
export declare function runHookGateCli(argv: string[]): void;
|