codiedev 0.9.52 → 0.10.0-beta.1

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.
Files changed (48) hide show
  1. package/README.md +9 -1
  2. package/dist/captureLog.d.ts +1 -1
  3. package/dist/cli.js +17 -12
  4. package/dist/codie.js +146 -1
  5. package/dist/codieLauncher.d.ts +5 -0
  6. package/dist/codieLauncher.js +22 -1
  7. package/dist/codieRuntime.d.ts +11 -1
  8. package/dist/codieRuntime.js +20 -3
  9. package/dist/codieRuntimeInstall.d.ts +12 -11
  10. package/dist/codieRuntimeInstall.js +3 -1
  11. package/dist/codieStartup.d.ts +26 -0
  12. package/dist/codieStartup.js +30 -0
  13. package/dist/commands/doctor.js +8 -0
  14. package/dist/commands/security.d.ts +1 -0
  15. package/dist/commands/security.js +156 -0
  16. package/dist/connect.js +56 -4
  17. package/dist/cursorHooks.d.ts +23 -0
  18. package/dist/cursorHooks.js +81 -0
  19. package/dist/hook.js +272 -26
  20. package/dist/limitEvent.d.ts +5 -1
  21. package/dist/limitEvent.js +5 -1
  22. package/dist/mcp.js +1 -93
  23. package/dist/repoResolver.js +10 -4
  24. package/dist/security.d.ts +102 -0
  25. package/dist/security.js +437 -0
  26. package/dist/securityDeepScan.d.ts +41 -0
  27. package/dist/securityDeepScan.js +248 -0
  28. package/dist/securityDoctor.d.ts +32 -0
  29. package/dist/securityDoctor.js +63 -0
  30. package/dist/securityFeature.d.ts +5 -0
  31. package/dist/securityFeature.js +7 -0
  32. package/dist/securityInstall.d.ts +63 -0
  33. package/dist/securityInstall.js +239 -0
  34. package/dist/securityInventory.d.ts +54 -0
  35. package/dist/securityInventory.js +556 -0
  36. package/dist/securityPilot.d.ts +19 -0
  37. package/dist/securityPilot.js +61 -0
  38. package/dist/securityPolicy.d.ts +33 -0
  39. package/dist/securityPolicy.js +188 -0
  40. package/dist/securitySession.d.ts +6 -0
  41. package/dist/securitySession.js +72 -0
  42. package/dist/upgradeNudge.d.ts +1 -0
  43. package/dist/upgradeNudge.js +1 -0
  44. package/dist/utils.d.ts +16 -6
  45. package/dist/utils.js +311 -70
  46. package/package.json +14 -3
  47. package/dist/commands/post.d.ts +0 -2
  48. package/dist/commands/post.js +0 -96
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** — `sessionEnd` hook 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
@@ -49,6 +49,14 @@ container needs its own rule unless `~/.codiedev/config.json` is persisted.
49
49
 
50
50
  Codex doesn't currently expose a `SessionEnd` event, so the hook runs on `Stop` (fires after each assistant turn). The backend dedupes by `(company, session_id)` and replaces the stored transcript on re-upload so the latest turn's snapshot wins. Summarization happens once per session; subsequent turn uploads keep the raw transcript fresh but don't re-summarize (to avoid running up LLM cost).
51
51
 
52
+ ## Cursor note
53
+
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
58
+ label and real transcript statistics through the shared capture pipeline.
59
+
52
60
  ## OpenCode note
53
61
 
54
62
  OpenCode exposes session lifecycle events through plugins. Codiedev snapshots
@@ -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";
5
+ stage: "capture-finish" | "upload" | "upload-deferred" | "security" | "stderr";
6
6
  sessionId: string | undefined;
7
7
  outcome: string;
8
8
  detail?: string;
package/dist/cli.js CHANGED
@@ -29,11 +29,11 @@ const createTicket_1 = require("./commands/createTicket");
29
29
  const doctor_1 = require("./commands/doctor");
30
30
  const search_1 = require("./commands/search");
31
31
  const library_1 = require("./commands/library");
32
- const post_1 = require("./commands/post");
33
32
  const share_1 = require("./commands/share");
34
33
  const send_1 = require("./commands/send");
35
34
  const react_1 = require("./commands/react");
36
35
  const capture_1 = require("./commands/capture");
36
+ const security_1 = require("./commands/security");
37
37
  const version_1 = require("./version");
