codiedev 0.10.0-beta.1 → 0.10.0-beta.11

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 (155) hide show
  1. package/README.md +11 -5
  2. package/assets/opencode-plugin.mjs +6 -0
  3. package/assets/session-key.mjs +48 -0
  4. package/assets/tui-brand-plugin.tsx +73 -0
  5. package/dist/backup/cache.js +1 -5
  6. package/dist/backup/match.js +0 -6
  7. package/dist/backup/run.js +3 -35
  8. package/dist/captureExclusions.js +0 -12
  9. package/dist/captureLog.js +0 -28
  10. package/dist/captureReadiness.js +0 -58
  11. package/dist/captureSignals.js +184 -0
  12. package/dist/cli.js +135 -72
  13. package/dist/cliSession.js +456 -0
  14. package/dist/codexHookTrust.js +125 -6
  15. package/dist/codexLimitScan.js +359 -0
  16. package/dist/codie.js +77 -130
  17. package/dist/codieBrandInstall.js +80 -0
  18. package/dist/codieLauncher.js +76 -79
  19. package/dist/codieRuntime.js +19 -23
  20. package/dist/codieRuntimeInstall.js +108 -23
  21. package/dist/codieUsage.js +181 -0
  22. package/dist/commands/agentTemplates.generated.js +136 -0
  23. package/dist/commands/agents.js +1101 -0
  24. package/dist/commands/agentsSdk.generated.js +7 -0
  25. package/dist/commands/ask.js +0 -3
  26. package/dist/commands/capture.js +0 -2
  27. package/dist/commands/createTicket.js +0 -10
  28. package/dist/commands/doctor.js +53 -120
  29. package/dist/commands/evals.js +187 -0
  30. package/dist/commands/inbox.js +0 -2
  31. package/dist/commands/library.js +0 -3
  32. package/dist/commands/pack.js +573 -0
  33. package/dist/commands/ping.js +0 -2
  34. package/dist/commands/pull.js +6 -10
  35. package/dist/commands/push.js +2 -12
  36. package/dist/commands/search.js +0 -4
  37. package/dist/commands/send.js +0 -5
  38. package/dist/commands/share.js +0 -4
  39. package/dist/commands/shared.js +0 -35
  40. package/dist/commands/traces.js +344 -0
  41. package/dist/commands/tracesLabel.js +348 -0
  42. package/dist/configDir.js +18 -11
  43. package/dist/connect.js +55 -118
  44. package/dist/connectFlow.js +35 -9
  45. package/dist/copilotHooks.js +391 -0
  46. package/dist/copilotInstallation.js +64 -0
  47. package/dist/cursorHooks.js +38 -30
  48. package/dist/cursorLifecycle.js +109 -0
  49. package/dist/detection.js +0 -30
  50. package/dist/harnessDirs.js +77 -0
  51. package/dist/hook.js +256 -445
  52. package/dist/inboxFormat.js +0 -24
  53. package/dist/launchContext.js +150 -0
  54. package/dist/launcher.js +168 -0
  55. package/dist/limitEvent.js +44 -13
  56. package/dist/limitEventReporter.js +1 -1
  57. package/dist/managedPi.js +256 -0
  58. package/dist/mcp.js +5 -384
  59. package/dist/nativeSteering.js +24 -0
  60. package/dist/packs/codie.js +182 -0
  61. package/dist/packs/harnesses.js +411 -0
  62. package/dist/packs/hookGate.js +256 -0
  63. package/dist/packs/install.js +286 -0
  64. package/dist/packs/manifest.js +126 -0
  65. package/dist/packs/paths.js +81 -0
  66. package/dist/packs/shim.js +214 -0
  67. package/dist/packs/store.js +155 -0
  68. package/dist/packs/sync.js +170 -0
  69. package/dist/piHooks.js +216 -0
  70. package/dist/piRuntime.js +120 -0
  71. package/dist/repoResolver.js +0 -19
  72. package/dist/secret.js +0 -3
  73. package/dist/selfUpdate.js +376 -0
  74. package/dist/skillMarker.js +23 -0
  75. package/dist/subagents.js +135 -0
  76. package/dist/teamSkillContext.js +10 -0
  77. package/dist/transcriptModels.js +205 -0
  78. package/dist/upgradeNudge.js +0 -30
  79. package/dist/utils.js +502 -828
  80. package/dist/version.js +0 -10
  81. package/dist/versionCheck.js +0 -33
  82. package/package.json +8 -9
  83. package/dist/backup/cache.d.ts +0 -14
  84. package/dist/backup/match.d.ts +0 -2
  85. package/dist/backup/run.d.ts +0 -25
  86. package/dist/captureExclusions.d.ts +0 -30
  87. package/dist/captureLog.d.ts +0 -20
  88. package/dist/captureReadiness.d.ts +0 -65
  89. package/dist/cli.d.ts +0 -16
  90. package/dist/codexHookTrust.d.ts +0 -34
  91. package/dist/codie.d.ts +0 -2
  92. package/dist/codieLauncher.d.ts +0 -53
  93. package/dist/codieRuntime.d.ts +0 -29
  94. package/dist/codieRuntimeInstall.d.ts +0 -28
  95. package/dist/codieStartup.d.ts +0 -26
  96. package/dist/codieStartup.js +0 -30
  97. package/dist/commands/ask.d.ts +0 -1
  98. package/dist/commands/capture.d.ts +0 -14
  99. package/dist/commands/createTicket.d.ts +0 -27
  100. package/dist/commands/delete.d.ts +0 -1
  101. package/dist/commands/doctor.d.ts +0 -1
  102. package/dist/commands/inbox.d.ts +0 -3
  103. package/dist/commands/library.d.ts +0 -2
  104. package/dist/commands/note.d.ts +0 -1
  105. package/dist/commands/ping.d.ts +0 -1
  106. package/dist/commands/promote.d.ts +0 -1
  107. package/dist/commands/promote.js +0 -53
  108. package/dist/commands/pull.d.ts +0 -2
  109. package/dist/commands/push.d.ts +0 -33
  110. package/dist/commands/react.d.ts +0 -2
  111. package/dist/commands/react.js +0 -40
  112. package/dist/commands/reverseTicket.d.ts +0 -2
  113. package/dist/commands/reverseTicket.js +0 -477
  114. package/dist/commands/search.d.ts +0 -2
  115. package/dist/commands/security.d.ts +0 -1
  116. package/dist/commands/security.js +0 -156
  117. package/dist/commands/send.d.ts +0 -2
  118. package/dist/commands/share.d.ts +0 -2
  119. package/dist/commands/shared.d.ts +0 -64
  120. package/dist/configDir.d.ts +0 -1
  121. package/dist/connect.d.ts +0 -1
  122. package/dist/connectFlow.d.ts +0 -13
  123. package/dist/cursorHooks.d.ts +0 -23
  124. package/dist/detection.d.ts +0 -30
  125. package/dist/hook.d.ts +0 -6
  126. package/dist/inboxFormat.d.ts +0 -38
  127. package/dist/limitEvent.d.ts +0 -16
  128. package/dist/limitEventReporter.d.ts +0 -21
  129. package/dist/mcp.d.ts +0 -12
  130. package/dist/prUrl.d.ts +0 -9
  131. package/dist/prUrl.js +0 -33
  132. package/dist/repoResolver.d.ts +0 -17
  133. package/dist/secret.d.ts +0 -1
  134. package/dist/security.d.ts +0 -102
  135. package/dist/security.js +0 -437
  136. package/dist/securityDeepScan.d.ts +0 -41
  137. package/dist/securityDeepScan.js +0 -248
  138. package/dist/securityDoctor.d.ts +0 -32
  139. package/dist/securityDoctor.js +0 -63
  140. package/dist/securityFeature.d.ts +0 -5
  141. package/dist/securityFeature.js +0 -7
  142. package/dist/securityInstall.d.ts +0 -63
  143. package/dist/securityInstall.js +0 -239
  144. package/dist/securityInventory.d.ts +0 -54
  145. package/dist/securityInventory.js +0 -556
  146. package/dist/securityPilot.d.ts +0 -19
  147. package/dist/securityPilot.js +0 -61
  148. package/dist/securityPolicy.d.ts +0 -33
  149. package/dist/securityPolicy.js +0 -188
  150. package/dist/securitySession.d.ts +0 -6
  151. package/dist/securitySession.js +0 -72
  152. package/dist/upgradeNudge.d.ts +0 -25
  153. package/dist/utils.d.ts +0 -342
  154. package/dist/version.d.ts +0 -1
  155. package/dist/versionCheck.d.ts +0 -68
