@ulpi/cli 0.1.5 → 0.1.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/README.md +143 -214
- package/dist/{auth-PN7TMQHV-2W4ICG64.js → auth-FWM7MM4Q-VZC3U2XZ.js} +1 -1
- package/dist/{auth-BFFBUJUC.js → auth-HDK7ECJL.js} +2 -1
- package/dist/{chunk-RJIRWQJD.js → chunk-3BCW6ABU.js} +402 -142
- package/dist/{chunk-L3PWNHSA.js → chunk-3WB5CXH4.js} +180 -5
- package/dist/{chunk-K4OVPFY2.js → chunk-4UCJIAOU.js} +2 -2
- package/dist/chunk-4XTHZVDS.js +109 -0
- package/dist/chunk-4ZPOZULQ.js +6522 -0
- package/dist/{chunk-SIAQVRKG.js → chunk-5MI5GIXM.js} +48 -2
- package/dist/{chunk-KLEASXUR.js → chunk-6ZL6NXMV.js} +1 -1
- package/dist/{chunk-AV5RB3N2.js → chunk-76D3BYJD.js} +48 -0
- package/dist/{chunk-DOIKS6C5.js → chunk-AWOSRA5F.js} +1 -1
- package/dist/{chunk-UCMT5OKP.js → chunk-BFEKZZHM.js} +274 -57
- package/dist/chunk-C7CLUQI6.js +1286 -0
- package/dist/{chunk-ELTGWMDE.js → chunk-E3B5NROU.js} +7 -7
- package/dist/chunk-EJ7TW77N.js +1418 -0
- package/dist/{chunk-P2RESJRN.js → chunk-EWLYVXQ4.js} +2 -2
- package/dist/{chunk-6OURRFP7.js → chunk-IV6MWETF.js} +383 -168
- package/dist/chunk-IZPJHSPX.js +1478 -0
- package/dist/chunk-JLHNLM3C.js +228 -0
- package/dist/chunk-PO4NUZUU.js +147 -0
- package/dist/chunk-S6ANCSYO.js +1271 -0
- package/dist/chunk-SEU7WWNQ.js +1251 -0
- package/dist/chunk-SNQ7NAIS.js +453 -0
- package/dist/{ulpi-RMMCUAGP-EWYUE7RU.js → chunk-TSLDGT5O.js} +73 -35
- package/dist/{chunk-EIWYSP3A.js → chunk-UXHCHOWQ.js} +83 -62
- package/dist/chunk-WED4LM5N.js +322 -0
- package/dist/chunk-WVOZE25N.js +6757 -0
- package/dist/{chunk-5SCG7UYM.js → chunk-XKF4DPUM.js} +7 -7
- package/dist/{chunk-74WVVWJ4.js → chunk-YOKL7RB5.js} +184 -15
- package/dist/chunk-Z53CAR7G.js +298 -0
- package/dist/{ci-JQ56YIKC.js → ci-COZRTPGQ.js} +124 -26
- package/dist/cloud-2F3NLVHN.js +274 -0
- package/dist/{codemap-HMYBXJL2.js → codemap-XNGMAF3F.js} +37 -37
- package/dist/codex-MB5YTMRT.js +132 -0
- package/dist/{config-YYWEN7U2.js → config-OOELBYTH.js} +1 -1
- package/dist/dist-2BJYR5EI.js +59 -0
- package/dist/dist-3EIQTZHT.js +1380 -0
- package/dist/{dist-WAMAQVPK.js → dist-4U5L2X2C.js} +2 -2
- package/dist/{dist-4XTJ6HLM.js → dist-54KAMNLO.js} +16 -15
- package/dist/dist-6M4MZWZW.js +58 -0
- package/dist/dist-6X576SU2.js +27 -0
- package/dist/dist-7QOEYLFX.js +103 -0
- package/dist/dist-AYBGHEDY.js +2541 -0
- package/dist/dist-EK45QNEM.js +45 -0
- package/dist/{dist-U7ZIJMZD.js → dist-FKFEJRPX.js} +16 -15
- package/dist/dist-GTEJUBBT.js +66 -0
- package/dist/dist-HA74OKJZ.js +40 -0
- package/dist/{dist-XG2GG5SD.js → dist-HU5RZAON.js} +14 -2
- package/dist/dist-IYE3OBRB.js +374 -0
- package/dist/{dist-7WLLPWWB.js → dist-JLU26AB6.js} +12 -9
- package/dist/{dist-6G7JC2RA.js → dist-KUCI6JFE.js} +49 -9
- package/dist/dist-NUEMFZFL.js +33 -0
- package/dist/{dist-GWGTAHNM.js → dist-NUXMDXZ3.js} +31 -3
- package/dist/{dist-5R4RYNQO.js → dist-YCNWHSLN.js} +15 -5
- package/dist/{dist-6MFVWIFF.js → dist-YFFG2ZD6.js} +9 -16
- package/dist/dist-ZG4OKCSR.js +15 -0
- package/dist/doctor-FKYSIHER.js +345 -0
- package/dist/{export-import-4A5MWLIA.js → export-import-JFQH4KSJ.js} +1 -1
- package/dist/{history-RNUWO4JZ.js → history-UMGQNQQ7.js} +7 -7
- package/dist/{hooks-installer-K2JXEBNN.js → hooks-installer-YEYTYA6Q.js} +2 -2
- package/dist/index.js +398 -622
- package/dist/{init-NQWFZPKO.js → init-TJYW5ROZ.js} +78 -12
- package/dist/job-HIDMAFW2.js +376 -0
- package/dist/jobs.memory-PLMMSFHB-VBECCTHN.js +33 -0
- package/dist/kiro-VMUHDFGK.js +153 -0
- package/dist/{launchd-OYXUAVW6.js → launchd-U3MSWBRH.js} +9 -17
- package/dist/mcp-PDUD7SGP.js +249 -0
- package/dist/mcp-installer-PQU3XOGO.js +259 -0
- package/dist/mcp-setup-OA7IB3H3.js +263 -0
- package/dist/{memory-D6ZFFCI2.js → memory-ZNAEAK3B.js} +17 -17
- package/dist/{ollama-3XCUZMZT-FYKHW4TZ.js → ollama-3XCUZMZT-4JMH6B7P.js} +1 -1
- package/dist/{openai-E7G2YAHU-IG33BFYF.js → openai-E7G2YAHU-T3HMBPH7.js} +2 -2
- package/dist/portal-JYWVHXDU.js +210 -0
- package/dist/prd-Q4J5NVAR.js +408 -0
- package/dist/repos-WWZXNN3P.js +271 -0
- package/dist/review-integration-RQE4KMAV.js +14 -0
- package/dist/{rules-3OFGWHP4.js → rules-Y4VSOY5Y.js} +3 -3
- package/dist/run-VPNXEIBY.js +687 -0
- package/dist/server-COL4AXKU-P7S7NNF6.js +11 -0
- package/dist/server-U7PQ6FTS-MG4MJPTS.js +20 -0
- package/dist/{skills-GY2CTPWN.js → skills-QEYU2N27.js} +4 -2
- package/dist/start-IJKY5RVT.js +303 -0
- package/dist/{status-SE43TIFJ.js → status-BHQYYGAL.js} +2 -2
- package/dist/{templates-O2XDKB5R.js → templates-CBRUJ66V.js} +6 -5
- package/dist/tui-DP7736EX.js +61 -0
- package/dist/ulpi-5EN6JCAS-LFE3WSL4.js +10 -0
- package/dist/{uninstall-KWGSGZTI.js → uninstall-BX6FOV77.js} +3 -3
- package/dist/{update-QYZA4D23.js → update-AQKTHFVQ.js} +3 -3
- package/dist/{version-checker-MVB74DEX.js → version-checker-5L5PUOEX.js} +2 -2
- package/package.json +13 -4
- package/dist/chunk-26LLDX2T.js +0 -553
- package/dist/chunk-DDRLI6JU.js +0 -331
- package/dist/chunk-IFATANHR.js +0 -453
- package/dist/chunk-JWUUVXIV.js +0 -13694
- package/dist/chunk-LD52XG3X.js +0 -4273
- package/dist/chunk-MIAQVCFW.js +0 -39
- package/dist/chunk-YYZOFYS6.js +0 -415
- package/dist/dist-XD4YI27T.js +0 -26
- package/dist/mcp-installer-TOYDP77X.js +0 -124
- package/dist/projects-COUJP4ZC.js +0 -271
- package/dist/review-KMGP2S25.js +0 -152
- package/dist/server-USLHY6GH-F4JSXCWA.js +0 -18
- package/dist/server-X5P6WH2M-ULZF5WHZ.js +0 -11
- package/dist/ui-4SM2SUI6.js +0 -167
- package/dist/ui.html +0 -698
- /package/dist/skills/{ulpi-generate-guardian → ulpi-generate-guards}/SKILL.md +0 -0
- /package/dist/skills/{ulpi-generate-guardian → ulpi-generate-guards}/references/framework-rules.md +0 -0
- /package/dist/skills/{ulpi-generate-guardian → ulpi-generate-guards}/references/language-rules.md +0 -0
|
@@ -0,0 +1,1478 @@
|
|
|
1
|
+
import {
|
|
2
|
+
evaluateRules,
|
|
3
|
+
isDangerousCommand,
|
|
4
|
+
matchesFilePattern
|
|
5
|
+
} from "./chunk-5MI5GIXM.js";
|
|
6
|
+
|
|
7
|
+
// ../../packages/agent-plugins/dist/index.js
|
|
8
|
+
import { spawn } from "child_process";
|
|
9
|
+
import { accessSync } from "fs";
|
|
10
|
+
import { platform } from "os";
|
|
11
|
+
import { isAbsolute } from "path";
|
|
12
|
+
import { randomUUID } from "crypto";
|
|
13
|
+
import { execFileSync } from "child_process";
|
|
14
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
15
|
+
import * as fs from "fs";
|
|
16
|
+
import * as path from "path";
|
|
17
|
+
import * as os from "os";
|
|
18
|
+
var DEFAULT_ENV_EXCLUDE_PATTERNS = [
|
|
19
|
+
"*_API_KEY",
|
|
20
|
+
"*_SECRET_KEY",
|
|
21
|
+
"*_SECRET"
|
|
22
|
+
];
|
|
23
|
+
function globMatch(pattern, str) {
|
|
24
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
25
|
+
const regex = new RegExp(
|
|
26
|
+
"^" + escaped.replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
|
|
27
|
+
);
|
|
28
|
+
return regex.test(str);
|
|
29
|
+
}
|
|
30
|
+
function filterEnv(env, excludePatterns, passthroughPatterns) {
|
|
31
|
+
if (!excludePatterns || excludePatterns.length === 0) {
|
|
32
|
+
return env;
|
|
33
|
+
}
|
|
34
|
+
const filtered = {};
|
|
35
|
+
for (const [key, value] of Object.entries(env)) {
|
|
36
|
+
const matchesExclude = excludePatterns.some(
|
|
37
|
+
(pattern) => globMatch(pattern, key)
|
|
38
|
+
);
|
|
39
|
+
if (!matchesExclude) {
|
|
40
|
+
filtered[key] = value;
|
|
41
|
+
} else if (passthroughPatterns.length > 0 && passthroughPatterns.some((pattern) => globMatch(pattern, key))) {
|
|
42
|
+
filtered[key] = value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return filtered;
|
|
46
|
+
}
|
|
47
|
+
function getEnvExclusionReport(env = process.env, passthroughPatterns = [], additionalExclude = []) {
|
|
48
|
+
const excludePatterns = [...DEFAULT_ENV_EXCLUDE_PATTERNS, ...additionalExclude];
|
|
49
|
+
const blocked = [];
|
|
50
|
+
const allowed = [];
|
|
51
|
+
for (const key of Object.keys(env)) {
|
|
52
|
+
const matchesExclude = excludePatterns.some(
|
|
53
|
+
(pattern) => globMatch(pattern, key)
|
|
54
|
+
);
|
|
55
|
+
if (!matchesExclude) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const matchesPassthrough = passthroughPatterns.length > 0 && passthroughPatterns.some((pattern) => globMatch(pattern, key));
|
|
59
|
+
if (matchesPassthrough) {
|
|
60
|
+
allowed.push(key);
|
|
61
|
+
} else {
|
|
62
|
+
blocked.push(key);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { blocked: blocked.sort(), allowed: allowed.sort() };
|
|
66
|
+
}
|
|
67
|
+
function findCommandPath(command) {
|
|
68
|
+
return new Promise((resolve) => {
|
|
69
|
+
const isWindows = platform() === "win32";
|
|
70
|
+
const trimmed = command.trim();
|
|
71
|
+
const normalized = trimmed.startsWith('"') && trimmed.endsWith('"') ? trimmed.slice(1, -1) : trimmed;
|
|
72
|
+
const isPathLike = isAbsolute(normalized) || normalized.includes("/") || normalized.includes("\\");
|
|
73
|
+
if (isPathLike) {
|
|
74
|
+
try {
|
|
75
|
+
accessSync(normalized);
|
|
76
|
+
return resolve({ found: true, path: normalized });
|
|
77
|
+
} catch {
|
|
78
|
+
return resolve({ found: false, path: "" });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const whichCmd = isWindows ? "where" : "which";
|
|
82
|
+
const proc = spawn(whichCmd, [command], {
|
|
83
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
84
|
+
shell: isWindows
|
|
85
|
+
});
|
|
86
|
+
let stdout = "";
|
|
87
|
+
proc.stdout?.on("data", (data) => {
|
|
88
|
+
stdout += data.toString();
|
|
89
|
+
});
|
|
90
|
+
proc.on("error", () => {
|
|
91
|
+
resolve({ found: false, path: "" });
|
|
92
|
+
});
|
|
93
|
+
proc.on("close", (code) => {
|
|
94
|
+
if (code === 0 && stdout.trim()) {
|
|
95
|
+
const firstPath = stdout.trim().split(/\r?\n/)[0] ?? "";
|
|
96
|
+
resolve({ found: true, path: firstPath.trim() });
|
|
97
|
+
} else {
|
|
98
|
+
resolve({ found: false, path: "" });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
setTimeout(() => {
|
|
102
|
+
proc.kill();
|
|
103
|
+
resolve({ found: false, path: "" });
|
|
104
|
+
}, 15e3);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
var MAX_STREAM_CHARS = 2e6;
|
|
108
|
+
function appendCapped(current, chunk, max) {
|
|
109
|
+
const combined = current + chunk;
|
|
110
|
+
if (combined.length <= max) {
|
|
111
|
+
return combined;
|
|
112
|
+
}
|
|
113
|
+
return "[...truncated...]\n" + combined.slice(combined.length - max);
|
|
114
|
+
}
|
|
115
|
+
var BaseAgentPlugin = class {
|
|
116
|
+
config = {};
|
|
117
|
+
ready = false;
|
|
118
|
+
commandPath;
|
|
119
|
+
defaultFlags = [];
|
|
120
|
+
defaultTimeout = 0;
|
|
121
|
+
envExclude = [];
|
|
122
|
+
envPassthrough = [];
|
|
123
|
+
executions = /* @__PURE__ */ new Map();
|
|
124
|
+
currentExecutionId;
|
|
125
|
+
async initialize(config) {
|
|
126
|
+
this.config = config;
|
|
127
|
+
if (typeof config.command === "string") {
|
|
128
|
+
this.commandPath = config.command;
|
|
129
|
+
}
|
|
130
|
+
if (Array.isArray(config.defaultFlags)) {
|
|
131
|
+
this.defaultFlags = config.defaultFlags.filter(
|
|
132
|
+
(f) => typeof f === "string"
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (typeof config.timeout === "number" && config.timeout > 0) {
|
|
136
|
+
this.defaultTimeout = config.timeout;
|
|
137
|
+
}
|
|
138
|
+
if (Array.isArray(config.envExclude)) {
|
|
139
|
+
this.envExclude = config.envExclude.filter(
|
|
140
|
+
(p) => typeof p === "string" && p.length > 0
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
if (Array.isArray(config.envPassthrough)) {
|
|
144
|
+
this.envPassthrough = config.envPassthrough.filter(
|
|
145
|
+
(p) => typeof p === "string" && p.length > 0
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
this.ready = true;
|
|
149
|
+
}
|
|
150
|
+
async isReady() {
|
|
151
|
+
return this.ready;
|
|
152
|
+
}
|
|
153
|
+
async detect() {
|
|
154
|
+
const command = this.commandPath ?? this.meta.cliCommand;
|
|
155
|
+
const result = await findCommandPath(command);
|
|
156
|
+
if (!result.found) {
|
|
157
|
+
return {
|
|
158
|
+
available: false,
|
|
159
|
+
error: `${this.meta.name} CLI not found in PATH`
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
this.commandPath = result.path;
|
|
163
|
+
return {
|
|
164
|
+
available: true,
|
|
165
|
+
executablePath: result.path
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get input to write to stdin after spawning the process.
|
|
170
|
+
* Override in subclasses to provide stdin input (e.g., prompt content).
|
|
171
|
+
*/
|
|
172
|
+
getStdinInput(prompt, options) {
|
|
173
|
+
return void 0;
|
|
174
|
+
}
|
|
175
|
+
execute(prompt, options) {
|
|
176
|
+
const executionId = randomUUID();
|
|
177
|
+
const command = this.commandPath ?? this.meta.cliCommand;
|
|
178
|
+
const args = this.buildArgs(prompt, options);
|
|
179
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
180
|
+
const timeout = options?.timeout ?? this.defaultTimeout;
|
|
181
|
+
const effectiveExclude = [
|
|
182
|
+
...DEFAULT_ENV_EXCLUDE_PATTERNS,
|
|
183
|
+
...this.envExclude
|
|
184
|
+
];
|
|
185
|
+
const baseEnv = filterEnv(process.env, effectiveExclude, this.envPassthrough);
|
|
186
|
+
const env = { ...baseEnv, ...options?.env };
|
|
187
|
+
const allArgs = [...this.defaultFlags, ...options?.flags ?? [], ...args];
|
|
188
|
+
let resolvePromise;
|
|
189
|
+
let rejectPromise;
|
|
190
|
+
const promise = new Promise((resolve, reject) => {
|
|
191
|
+
resolvePromise = resolve;
|
|
192
|
+
rejectPromise = reject;
|
|
193
|
+
});
|
|
194
|
+
const isWindows = platform() === "win32";
|
|
195
|
+
const proc = spawn(command, allArgs, {
|
|
196
|
+
cwd: options?.cwd ?? process.cwd(),
|
|
197
|
+
env,
|
|
198
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
199
|
+
shell: isWindows
|
|
200
|
+
});
|
|
201
|
+
const stdinInput = this.getStdinInput(prompt, options);
|
|
202
|
+
if (stdinInput !== void 0 && proc.stdin) {
|
|
203
|
+
proc.stdin.write(stdinInput);
|
|
204
|
+
proc.stdin.end();
|
|
205
|
+
} else if (proc.stdin) {
|
|
206
|
+
proc.stdin.end();
|
|
207
|
+
}
|
|
208
|
+
const execution = {
|
|
209
|
+
executionId,
|
|
210
|
+
process: proc,
|
|
211
|
+
startedAt,
|
|
212
|
+
stdout: "",
|
|
213
|
+
stderr: "",
|
|
214
|
+
interrupted: false,
|
|
215
|
+
resolve: resolvePromise,
|
|
216
|
+
reject: rejectPromise,
|
|
217
|
+
options
|
|
218
|
+
};
|
|
219
|
+
this.executions.set(executionId, execution);
|
|
220
|
+
this.currentExecutionId = executionId;
|
|
221
|
+
options?.onStart?.(executionId);
|
|
222
|
+
proc.stdout?.on("data", (data) => {
|
|
223
|
+
const text = data.toString();
|
|
224
|
+
execution.stdout = appendCapped(
|
|
225
|
+
execution.stdout,
|
|
226
|
+
text,
|
|
227
|
+
MAX_STREAM_CHARS
|
|
228
|
+
);
|
|
229
|
+
options?.onStdout?.(text);
|
|
230
|
+
});
|
|
231
|
+
proc.stderr?.on("data", (data) => {
|
|
232
|
+
const text = data.toString();
|
|
233
|
+
execution.stderr = appendCapped(
|
|
234
|
+
execution.stderr,
|
|
235
|
+
text,
|
|
236
|
+
MAX_STREAM_CHARS
|
|
237
|
+
);
|
|
238
|
+
options?.onStderr?.(text);
|
|
239
|
+
});
|
|
240
|
+
proc.on("error", (error) => {
|
|
241
|
+
this.completeExecution(executionId, "failed", void 0, error.message);
|
|
242
|
+
});
|
|
243
|
+
proc.on("close", (code, signal) => {
|
|
244
|
+
let status;
|
|
245
|
+
if (execution.interrupted) {
|
|
246
|
+
status = "interrupted";
|
|
247
|
+
} else if (signal === "SIGTERM" || signal === "SIGKILL") {
|
|
248
|
+
status = execution.timeoutId ? "timeout" : "interrupted";
|
|
249
|
+
} else if (code === 0) {
|
|
250
|
+
status = "completed";
|
|
251
|
+
} else {
|
|
252
|
+
status = "failed";
|
|
253
|
+
}
|
|
254
|
+
this.completeExecution(executionId, status, code ?? void 0);
|
|
255
|
+
});
|
|
256
|
+
if (timeout > 0) {
|
|
257
|
+
execution.timeoutId = setTimeout(() => {
|
|
258
|
+
if (this.executions.has(executionId)) {
|
|
259
|
+
proc.kill("SIGTERM");
|
|
260
|
+
setTimeout(() => {
|
|
261
|
+
if (this.executions.has(executionId)) {
|
|
262
|
+
proc.kill("SIGKILL");
|
|
263
|
+
}
|
|
264
|
+
}, 5e3);
|
|
265
|
+
}
|
|
266
|
+
}, timeout);
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
executionId,
|
|
270
|
+
promise,
|
|
271
|
+
interrupt: () => this.interrupt(executionId),
|
|
272
|
+
isRunning: () => this.executions.has(executionId)
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
completeExecution(executionId, status, exitCode, error) {
|
|
276
|
+
const execution = this.executions.get(executionId);
|
|
277
|
+
if (!execution) return;
|
|
278
|
+
if (execution.timeoutId) {
|
|
279
|
+
clearTimeout(execution.timeoutId);
|
|
280
|
+
}
|
|
281
|
+
const endedAt = /* @__PURE__ */ new Date();
|
|
282
|
+
const durationMs = endedAt.getTime() - execution.startedAt.getTime();
|
|
283
|
+
const result = {
|
|
284
|
+
executionId,
|
|
285
|
+
status,
|
|
286
|
+
exitCode,
|
|
287
|
+
stdout: execution.stdout,
|
|
288
|
+
stderr: execution.stderr,
|
|
289
|
+
durationMs,
|
|
290
|
+
error,
|
|
291
|
+
interrupted: execution.interrupted,
|
|
292
|
+
startedAt: execution.startedAt.toISOString(),
|
|
293
|
+
endedAt: endedAt.toISOString()
|
|
294
|
+
};
|
|
295
|
+
this.executions.delete(executionId);
|
|
296
|
+
if (this.currentExecutionId === executionId) {
|
|
297
|
+
this.currentExecutionId = void 0;
|
|
298
|
+
}
|
|
299
|
+
if (execution.options?.onEnd) {
|
|
300
|
+
try {
|
|
301
|
+
execution.options.onEnd(result);
|
|
302
|
+
} catch {
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
execution.resolve(result);
|
|
306
|
+
}
|
|
307
|
+
interrupt(executionId) {
|
|
308
|
+
const execution = this.executions.get(executionId);
|
|
309
|
+
if (!execution) return false;
|
|
310
|
+
execution.interrupted = true;
|
|
311
|
+
execution.process.kill("SIGTERM");
|
|
312
|
+
setTimeout(() => {
|
|
313
|
+
if (this.executions.has(executionId)) {
|
|
314
|
+
execution.process.kill("SIGKILL");
|
|
315
|
+
}
|
|
316
|
+
}, 5e3);
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
interruptAll() {
|
|
320
|
+
for (const executionId of this.executions.keys()) {
|
|
321
|
+
this.interrupt(executionId);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
getCurrentExecution() {
|
|
325
|
+
if (!this.currentExecutionId) return void 0;
|
|
326
|
+
const execution = this.executions.get(this.currentExecutionId);
|
|
327
|
+
if (!execution) return void 0;
|
|
328
|
+
const executionId = this.currentExecutionId;
|
|
329
|
+
return {
|
|
330
|
+
executionId,
|
|
331
|
+
promise: new Promise((resolve, reject) => {
|
|
332
|
+
execution.resolve = resolve;
|
|
333
|
+
execution.reject = reject;
|
|
334
|
+
}),
|
|
335
|
+
interrupt: () => this.interrupt(executionId),
|
|
336
|
+
isRunning: () => this.executions.has(executionId)
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
async preflight(options) {
|
|
340
|
+
const startTime = Date.now();
|
|
341
|
+
const timeout = options?.timeout ?? 15e3;
|
|
342
|
+
try {
|
|
343
|
+
const detection = await this.detect();
|
|
344
|
+
if (!detection.available) {
|
|
345
|
+
return {
|
|
346
|
+
success: false,
|
|
347
|
+
error: detection.error ?? "Agent not available",
|
|
348
|
+
suggestion: `Make sure ${this.meta.name} is installed and accessible`,
|
|
349
|
+
durationMs: Date.now() - startTime
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
const testPrompt = "Respond with exactly: PREFLIGHT_OK";
|
|
353
|
+
let stdoutCapture = "";
|
|
354
|
+
const handle = this.execute(testPrompt, {
|
|
355
|
+
timeout,
|
|
356
|
+
onStdout: (data) => {
|
|
357
|
+
stdoutCapture += data;
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
const result = await handle.promise;
|
|
361
|
+
const durationMs = Date.now() - startTime;
|
|
362
|
+
if (result.status === "completed" && stdoutCapture.length > 0) {
|
|
363
|
+
return { success: true, durationMs };
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
success: false,
|
|
367
|
+
error: result.error ?? `Agent ${result.status} without output`,
|
|
368
|
+
suggestion: `Verify ${this.meta.name} is properly configured`,
|
|
369
|
+
durationMs
|
|
370
|
+
};
|
|
371
|
+
} catch (error) {
|
|
372
|
+
return {
|
|
373
|
+
success: false,
|
|
374
|
+
error: error instanceof Error ? error.message : String(error),
|
|
375
|
+
durationMs: Date.now() - startTime
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
getSandboxRequirements() {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
async dispose() {
|
|
383
|
+
this.interruptAll();
|
|
384
|
+
this.ready = false;
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
var ClaudeAgentPlugin = class extends BaseAgentPlugin {
|
|
388
|
+
meta = {
|
|
389
|
+
name: "claude",
|
|
390
|
+
version: "1.0.0",
|
|
391
|
+
description: "Anthropic Claude Code CLI for AI-assisted coding",
|
|
392
|
+
cliCommand: "claude",
|
|
393
|
+
supportsHooks: true,
|
|
394
|
+
supportsSkills: true,
|
|
395
|
+
supportsStreaming: true,
|
|
396
|
+
supportsSubagentTracing: true
|
|
397
|
+
};
|
|
398
|
+
model;
|
|
399
|
+
agent;
|
|
400
|
+
skipPermissions = true;
|
|
401
|
+
async initialize(config) {
|
|
402
|
+
await super.initialize(config);
|
|
403
|
+
if (typeof config.model === "string" && config.model.length > 0) {
|
|
404
|
+
this.model = config.model;
|
|
405
|
+
}
|
|
406
|
+
if (typeof config.agent === "string" && config.agent.length > 0) {
|
|
407
|
+
this.agent = config.agent;
|
|
408
|
+
}
|
|
409
|
+
if (typeof config.skipPermissions === "boolean") {
|
|
410
|
+
this.skipPermissions = config.skipPermissions;
|
|
411
|
+
}
|
|
412
|
+
if (typeof config.sandbox === "string" && config.sandbox.length > 0) {
|
|
413
|
+
this.skipPermissions = config.sandbox === "danger-full-access";
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
async detect() {
|
|
417
|
+
const command = this.commandPath ?? this.meta.cliCommand;
|
|
418
|
+
const findResult = await findCommandPath(command);
|
|
419
|
+
if (!findResult.found) {
|
|
420
|
+
return {
|
|
421
|
+
available: false,
|
|
422
|
+
error: "Claude CLI not found in PATH. Install with: npm install -g @anthropic-ai/claude-code"
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
this.commandPath = findResult.path;
|
|
426
|
+
return {
|
|
427
|
+
available: true,
|
|
428
|
+
executablePath: findResult.path
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
buildArgs(prompt, options) {
|
|
432
|
+
const args = ["--print"];
|
|
433
|
+
args.push("--verbose", "--output-format", "stream-json");
|
|
434
|
+
if (this.model) {
|
|
435
|
+
args.push("--model", this.model);
|
|
436
|
+
}
|
|
437
|
+
if (this.skipPermissions) {
|
|
438
|
+
args.push("--dangerously-skip-permissions");
|
|
439
|
+
}
|
|
440
|
+
if (this.agent) {
|
|
441
|
+
args.push("--agent", this.agent);
|
|
442
|
+
}
|
|
443
|
+
return args;
|
|
444
|
+
}
|
|
445
|
+
getStdinInput(prompt, options) {
|
|
446
|
+
return prompt;
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
var createClaudeAgent = () => new ClaudeAgentPlugin();
|
|
450
|
+
var claude_default = createClaudeAgent;
|
|
451
|
+
var GeminiAgentPlugin = class extends BaseAgentPlugin {
|
|
452
|
+
meta = {
|
|
453
|
+
name: "gemini",
|
|
454
|
+
version: "1.0.0",
|
|
455
|
+
description: "Google Gemini CLI for AI-assisted coding",
|
|
456
|
+
cliCommand: "gemini",
|
|
457
|
+
supportsHooks: false,
|
|
458
|
+
supportsSkills: true,
|
|
459
|
+
supportsStreaming: true,
|
|
460
|
+
supportsSubagentTracing: true
|
|
461
|
+
};
|
|
462
|
+
model;
|
|
463
|
+
yoloMode = true;
|
|
464
|
+
async initialize(config) {
|
|
465
|
+
await super.initialize(config);
|
|
466
|
+
if (typeof config.model === "string" && config.model.length > 0) {
|
|
467
|
+
this.model = config.model;
|
|
468
|
+
}
|
|
469
|
+
if (typeof config.yoloMode === "boolean") {
|
|
470
|
+
this.yoloMode = config.yoloMode;
|
|
471
|
+
}
|
|
472
|
+
if (typeof config.sandbox === "string" && config.sandbox.length > 0) {
|
|
473
|
+
this.yoloMode = config.sandbox === "danger-full-access";
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
async detect() {
|
|
477
|
+
const command = this.commandPath ?? this.meta.cliCommand;
|
|
478
|
+
const findResult = await findCommandPath(command);
|
|
479
|
+
if (!findResult.found) {
|
|
480
|
+
return {
|
|
481
|
+
available: false,
|
|
482
|
+
error: "Gemini CLI not found in PATH. Install from: https://github.com/google-gemini/gemini-cli"
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
this.commandPath = findResult.path;
|
|
486
|
+
return {
|
|
487
|
+
available: true,
|
|
488
|
+
executablePath: findResult.path
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
buildArgs(prompt, options) {
|
|
492
|
+
const args = [];
|
|
493
|
+
args.push("--output-format", "stream-json");
|
|
494
|
+
if (this.model) {
|
|
495
|
+
args.push("-m", this.model);
|
|
496
|
+
}
|
|
497
|
+
if (this.yoloMode) {
|
|
498
|
+
args.push("--yolo");
|
|
499
|
+
}
|
|
500
|
+
return args;
|
|
501
|
+
}
|
|
502
|
+
getStdinInput(prompt, options) {
|
|
503
|
+
return prompt;
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
var createGeminiAgent = () => new GeminiAgentPlugin();
|
|
507
|
+
var gemini_default = createGeminiAgent;
|
|
508
|
+
var OpenCodeAgentPlugin = class extends BaseAgentPlugin {
|
|
509
|
+
meta = {
|
|
510
|
+
name: "opencode",
|
|
511
|
+
version: "1.0.0",
|
|
512
|
+
description: "OpenCode AI coding assistant CLI",
|
|
513
|
+
cliCommand: "opencode",
|
|
514
|
+
supportsHooks: true,
|
|
515
|
+
supportsSkills: true,
|
|
516
|
+
supportsStreaming: true,
|
|
517
|
+
supportsSubagentTracing: true
|
|
518
|
+
};
|
|
519
|
+
provider;
|
|
520
|
+
model;
|
|
521
|
+
agent = "general";
|
|
522
|
+
async initialize(config) {
|
|
523
|
+
await super.initialize(config);
|
|
524
|
+
if (typeof config.provider === "string" && config.provider.length > 0) {
|
|
525
|
+
this.provider = config.provider;
|
|
526
|
+
}
|
|
527
|
+
if (typeof config.model === "string" && config.model.length > 0) {
|
|
528
|
+
this.model = config.model;
|
|
529
|
+
}
|
|
530
|
+
if (typeof config.agent === "string" && ["general", "build", "plan"].includes(config.agent)) {
|
|
531
|
+
this.agent = config.agent;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
async detect() {
|
|
535
|
+
const command = this.commandPath ?? this.meta.cliCommand;
|
|
536
|
+
const findResult = await findCommandPath(command);
|
|
537
|
+
if (!findResult.found) {
|
|
538
|
+
return {
|
|
539
|
+
available: false,
|
|
540
|
+
error: "OpenCode CLI not found in PATH. Install with: curl -fsSL https://opencode.ai/install | bash"
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
this.commandPath = findResult.path;
|
|
544
|
+
return {
|
|
545
|
+
available: true,
|
|
546
|
+
executablePath: findResult.path
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
buildArgs(prompt, options) {
|
|
550
|
+
const args = ["run"];
|
|
551
|
+
if (this.agent !== "general") {
|
|
552
|
+
args.push("--agent", this.agent);
|
|
553
|
+
}
|
|
554
|
+
const modelStr = this.buildModelString();
|
|
555
|
+
if (modelStr) {
|
|
556
|
+
args.push("--model", modelStr);
|
|
557
|
+
}
|
|
558
|
+
args.push("--format", "json");
|
|
559
|
+
return args;
|
|
560
|
+
}
|
|
561
|
+
getStdinInput(prompt, options) {
|
|
562
|
+
return prompt;
|
|
563
|
+
}
|
|
564
|
+
buildModelString() {
|
|
565
|
+
if (this.provider && this.model) {
|
|
566
|
+
return `${this.provider}/${this.model}`;
|
|
567
|
+
}
|
|
568
|
+
if (this.model) {
|
|
569
|
+
return this.model;
|
|
570
|
+
}
|
|
571
|
+
return void 0;
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
var createOpenCodeAgent = () => new OpenCodeAgentPlugin();
|
|
575
|
+
var opencode_default = createOpenCodeAgent;
|
|
576
|
+
var CodexAgentPlugin = class extends BaseAgentPlugin {
|
|
577
|
+
meta = {
|
|
578
|
+
name: "codex",
|
|
579
|
+
version: "1.0.0",
|
|
580
|
+
description: "OpenAI Codex CLI for AI-assisted coding",
|
|
581
|
+
cliCommand: "codex",
|
|
582
|
+
supportsHooks: false,
|
|
583
|
+
supportsSkills: true,
|
|
584
|
+
supportsStreaming: true,
|
|
585
|
+
supportsSubagentTracing: true
|
|
586
|
+
};
|
|
587
|
+
model;
|
|
588
|
+
agent;
|
|
589
|
+
fullAuto = true;
|
|
590
|
+
sandbox = "workspace-write";
|
|
591
|
+
async initialize(config) {
|
|
592
|
+
await super.initialize(config);
|
|
593
|
+
if (typeof config.model === "string" && config.model.length > 0) {
|
|
594
|
+
this.model = config.model;
|
|
595
|
+
}
|
|
596
|
+
if (typeof config.agent === "string" && config.agent.length > 0) {
|
|
597
|
+
this.agent = config.agent;
|
|
598
|
+
}
|
|
599
|
+
if (typeof config.fullAuto === "boolean") {
|
|
600
|
+
this.fullAuto = config.fullAuto;
|
|
601
|
+
}
|
|
602
|
+
if (typeof config.sandbox === "string" && ["read-only", "workspace-write", "danger-full-access"].includes(
|
|
603
|
+
config.sandbox
|
|
604
|
+
)) {
|
|
605
|
+
this.sandbox = config.sandbox;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
async detect() {
|
|
609
|
+
const command = this.commandPath ?? this.meta.cliCommand;
|
|
610
|
+
const findResult = await findCommandPath(command);
|
|
611
|
+
if (!findResult.found) {
|
|
612
|
+
return {
|
|
613
|
+
available: false,
|
|
614
|
+
error: "Codex CLI not found in PATH. Install from: https://github.com/openai/codex"
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
this.commandPath = findResult.path;
|
|
618
|
+
return {
|
|
619
|
+
available: true,
|
|
620
|
+
executablePath: findResult.path
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
buildArgs(prompt, options) {
|
|
624
|
+
const preArgs = [];
|
|
625
|
+
const args = [];
|
|
626
|
+
if (this.fullAuto && this.sandbox !== "workspace-write") {
|
|
627
|
+
preArgs.push("-a", "on-request");
|
|
628
|
+
}
|
|
629
|
+
args.push("exec");
|
|
630
|
+
if (this.fullAuto && this.sandbox === "workspace-write") {
|
|
631
|
+
args.push("--full-auto");
|
|
632
|
+
}
|
|
633
|
+
args.push("--json");
|
|
634
|
+
if (this.model) {
|
|
635
|
+
args.push("--model", this.model);
|
|
636
|
+
}
|
|
637
|
+
args.push("--sandbox", this.sandbox);
|
|
638
|
+
if (this.agent) {
|
|
639
|
+
args.push("--profile", this.agent);
|
|
640
|
+
}
|
|
641
|
+
args.push("-");
|
|
642
|
+
return [...preArgs, ...args];
|
|
643
|
+
}
|
|
644
|
+
getStdinInput(prompt, options) {
|
|
645
|
+
return prompt;
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
var createCodexAgent = () => new CodexAgentPlugin();
|
|
649
|
+
var codex_default = createCodexAgent;
|
|
650
|
+
var KiroAgentPlugin = class extends BaseAgentPlugin {
|
|
651
|
+
meta = {
|
|
652
|
+
name: "kiro",
|
|
653
|
+
version: "1.0.0",
|
|
654
|
+
description: "AWS Kiro CLI for AI-assisted coding",
|
|
655
|
+
cliCommand: "kiro-cli",
|
|
656
|
+
supportsHooks: true,
|
|
657
|
+
supportsSkills: true,
|
|
658
|
+
supportsStreaming: true,
|
|
659
|
+
supportsSubagentTracing: false
|
|
660
|
+
};
|
|
661
|
+
trustAllTools = true;
|
|
662
|
+
agent;
|
|
663
|
+
model;
|
|
664
|
+
async initialize(config) {
|
|
665
|
+
await super.initialize(config);
|
|
666
|
+
if (typeof config.trustAllTools === "boolean") {
|
|
667
|
+
this.trustAllTools = config.trustAllTools;
|
|
668
|
+
}
|
|
669
|
+
if (typeof config.agent === "string" && config.agent.length > 0) {
|
|
670
|
+
this.agent = config.agent;
|
|
671
|
+
}
|
|
672
|
+
if (typeof config.model === "string" && config.model.length > 0) {
|
|
673
|
+
this.model = config.model;
|
|
674
|
+
}
|
|
675
|
+
if (typeof config.sandbox === "string" && config.sandbox.length > 0) {
|
|
676
|
+
this.trustAllTools = config.sandbox === "danger-full-access";
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
async detect() {
|
|
680
|
+
const command = this.commandPath ?? this.meta.cliCommand;
|
|
681
|
+
const findResult = await findCommandPath(command);
|
|
682
|
+
if (!findResult.found) {
|
|
683
|
+
return {
|
|
684
|
+
available: false,
|
|
685
|
+
error: "Kiro CLI not found in PATH. Install from: https://kiro.dev"
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
this.commandPath = findResult.path;
|
|
689
|
+
return {
|
|
690
|
+
available: true,
|
|
691
|
+
executablePath: findResult.path
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
buildArgs(prompt, options) {
|
|
695
|
+
const args = ["chat", "--no-interactive"];
|
|
696
|
+
if (this.trustAllTools) {
|
|
697
|
+
args.push("--trust-all-tools");
|
|
698
|
+
}
|
|
699
|
+
if (this.agent) {
|
|
700
|
+
args.push("--agent", this.agent);
|
|
701
|
+
}
|
|
702
|
+
if (this.model) {
|
|
703
|
+
args.push("--model", this.model);
|
|
704
|
+
}
|
|
705
|
+
return args;
|
|
706
|
+
}
|
|
707
|
+
getStdinInput(prompt, options) {
|
|
708
|
+
return prompt;
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
var createKiroAgent = () => new KiroAgentPlugin();
|
|
712
|
+
var kiro_default = createKiroAgent;
|
|
713
|
+
var FactoryDroidAgentPlugin = class extends BaseAgentPlugin {
|
|
714
|
+
meta = {
|
|
715
|
+
name: "factory-droid",
|
|
716
|
+
version: "1.0.0",
|
|
717
|
+
description: "Factory Droid CLI for AI-assisted coding",
|
|
718
|
+
cliCommand: "droid",
|
|
719
|
+
supportsHooks: false,
|
|
720
|
+
supportsSkills: false,
|
|
721
|
+
supportsStreaming: false,
|
|
722
|
+
supportsSubagentTracing: false
|
|
723
|
+
};
|
|
724
|
+
async detect() {
|
|
725
|
+
const command = this.commandPath ?? this.meta.cliCommand;
|
|
726
|
+
const findResult = await findCommandPath(command);
|
|
727
|
+
if (!findResult.found) {
|
|
728
|
+
return {
|
|
729
|
+
available: false,
|
|
730
|
+
error: "Droid CLI not found in PATH. Install from: https://docs.factory.ai"
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
this.commandPath = findResult.path;
|
|
734
|
+
return {
|
|
735
|
+
available: true,
|
|
736
|
+
executablePath: findResult.path
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
buildArgs(prompt, options) {
|
|
740
|
+
const args = ["exec"];
|
|
741
|
+
return args;
|
|
742
|
+
}
|
|
743
|
+
getStdinInput(prompt, options) {
|
|
744
|
+
return prompt;
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
var createFactoryDroidAgent = () => new FactoryDroidAgentPlugin();
|
|
748
|
+
var factory_droid_default = createFactoryDroidAgent;
|
|
749
|
+
var AgentRegistry = class _AgentRegistry {
|
|
750
|
+
static instance = null;
|
|
751
|
+
plugins = /* @__PURE__ */ new Map();
|
|
752
|
+
loadedInstances = /* @__PURE__ */ new Map();
|
|
753
|
+
initialized = false;
|
|
754
|
+
constructor() {
|
|
755
|
+
}
|
|
756
|
+
static getInstance() {
|
|
757
|
+
if (!_AgentRegistry.instance) {
|
|
758
|
+
_AgentRegistry.instance = new _AgentRegistry();
|
|
759
|
+
}
|
|
760
|
+
return _AgentRegistry.instance;
|
|
761
|
+
}
|
|
762
|
+
static resetInstance() {
|
|
763
|
+
if (_AgentRegistry.instance) {
|
|
764
|
+
for (const instance of _AgentRegistry.instance.loadedInstances.values()) {
|
|
765
|
+
instance.dispose().catch(() => {
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
_AgentRegistry.instance.plugins.clear();
|
|
769
|
+
_AgentRegistry.instance.loadedInstances.clear();
|
|
770
|
+
_AgentRegistry.instance.initialized = false;
|
|
771
|
+
}
|
|
772
|
+
_AgentRegistry.instance = null;
|
|
773
|
+
}
|
|
774
|
+
registerBuiltin(factory) {
|
|
775
|
+
const instance = factory();
|
|
776
|
+
const { meta } = instance;
|
|
777
|
+
this.plugins.set(meta.name, { factory, meta, builtin: true });
|
|
778
|
+
instance.dispose().catch(() => {
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
getRegisteredPlugins() {
|
|
782
|
+
return Array.from(this.plugins.values()).map((p) => p.meta);
|
|
783
|
+
}
|
|
784
|
+
getPluginMeta(pluginName) {
|
|
785
|
+
return this.plugins.get(pluginName)?.meta;
|
|
786
|
+
}
|
|
787
|
+
hasPlugin(pluginName) {
|
|
788
|
+
return this.plugins.has(pluginName);
|
|
789
|
+
}
|
|
790
|
+
isBuiltin(pluginName) {
|
|
791
|
+
return this.plugins.get(pluginName)?.builtin ?? false;
|
|
792
|
+
}
|
|
793
|
+
createInstance(pluginName) {
|
|
794
|
+
const registered = this.plugins.get(pluginName);
|
|
795
|
+
if (!registered) return void 0;
|
|
796
|
+
return registered.factory();
|
|
797
|
+
}
|
|
798
|
+
async getInstance(config) {
|
|
799
|
+
const cacheKey = config.name;
|
|
800
|
+
const cached = this.loadedInstances.get(cacheKey);
|
|
801
|
+
if (cached) return cached;
|
|
802
|
+
const instance = this.createInstance(config.plugin);
|
|
803
|
+
if (!instance) {
|
|
804
|
+
throw new Error(`Unknown agent plugin: ${config.plugin}`);
|
|
805
|
+
}
|
|
806
|
+
const initConfig = {
|
|
807
|
+
...config.options,
|
|
808
|
+
command: config.command,
|
|
809
|
+
defaultFlags: config.defaultFlags,
|
|
810
|
+
timeout: config.timeout,
|
|
811
|
+
envExclude: config.envExclude,
|
|
812
|
+
envPassthrough: config.envPassthrough
|
|
813
|
+
};
|
|
814
|
+
await instance.initialize(initConfig);
|
|
815
|
+
this.loadedInstances.set(cacheKey, instance);
|
|
816
|
+
return instance;
|
|
817
|
+
}
|
|
818
|
+
async disposeInstance(configName) {
|
|
819
|
+
const instance = this.loadedInstances.get(configName);
|
|
820
|
+
if (instance) {
|
|
821
|
+
await instance.dispose();
|
|
822
|
+
this.loadedInstances.delete(configName);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
async disposeAll() {
|
|
826
|
+
const disposals = Array.from(this.loadedInstances.values()).map(
|
|
827
|
+
(instance) => instance.dispose()
|
|
828
|
+
);
|
|
829
|
+
await Promise.all(disposals);
|
|
830
|
+
this.loadedInstances.clear();
|
|
831
|
+
}
|
|
832
|
+
async initialize() {
|
|
833
|
+
if (this.initialized) return;
|
|
834
|
+
this.initialized = true;
|
|
835
|
+
const builtins = [
|
|
836
|
+
claude_default,
|
|
837
|
+
gemini_default,
|
|
838
|
+
opencode_default,
|
|
839
|
+
codex_default,
|
|
840
|
+
kiro_default,
|
|
841
|
+
factory_droid_default
|
|
842
|
+
];
|
|
843
|
+
for (const factory of builtins) {
|
|
844
|
+
this.registerBuiltin(factory);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
function getAgentRegistry() {
|
|
849
|
+
return AgentRegistry.getInstance();
|
|
850
|
+
}
|
|
851
|
+
function translateToHookInput(event, defaults) {
|
|
852
|
+
return {
|
|
853
|
+
session_id: event.sessionId ?? defaults.sessionId,
|
|
854
|
+
transcript_path: defaults.transcriptPath ?? "",
|
|
855
|
+
cwd: event.cwd ?? defaults.cwd,
|
|
856
|
+
permission_mode: defaults.permissionMode ?? "default",
|
|
857
|
+
hook_event_name: event.eventName,
|
|
858
|
+
tool_name: event.toolName,
|
|
859
|
+
tool_input: event.toolInput,
|
|
860
|
+
tool_response: event.toolResponse
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
function evaluateHookEvent(event, rules, state, projectDir, defaults) {
|
|
864
|
+
const input = translateToHookInput(event, defaults);
|
|
865
|
+
return evaluateRules(input, rules, state, projectDir);
|
|
866
|
+
}
|
|
867
|
+
function fromClaudeHookEvent(input) {
|
|
868
|
+
return {
|
|
869
|
+
eventName: input.hook_event_name,
|
|
870
|
+
toolName: input.tool_name,
|
|
871
|
+
toolInput: input.tool_input,
|
|
872
|
+
toolResponse: input.tool_response,
|
|
873
|
+
sessionId: input.session_id,
|
|
874
|
+
cwd: input.cwd
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
function toClaudeHookInput(event, defaults) {
|
|
878
|
+
return {
|
|
879
|
+
session_id: event.sessionId ?? "",
|
|
880
|
+
transcript_path: defaults?.transcriptPath ?? "",
|
|
881
|
+
cwd: event.cwd ?? "",
|
|
882
|
+
permission_mode: defaults?.permissionMode ?? "default",
|
|
883
|
+
hook_event_name: event.eventName,
|
|
884
|
+
tool_name: event.toolName,
|
|
885
|
+
tool_input: event.toolInput,
|
|
886
|
+
tool_response: event.toolResponse
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
var EVENT_MAP = {
|
|
890
|
+
"tool.execute.before": "PreToolUse",
|
|
891
|
+
"tool.execute.after": "PostToolUse",
|
|
892
|
+
"permission.request": "PermissionRequest",
|
|
893
|
+
"session.start": "SessionStart",
|
|
894
|
+
"session.end": "SessionEnd"
|
|
895
|
+
};
|
|
896
|
+
function fromOpenCodeEvent(event) {
|
|
897
|
+
const hookEvent = EVENT_MAP[event.type];
|
|
898
|
+
return {
|
|
899
|
+
eventName: hookEvent ?? event.type,
|
|
900
|
+
toolName: event.tool?.name,
|
|
901
|
+
toolInput: event.tool?.input,
|
|
902
|
+
toolResponse: event.tool?.output ? { output: event.tool.output } : void 0,
|
|
903
|
+
sessionId: event.session?.id,
|
|
904
|
+
cwd: event.session?.cwd
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
function toOpenCodeResponse(decision, reason) {
|
|
908
|
+
return {
|
|
909
|
+
action: decision === "allow" ? "proceed" : decision === "deny" ? "block" : "ask",
|
|
910
|
+
message: reason
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
var EVENT_MAP2 = {
|
|
914
|
+
BeforeTool: "PreToolUse",
|
|
915
|
+
AfterTool: "PostToolUse",
|
|
916
|
+
BeforePermission: "PermissionRequest",
|
|
917
|
+
SessionStart: "SessionStart",
|
|
918
|
+
SessionEnd: "SessionEnd"
|
|
919
|
+
};
|
|
920
|
+
function fromGeminiEvent(event) {
|
|
921
|
+
const hookEvent = EVENT_MAP2[event.type];
|
|
922
|
+
return {
|
|
923
|
+
eventName: hookEvent ?? event.type,
|
|
924
|
+
toolName: event.toolName,
|
|
925
|
+
toolInput: event.toolInput,
|
|
926
|
+
toolResponse: event.toolOutput ? { output: event.toolOutput } : void 0,
|
|
927
|
+
sessionId: event.sessionId,
|
|
928
|
+
cwd: event.workingDir
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
function toGeminiResponse(decision, reason) {
|
|
932
|
+
return {
|
|
933
|
+
allowed: decision === "allow",
|
|
934
|
+
reason
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
var EVENT_MAP3 = {
|
|
938
|
+
preToolUse: "PreToolUse",
|
|
939
|
+
postToolUse: "PostToolUse",
|
|
940
|
+
permissionRequest: "PermissionRequest",
|
|
941
|
+
agentSpawn: "SessionStart",
|
|
942
|
+
sessionStart: "SessionStart",
|
|
943
|
+
sessionEnd: "SessionEnd",
|
|
944
|
+
userPromptSubmit: "UserPromptSubmit",
|
|
945
|
+
stop: "Stop"
|
|
946
|
+
};
|
|
947
|
+
var TOOL_MAP = {
|
|
948
|
+
fs_read: "Read",
|
|
949
|
+
read: "Read",
|
|
950
|
+
fs_write: "Write",
|
|
951
|
+
write: "Write",
|
|
952
|
+
fs_edit: "Edit",
|
|
953
|
+
edit: "Edit",
|
|
954
|
+
execute_bash: "Bash",
|
|
955
|
+
shell: "Bash",
|
|
956
|
+
fs_glob: "Glob",
|
|
957
|
+
glob: "Glob",
|
|
958
|
+
fs_grep: "Grep",
|
|
959
|
+
grep: "Grep",
|
|
960
|
+
web_fetch: "WebFetch",
|
|
961
|
+
web_search: "WebSearch"
|
|
962
|
+
};
|
|
963
|
+
function fromKiroEvent(event) {
|
|
964
|
+
const hookEvent = EVENT_MAP3[event.hook_event_name];
|
|
965
|
+
const toolName = event.tool_name ? TOOL_MAP[event.tool_name.toLowerCase()] ?? event.tool_name : void 0;
|
|
966
|
+
return {
|
|
967
|
+
eventName: hookEvent ?? event.hook_event_name,
|
|
968
|
+
toolName,
|
|
969
|
+
toolInput: event.tool_input,
|
|
970
|
+
toolResponse: event.tool_response,
|
|
971
|
+
sessionId: event.session_id,
|
|
972
|
+
cwd: event.cwd
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
function toKiroResponse(decision, reason) {
|
|
976
|
+
return {
|
|
977
|
+
exitCode: decision === "allow" ? 0 : 2,
|
|
978
|
+
message: reason
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
function mapCodexEvent(eventType, itemType) {
|
|
982
|
+
if (eventType === "thread.started") return "SessionStart";
|
|
983
|
+
if (eventType === "turn.failed") return "Stop";
|
|
984
|
+
if (eventType === "item.started") {
|
|
985
|
+
if (itemType === "command_execution") return "PreToolUse";
|
|
986
|
+
if (itemType === "file_change") return "PreToolUse";
|
|
987
|
+
if (itemType === "mcp_tool_call") return "PreToolUse";
|
|
988
|
+
if (itemType === "web_search") return "PreToolUse";
|
|
989
|
+
}
|
|
990
|
+
if (eventType === "item.completed") {
|
|
991
|
+
if (itemType === "command_execution") return "PostToolUse";
|
|
992
|
+
if (itemType === "file_change") return "PostToolUse";
|
|
993
|
+
if (itemType === "mcp_tool_call") return "PostToolUse";
|
|
994
|
+
if (itemType === "web_search") return "PostToolUse";
|
|
995
|
+
}
|
|
996
|
+
return null;
|
|
997
|
+
}
|
|
998
|
+
function mapToolName(item) {
|
|
999
|
+
if (!item) return void 0;
|
|
1000
|
+
switch (item.type) {
|
|
1001
|
+
case "command_execution":
|
|
1002
|
+
return "Bash";
|
|
1003
|
+
case "file_change":
|
|
1004
|
+
return item.status === "in_progress" ? "Write" : "Write";
|
|
1005
|
+
case "mcp_tool_call":
|
|
1006
|
+
return item.tool_name ?? "McpTool";
|
|
1007
|
+
case "web_search":
|
|
1008
|
+
return "WebSearch";
|
|
1009
|
+
case "agent_message":
|
|
1010
|
+
return "AgentMessage";
|
|
1011
|
+
case "reasoning":
|
|
1012
|
+
return "Reasoning";
|
|
1013
|
+
case "plan_update":
|
|
1014
|
+
return "PlanUpdate";
|
|
1015
|
+
default:
|
|
1016
|
+
return item.type;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
function buildToolInput(item) {
|
|
1020
|
+
if (!item) return void 0;
|
|
1021
|
+
switch (item.type) {
|
|
1022
|
+
case "command_execution":
|
|
1023
|
+
return { command: item.command };
|
|
1024
|
+
case "file_change":
|
|
1025
|
+
return {
|
|
1026
|
+
file_path: item.file_path,
|
|
1027
|
+
content: item.content
|
|
1028
|
+
};
|
|
1029
|
+
case "mcp_tool_call":
|
|
1030
|
+
return {
|
|
1031
|
+
tool_name: item.tool_name,
|
|
1032
|
+
arguments: item.arguments
|
|
1033
|
+
};
|
|
1034
|
+
case "agent_message":
|
|
1035
|
+
return { text: item.text };
|
|
1036
|
+
case "reasoning":
|
|
1037
|
+
return { text: item.text };
|
|
1038
|
+
default:
|
|
1039
|
+
return void 0;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
function buildToolResponse(item) {
|
|
1043
|
+
if (!item) return void 0;
|
|
1044
|
+
const response = {};
|
|
1045
|
+
if (item.aggregated_output !== void 0) {
|
|
1046
|
+
response.output = item.aggregated_output;
|
|
1047
|
+
}
|
|
1048
|
+
if (item.exit_code !== void 0 && item.exit_code !== null) {
|
|
1049
|
+
response.exit_code = item.exit_code;
|
|
1050
|
+
}
|
|
1051
|
+
if (item.text !== void 0) {
|
|
1052
|
+
response.output = item.text;
|
|
1053
|
+
}
|
|
1054
|
+
return Object.keys(response).length > 0 ? response : void 0;
|
|
1055
|
+
}
|
|
1056
|
+
function fromCodexEvent(event, sessionId, cwd) {
|
|
1057
|
+
const hookEvent = mapCodexEvent(event.type, event.item?.type);
|
|
1058
|
+
if (!hookEvent) return null;
|
|
1059
|
+
return {
|
|
1060
|
+
eventName: hookEvent,
|
|
1061
|
+
toolName: mapToolName(event.item),
|
|
1062
|
+
toolInput: buildToolInput(event.item),
|
|
1063
|
+
toolResponse: buildToolResponse(event.item),
|
|
1064
|
+
sessionId: sessionId ?? event.thread_id,
|
|
1065
|
+
cwd
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
function parseCodexLine(line) {
|
|
1069
|
+
const trimmed = line.trim();
|
|
1070
|
+
if (!trimmed) return null;
|
|
1071
|
+
try {
|
|
1072
|
+
return JSON.parse(trimmed);
|
|
1073
|
+
} catch {
|
|
1074
|
+
return null;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
function isToolEvent(event) {
|
|
1078
|
+
if (event.type !== "item.started" && event.type !== "item.completed") {
|
|
1079
|
+
return false;
|
|
1080
|
+
}
|
|
1081
|
+
const itemType = event.item?.type;
|
|
1082
|
+
return itemType === "command_execution" || itemType === "file_change" || itemType === "mcp_tool_call" || itemType === "web_search";
|
|
1083
|
+
}
|
|
1084
|
+
function rulesToPromptInstructions(rules) {
|
|
1085
|
+
const sections = [];
|
|
1086
|
+
sections.push("## ULPI Governance Rules\n");
|
|
1087
|
+
sections.push(
|
|
1088
|
+
"You MUST follow these rules during this session. Violations will be caught and rolled back.\n"
|
|
1089
|
+
);
|
|
1090
|
+
sections.push("### Forbidden Actions");
|
|
1091
|
+
sections.push("- NEVER run `rm -rf /` or any recursive force-delete on root");
|
|
1092
|
+
sections.push("- NEVER run `git push --force` to main or master");
|
|
1093
|
+
sections.push("- NEVER run `git reset --hard` without explicit user approval");
|
|
1094
|
+
sections.push("- NEVER modify `.env` files containing secrets\n");
|
|
1095
|
+
const permissions = Object.entries(rules.permissions ?? {});
|
|
1096
|
+
if (permissions.length > 0) {
|
|
1097
|
+
sections.push("### Permission Rules");
|
|
1098
|
+
for (const [name, rule] of permissions) {
|
|
1099
|
+
if (!rule.enabled) continue;
|
|
1100
|
+
const r = rule;
|
|
1101
|
+
const action = r.decision === "allow" ? "Auto-approved" : r.decision === "deny" ? "BLOCKED" : "Requires approval";
|
|
1102
|
+
const scope = r.command_pattern ? ` (command: ${r.command_pattern})` : r.file_pattern ? ` (files: ${r.file_pattern})` : "";
|
|
1103
|
+
sections.push(`- **${name}**: ${action} for ${r.matcher}${scope}`);
|
|
1104
|
+
if (r.message) {
|
|
1105
|
+
sections.push(` Reason: ${r.message}`);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
sections.push("");
|
|
1109
|
+
}
|
|
1110
|
+
const preconditions = Object.entries(rules.preconditions ?? {});
|
|
1111
|
+
if (preconditions.length > 0) {
|
|
1112
|
+
sections.push("### Precondition Rules");
|
|
1113
|
+
for (const [name, rule] of preconditions) {
|
|
1114
|
+
if (!rule.enabled) continue;
|
|
1115
|
+
const r = rule;
|
|
1116
|
+
const conditions = [];
|
|
1117
|
+
if (r.requires_read) {
|
|
1118
|
+
conditions.push("read the file first");
|
|
1119
|
+
}
|
|
1120
|
+
if (r.requires?.length) {
|
|
1121
|
+
conditions.push(`require flags: ${r.requires.join(", ")}`);
|
|
1122
|
+
}
|
|
1123
|
+
sections.push(
|
|
1124
|
+
`- **${name}**: Before using ${r.matcher}, you must ${conditions.join(" and ")}`
|
|
1125
|
+
);
|
|
1126
|
+
if (r.message) {
|
|
1127
|
+
sections.push(` ${r.message}`);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
sections.push("");
|
|
1131
|
+
}
|
|
1132
|
+
return sections.join("\n");
|
|
1133
|
+
}
|
|
1134
|
+
function injectGovernance(prompt, rules) {
|
|
1135
|
+
const instructions = rulesToPromptInstructions(rules);
|
|
1136
|
+
return `${instructions}
|
|
1137
|
+
---
|
|
1138
|
+
|
|
1139
|
+
${prompt}`;
|
|
1140
|
+
}
|
|
1141
|
+
function createOutputMonitor(config) {
|
|
1142
|
+
const violations = [];
|
|
1143
|
+
let buffer = "";
|
|
1144
|
+
function processLine(line) {
|
|
1145
|
+
const trimmed = line.trim();
|
|
1146
|
+
if (!trimmed) return;
|
|
1147
|
+
const commandMatch = trimmed.match(
|
|
1148
|
+
/(?:^\$\s+|Running:\s+|Executing:\s+|Command:\s+)(.+)/
|
|
1149
|
+
);
|
|
1150
|
+
if (commandMatch) {
|
|
1151
|
+
const cmd = commandMatch[1];
|
|
1152
|
+
const result = isDangerousCommand(cmd);
|
|
1153
|
+
if (result.dangerous) {
|
|
1154
|
+
const violation = {
|
|
1155
|
+
type: "dangerous_command",
|
|
1156
|
+
message: result.reason ?? "Dangerous command detected",
|
|
1157
|
+
line: trimmed,
|
|
1158
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1159
|
+
};
|
|
1160
|
+
violations.push(violation);
|
|
1161
|
+
config.onViolation(violation);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
if (config.blockedFilePatterns) {
|
|
1165
|
+
for (const pattern of config.blockedFilePatterns) {
|
|
1166
|
+
const pathMatch = trimmed.match(
|
|
1167
|
+
/(?:Writing|Creating|Modifying|Editing|Deleting)\s+(.+)/i
|
|
1168
|
+
);
|
|
1169
|
+
if (pathMatch) {
|
|
1170
|
+
const filePath = pathMatch[1].trim();
|
|
1171
|
+
if (matchesFilePattern(pattern, filePath, config.projectDir)) {
|
|
1172
|
+
const violation = {
|
|
1173
|
+
type: "file_access",
|
|
1174
|
+
message: `File access blocked by pattern: ${pattern}`,
|
|
1175
|
+
line: trimmed,
|
|
1176
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1177
|
+
};
|
|
1178
|
+
violations.push(violation);
|
|
1179
|
+
config.onViolation(violation);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
function processData(data) {
|
|
1186
|
+
buffer += data;
|
|
1187
|
+
const lines = buffer.split("\n");
|
|
1188
|
+
buffer = lines.pop() ?? "";
|
|
1189
|
+
for (const line of lines) {
|
|
1190
|
+
processLine(line);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
return {
|
|
1194
|
+
pushStdout: processData,
|
|
1195
|
+
pushStderr: processData,
|
|
1196
|
+
getViolations: () => [...violations]
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
function getChangedFiles(projectDir) {
|
|
1200
|
+
try {
|
|
1201
|
+
const output = execFileSync("git", ["diff", "--name-only"], {
|
|
1202
|
+
cwd: projectDir,
|
|
1203
|
+
encoding: "utf-8",
|
|
1204
|
+
timeout: 1e4
|
|
1205
|
+
});
|
|
1206
|
+
return output.split("\n").map((f) => f.trim()).filter(Boolean);
|
|
1207
|
+
} catch {
|
|
1208
|
+
return [];
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
function getUntrackedFiles(projectDir) {
|
|
1212
|
+
try {
|
|
1213
|
+
const output = execFileSync(
|
|
1214
|
+
"git",
|
|
1215
|
+
["ls-files", "--others", "--exclude-standard"],
|
|
1216
|
+
{
|
|
1217
|
+
cwd: projectDir,
|
|
1218
|
+
encoding: "utf-8",
|
|
1219
|
+
timeout: 1e4
|
|
1220
|
+
}
|
|
1221
|
+
);
|
|
1222
|
+
return output.split("\n").map((f) => f.trim()).filter(Boolean);
|
|
1223
|
+
} catch {
|
|
1224
|
+
return [];
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
function validatePostExecution(rules, projectDir) {
|
|
1228
|
+
const violations = [];
|
|
1229
|
+
const changedFiles = [...getChangedFiles(projectDir), ...getUntrackedFiles(projectDir)];
|
|
1230
|
+
const denyRules = [];
|
|
1231
|
+
for (const [name, rule] of Object.entries(rules.permissions ?? {})) {
|
|
1232
|
+
if (!rule.enabled) continue;
|
|
1233
|
+
const r = rule;
|
|
1234
|
+
if (r.decision === "deny" && r.file_pattern) {
|
|
1235
|
+
denyRules.push({ name, rule: r });
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
for (const filePath of changedFiles) {
|
|
1239
|
+
for (const { name, rule } of denyRules) {
|
|
1240
|
+
if (rule.file_pattern && matchesFilePattern(rule.file_pattern, filePath, projectDir)) {
|
|
1241
|
+
violations.push({
|
|
1242
|
+
type: "unauthorized_file_change",
|
|
1243
|
+
filePath,
|
|
1244
|
+
ruleName: name,
|
|
1245
|
+
message: rule.message ?? `File change denied by rule: ${name}`
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
return {
|
|
1251
|
+
valid: violations.length === 0,
|
|
1252
|
+
violations
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
function rollbackFiles(projectDir, filePaths) {
|
|
1256
|
+
const rolledBack = [];
|
|
1257
|
+
const errors = [];
|
|
1258
|
+
for (const filePath of filePaths) {
|
|
1259
|
+
try {
|
|
1260
|
+
execFileSync2("git", ["checkout", "--", filePath], {
|
|
1261
|
+
cwd: projectDir,
|
|
1262
|
+
encoding: "utf-8",
|
|
1263
|
+
timeout: 1e4
|
|
1264
|
+
});
|
|
1265
|
+
rolledBack.push(filePath);
|
|
1266
|
+
} catch (err) {
|
|
1267
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1268
|
+
errors.push(`Failed to rollback ${filePath}: ${message}`);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
return {
|
|
1272
|
+
success: errors.length === 0,
|
|
1273
|
+
filesRolledBack: rolledBack,
|
|
1274
|
+
error: errors.length > 0 ? errors.join("; ") : void 0
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
function removeUntrackedFiles(projectDir, filePaths) {
|
|
1278
|
+
const rolledBack = [];
|
|
1279
|
+
const errors = [];
|
|
1280
|
+
for (const filePath of filePaths) {
|
|
1281
|
+
try {
|
|
1282
|
+
execFileSync2("git", ["clean", "-f", "--", filePath], {
|
|
1283
|
+
cwd: projectDir,
|
|
1284
|
+
encoding: "utf-8",
|
|
1285
|
+
timeout: 1e4
|
|
1286
|
+
});
|
|
1287
|
+
rolledBack.push(filePath);
|
|
1288
|
+
} catch (err) {
|
|
1289
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1290
|
+
errors.push(`Failed to clean ${filePath}: ${message}`);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
return {
|
|
1294
|
+
success: errors.length === 0,
|
|
1295
|
+
filesRolledBack: rolledBack,
|
|
1296
|
+
error: errors.length > 0 ? errors.join("; ") : void 0
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
function fullRollback(projectDir, reason) {
|
|
1300
|
+
try {
|
|
1301
|
+
const stashMessage = `ulpi-rollback: ${reason} (${(/* @__PURE__ */ new Date()).toISOString()})`;
|
|
1302
|
+
execFileSync2("git", ["stash", "push", "-u", "-m", stashMessage], {
|
|
1303
|
+
cwd: projectDir,
|
|
1304
|
+
encoding: "utf-8",
|
|
1305
|
+
timeout: 3e4
|
|
1306
|
+
});
|
|
1307
|
+
let filesStashed = [];
|
|
1308
|
+
try {
|
|
1309
|
+
const diffOutput = execFileSync2(
|
|
1310
|
+
"git",
|
|
1311
|
+
["stash", "show", "--name-only", "stash@{0}"],
|
|
1312
|
+
{
|
|
1313
|
+
cwd: projectDir,
|
|
1314
|
+
encoding: "utf-8",
|
|
1315
|
+
timeout: 1e4
|
|
1316
|
+
}
|
|
1317
|
+
);
|
|
1318
|
+
filesStashed = diffOutput.split("\n").map((f) => f.trim()).filter(Boolean);
|
|
1319
|
+
} catch {
|
|
1320
|
+
}
|
|
1321
|
+
return {
|
|
1322
|
+
success: true,
|
|
1323
|
+
filesRolledBack: filesStashed
|
|
1324
|
+
};
|
|
1325
|
+
} catch (err) {
|
|
1326
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1327
|
+
return {
|
|
1328
|
+
success: false,
|
|
1329
|
+
filesRolledBack: [],
|
|
1330
|
+
error: `Full rollback failed: ${message}`
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
function renderSkillForClaude(skill) {
|
|
1335
|
+
const sections = [];
|
|
1336
|
+
sections.push(`# ${skill.name}
|
|
1337
|
+
`);
|
|
1338
|
+
if (skill.description) {
|
|
1339
|
+
sections.push(`${skill.description}
|
|
1340
|
+
`);
|
|
1341
|
+
}
|
|
1342
|
+
sections.push("## Instructions\n");
|
|
1343
|
+
sections.push("Follow the instructions in this skill when triggered.\n");
|
|
1344
|
+
return sections.join("\n");
|
|
1345
|
+
}
|
|
1346
|
+
function getClaudeSkillPath(skillName, scope) {
|
|
1347
|
+
if (scope === "personal") {
|
|
1348
|
+
return `~/.claude/skills/${skillName}.md`;
|
|
1349
|
+
}
|
|
1350
|
+
return `.claude/skills/${skillName}.md`;
|
|
1351
|
+
}
|
|
1352
|
+
function renderSkillForOpenCode(skill) {
|
|
1353
|
+
const sections = [];
|
|
1354
|
+
sections.push(`# ${skill.name}
|
|
1355
|
+
`);
|
|
1356
|
+
if (skill.description) {
|
|
1357
|
+
sections.push(`> ${skill.description}
|
|
1358
|
+
`);
|
|
1359
|
+
}
|
|
1360
|
+
sections.push("## Instructions\n");
|
|
1361
|
+
sections.push("Follow the instructions in this skill when triggered.\n");
|
|
1362
|
+
return sections.join("\n");
|
|
1363
|
+
}
|
|
1364
|
+
function getOpenCodeSkillPath(skillName, scope) {
|
|
1365
|
+
if (scope === "personal") {
|
|
1366
|
+
return `~/.config/opencode/skills/${skillName}.md`;
|
|
1367
|
+
}
|
|
1368
|
+
return `.opencode/skills/${skillName}.md`;
|
|
1369
|
+
}
|
|
1370
|
+
function renderSkillForKiro(skill) {
|
|
1371
|
+
const safeName = `ulpi-${skill.name}`;
|
|
1372
|
+
const description = skill.description ?? `ULPI ${skill.name} skill`;
|
|
1373
|
+
const sections = [];
|
|
1374
|
+
sections.push("---");
|
|
1375
|
+
sections.push(`name: ${safeName}`);
|
|
1376
|
+
sections.push(`description: ${description}`);
|
|
1377
|
+
sections.push("---");
|
|
1378
|
+
sections.push("");
|
|
1379
|
+
sections.push(`# ${skill.name}`);
|
|
1380
|
+
sections.push("");
|
|
1381
|
+
if (skill.description) {
|
|
1382
|
+
sections.push(skill.description);
|
|
1383
|
+
sections.push("");
|
|
1384
|
+
}
|
|
1385
|
+
sections.push("## Instructions");
|
|
1386
|
+
sections.push("");
|
|
1387
|
+
sections.push("Follow the instructions in this skill when triggered.");
|
|
1388
|
+
sections.push("");
|
|
1389
|
+
return sections.join("\n");
|
|
1390
|
+
}
|
|
1391
|
+
function getKiroSkillPath(skillName, scope) {
|
|
1392
|
+
const dirName = skillName.startsWith("ulpi-") ? skillName : `ulpi-${skillName}`;
|
|
1393
|
+
if (scope === "personal") {
|
|
1394
|
+
return path.join("~", ".kiro", "skills", dirName, "SKILL.md");
|
|
1395
|
+
}
|
|
1396
|
+
return path.join(".kiro", "skills", dirName, "SKILL.md");
|
|
1397
|
+
}
|
|
1398
|
+
function installKiroSkill(skill, projectDir, scope) {
|
|
1399
|
+
const content = renderSkillForKiro(skill);
|
|
1400
|
+
const dirName = `ulpi-${skill.name}`;
|
|
1401
|
+
const dir = scope === "personal" ? path.join(os.homedir(), ".kiro", "skills", dirName) : path.join(projectDir, ".kiro", "skills", dirName);
|
|
1402
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1403
|
+
const skillPath = path.join(dir, "SKILL.md");
|
|
1404
|
+
fs.writeFileSync(skillPath, content, "utf-8");
|
|
1405
|
+
return skillPath;
|
|
1406
|
+
}
|
|
1407
|
+
function renderSkillAsPrompt(skill) {
|
|
1408
|
+
const sections = [];
|
|
1409
|
+
sections.push(`[Skill: ${skill.name}]`);
|
|
1410
|
+
if (skill.description) {
|
|
1411
|
+
sections.push(skill.description);
|
|
1412
|
+
}
|
|
1413
|
+
sections.push("");
|
|
1414
|
+
sections.push("You must follow the instructions in this skill.");
|
|
1415
|
+
sections.push(`[End Skill: ${skill.name}]`);
|
|
1416
|
+
return sections.join("\n");
|
|
1417
|
+
}
|
|
1418
|
+
function renderSkillsAsPrompt(skills) {
|
|
1419
|
+
if (skills.length === 0) return "";
|
|
1420
|
+
const sections = [];
|
|
1421
|
+
sections.push("## Active Skills\n");
|
|
1422
|
+
for (const skill of skills) {
|
|
1423
|
+
sections.push(renderSkillAsPrompt(skill));
|
|
1424
|
+
sections.push("");
|
|
1425
|
+
}
|
|
1426
|
+
return sections.join("\n");
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
export {
|
|
1430
|
+
DEFAULT_ENV_EXCLUDE_PATTERNS,
|
|
1431
|
+
filterEnv,
|
|
1432
|
+
getEnvExclusionReport,
|
|
1433
|
+
findCommandPath,
|
|
1434
|
+
BaseAgentPlugin,
|
|
1435
|
+
ClaudeAgentPlugin,
|
|
1436
|
+
claude_default,
|
|
1437
|
+
GeminiAgentPlugin,
|
|
1438
|
+
gemini_default,
|
|
1439
|
+
OpenCodeAgentPlugin,
|
|
1440
|
+
opencode_default,
|
|
1441
|
+
CodexAgentPlugin,
|
|
1442
|
+
codex_default,
|
|
1443
|
+
KiroAgentPlugin,
|
|
1444
|
+
kiro_default,
|
|
1445
|
+
FactoryDroidAgentPlugin,
|
|
1446
|
+
factory_droid_default,
|
|
1447
|
+
AgentRegistry,
|
|
1448
|
+
getAgentRegistry,
|
|
1449
|
+
translateToHookInput,
|
|
1450
|
+
evaluateHookEvent,
|
|
1451
|
+
fromClaudeHookEvent,
|
|
1452
|
+
toClaudeHookInput,
|
|
1453
|
+
fromOpenCodeEvent,
|
|
1454
|
+
toOpenCodeResponse,
|
|
1455
|
+
fromGeminiEvent,
|
|
1456
|
+
toGeminiResponse,
|
|
1457
|
+
fromKiroEvent,
|
|
1458
|
+
toKiroResponse,
|
|
1459
|
+
fromCodexEvent,
|
|
1460
|
+
parseCodexLine,
|
|
1461
|
+
isToolEvent,
|
|
1462
|
+
rulesToPromptInstructions,
|
|
1463
|
+
injectGovernance,
|
|
1464
|
+
createOutputMonitor,
|
|
1465
|
+
validatePostExecution,
|
|
1466
|
+
rollbackFiles,
|
|
1467
|
+
removeUntrackedFiles,
|
|
1468
|
+
fullRollback,
|
|
1469
|
+
renderSkillForClaude,
|
|
1470
|
+
getClaudeSkillPath,
|
|
1471
|
+
renderSkillForOpenCode,
|
|
1472
|
+
getOpenCodeSkillPath,
|
|
1473
|
+
renderSkillForKiro,
|
|
1474
|
+
getKiroSkillPath,
|
|
1475
|
+
installKiroSkill,
|
|
1476
|
+
renderSkillAsPrompt,
|
|
1477
|
+
renderSkillsAsPrompt
|
|
1478
|
+
};
|