codiedev 0.9.56 → 0.10.0-beta.2
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 +5 -4
- package/dist/captureLog.d.ts +1 -1
- package/dist/cli.js +11 -1
- package/dist/codexLimitScan.d.ts +0 -2
- package/dist/codexLimitScan.js +2 -15
- package/dist/codie.js +146 -1
- package/dist/codieLauncher.d.ts +5 -0
- package/dist/codieLauncher.js +23 -1
- package/dist/codieRuntime.d.ts +11 -1
- package/dist/codieRuntime.js +20 -3
- package/dist/codieRuntimeInstall.d.ts +12 -11
- package/dist/codieRuntimeInstall.js +3 -1
- package/dist/codieStartup.d.ts +26 -0
- package/dist/codieStartup.js +30 -0
- package/dist/commands/doctor.js +8 -0
- package/dist/commands/security.d.ts +1 -0
- package/dist/commands/security.js +156 -0
- package/dist/connect.js +56 -4
- package/dist/cursorHooks.d.ts +16 -2
- package/dist/cursorHooks.js +31 -6
- package/dist/hook.js +232 -22
- package/dist/limitEvent.d.ts +5 -1
- package/dist/limitEvent.js +6 -27
- package/dist/security.d.ts +102 -0
- package/dist/security.js +437 -0
- package/dist/securityDeepScan.d.ts +41 -0
- package/dist/securityDeepScan.js +248 -0
- package/dist/securityDoctor.d.ts +32 -0
- package/dist/securityDoctor.js +63 -0
- package/dist/securityFeature.d.ts +5 -0
- package/dist/securityFeature.js +7 -0
- package/dist/securityInstall.d.ts +63 -0
- package/dist/securityInstall.js +239 -0
- package/dist/securityInventory.d.ts +54 -0
- package/dist/securityInventory.js +556 -0
- package/dist/securityPilot.d.ts +19 -0
- package/dist/securityPilot.js +61 -0
- package/dist/securityPolicy.d.ts +33 -0
- package/dist/securityPolicy.js +188 -0
- package/dist/securitySession.d.ts +6 -0
- package/dist/securitySession.js +72 -0
- package/dist/upgradeNudge.d.ts +1 -0
- package/dist/upgradeNudge.js +1 -0
- package/dist/utils.d.ts +12 -6
- package/dist/utils.js +201 -38
- package/package.json +11 -2
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ The CLI will prompt you for an API token (get one at https://codiedev.com/portal
|
|
|
12
12
|
|
|
13
13
|
- **Claude Code** — `SessionEnd` hook in `~/.claude/settings.json`
|
|
14
14
|
- **Codex** — `Stop` hook in `~/.codex/hooks.json`
|
|
15
|
-
- **Cursor** — native session, backup, and capture hooks in `~/.cursor/hooks.json`
|
|
15
|
+
- **Cursor** — native session, prompt-security, tool-security, backup, and capture hooks in `~/.cursor/hooks.json`
|
|
16
16
|
- **OpenCode / Codie** — plugin in `~/.config/opencode/plugins/codiedev.js`
|
|
17
17
|
|
|
18
18
|
Installed clients are wired automatically. The OpenCode adapter is staged even
|
|
@@ -51,9 +51,10 @@ Codex doesn't currently expose a `SessionEnd` event, so the hook runs on `Stop`
|
|
|
51
51
|
|
|
52
52
|
## Cursor note
|
|
53
53
|
|
|
54
|
-
Cursor uses its native hook events for session start/end
|
|
55
|
-
Cursor's `beforeSubmitPrompt`
|
|
56
|
-
|
|
54
|
+
Cursor uses its native hook events for session start/end, prompt blocking,
|
|
55
|
+
pre/post-tool security, and stop-time backup. Cursor's `beforeSubmitPrompt`
|
|
56
|
+
cannot inject dynamic context today, so task-specific discovery is not claimed.
|
|
57
|
+
Cursor sessions keep their source
|
|
57
58
|
label and real transcript statistics through the shared capture pipeline.
|
|
58
59
|
|
|
59
60
|
## OpenCode note
|
package/dist/captureLog.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from "fs";
|
|
|
2
2
|
export declare const CAPTURE_LOG_MAX_BYTES: number;
|
|
3
3
|
export declare const CAPTURE_LOG_PATH: string;
|
|
4
4
|
export interface CaptureLogEntry {
|
|
5
|
-
stage: "capture-finish" | "upload" | "upload-deferred" | "stderr" | "limit";
|
|
5
|
+
stage: "capture-finish" | "upload" | "upload-deferred" | "security" | "stderr" | "limit";
|
|
6
6
|
sessionId: string | undefined;
|
|
7
7
|
outcome: string;
|
|
8
8
|
detail?: string;
|
package/dist/cli.js
CHANGED
|
@@ -33,6 +33,7 @@ const share_1 = require("./commands/share");
|
|
|
33
33
|
const send_1 = require("./commands/send");
|
|
34
34
|
const react_1 = require("./commands/react");
|
|
35
35
|
const capture_1 = require("./commands/capture");
|
|
36
|
+
const security_1 = require("./commands/security");
|
|
36
37
|
const version_1 = require("./version");
|
|
37
38
|
const HELP = `
|
|
38
39
|
Codiedev CLI
|
|
@@ -43,7 +44,13 @@ Codiedev CLI
|
|
|
43
44
|
|
|
44
45
|
Connect:
|
|
45
46
|
codiedev connect Link Claude Code / Codex / Cursor to your org
|
|
46
|
-
codiedev doctor Verify
|
|
47
|
+
codiedev doctor Verify setup, capture, and Agent Security
|
|
48
|
+
codiedev security pilot Run safe synthetic policy checks
|
|
49
|
+
codiedev security inventory Inventory MCPs, skills, hooks, plugins,
|
|
50
|
+
instructions, and editor extensions
|
|
51
|
+
codiedev security scan --deep Run installed open-source scanners too
|
|
52
|
+
codiedev security sync Fetch administrator policy locally
|
|
53
|
+
codiedev security rollback --yes Restore pre-update hook configuration
|
|
47
54
|
|
|
48
55
|
Harness:
|
|
49
56
|
codie Launch the governed Codie harness.
|
|
@@ -451,6 +458,9 @@ async function main() {
|
|
|
451
458
|
case "verify":
|
|
452
459
|
await (0, doctor_1.runDoctor)(rest);
|
|
453
460
|
return;
|
|
461
|
+
case "security":
|
|
462
|
+
await (0, security_1.runSecurityCommand)(rest);
|
|
463
|
+
return;
|
|
454
464
|
case "push":
|
|
455
465
|
await (0, push_1.runPush)(rest);
|
|
456
466
|
return;
|
package/dist/codexLimitScan.d.ts
CHANGED
|
@@ -45,8 +45,6 @@ export interface SweepArgs {
|
|
|
45
45
|
scanOptions?: ScanOptions;
|
|
46
46
|
/** How many rollouts may actually be READ in one sweep. */
|
|
47
47
|
maxScans?: number;
|
|
48
|
-
/** Stop starting new scans once this many ms have elapsed. */
|
|
49
|
-
deadlineMs?: number;
|
|
50
48
|
}
|
|
51
49
|
/**
|
|
52
50
|
* Report every Codex block this machine has not reported yet. Returns how many
|
package/dist/codexLimitScan.js
CHANGED
|
@@ -382,20 +382,8 @@ function recordCodexLimitReported(filePath, at, statePath = exports.CODEX_LIMIT_
|
|
|
382
382
|
// Candidates are listed generously and settled by one statSync each; only
|
|
383
383
|
// files that actually changed cost a read. The budget therefore bounds work
|
|
384
384
|
// without letting idle sessions bury an unreported block.
|
|
385
|
-
//
|
|
386
|
-
// The budget binds only on a COLD START, where nothing has been seen before,
|
|
387
|
-
// so it must be big enough to finish that first pass. A budget of 8 silently
|
|
388
|
-
// starved the older blocked rollouts on a real machine: 49 candidates in the
|
|
389
|
-
// window, the newest 8 all unblocked, nothing reported. A full pass over
|
|
390
|
-
// those 49 costs ~205ms — the backward scan stops at the marker rather than
|
|
391
|
-
// reading each file to its cap — so the budget can afford to cover them.
|
|
392
385
|
const DEFAULT_CANDIDATES = 200;
|
|
393
|
-
const DEFAULT_MAX_SCANS =
|
|
394
|
-
// The budget bounds how MANY files are read; this bounds how LONG. A count is
|
|
395
|
-
// not enough on a slow or network-mounted disk, and this runs on the user's
|
|
396
|
-
// prompt clock. Whatever is not reached stays unrecorded, so the next prompt
|
|
397
|
-
// picks it up rather than skipping it.
|
|
398
|
-
const DEFAULT_DEADLINE_MS = 1_500;
|
|
386
|
+
const DEFAULT_MAX_SCANS = 8;
|
|
399
387
|
/**
|
|
400
388
|
* Report every Codex block this machine has not reported yet. Returns how many
|
|
401
389
|
* events were posted. Never throws — it runs inside a hook.
|
|
@@ -419,7 +407,6 @@ async function sweepCodexLimits(args) {
|
|
|
419
407
|
const now = Date.now();
|
|
420
408
|
const touched = {};
|
|
421
409
|
let budget = args.maxScans ?? DEFAULT_MAX_SCANS;
|
|
422
|
-
const deadline = now + (args.deadlineMs ?? DEFAULT_DEADLINE_MS);
|
|
423
410
|
const pending = [];
|
|
424
411
|
for (const file of files) {
|
|
425
412
|
const print = fingerprint(file);
|
|
@@ -430,7 +417,7 @@ async function sweepCodexLimits(args) {
|
|
|
430
417
|
// it holds nothing new and does not spend the read budget.
|
|
431
418
|
if (record && record.size === print.size && record.mtime === print.mtime)
|
|
432
419
|
continue;
|
|
433
|
-
if (budget <= 0
|
|
420
|
+
if (budget <= 0)
|
|
434
421
|
break;
|
|
435
422
|
budget -= 1;
|
|
436
423
|
const hit = scanCodexRolloutForLimit(file, args.scanOptions);
|
package/dist/codie.js
CHANGED
|
@@ -53,6 +53,7 @@ const configDir_1 = require("./configDir");
|
|
|
53
53
|
const codieLauncher_1 = require("./codieLauncher");
|
|
54
54
|
const codieRuntime_1 = require("./codieRuntime");
|
|
55
55
|
const codieRuntimeInstall_1 = require("./codieRuntimeInstall");
|
|
56
|
+
const codieStartup_1 = require("./codieStartup");
|
|
56
57
|
const upgradeNudge_1 = require("./upgradeNudge");
|
|
57
58
|
function git(args) {
|
|
58
59
|
const result = (0, child_process_1.spawnSync)("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -169,6 +170,120 @@ function installBrandWordmarkUnsafe(runtime) {
|
|
|
169
170
|
(0, child_process_1.spawnSync)("codesign", ["--force", "--sign", "-", runtime], { stdio: "ignore" });
|
|
170
171
|
}
|
|
171
172
|
}
|
|
173
|
+
async function runNonInteractive(runtime, runtimeArgs, env) {
|
|
174
|
+
const detached = process.platform !== "win32";
|
|
175
|
+
const child = (0, child_process_1.spawn)(runtime, runtimeArgs, {
|
|
176
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
177
|
+
env,
|
|
178
|
+
detached,
|
|
179
|
+
});
|
|
180
|
+
if (child.stdin)
|
|
181
|
+
process.stdin.pipe(child.stdin);
|
|
182
|
+
const signalChild = (signal) => {
|
|
183
|
+
if (detached && child.pid) {
|
|
184
|
+
try {
|
|
185
|
+
process.kill(-child.pid, signal);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
// Fall back to the direct child if its process group already exited.
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
child.kill(signal);
|
|
193
|
+
};
|
|
194
|
+
const forwardInterrupt = () => signalChild("SIGINT");
|
|
195
|
+
const forwardTermination = () => signalChild("SIGTERM");
|
|
196
|
+
process.once("SIGINT", forwardInterrupt);
|
|
197
|
+
process.once("SIGTERM", forwardTermination);
|
|
198
|
+
return await new Promise((resolve, reject) => {
|
|
199
|
+
let blockReason;
|
|
200
|
+
const errorPrefixes = [Buffer.from("\x1b[91m\x1b[1mError:"), Buffer.from("Error: {")];
|
|
201
|
+
const retainedPrefixBytes = Math.max(...errorPrefixes.map((prefix) => prefix.length)) - 1;
|
|
202
|
+
// OpenCode's generic UnknownError and the adapter's private reason can be
|
|
203
|
+
// delivered in either order — and the generic block can arrive on stdout
|
|
204
|
+
// or stderr. Gate both streams by content, retaining only a possible final
|
|
205
|
+
// error block. Ordinary output stays streaming apart from the few bytes
|
|
206
|
+
// needed to recognize a split prefix.
|
|
207
|
+
const createOutputGate = (write) => {
|
|
208
|
+
let tail = Buffer.alloc(0);
|
|
209
|
+
let possibleError;
|
|
210
|
+
return {
|
|
211
|
+
push(chunk, blocked) {
|
|
212
|
+
if (blocked) {
|
|
213
|
+
if (possibleError)
|
|
214
|
+
possibleError.push(Buffer.from(chunk));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (possibleError) {
|
|
218
|
+
possibleError.push(Buffer.from(chunk));
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const combined = Buffer.concat([tail, chunk]);
|
|
222
|
+
let errorStart = -1;
|
|
223
|
+
for (const prefix of errorPrefixes) {
|
|
224
|
+
const index = combined.indexOf(prefix);
|
|
225
|
+
if (index >= 0 && (errorStart < 0 || index < errorStart))
|
|
226
|
+
errorStart = index;
|
|
227
|
+
}
|
|
228
|
+
if (errorStart >= 0) {
|
|
229
|
+
if (errorStart > 0)
|
|
230
|
+
write(combined.subarray(0, errorStart));
|
|
231
|
+
possibleError = [combined.subarray(errorStart)];
|
|
232
|
+
tail = Buffer.alloc(0);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const flushBytes = Math.max(0, combined.length - retainedPrefixBytes);
|
|
236
|
+
if (flushBytes > 0)
|
|
237
|
+
write(combined.subarray(0, flushBytes));
|
|
238
|
+
tail = combined.subarray(flushBytes);
|
|
239
|
+
},
|
|
240
|
+
finish(blocked) {
|
|
241
|
+
if (tail.length > 0)
|
|
242
|
+
write(tail);
|
|
243
|
+
const heldError = possibleError ? Buffer.concat(possibleError).toString() : "";
|
|
244
|
+
const genericBlockError = heldError.includes("UnknownError") &&
|
|
245
|
+
heldError.includes("Unexpected server error");
|
|
246
|
+
if (heldError && (!blocked || !genericBlockError))
|
|
247
|
+
write(heldError);
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
const stdoutGate = createOutputGate((chunk) => process.stdout.write(chunk));
|
|
252
|
+
const stderrGate = createOutputGate((chunk) => process.stderr.write(chunk));
|
|
253
|
+
child.stdout?.on("data", (chunk) => {
|
|
254
|
+
stdoutGate.push(Buffer.from(chunk), Boolean(blockReason));
|
|
255
|
+
});
|
|
256
|
+
child.stderr?.on("data", (chunk) => {
|
|
257
|
+
const text = chunk.toString();
|
|
258
|
+
const reason = (0, codieLauncher_1.extractCodieSecurityBlock)(text);
|
|
259
|
+
if (reason) {
|
|
260
|
+
blockReason = reason;
|
|
261
|
+
signalChild("SIGTERM");
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
stderrGate.push(Buffer.from(chunk), Boolean(blockReason));
|
|
265
|
+
});
|
|
266
|
+
child.once("error", (error) => {
|
|
267
|
+
if (child.stdin)
|
|
268
|
+
process.stdin.unpipe(child.stdin);
|
|
269
|
+
process.removeListener("SIGINT", forwardInterrupt);
|
|
270
|
+
process.removeListener("SIGTERM", forwardTermination);
|
|
271
|
+
reject(error);
|
|
272
|
+
});
|
|
273
|
+
child.once("close", (status) => {
|
|
274
|
+
if (child.stdin)
|
|
275
|
+
process.stdin.unpipe(child.stdin);
|
|
276
|
+
process.removeListener("SIGINT", forwardInterrupt);
|
|
277
|
+
process.removeListener("SIGTERM", forwardTermination);
|
|
278
|
+
stdoutGate.finish(Boolean(blockReason));
|
|
279
|
+
stderrGate.finish(Boolean(blockReason));
|
|
280
|
+
if (blockReason) {
|
|
281
|
+
console.error(`Codie Security blocked this action:\n${blockReason}`);
|
|
282
|
+
}
|
|
283
|
+
resolve(status);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
}
|
|
172
287
|
async function main() {
|
|
173
288
|
(0, codieRuntime_1.assertCodieNodeVersion)(process.versions.node);
|
|
174
289
|
const connected = connectedCodiedevConfig();
|
|
@@ -186,6 +301,10 @@ async function main() {
|
|
|
186
301
|
process.exitCode = update.status ?? 0;
|
|
187
302
|
return;
|
|
188
303
|
}
|
|
304
|
+
const policyRefresh = await (0, codieStartup_1.refreshCodieSecurityPolicy)(connected);
|
|
305
|
+
if (policyRefresh.status === "cached") {
|
|
306
|
+
console.error(`codie: security policy refresh failed; using the cached policy (${policyRefresh.error})`);
|
|
307
|
+
}
|
|
189
308
|
const gatewayUrl = process.env.CODIEDEV_GATEWAY_URL || "https://gateway.codiedev.com/api/v1";
|
|
190
309
|
const catalog = await (0, codieLauncher_1.fetchApprovedCatalog)({ baseUrl: gatewayUrl, key });
|
|
191
310
|
const parsed = (0, codieLauncher_1.parseCodieArgs)(process.argv.slice(2));
|
|
@@ -196,11 +315,20 @@ async function main() {
|
|
|
196
315
|
}
|
|
197
316
|
(0, codieLauncher_1.validateCodieArgs)(parsed.args, catalog);
|
|
198
317
|
const stateDir = (0, configDir_1.codiedevStateDir)();
|
|
199
|
-
const runtime = (0, codieRuntimeInstall_1.ensureCodieRuntimeBinary)({
|
|
318
|
+
const runtime = await (0, codieRuntimeInstall_1.ensureCodieRuntimeBinary)({
|
|
200
319
|
resolve: () => (0, codieRuntime_1.resolveCodieRuntime)({
|
|
201
320
|
env: process.env,
|
|
202
321
|
stateDir,
|
|
203
322
|
existsSync: (candidate) => fs.existsSync(candidate),
|
|
323
|
+
readInstalledVersion: (candidate) => {
|
|
324
|
+
try {
|
|
325
|
+
const value = JSON.parse(fs.readFileSync(candidate, "utf8"));
|
|
326
|
+
return typeof value.version === "string" ? value.version : null;
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
},
|
|
204
332
|
resolveBundled: () => {
|
|
205
333
|
try {
|
|
206
334
|
return require.resolve("opencode-ai/bin/opencode.exe");
|
|
@@ -211,6 +339,16 @@ async function main() {
|
|
|
211
339
|
},
|
|
212
340
|
}),
|
|
213
341
|
install: (missing) => (0, codieRuntimeInstall_1.installCodieRuntimeOnDisk)(missing, stateDir),
|
|
342
|
+
confirmUpdate: async (outdated) => {
|
|
343
|
+
console.error("");
|
|
344
|
+
console.error(`Codie harness ${outdated.expectedVersion} is available (you're on ${outdated.currentVersion}).`);
|
|
345
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
346
|
+
console.error("Restart `codie` in an interactive terminal to update the harness.");
|
|
347
|
+
console.error("");
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
return await (0, upgradeNudge_1.promptYesNo)(" Update the harness now? [y/N] ");
|
|
351
|
+
},
|
|
214
352
|
});
|
|
215
353
|
const metadataPlugin = (0, url_1.pathToFileURL)(assetPath("opencode-plugin.mjs")).href;
|
|
216
354
|
const capturePlugin = (0, codieLauncher_1.resolveCapturePlugin)({ homeDir: os.homedir(), existsSync: fs.existsSync });
|
|
@@ -232,6 +370,13 @@ async function main() {
|
|
|
232
370
|
OPENCODE_CONFIG_CONTENT: JSON.stringify(config),
|
|
233
371
|
};
|
|
234
372
|
const runtimeArgs = (0, codieLauncher_1.withCodieWorkingDirectory)(parsed.args, process.cwd());
|
|
373
|
+
const nonInteractiveRun = runtimeArgs[0] === "run";
|
|
374
|
+
if (nonInteractiveRun)
|
|
375
|
+
env.CODIEDEV_SECURITY_BLOCK_OUTPUT = "marker";
|
|
376
|
+
if (nonInteractiveRun) {
|
|
377
|
+
process.exitCode = (await runNonInteractive(runtime, runtimeArgs, env)) ?? 1;
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
235
380
|
const result = (0, child_process_1.spawnSync)(runtime, runtimeArgs, { stdio: "inherit", env });
|
|
236
381
|
if (result.error)
|
|
237
382
|
throw result.error;
|
package/dist/codieLauncher.d.ts
CHANGED
|
@@ -2,6 +2,11 @@ export interface CatalogModel {
|
|
|
2
2
|
id: string;
|
|
3
3
|
name?: string;
|
|
4
4
|
}
|
|
5
|
+
export declare const CODIE_SECURITY_BLOCK_MARKER = "__CODIEDEV_SECURITY_BLOCK__";
|
|
6
|
+
/** Extract the private adapter marker used to carry a local block reason
|
|
7
|
+
* across OpenCode's generic 500 boundary. The marker never contains prompt or
|
|
8
|
+
* tool input — only the deterministic policy reason. */
|
|
9
|
+
export declare function extractCodieSecurityBlock(stderr: string): string | undefined;
|
|
5
10
|
/**
|
|
6
11
|
* Pin non-interactive runs to the directory where `codie` was launched.
|
|
7
12
|
* OpenCode can reuse a previously-known project instance; without an explicit
|
package/dist/codieLauncher.js
CHANGED
|
@@ -42,7 +42,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
42
42
|
};
|
|
43
43
|
})();
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
-
exports.HIDDEN_WORDMARK = exports.CODIEDEV_WORDMARK = exports.LEGACY_CODIEDEV_WORDMARK = exports.STOCK_WORDMARK = exports.BRAND_PLUGIN_SPEC = void 0;
|
|
45
|
+
exports.HIDDEN_WORDMARK = exports.CODIEDEV_WORDMARK = exports.LEGACY_CODIEDEV_WORDMARK = exports.STOCK_WORDMARK = exports.BRAND_PLUGIN_SPEC = exports.CODIE_SECURITY_BLOCK_MARKER = void 0;
|
|
46
|
+
exports.extractCodieSecurityBlock = extractCodieSecurityBlock;
|
|
46
47
|
exports.withCodieWorkingDirectory = withCodieWorkingDirectory;
|
|
47
48
|
exports.bareModel = bareModel;
|
|
48
49
|
exports.buildCodieConfig = buildCodieConfig;
|
|
@@ -55,6 +56,7 @@ exports.validateCodieArgs = validateCodieArgs;
|
|
|
55
56
|
const path = __importStar(require("path"));
|
|
56
57
|
const url_1 = require("url");
|
|
57
58
|
const DEFAULT_NAMES = {
|
|
59
|
+
"openai/gpt-5.6-luna": "GPT-5.6 Luna",
|
|
58
60
|
"openai/gpt-5.3-codex": "GPT-5.3 Codex",
|
|
59
61
|
"openai/gpt-5.2-codex": "GPT-5.2 Codex",
|
|
60
62
|
"openai/gpt-5.1-codex-mini": "GPT-5.1 Codex Mini",
|
|
@@ -62,6 +64,26 @@ const DEFAULT_NAMES = {
|
|
|
62
64
|
"z-ai/glm-5.2": "GLM 5.2 · Fireworks ZDR",
|
|
63
65
|
"moonshotai/kimi-k2.7-code": "Kimi K2.7 Code · Fireworks ZDR",
|
|
64
66
|
};
|
|
67
|
+
exports.CODIE_SECURITY_BLOCK_MARKER = "__CODIEDEV_SECURITY_BLOCK__";
|
|
68
|
+
/** Extract the private adapter marker used to carry a local block reason
|
|
69
|
+
* across OpenCode's generic 500 boundary. The marker never contains prompt or
|
|
70
|
+
* tool input — only the deterministic policy reason. */
|
|
71
|
+
function extractCodieSecurityBlock(stderr) {
|
|
72
|
+
for (const line of stderr.split(/\r?\n/)) {
|
|
73
|
+
const markerIndex = line.indexOf(exports.CODIE_SECURITY_BLOCK_MARKER);
|
|
74
|
+
if (markerIndex < 0)
|
|
75
|
+
continue;
|
|
76
|
+
try {
|
|
77
|
+
const parsed = JSON.parse(line.slice(markerIndex + exports.CODIE_SECURITY_BLOCK_MARKER.length));
|
|
78
|
+
if (typeof parsed?.reason === "string" && parsed.reason.trim())
|
|
79
|
+
return parsed.reason.trim();
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Ignore malformed or foreign stderr and preserve the original runtime error.
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
65
87
|
/**
|
|
66
88
|
* Pin non-interactive runs to the directory where `codie` was launched.
|
|
67
89
|
* OpenCode can reuse a previously-known project instance; without an explicit
|
package/dist/codieRuntime.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare const OPENCODE_RUNTIME_SPEC = "opencode-ai@1.18.4";
|
|
2
|
+
export declare const OPENCODE_RUNTIME_VERSION: string;
|
|
2
3
|
export declare const CODIE_MIN_NODE_MAJOR = 20;
|
|
3
4
|
export type CodieRuntime = {
|
|
4
5
|
kind: "ready";
|
|
@@ -8,12 +9,21 @@ export type CodieRuntime = {
|
|
|
8
9
|
installDir: string;
|
|
9
10
|
binary: string;
|
|
10
11
|
spec: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: "outdated";
|
|
14
|
+
installDir: string;
|
|
15
|
+
binary: string;
|
|
16
|
+
spec: string;
|
|
17
|
+
currentVersion: string;
|
|
18
|
+
expectedVersion: string;
|
|
11
19
|
};
|
|
12
20
|
export declare function codieRuntimeDir(stateDir: string): string;
|
|
13
|
-
export declare function
|
|
21
|
+
export declare function installedRuntimePackageJson(stateDir: string): string;
|
|
22
|
+
export declare function resolveCodieRuntime({ env, stateDir, existsSync, readInstalledVersion, resolveBundled, }: {
|
|
14
23
|
env: Record<string, string | undefined>;
|
|
15
24
|
stateDir: string;
|
|
16
25
|
existsSync: (candidate: string) => boolean;
|
|
26
|
+
readInstalledVersion?: (packageJson: string) => string | null;
|
|
17
27
|
resolveBundled: () => string | null;
|
|
18
28
|
}): CodieRuntime;
|
|
19
29
|
export declare function assertCodieNodeVersion(version: string): void;
|
package/dist/codieRuntime.js
CHANGED
|
@@ -43,8 +43,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
43
43
|
};
|
|
44
44
|
})();
|
|
45
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
-
exports.CODIE_MIN_NODE_MAJOR = exports.OPENCODE_RUNTIME_SPEC = void 0;
|
|
46
|
+
exports.CODIE_MIN_NODE_MAJOR = exports.OPENCODE_RUNTIME_VERSION = exports.OPENCODE_RUNTIME_SPEC = void 0;
|
|
47
47
|
exports.codieRuntimeDir = codieRuntimeDir;
|
|
48
|
+
exports.installedRuntimePackageJson = installedRuntimePackageJson;
|
|
48
49
|
exports.resolveCodieRuntime = resolveCodieRuntime;
|
|
49
50
|
exports.assertCodieNodeVersion = assertCodieNodeVersion;
|
|
50
51
|
const path = __importStar(require("path"));
|
|
@@ -52,6 +53,7 @@ const path = __importStar(require("path"));
|
|
|
52
53
|
// recognizes byte-for-byte, so a floating version would silently regress the
|
|
53
54
|
// terminal branding whenever OpenCode ships a new build.
|
|
54
55
|
exports.OPENCODE_RUNTIME_SPEC = "opencode-ai@1.18.4";
|
|
56
|
+
exports.OPENCODE_RUNTIME_VERSION = exports.OPENCODE_RUNTIME_SPEC.slice(exports.OPENCODE_RUNTIME_SPEC.lastIndexOf("@") + 1);
|
|
55
57
|
// The harness needs 20; capture does not. package.json `engines` stays >=18 so
|
|
56
58
|
// `codiedev connect` still installs for Node 18 users — this floor is enforced
|
|
57
59
|
// at `codie` launch instead.
|
|
@@ -62,7 +64,10 @@ function codieRuntimeDir(stateDir) {
|
|
|
62
64
|
function installedBinary(stateDir) {
|
|
63
65
|
return path.join(codieRuntimeDir(stateDir), "node_modules", "opencode-ai", "bin", "opencode.exe");
|
|
64
66
|
}
|
|
65
|
-
function
|
|
67
|
+
function installedRuntimePackageJson(stateDir) {
|
|
68
|
+
return path.join(codieRuntimeDir(stateDir), "node_modules", "opencode-ai", "package.json");
|
|
69
|
+
}
|
|
70
|
+
function resolveCodieRuntime({ env, stateDir, existsSync, readInstalledVersion, resolveBundled, }) {
|
|
66
71
|
// 1. Explicit override — the air-gapped / pre-provisioned escape hatch.
|
|
67
72
|
const override = env.CODIEDEV_OPENCODE_BINARY?.trim();
|
|
68
73
|
if (override) {
|
|
@@ -78,8 +83,20 @@ function resolveCodieRuntime({ env, stateDir, existsSync, resolveBundled, }) {
|
|
|
78
83
|
return { kind: "ready", binary: bundled };
|
|
79
84
|
// 3. The lazily installed engine.
|
|
80
85
|
const binary = installedBinary(stateDir);
|
|
81
|
-
if (existsSync(binary))
|
|
86
|
+
if (existsSync(binary)) {
|
|
87
|
+
const currentVersion = readInstalledVersion?.(installedRuntimePackageJson(stateDir)) ?? null;
|
|
88
|
+
if (currentVersion && currentVersion !== exports.OPENCODE_RUNTIME_VERSION) {
|
|
89
|
+
return {
|
|
90
|
+
kind: "outdated",
|
|
91
|
+
installDir: codieRuntimeDir(stateDir),
|
|
92
|
+
binary,
|
|
93
|
+
spec: exports.OPENCODE_RUNTIME_SPEC,
|
|
94
|
+
currentVersion,
|
|
95
|
+
expectedVersion: exports.OPENCODE_RUNTIME_VERSION,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
82
98
|
return { kind: "ready", binary };
|
|
99
|
+
}
|
|
83
100
|
return { kind: "missing", installDir: codieRuntimeDir(stateDir), binary, spec: exports.OPENCODE_RUNTIME_SPEC };
|
|
84
101
|
}
|
|
85
102
|
function assertCodieNodeVersion(version) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { CodieRuntime } from "./codieRuntime";
|
|
2
|
+
type InstallableCodieRuntime = Exclude<CodieRuntime, {
|
|
3
|
+
kind: "ready";
|
|
4
|
+
}>;
|
|
2
5
|
export declare function buildRuntimeInstallArgs(stagingDir: string, spec: string): string[];
|
|
3
6
|
export interface InstallDeps {
|
|
4
|
-
runtime:
|
|
5
|
-
kind: "missing";
|
|
6
|
-
}>;
|
|
7
|
+
runtime: InstallableCodieRuntime;
|
|
7
8
|
makeStagingDir: () => string;
|
|
8
9
|
run: (args: string[]) => {
|
|
9
10
|
status: number | null;
|
|
@@ -15,13 +16,13 @@ export interface InstallDeps {
|
|
|
15
16
|
log: (message: string) => void;
|
|
16
17
|
}
|
|
17
18
|
export declare function installCodieRuntime(deps: InstallDeps): void;
|
|
18
|
-
export declare function ensureCodieRuntimeBinary({ resolve, install, }: {
|
|
19
|
+
export declare function ensureCodieRuntimeBinary({ resolve, install, confirmUpdate, }: {
|
|
19
20
|
resolve: () => CodieRuntime;
|
|
20
|
-
install: (runtime:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}>, stateDir: string): void;
|
|
21
|
+
install: (runtime: InstallableCodieRuntime) => void;
|
|
22
|
+
confirmUpdate?: (runtime: Extract<CodieRuntime, {
|
|
23
|
+
kind: "outdated";
|
|
24
|
+
}>) => Promise<boolean>;
|
|
25
|
+
}): Promise<string>;
|
|
26
|
+
export declare function installCodieRuntimeOnDisk(runtime: InstallableCodieRuntime, stateDir: string): void;
|
|
27
27
|
export declare function defaultStagingRoot(): string;
|
|
28
|
+
export {};
|
|
@@ -98,10 +98,12 @@ function installCodieRuntime(deps) {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
// Resolve the engine, downloading it once if this is the first `codie` run.
|
|
101
|
-
function ensureCodieRuntimeBinary({ resolve, install, }) {
|
|
101
|
+
async function ensureCodieRuntimeBinary({ resolve, install, confirmUpdate = async () => false, }) {
|
|
102
102
|
const runtime = resolve();
|
|
103
103
|
if (runtime.kind === "ready")
|
|
104
104
|
return runtime.binary;
|
|
105
|
+
if (runtime.kind === "outdated" && !(await confirmUpdate(runtime)))
|
|
106
|
+
return runtime.binary;
|
|
105
107
|
install(runtime);
|
|
106
108
|
return runtime.binary;
|
|
107
109
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AgentSurface, EndpointSecurityPolicy } from "./security";
|
|
2
|
+
import type { AgentSecurityEntitlement } from "./securityFeature";
|
|
3
|
+
export interface CodieStartupConfig extends AgentSecurityEntitlement {
|
|
4
|
+
backendUrl?: string;
|
|
5
|
+
token?: string;
|
|
6
|
+
companyId?: string;
|
|
7
|
+
deviceId?: string;
|
|
8
|
+
}
|
|
9
|
+
export type CodiePolicyRefreshResult = {
|
|
10
|
+
status: "disabled";
|
|
11
|
+
} | {
|
|
12
|
+
status: "synced";
|
|
13
|
+
policy: EndpointSecurityPolicy;
|
|
14
|
+
} | {
|
|
15
|
+
status: "cached";
|
|
16
|
+
error: string;
|
|
17
|
+
};
|
|
18
|
+
/** Refresh the endpoint policy before Codie hands the terminal to OpenCode.
|
|
19
|
+
* Enforcement remains available offline because hooks keep using the last
|
|
20
|
+
* verified company-scoped policy when this best-effort refresh cannot land. */
|
|
21
|
+
export declare function refreshCodieSecurityPolicy(config: CodieStartupConfig, syncPolicy?: (config: {
|
|
22
|
+
backendUrl: string;
|
|
23
|
+
token: string;
|
|
24
|
+
companyId: string;
|
|
25
|
+
deviceId?: string;
|
|
26
|
+
}, source?: AgentSurface) => Promise<EndpointSecurityPolicy>): Promise<CodiePolicyRefreshResult>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.refreshCodieSecurityPolicy = refreshCodieSecurityPolicy;
|
|
4
|
+
const securityFeature_1 = require("./securityFeature");
|
|
5
|
+
const securityPolicy_1 = require("./securityPolicy");
|
|
6
|
+
/** Refresh the endpoint policy before Codie hands the terminal to OpenCode.
|
|
7
|
+
* Enforcement remains available offline because hooks keep using the last
|
|
8
|
+
* verified company-scoped policy when this best-effort refresh cannot land. */
|
|
9
|
+
async function refreshCodieSecurityPolicy(config, syncPolicy = securityPolicy_1.syncEndpointSecurityPolicy) {
|
|
10
|
+
if (!(0, securityFeature_1.isAgentSecurityEnabled)(config))
|
|
11
|
+
return { status: "disabled" };
|
|
12
|
+
if (!config.backendUrl || !config.token || !config.companyId) {
|
|
13
|
+
return { status: "cached", error: "connected account details are incomplete" };
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const policy = await syncPolicy({
|
|
17
|
+
backendUrl: config.backendUrl,
|
|
18
|
+
token: config.token,
|
|
19
|
+
companyId: config.companyId,
|
|
20
|
+
...(config.deviceId ? { deviceId: config.deviceId } : {}),
|
|
21
|
+
}, "codie-harness");
|
|
22
|
+
return { status: "synced", policy };
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
return {
|
|
26
|
+
status: "cached",
|
|
27
|
+
error: error instanceof Error ? error.message : String(error),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -45,6 +45,8 @@ const version_1 = require("../version");
|
|
|
45
45
|
const upgradeNudge_1 = require("../upgradeNudge");
|
|
46
46
|
const captureReadiness_1 = require("../captureReadiness");
|
|
47
47
|
const captureLog_1 = require("../captureLog");
|
|
48
|
+
const securityDoctor_1 = require("../securityDoctor");
|
|
49
|
+
const securityFeature_1 = require("../securityFeature");
|
|
48
50
|
const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), ".claude", "settings.json");
|
|
49
51
|
const CLAUDE_INSTRUCTIONS_PATH = path.join(os.homedir(), ".claude", "CLAUDE.md");
|
|
50
52
|
const CODEX_HOOKS_PATH = path.join(os.homedir(), ".codex", "hooks.json");
|
|
@@ -482,6 +484,12 @@ async function runDoctor(_args) {
|
|
|
482
484
|
detail: "not installed on this machine",
|
|
483
485
|
});
|
|
484
486
|
}
|
|
487
|
+
// Agent Security is part of installation health, not a separate setup
|
|
488
|
+
// concept. Keep `codiedev security doctor` as an alias for focused output,
|
|
489
|
+
// while the main doctor remains the one command users need to remember.
|
|
490
|
+
if ((0, securityFeature_1.isAgentSecurityEnabled)(config)) {
|
|
491
|
+
checks.push(...(0, securityDoctor_1.securityDoctorChecks)((0, securityDoctor_1.collectSecurityDoctorReport)(config.companyId)));
|
|
492
|
+
}
|
|
485
493
|
// 8. gh / glab CLIs (optional — only needed for reverse-ticket from a
|
|
486
494
|
// specific PR/MR URL; branch mode works without either).
|
|
487
495
|
checks.push({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runSecurityCommand(args: string[]): Promise<void>;
|