@@ -0,0 +1,456 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.DEVICE_CODE_GRANT = exports.LOCK_STALE_MS = exports.DEFAULT_MIN_VALIDITY_MS = void 0;
37
+ exports.readConfigAt = readConfigAt;
38
+ exports.renameWithRetry = renameWithRetry;
39
+ exports.writeConfigAtomic = writeConfigAtomic;
40
+ exports.ensureFreshToken = ensureFreshToken;
41
+ exports.pkcePair = pkcePair;
42
+ exports.openBrowser = openBrowser;
43
+ exports.parseCallback = parseCallback;
44
+ exports.browserLogin = browserLogin;
45
+ exports.isHeadless = isHeadless;
46
+ exports.deviceLogin = deviceLogin;
47
+ exports.logout = logout;
48
+ const crypto = __importStar(require("crypto"));
49
+ const fs = __importStar(require("fs"));
50
+ const http = __importStar(require("http"));
51
+ const os = __importStar(require("os"));
52
+ const path = __importStar(require("path"));
53
+ const child_process_1 = require("child_process");
54
+ exports.DEFAULT_MIN_VALIDITY_MS = 10 * 60_000;
55
+ exports.LOCK_STALE_MS = 30_000;
56
+ const LOCK_WAIT_MS = 20_000;
57
+ const REFRESH_TIMEOUT_MS = 10_000;
58
+ const LOGIN_TIMEOUT_MS = 10 * 60_000;
59
+ function readConfigAt(configPath) {
60
+ try {
61
+ return JSON.parse(fs.readFileSync(configPath, "utf8"));
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ }
67
+ const RENAME_RETRY_CODES = new Set(["EPERM", "EACCES", "EBUSY"]);
68
+ const RENAME_RETRY_MS = [10, 25, 50, 100, 200, 400, 800];
69
+ function sleepSync(ms) {
70
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
71
+ }
72
+ function renameWithRetry(from, to, rename = fs.renameSync, sleep = sleepSync) {
73
+ for (let attempt = 0;; attempt++) {
74
+ try {
75
+ rename(from, to);
76
+ return;
77
+ }
78
+ catch (err) {
79
+ const code = err?.code ?? "";
80
+ if (!RENAME_RETRY_CODES.has(code) || attempt >= RENAME_RETRY_MS.length)
81
+ throw err;
82
+ sleep(RENAME_RETRY_MS[attempt]);
83
+ }
84
+ }
85
+ }
86
+ function writeConfigAtomic(configPath, config) {
87
+ const dir = path.dirname(configPath);
88
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
89
+ const data = JSON.stringify(config, null, 2);
90
+ const tmp = path.join(dir, `.config.${process.pid}.${crypto.randomBytes(6).toString("hex")}.tmp`);
91
+ fs.writeFileSync(tmp, data, { encoding: "utf8", mode: 0o600 });
92
+ try {
93
+ fs.chmodSync(tmp, 0o600);
94
+ renameWithRetry(tmp, configPath);
95
+ return;
96
+ }
97
+ catch (err) {
98
+ try {
99
+ fs.unlinkSync(tmp);
100
+ }
101
+ catch { }
102
+ if (!RENAME_RETRY_CODES.has(err?.code ?? ""))
103
+ throw err;
104
+ }
105
+ fs.writeFileSync(configPath, data, { encoding: "utf8", mode: 0o600 });
106
+ fs.chmodSync(configPath, 0o600);
107
+ }
108
+ function lockPathFor(configPath) {
109
+ return path.join(path.dirname(configPath), "refresh.lock");
110
+ }
111
+ function tryLock(lockPath, now) {
112
+ try {
113
+ const fd = fs.openSync(lockPath, "wx", 0o600);
114
+ fs.writeSync(fd, JSON.stringify({ pid: process.pid, at: now }));
115
+ fs.closeSync(fd);
116
+ return true;
117
+ }
118
+ catch (err) {
119
+ if (err?.code !== "EEXIST")
120
+ throw err;
121
+ try {
122
+ const age = now - fs.statSync(lockPath).mtimeMs;
123
+ if (age > exports.LOCK_STALE_MS)
124
+ fs.unlinkSync(lockPath);
125
+ }
126
+ catch {
127
+ }
128
+ return false;
129
+ }
130
+ }
131
+ function unlock(lockPath) {
132
+ try {
133
+ fs.unlinkSync(lockPath);
134
+ }
135
+ catch { }
136
+ }
137
+ function needsRefresh(config, now, minValidityMs) {
138
+ const auth = config.auth;
139
+ return !!auth && !!config.token && auth.accessTokenExpiresAt - now < minValidityMs;
140
+ }
141
+ async function postForm(fetchImpl, url, form, timeoutMs) {
142
+ const ctl = new AbortController();
143
+ const timer = setTimeout(() => ctl.abort(), timeoutMs);
144
+ try {
145
+ const res = await fetchImpl(url, {
146
+ method: "POST",
147
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
148
+ body: new URLSearchParams(form).toString(),
149
+ signal: ctl.signal,
150
+ });
151
+ return { status: res.status, body: (await res.json().catch(() => null)) };
152
+ }
153
+ finally {
154
+ clearTimeout(timer);
155
+ }
156
+ }
157
+ async function ensureFreshToken(deps, minValidityMs = exports.DEFAULT_MIN_VALIDITY_MS) {
158
+ const now = deps.now ?? Date.now;
159
+ const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
160
+ const fetchImpl = deps.fetchImpl ?? fetch;
161
+ const first = readConfigAt(deps.configPath);
162
+ if (!first?.auth)
163
+ return { status: "none", config: first };
164
+ if (!needsRefresh(first, now(), minValidityMs))
165
+ return { status: "fresh", config: first };
166
+ const seen = first.token;
167
+ const lockPath = lockPathFor(deps.configPath);
168
+ const deadline = now() + LOCK_WAIT_MS;
169
+ let locked = false;
170
+ try {
171
+ while (!(locked = tryLock(lockPath, now()))) {
172
+ if (now() > deadline)
173
+ return { status: "unavailable", config: first };
174
+ await sleep(50 + Math.floor(Math.random() * 100));
175
+ }
176
+ const current = readConfigAt(deps.configPath);
177
+ if (!current?.auth)
178
+ return { status: "none", config: current };
179
+ if (current.token !== seen)
180
+ return { status: "adopted", config: current };
181
+ let res;
182
+ try {
183
+ res = await postForm(fetchImpl, `${current.backendUrl}/oauth/token`, {
184
+ grant_type: "refresh_token",
185
+ client_id: current.auth.clientId,
186
+ refresh_token: current.auth.refreshToken,
187
+ }, REFRESH_TIMEOUT_MS);
188
+ }
189
+ catch {
190
+ return { status: "unavailable", config: current };
191
+ }
192
+ if (res.status === 200 && typeof res.body?.access_token === "string" && typeof res.body.refresh_token === "string") {
193
+ const next = {
194
+ ...current,
195
+ token: res.body.access_token,
196
+ auth: {
197
+ ...current.auth,
198
+ refreshToken: res.body.refresh_token,
199
+ accessTokenExpiresAt: now() + Number(res.body.expires_in ?? 0) * 1000,
200
+ },
201
+ };
202
+ writeConfigAtomic(deps.configPath, next);
203
+ return { status: "refreshed", config: next };
204
+ }
205
+ if (res.status === 400 && res.body?.error === "invalid_grant") {
206
+ const out = { ...current, token: "" };
207
+ delete out.auth;
208
+ writeConfigAtomic(deps.configPath, out);
209
+ return { status: "signed_out", config: out };
210
+ }
211
+ return { status: "unavailable", config: current };
212
+ }
213
+ finally {
214
+ if (locked)
215
+ unlock(lockPath);
216
+ }
217
+ }
218
+ function base64Url(buf) {
219
+ return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
220
+ }
221
+ function pkcePair() {
222
+ const verifier = base64Url(crypto.randomBytes(32));
223
+ const challenge = base64Url(crypto.createHash("sha256").update(verifier).digest());
224
+ return { verifier, challenge };
225
+ }
226
+ function openBrowser(url) {
227
+ try {
228
+ const [cmd, args] = process.platform === "darwin"
229
+ ? ["open", [url]]
230
+ : process.platform === "win32"
231
+ ? ["rundll32", ["url.dll,FileProtocolHandler", url]]
232
+ : ["xdg-open", [url]];
233
+ const child = (0, child_process_1.spawn)(cmd, args, { detached: true, stdio: "ignore" });
234
+ child.on("error", () => { });
235
+ child.unref();
236
+ return true;
237
+ }
238
+ catch {
239
+ return false;
240
+ }
241
+ }
242
+ const DONE_PAGE = (ok, msg) => `<!doctype html><html><head><meta charset="utf-8">
243
+ <title>Codiedev CLI</title><style>body{font-family:system-ui,sans-serif;display:grid;place-items:center;
244
+ height:100vh;margin:0;background:#fbfbfa;color:#1f1f1f}main{max-width:28rem;text-align:center}
245
+ h1{font-size:1.25rem}</style></head><body><main><h1>${ok ? "You're signed in" : "Sign-in didn't finish"}</h1>
246
+ <p>${msg}</p></main></body></html>`;
247
+ function parseCallback(input) {
248
+ try {
249
+ const u = new URL(input.trim());
250
+ return {
251
+ code: u.searchParams.get("code") ?? undefined,
252
+ state: u.searchParams.get("state") ?? undefined,
253
+ error: u.searchParams.get("error_description") ?? u.searchParams.get("error") ?? undefined,
254
+ };
255
+ }
256
+ catch {
257
+ return { error: "That isn't the address from the browser's address bar." };
258
+ }
259
+ }
260
+ async function browserLogin(opts) {
261
+ const fetchImpl = opts.fetchImpl ?? fetch;
262
+ const log = opts.log ?? ((line) => console.log(line));
263
+ const now = opts.now ?? Date.now;
264
+ const state = base64Url(crypto.randomBytes(16));
265
+ const { verifier, challenge } = pkcePair();
266
+ let settle;
267
+ const result = new Promise((r) => (settle = r));
268
+ const server = http.createServer((req, res) => {
269
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
270
+ if (url.pathname !== "/callback") {
271
+ res.writeHead(404).end();
272
+ return;
273
+ }
274
+ const cb = parseCallback(url.toString());
275
+ const ok = !cb.error && cb.state === state && !!cb.code;
276
+ res.writeHead(ok ? 200 : 400, { "Content-Type": "text/html; charset=utf-8" });
277
+ res.end(DONE_PAGE(ok, ok
278
+ ? "You can close this tab and go back to your terminal."
279
+ : "Go back to your terminal and run <code>codiedev connect</code> again."));
280
+ if (cb.error)
281
+ settle({ error: cb.error });
282
+ else if (cb.state !== state)
283
+ settle({ error: "The sign-in response didn't match this request (state mismatch)." });
284
+ else
285
+ settle({ code: cb.code });
286
+ });
287
+ await new Promise((resolve, reject) => {
288
+ server.once("error", reject);
289
+ server.listen(0, "127.0.0.1", () => resolve());
290
+ });
291
+ const port = server.address().port;
292
+ const redirectUri = `http://127.0.0.1:${port}/callback`;
293
+ const timer = setTimeout(() => settle({ error: "Timed out waiting for the browser (10 minutes)." }), LOGIN_TIMEOUT_MS);
294
+ try {
295
+ const reg = await fetchImpl(`${opts.backendUrl}/oauth/register`, {
296
+ method: "POST",
297
+ headers: { "Content-Type": "application/json" },
298
+ body: JSON.stringify({
299
+ client_name: opts.clientName ?? `Codiedev CLI on ${os.hostname()}`,
300
+ redirect_uris: [redirectUri],
301
+ }),
302
+ });
303
+ if (reg.status !== 201)
304
+ throw new Error(`Sign-in setup failed (HTTP ${reg.status}).`);
305
+ const { client_id: clientId } = (await reg.json());
306
+ const authorize = new URL(`${opts.backendUrl}/oauth/authorize`);
307
+ authorize.search = new URLSearchParams({
308
+ client_id: clientId,
309
+ redirect_uri: redirectUri,
310
+ response_type: "code",
311
+ code_challenge: challenge,
312
+ code_challenge_method: "S256",
313
+ state,
314
+ scope: "cli",
315
+ }).toString();
316
+ const link = authorize.toString();
317
+ if (opts.onAuthorizeUrl) {
318
+ opts.onAuthorizeUrl(link);
319
+ }
320
+ else {
321
+ log("Opening your browser to sign in…");
322
+ log(`If it doesn't open, visit:\n ${link}\n`);
323
+ openBrowser(link);
324
+ }
325
+ const cb = await result;
326
+ if (cb.error || !cb.code)
327
+ throw new Error(cb.error ?? "Sign-in was cancelled.");
328
+ const tok = await postForm(fetchImpl, `${opts.backendUrl}/oauth/token`, {
329
+ grant_type: "authorization_code",
330
+ code: cb.code,
331
+ client_id: clientId,
332
+ redirect_uri: redirectUri,
333
+ code_verifier: verifier,
334
+ }, REFRESH_TIMEOUT_MS);
335
+ const body = tok.body;
336
+ if (tok.status !== 200 || typeof body?.access_token !== "string" || typeof body.refresh_token !== "string") {
337
+ throw new Error(`Sign-in failed: ${body?.error_description ?? body?.error ?? `HTTP ${tok.status}`}`);
338
+ }
339
+ if (body.scope !== "cli")
340
+ throw new Error("The server didn't grant CLI access.");
341
+ return {
342
+ accessToken: body.access_token,
343
+ refreshToken: body.refresh_token,
344
+ expiresAt: now() + Number(body.expires_in ?? 0) * 1000,
345
+ clientId,
346
+ };
347
+ }
348
+ finally {
349
+ clearTimeout(timer);
350
+ server.close();
351
+ }
352
+ }
353
+ exports.DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
354
+ function isHeadless(env = process.env, platform = process.platform) {
355
+ if (env.SSH_CONNECTION || env.SSH_CLIENT || env.SSH_TTY)
356
+ return true;
357
+ if (platform === "linux" && !env.DISPLAY && !env.WAYLAND_DISPLAY)
358
+ return true;
359
+ return false;
360
+ }
361
+ async function deviceLogin(opts) {
362
+ const fetchImpl = opts.fetchImpl ?? fetch;
363
+ const log = opts.log ?? ((line) => console.log(line));
364
+ const now = opts.now ?? Date.now;
365
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
366
+ const reg = await fetchImpl(`${opts.backendUrl}/oauth/register`, {
367
+ method: "POST",
368
+ headers: { "Content-Type": "application/json" },
369
+ body: JSON.stringify({
370
+ client_name: opts.clientName ?? `Codiedev CLI on ${os.hostname()}`,
371
+ redirect_uris: [],
372
+ grant_types: [exports.DEVICE_CODE_GRANT, "refresh_token"],
373
+ }),
374
+ });
375
+ if (reg.status !== 201)
376
+ throw new Error(`Sign-in setup failed (HTTP ${reg.status}).`);
377
+ const { client_id: clientId } = (await reg.json());
378
+ const start = await fetchImpl(`${opts.backendUrl}/oauth/device_authorization`, {
379
+ method: "POST",
380
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
381
+ body: new URLSearchParams({ client_id: clientId, scope: "cli" }).toString(),
382
+ });
383
+ const auth = (await start.json().catch(() => null));
384
+ if (start.status !== 200 || !auth?.device_code || !auth.user_code || !auth.verification_uri) {
385
+ throw new Error(`Sign-in setup failed: ${auth?.error_description ?? `HTTP ${start.status}`}`);
386
+ }
387
+ const complete = auth.verification_uri_complete ?? auth.verification_uri;
388
+ if (opts.onCode) {
389
+ opts.onCode({ userCode: auth.user_code, verificationUri: auth.verification_uri, verificationUriComplete: complete });
390
+ }
391
+ log("To sign in, open this link on any device (your laptop or phone) and approve:\n");
392
+ log(` ${complete}\n`);
393
+ log(`Your code: ${auth.user_code} (check it matches the page)\n`);
394
+ log("Waiting for approval…");
395
+ let intervalMs = Math.max(1, Number(auth.interval ?? 5)) * 1000;
396
+ const deadline = now() + Number(auth.expires_in ?? 600) * 1000;
397
+ for (;;) {
398
+ await sleep(intervalMs);
399
+ if (now() > deadline)
400
+ throw new Error("The code expired before it was approved. Run `codiedev connect` again.");
401
+ let res;
402
+ try {
403
+ res = await postForm(fetchImpl, `${opts.backendUrl}/oauth/token`, { grant_type: exports.DEVICE_CODE_GRANT, device_code: auth.device_code, client_id: clientId }, REFRESH_TIMEOUT_MS);
404
+ }
405
+ catch {
406
+ continue;
407
+ }
408
+ const body = res.body;
409
+ if (res.status === 200 && typeof body?.access_token === "string" && typeof body.refresh_token === "string") {
410
+ if (body.scope !== "cli")
411
+ throw new Error("The server didn't grant CLI access.");
412
+ return {
413
+ accessToken: body.access_token,
414
+ refreshToken: body.refresh_token,
415
+ expiresAt: now() + Number(body.expires_in ?? 0) * 1000,
416
+ clientId,
417
+ };
418
+ }
419
+ switch (body?.error) {
420
+ case "authorization_pending":
421
+ continue;
422
+ case "slow_down":
423
+ intervalMs += 5000;
424
+ continue;
425
+ case "access_denied":
426
+ throw new Error("Sign-in was denied in the browser.");
427
+ case "expired_token":
428
+ throw new Error("The code expired before it was approved. Run `codiedev connect` again.");
429
+ default:
430
+ throw new Error(`Sign-in failed: ${body?.error_description ?? body?.error ?? `HTTP ${res.status}`}`);
431
+ }
432
+ }
433
+ }
434
+ async function logout(deps) {
435
+ const fetchImpl = deps.fetchImpl ?? fetch;
436
+ const config = readConfigAt(deps.configPath);
437
+ if (!config?.token)
438
+ return { wasConnected: false, hadBrowserAuth: false, serverRevoked: false };
439
+ let serverRevoked = false;
440
+ const hadBrowserAuth = !!config.auth;
441
+ if (config.token && config.auth) {
442
+ try {
443
+ const res = await fetchImpl(`${config.backendUrl}/api/cli/logout`, {
444
+ method: "POST",
445
+ headers: { Authorization: `Bearer ${config.token}` },
446
+ });
447
+ serverRevoked = res.ok && (await res.json().catch(() => null))?.revoked === true;
448
+ }
449
+ catch {
450
+ }
451
+ }
452
+ const out = { ...config, token: "" };
453
+ delete out.auth;
454
+ writeConfigAtomic(deps.configPath, out);
455
+ return { wasConnected: true, hadBrowserAuth, serverRevoked };
456
+ }
@@ -33,10 +33,13 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.codexPluginContentHash = codexPluginContentHash;
36
37
  exports.codexCommandHookHash = codexCommandHookHash;
37
38
  exports.codiedevCodexHookTrustEntries = codiedevCodexHookTrustEntries;
38
39
  exports.writeCodexHookTrust = writeCodexHookTrust;
39
40
  exports.inspectCodexHookTrust = inspectCodexHookTrust;
41
+ exports.packCodexHookTrustEntries = packCodexHookTrustEntries;
42
+ exports.writeCodexPackHookTrust = writeCodexPackHookTrust;
40
43
  const crypto = __importStar(require("crypto"));
41
44
  const fs = __importStar(require("fs"));
42
45
  const path = __importStar(require("path"));
@@ -67,6 +70,42 @@ const MATCHER_EVENTS = new Set([
67
70
  "SubagentStart",
68
71
  "SubagentStop",
69
72
  ]);
73
+ function codexPluginContentHash(root) {
74
+ const hash = crypto.createHash("sha256");
75
+ hash.update("codex-plugin-bundle-v1");
76
+ const u64 = (n) => {
77
+ const b = Buffer.alloc(8);
78
+ b.writeBigUInt64LE(BigInt(n));
79
+ return b;
80
+ };
81
+ const pending = [root];
82
+ while (pending.length) {
83
+ const current = pending.pop();
84
+ const relative = path.relative(root, current).split(path.sep).join("/");
85
+ const name = path.posix.basename(relative);
86
+ if (name === "__pycache__" || name === ".venv" || name.endsWith(".pyc") || name.endsWith(".egg-info"))
87
+ continue;
88
+ const nameBytes = Buffer.from(relative, "utf8");
89
+ hash.update(u64(nameBytes.length));
90
+ hash.update(nameBytes);
91
+ const stat = fs.lstatSync(current);
92
+ if (stat.isDirectory()) {
93
+ hash.update("directory");
94
+ const entries = fs.readdirSync(current).sort((a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b)));
95
+ for (const entry of entries.reverse())
96
+ pending.push(path.join(current, entry));
97
+ }
98
+ else if (stat.isFile()) {
99
+ hash.update("file");
100
+ hash.update(u64(stat.size));
101
+ hash.update(fs.readFileSync(current));
102
+ }
103
+ else {
104
+ throw new Error(`unsupported plugin entry type: ${current}`);
105
+ }
106
+ }
107
+ return `sha256:${hash.digest("hex")}`;
108
+ }
70
109
  function canonicalize(value) {
71
110
  if (Array.isArray(value))
72
111
  return value.map(canonicalize);
@@ -85,8 +124,6 @@ function normalizedTimeout(eventName, value) {
85
124
  return Math.min(3, Math.max(1, configured ?? 1));
86
125
  return Math.max(1, configured ?? 600);
87
126
  }
88
- /** Codex hashes a canonical, normalized hook identity before it will execute a
89
- * user hook. Keep this compatible with codex_config::version_for_toml. */
90
127
  function codexCommandHookHash(eventName, group, handler) {
91
128
  const normalizedHandler = {
92
129
  type: "command",
@@ -155,6 +192,27 @@ function codiedevCodexHookTrustEntries(hooksPath, hooksFile) {
155
192
  }
156
193
  return entries;
157
194
  }
195
+ function stripBlockKeepingForeign(text, begin, end) {
196
+ const foreign = [];
197
+ const stripped = text.replace(new RegExp(`\\n?${begin}\\n([\\s\\S]*?)${end}\\n?`, "g"), (_match, body) => {
198
+ let table = null;
199
+ const flush = () => {
200
+ if (table && table.join("").trim())
201
+ foreign.push(table.join("\n").trimEnd());
202
+ };
203
+ for (const line of body.split("\n")) {
204
+ if (/^\s*\[/.test(line)) {
205
+ flush();
206
+ table = /^\s*\[hooks\.state\./.test(line) ? null : [];
207
+ }
208
+ if (table)
209
+ table.push(line);
210
+ }
211
+ flush();
212
+ return "\n";
213
+ });
214
+ return foreign.length ? `${stripped.trimEnd()}\n\n${foreign.join("\n\n")}\n` : stripped;
215
+ }
158
216
  function tableHeader(key) {
159
217
  return `[hooks.state.${JSON.stringify(key)}]`;
160
218
  }
@@ -173,13 +231,10 @@ function removeTable(lines, header) {
173
231
  }
174
232
  return output;
175
233
  }
176
- /** `codiedev connect` is an explicit install action, so trust only the exact
177
- * Codiedev commands it just wrote. Any later command change produces a new
178
- * hash and Codex disables that hook until connect is run again. */
179
234
  function writeCodexHookTrust(configPath, hooksPath, hooksFile) {
180
235
  const entries = codiedevCodexHookTrustEntries(hooksPath, hooksFile);
181
236
  let existing = fs.existsSync(configPath) ? fs.readFileSync(configPath, "utf8") : "";
182
- existing = existing.replace(new RegExp(`\\n?${TRUST_BEGIN}\\n[\\s\\S]*?${TRUST_END}\\n?`, "g"), "\n");
237
+ existing = stripBlockKeepingForeign(existing, TRUST_BEGIN, TRUST_END);
183
238
  let lines = existing.split("\n");
184
239
  for (const entry of entries)
185
240
  lines = removeTable(lines, tableHeader(entry.key));
@@ -226,3 +281,67 @@ function inspectCodexHookTrust(configPath, hooksPath, hooksFile, requiredSubcomm
226
281
  }).length;
227
282
  return { ok: entries.length > 0 && trusted === entries.length, total: entries.length, trusted };
228
283
  }
284
+ const PACK_TRUST_BEGIN = "# codiedev-pack-trust:begin";
285
+ const PACK_TRUST_END = "# codiedev-pack-trust:end";
286
+ function packCodexHookTrustEntries(hooksPath, hooksFile, plugin) {
287
+ const events = hooksFile?.hooks;
288
+ if (!events || typeof events !== "object")
289
+ return [];
290
+ const source = plugin
291
+ ? `${plugin.id}:${path.relative(plugin.root, hooksPath).split(path.sep).join("/")}`
292
+ : normalizedHooksPath(hooksPath);
293
+ const contentHash = plugin ? codexPluginContentHash(plugin.root) : undefined;
294
+ const entries = [];
295
+ for (const [eventName, groupsValue] of Object.entries(events)) {
296
+ const eventLabel = EVENT_LABELS[eventName];
297
+ if (!eventLabel || !Array.isArray(groupsValue))
298
+ continue;
299
+ groupsValue.forEach((groupValue, groupIndex) => {
300
+ const group = groupValue;
301
+ if (!group || !Array.isArray(group.hooks))
302
+ return;
303
+ group.hooks.forEach((handlerValue, handlerIndex) => {
304
+ const handler = handlerValue;
305
+ if (!handler || handler.type !== "command" || typeof handler.command !== "string")
306
+ return;
307
+ entries.push({
308
+ key: `${source}:${eventLabel}:${groupIndex}:${handlerIndex}`,
309
+ eventName,
310
+ command: handler.command,
311
+ currentHash: codexCommandHookHash(eventName, group, handler),
312
+ ...(contentHash ? { contentHash } : {}),
313
+ });
314
+ });
315
+ });
316
+ }
317
+ return entries;
318
+ }
319
+ function writeCodexPackHookTrust(configPath, entries) {
320
+ let existing = fs.existsSync(configPath) ? fs.readFileSync(configPath, "utf8") : "";
321
+ existing = stripBlockKeepingForeign(existing, PACK_TRUST_BEGIN, PACK_TRUST_END);
322
+ let lines = existing.split("\n");
323
+ for (const entry of entries)
324
+ lines = removeTable(lines, tableHeader(entry.key));
325
+ const base = lines.join("\n").trimEnd();
326
+ if (entries.length === 0) {
327
+ const next = base ? `${base}\n` : "";
328
+ if (!fs.existsSync(configPath) && !next)
329
+ return entries;
330
+ fs.mkdirSync(path.dirname(configPath), { recursive: true, mode: 0o700 });
331
+ fs.writeFileSync(configPath, next, { encoding: "utf8", mode: 0o600 });
332
+ fs.chmodSync(configPath, 0o600);
333
+ return entries;
334
+ }
335
+ const trustBlock = entries.flatMap((entry) => [
336
+ tableHeader(entry.key),
337
+ "enabled = true",
338
+ `trusted_hash = ${JSON.stringify(entry.currentHash)}`,
339
+ ...(entry.contentHash ? [`trusted_content_hash = ${JSON.stringify(entry.contentHash)}`] : []),
340
+ "",
341
+ ]).join("\n");
342
+ const next = `${base}${base ? "\n\n" : ""}${PACK_TRUST_BEGIN}\n${trustBlock}${PACK_TRUST_END}\n`;
343
+ fs.mkdirSync(path.dirname(configPath), { recursive: true, mode: 0o700 });
344
+ fs.writeFileSync(configPath, next, { encoding: "utf8", mode: 0o600 });
345
+ fs.chmodSync(configPath, 0o600);
346
+ return entries;
347
+ }