38
38
  const HELP = `
39
39
  Codiedev CLI
@@ -43,8 +43,14 @@ Codiedev CLI
43
43
  someone left off across sessions and providers.
44
44
 
45
45
  Connect:
46
- codiedev connect Link Claude Code / Codex to your org
47
- codiedev doctor Verify your setup after connecting
46
+ codiedev connect Link Claude Code / Codex / Cursor to your org
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
48
54
 
49
55
  Harness:
50
56
  codie Launch the governed Codie harness.
@@ -98,9 +104,6 @@ Discovery:
98
104
  List artifacts in your workspace
99
105
 
100
106
  Feed:
101
- codiedev post --title "<t>" --body "<b>" [--intent share|request_review|
102
- request_expertise|link_share|rfc] [--filename <key>]
103
- [--mention <name>]... Publish to the team feed
104
107
  codiedev react <postId> <emoji> Toggle a reaction on a feed post
105
108
 
106
109
  Privacy:
@@ -128,7 +131,7 @@ Other artifact types (rare — use the matching prefix):
128
131
 
129
132
  Skills (slash commands):
130
133
  Add 'name:' + 'description:' YAML frontmatter to any pushed file
131
- and it shows up as /<name> in Claude Code / Codex. Filename
134
+ and it shows up as /<name> in Claude Code / Codex / Cursor. Filename
132
135
  prefix doesn't matter — frontmatter is the signal.
133
136
 
134
137
  Examples:
@@ -165,6 +168,8 @@ codiedev connect — link your agent CLI to Codiedev
165
168
  - ~/.claude/CLAUDE.md instructions (teaches Claude the commands)
166
169
  - ~/.codex/hooks.json hook (same, for Codex)
167
170
  - ~/.codex/AGENTS.md instructions (same, for Codex)
171
+ - ~/.cursor/hooks.json hooks (session, prompt, security, capture)
172
+ - ~/.cursor/rules/codiedev.mdc (teaches Cursor the commands)
168
173
 
169
174
  Re-run anytime to swap accounts or refresh the instruction block.
170
175
 
@@ -367,7 +372,7 @@ Default path:
367
372
  # body...
368
373
  3. codiedev push portal-design.md
369
374
 
370
- It's now /portal-design in Claude Code / Codex.
375
+ It's now /portal-design in Claude Code / Codex / Cursor.
371
376
 
372
377
  Filename prefix is irrelevant for skills — frontmatter is the only
373
378
  signal. The stored artifact type still follows the filename rule
@@ -385,7 +390,7 @@ email notifications when pinged.
385
390
 
386
391
  ## Agent-native
387
392
 
388
- If you use Claude Code or Codex, the agent already knows the commands —
393
+ If you use Claude Code, Codex, or Cursor, the agent already knows the commands —
389
394
  'codiedev connect' installs instructions into your user-level config.
390
395
  Just say things like:
391
396
 
@@ -453,6 +458,9 @@ async function main() {
453
458
  case "verify":
454
459
  await (0, doctor_1.runDoctor)(rest);
455
460
  return;
461
+ case "security":
462
+ await (0, security_1.runSecurityCommand)(rest);
463
+ return;
456
464
  case "push":
457
465
  await (0, push_1.runPush)(rest);
458
466
  return;
@@ -503,9 +511,6 @@ async function main() {
503
511
  case "ls":
504
512
  await (0, library_1.runLibrary)(rest);
505
513
  return;
506
- case "post":
507
- await (0, post_1.runPost)(rest);
508
- return;
509
514
  case "share":
510
515
  await (0, share_1.runShare)(rest);
511
516
  return;
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;
@@ -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
@@ -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;
@@ -62,6 +63,26 @@ const DEFAULT_NAMES = {
62
63
  "z-ai/glm-5.2": "GLM 5.2 · Fireworks ZDR",
63
64
  "moonshotai/kimi-k2.7-code": "Kimi K2.7 Code · Fireworks ZDR",
64
65
  };
66
+ exports.CODIE_SECURITY_BLOCK_MARKER = "__CODIEDEV_SECURITY_BLOCK__";
67
+ /** Extract the private adapter marker used to carry a local block reason
68
+ * across OpenCode's generic 500 boundary. The marker never contains prompt or
69
+ * tool input — only the deterministic policy reason. */
70
+ function extractCodieSecurityBlock(stderr) {
71
+ for (const line of stderr.split(/\r?\n/)) {
72
+ const markerIndex = line.indexOf(exports.CODIE_SECURITY_BLOCK_MARKER);
73
+ if (markerIndex < 0)
74
+ continue;
75
+ try {
76
+ const parsed = JSON.parse(line.slice(markerIndex + exports.CODIE_SECURITY_BLOCK_MARKER.length));
77
+ if (typeof parsed?.reason === "string" && parsed.reason.trim())
78
+ return parsed.reason.trim();
79
+ }
80
+ catch {
81
+ // Ignore malformed or foreign stderr and preserve the original runtime error.
82
+ }
83
+ }
84
+ return undefined;
85
+ }
65
86
  /**
66
87
  * Pin non-interactive runs to the directory where `codie` was launched.
67
88
  * OpenCode can reuse a previously-known project instance; without an explicit
@@ -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 resolveCodieRuntime({ env, stateDir, existsSync, resolveBundled, }: {
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;
@@ -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 resolveCodieRuntime({ env, stateDir, existsSync, resolveBundled, }) {
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: Extract<CodieRuntime, {
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: Extract<CodieRuntime, {
21
- kind: "missing";
22
- }>) => void;
23
- }): string;
24
- export declare function installCodieRuntimeOnDisk(runtime: Extract<CodieRuntime, {
25
- kind: "missing";
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
+ }
@@ -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>;