codiedev 0.9.51 → 0.9.52

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 (44) hide show
  1. package/dist/captureLog.d.ts +1 -1
  2. package/dist/cli.js +1 -11
  3. package/dist/codie.js +1 -146
  4. package/dist/codieLauncher.d.ts +0 -5
  5. package/dist/codieLauncher.js +1 -22
  6. package/dist/codieRuntime.d.ts +1 -11
  7. package/dist/codieRuntime.js +3 -20
  8. package/dist/codieRuntimeInstall.d.ts +11 -12
  9. package/dist/codieRuntimeInstall.js +1 -3
  10. package/dist/commands/doctor.js +0 -8
  11. package/dist/connect.js +3 -52
  12. package/dist/hook.js +44 -215
  13. package/dist/limitEvent.d.ts +4 -1
  14. package/dist/limitEvent.js +54 -0
  15. package/dist/limitEventReporter.d.ts +21 -0
  16. package/dist/limitEventReporter.js +91 -0
  17. package/dist/repoResolver.js +4 -10
  18. package/dist/upgradeNudge.d.ts +0 -1
  19. package/dist/upgradeNudge.js +0 -1
  20. package/dist/utils.d.ts +5 -7
  21. package/dist/utils.js +48 -153
  22. package/package.json +3 -11
  23. package/dist/codieStartup.d.ts +0 -24
  24. package/dist/codieStartup.js +0 -29
  25. package/dist/commands/security.d.ts +0 -1
  26. package/dist/commands/security.js +0 -156
  27. package/dist/security.d.ts +0 -97
  28. package/dist/security.js +0 -422
  29. package/dist/securityDeepScan.d.ts +0 -41
  30. package/dist/securityDeepScan.js +0 -248
  31. package/dist/securityDoctor.d.ts +0 -32
  32. package/dist/securityDoctor.js +0 -63
  33. package/dist/securityFeature.d.ts +0 -5
  34. package/dist/securityFeature.js +0 -7
  35. package/dist/securityInstall.d.ts +0 -62
  36. package/dist/securityInstall.js +0 -227
  37. package/dist/securityInventory.d.ts +0 -54
  38. package/dist/securityInventory.js +0 -556
  39. package/dist/securityPilot.d.ts +0 -19
  40. package/dist/securityPilot.js +0 -61
  41. package/dist/securityPolicy.d.ts +0 -32
  42. package/dist/securityPolicy.js +0 -179
  43. package/dist/securitySession.d.ts +0 -6
  44. package/dist/securitySession.js +0 -72
package/dist/hook.js CHANGED
@@ -42,14 +42,9 @@ const https = __importStar(require("https"));
42
42
  const http = __importStar(require("http"));
43
43
  const child_process_1 = require("child_process");
44
44
  const utils_1 = require("./utils");
45
- const limitEvent_1 = require("./limitEvent");
45
+ const limitEventReporter_1 = require("./limitEventReporter");
46
46
  const version_1 = require("./version");
47
47
  const captureLog_1 = require("./captureLog");
48
- const security_1 = require("./security");
49
- const securitySession_1 = require("./securitySession");
50
- const securityPolicy_1 = require("./securityPolicy");
51
- const securityFeature_1 = require("./securityFeature");
52
- const securityInventory_1 = require("./securityInventory");
53
48
  const captureExclusions_1 = require("./captureExclusions");
54
49
  // Pure builder for the discovery additionalContext. Returns null when there are
55
50
  // no matches. The suggested `codiedev pull` command carries the EXACT top key
@@ -98,43 +93,56 @@ async function runDiscover(prompt) {
98
93
  }
99
94
  process.exit(0);
100
95
  }
96
+ function readTranscriptTailFile(transcriptPath, maxBytes = 512 * 1024) {
97
+ const size = fs.statSync(transcriptPath).size;
98
+ if (size <= maxBytes)
99
+ return fs.readFileSync(transcriptPath, "utf8");
100
+ const handle = fs.openSync(transcriptPath, "r");
101
+ try {
102
+ const buffer = Buffer.allocUnsafe(maxBytes);
103
+ const bytesRead = fs.readSync(handle, buffer, 0, maxBytes, size - maxBytes);
104
+ return buffer.subarray(0, bytesRead).toString("utf8");
105
+ }
106
+ finally {
107
+ fs.closeSync(handle);
108
+ }
109
+ }
101
110
  // StopFailure (matcher rate_limit): the dev just got blocked. Read the local
102
111
  // transcript tail for the ground-truth rate_limit line and POST a tiny event.
103
112
  // Best-effort + silent — StopFailure output is ignored by Claude Code anyway.
104
- async function runStopFailure(hookInput) {
113
+ async function readAndPostLimitEvent(hookInput, source, emitMinimalOnMiss) {
105
114
  const config = (0, utils_1.readConfig)();
106
115
  if (!config)
107
116
  process.exit(0);
108
- let event = null;
109
- let transcriptTail;
117
+ let transcript = "";
110
118
  const tp = hookInput.transcript_path;
111
119
  if (tp) {
112
120
  try {
113
- const transcript = fs.readFileSync(tp, "utf8");
114
- event = (0, limitEvent_1.parseLimitEventFromTranscript)(transcript);
115
- // Ship the tail with the event so the server can summarize "what they
116
- // were working on" right now — no waiting for the SessionEnd capture.
117
- transcriptTail = (0, limitEvent_1.tailForLimitEvent)(transcript) || undefined;
121
+ transcript = readTranscriptTailFile(tp);
118
122
  }
119
123
  catch {
120
124
  /* unreadable transcript — fall through to minimal event */
121
125
  }
122
126
  }
123
- // The hook is registered with matcher "rate_limit", so a miss still means a
124
- // real limit — emit a minimal event rather than dropping it.
125
- const payload = event ?? { kind: "other", at: Date.now(), resetText: null };
126
127
  try {
127
- await postJson(`${config.backendUrl}/api/cli/limitEvent`, {
128
- kind: payload.kind,
129
- at: payload.at,
130
- resetText: payload.resetText,
128
+ await (0, limitEventReporter_1.reportTranscriptLimitEvent)({
129
+ config,
130
+ source,
131
131
  sessionId: hookInput.session_id,
132
- transcriptTail,
133
- }, config.token);
132
+ transcript,
133
+ emitMinimalOnMiss,
134
+ });
134
135
  }
135
136
  catch {
136
137
  /* best-effort */
137
138
  }
139
+ }
140
+ async function runStopFailure(hookInput) {
141
+ await readAndPostLimitEvent(hookInput, "claude-code", true);
142
+ process.exit(0);
143
+ }
144
+ async function runCodexLimit(hookInput) {
145
+ await readAndPostLimitEvent(hookInput, "codex", false);
138
146
  process.exit(0);
139
147
  }
140
148
  // Skip-reason ping: this session is ending WITHOUT a capture, and here's why.
@@ -223,7 +231,7 @@ timeoutMs) {
223
231
  // postJson variant that returns the parsed response body — the upload-URL
224
232
  // handshake needs the { uploadUrl } the server hands back. No timeout: runs
225
233
  // only in detached uploader children, never on the shutdown clock.
226
- function postJsonForResponse(url, body, bearerToken, timeoutMs) {
234
+ function postJsonForResponse(url, body, bearerToken) {
227
235
  return new Promise((resolve, reject) => {
228
236
  const parsed = new URL(url);
229
237
  const data = JSON.stringify(body);
@@ -257,139 +265,10 @@ function postJsonForResponse(url, body, bearerToken, timeoutMs) {
257
265
  });
258
266
  });
259
267
  req.on("error", reject);
260
- if (timeoutMs) {
261
- req.setTimeout(timeoutMs, () => {
262
- req.destroy(new Error(`timeout after ${timeoutMs}ms`));
263
- });
264
- }
265
268
  req.write(data);
266
269
  req.end();
267
270
  });
268
271
  }
269
- // Local policy runs synchronously before a tool executes. Workflow telemetry
270
- // is dispatched separately and fail-open, so a detector outage or timeout can
271
- // never delay or override the deterministic endpoint decision.
272
- async function runSecurityHook(hookInput, phase, source, uploadTelemetry = true) {
273
- const config = (0, utils_1.readConfig)();
274
- if (!config || !(0, securityFeature_1.isAgentSecurityEnabled)(config))
275
- return;
276
- const policy = (0, securityPolicy_1.readEndpointSecurityPolicy)(undefined, undefined, config.companyId);
277
- const sessionId = hookInput.session_id ?? hookInput.sessionId;
278
- const sessionSignal = phase === "pre" ? (0, securitySession_1.readSessionSecuritySignal)(sessionId) : undefined;
279
- const localDecision = (0, security_1.evaluateToolSecurity)(hookInput, phase, policy, source, sessionSignal);
280
- if (phase === "post" && (0, security_1.securityInputHasSensitivePath)(hookInput)) {
281
- (0, securitySession_1.recordSensitiveReadSignal)(sessionId);
282
- }
283
- const event = (0, security_1.buildSecurityWorkflowEvent)(hookInput, phase, source);
284
- if (event) {
285
- event.localDecision = localDecision.action;
286
- event.localCategory = localDecision.category;
287
- }
288
- if (localDecision.action === "block" && phase === "pre") {
289
- (0, captureLog_1.appendCaptureLog)({
290
- stage: "security",
291
- sessionId: hookInput.session_id ?? hookInput.sessionId ?? "unknown",
292
- outcome: "ok",
293
- detail: `phase=${phase} localDecision=block category=${localDecision.category}`,
294
- });
295
- if (uploadTelemetry)
296
- dispatchSecurityEventUpload(event);
297
- process.stdout.write(JSON.stringify({
298
- hookSpecificOutput: {
299
- hookEventName: "PreToolUse",
300
- permissionDecision: "deny",
301
- permissionDecisionReason: localDecision.reason,
302
- },
303
- }));
304
- return;
305
- }
306
- if (!event)
307
- return;
308
- if (uploadTelemetry)
309
- dispatchSecurityEventUpload(event);
310
- }
311
- function runPromptSecurityHook(hookInput, source, uploadTelemetry = true) {
312
- const config = (0, utils_1.readConfig)();
313
- if (!config || !(0, securityFeature_1.isAgentSecurityEnabled)(config))
314
- return false;
315
- const decision = (0, security_1.evaluatePromptSecurity)(hookInput.prompt, (0, securityPolicy_1.readEndpointSecurityPolicy)(undefined, undefined, config.companyId), source);
316
- if (decision.action !== "block")
317
- return false;
318
- (0, captureLog_1.appendCaptureLog)({
319
- stage: "security",
320
- sessionId: hookInput.session_id ?? hookInput.sessionId ?? "unknown",
321
- outcome: "ok",
322
- detail: `phase=prompt localDecision=block category=${decision.category} telemetry=${uploadTelemetry ? "enabled" : "suppressed"} cwd=${hookInput.cwd ?? "unknown"}`,
323
- });
324
- if (uploadTelemetry) {
325
- dispatchSecurityEventUpload((0, security_1.buildBlockedSecurityPromptEvent)(hookInput, source, decision.category ?? "secret_in_prompt"));
326
- }
327
- process.stdout.write(JSON.stringify({
328
- decision: "block",
329
- reason: decision.reason,
330
- }));
331
- return true;
332
- }
333
- function dispatchAllowedPromptSecurityEvent(hookInput, source) {
334
- const config = (0, utils_1.readConfig)();
335
- if (!config || !(0, securityFeature_1.isAgentSecurityEnabled)(config))
336
- return;
337
- const event = (0, security_1.buildSecurityPromptEvent)(hookInput, source, Date.now, (0, securityPolicy_1.readEndpointSecurityPolicy)(undefined, undefined, config.companyId));
338
- if (event)
339
- dispatchSecurityEventUpload(event);
340
- }
341
- function dispatchSecurityEventUpload(event) {
342
- if (!event || !(0, securityFeature_1.isAgentSecurityEnabled)((0, utils_1.readConfig)()))
343
- return;
344
- try {
345
- const child = (0, child_process_1.spawn)(process.execPath, [process.argv[1], "security-upload"], {
346
- detached: true,
347
- stdio: ["pipe", "ignore", "ignore"],
348
- });
349
- child.stdin?.end(JSON.stringify(event));
350
- child.unref();
351
- }
352
- catch {
353
- // Telemetry is best-effort and must never delay local enforcement.
354
- }
355
- }
356
- async function runSecurityEventUpload() {
357
- const config = (0, utils_1.readConfig)();
358
- if (!config || !(0, securityFeature_1.isAgentSecurityEnabled)(config))
359
- return;
360
- const startedAt = Date.now();
361
- try {
362
- const raw = await readStdin();
363
- const event = JSON.parse(raw);
364
- const eventWithEndpoint = (0, security_1.withEndpointIdentity)(event, {
365
- endpointId: config.deviceId,
366
- endpointName: config.deviceName,
367
- cliVersion: version_1.CLI_VERSION,
368
- });
369
- const result = await postJsonForResponse(`${config.backendUrl}/api/cli/securityEvent`, { ...eventWithEndpoint }, config.token,
370
- // Leave room for a cold backend and durable scheduling. This uploader is
371
- // detached, so the network budget never delays the developer's prompt or
372
- // tool call. Policy refreshes use the separately signed sync endpoint.
373
- 5_000);
374
- if (result?.policy?.schemaVersion === 1) {
375
- (0, securityPolicy_1.writeEndpointSecurityPolicy)(result.policy, undefined, undefined, config.companyId);
376
- }
377
- (0, captureLog_1.appendCaptureLog)({
378
- stage: "security",
379
- sessionId: event.sessionId,
380
- outcome: "ok",
381
- detail: `phase=${event.phase} elapsedMs=${Date.now() - startedAt} verdict=${String(result?.verdict ?? "unknown")}`,
382
- });
383
- }
384
- catch (err) {
385
- (0, captureLog_1.appendCaptureLog)({
386
- stage: "security",
387
- sessionId: "upload",
388
- outcome: "ERROR",
389
- detail: `elapsedMs=${Date.now() - startedAt} ${err.message}`,
390
- });
391
- }
392
- }
393
272
  // Raw-bytes POST to a Convex storage upload URL (no auth header — the URL
394
273
  // itself is the one-time credential). Returns the parsed { storageId }.
395
274
  function postBytes(url, bytes, contentType) {
@@ -513,20 +392,6 @@ async function main() {
513
392
  }
514
393
  process.exit(0);
515
394
  }
516
- // Daily component drift scan runs in a detached hook process. The parent
517
- // SessionStart path only performs a timestamp read, so agent startup never
518
- // waits for filesystem traversal or an inventory upload.
519
- if (process.argv[2] === "security-inventory-upload") {
520
- const config = (0, utils_1.readConfig)();
521
- if (config && (0, securityFeature_1.isAgentSecurityEnabled)(config)) {
522
- const inventory = (0, securityInventory_1.buildSecurityInventory)({ workspace: process.argv[3] || process.cwd() });
523
- try {
524
- await (0, securityInventory_1.uploadSecurityInventory)(inventory, config);
525
- }
526
- catch { /* retry after the daily inventory window */ }
527
- }
528
- process.exit(0);
529
- }
530
395
  // Detached backup uploader child (spawned by runBackup below). The job file
531
396
  // carries only { root, files }; the child re-reads config from disk. No stdin
532
397
  // needed, so handle it before readStdin() like the capture uploader above.
@@ -563,13 +428,6 @@ async function main() {
563
428
  }
564
429
  process.exit(0);
565
430
  }
566
- // Detached, best-effort security telemetry. Local policy decisions have
567
- // already completed in the parent hook; this child can never delay or alter
568
- // the developer's action.
569
- if (process.argv[2] === "security-upload") {
570
- await runSecurityEventUpload();
571
- process.exit(0);
572
- }
573
431
  let hookInput = {};
574
432
  try {
575
433
  const raw = await readStdin();
@@ -580,60 +438,29 @@ async function main() {
580
438
  catch {
581
439
  process.exit(0);
582
440
  }
583
- // Privacy exclusions suppress every Codiedev upload, but they must never
584
- // disable local security enforcement. Security hooks run first with telemetry
585
- // disabled; all other hook modes still exit before making a network request.
586
- const captureIgnored = (0, captureExclusions_1.shouldIgnoreCaptureInput)((0, utils_1.readConfig)(), hookInput);
587
- const securityCommand = process.argv[2] ?? "";
588
- const securityMatch = securityCommand.match(/^security-(pre|post|failure)(?:-(codex|codie))?$/);
589
- if (securityMatch) {
590
- const source = securityMatch[2] === "codex"
591
- ? "codex"
592
- : securityMatch[2] === "codie"
593
- ? "codie-harness"
594
- : "claude-code";
595
- await runSecurityHook(hookInput, securityMatch[1], source, !captureIgnored);
441
+ // Local privacy boundary: every stdin-driven hook mode passes through this
442
+ // check BEFORE prompt discovery, limit-event tails, SessionStart pings,
443
+ // backup, self-heal, transcript reads, or detached dispatch. Ignored means
444
+ // no Codiedev request at all for this session.
445
+ if ((0, captureExclusions_1.shouldIgnoreCaptureInput)((0, utils_1.readConfig)(), hookInput)) {
596
446
  process.exit(0);
597
447
  }
598
448
  // UserPromptSubmit discovery path — separate from session capture.
599
- const promptCommand = process.argv[2] ?? "";
600
- if (process.argv[2] === "discover" ||
601
- process.argv[2] === "discover-codex" ||
602
- process.argv[2] === "discover-codie") {
603
- const source = promptCommand === "discover-codex"
604
- ? "codex"
605
- : promptCommand === "discover-codie"
606
- ? "codie-harness"
607
- : "claude-code";
608
- // Security runs first and locally. A blocked prompt is erased before it
609
- // reaches the model, so discovery must not send it to the backend.
610
- if (runPromptSecurityHook(hookInput, source, !captureIgnored))
611
- return;
612
- if (captureIgnored)
613
- return;
614
- dispatchAllowedPromptSecurityEvent(hookInput, source);
449
+ if (process.argv[2] === "discover") {
615
450
  await runDiscover(hookInput.prompt);
616
451
  return;
617
452
  }
618
- if (captureIgnored)
619
- process.exit(0);
620
453
  // StopFailure (matcher rate_limit): post the rate-limit event.
621
454
  if (process.argv[2] === "limit") {
622
455
  await runStopFailure(hookInput);
623
456
  return;
624
457
  }
458
+ if (process.argv[2] === "limit-codex") {
459
+ await runCodexLimit(hookInput);
460
+ return;
461
+ }
625
462
  // SessionStart: post the capture-coverage denominator ping. Tiny + fast.
626
463
  if (process.argv[2] === "session-start" || process.argv[2] === "session-start-opencode") {
627
- if ((0, securityFeature_1.isAgentSecurityEnabled)((0, utils_1.readConfig)()) && (0, securityInventory_1.securityInventoryDue)()) {
628
- try {
629
- const child = (0, child_process_1.spawn)(process.execPath, [process.argv[1], "security-inventory-upload", hookInput.cwd || process.cwd()], {
630
- detached: true,
631
- stdio: "ignore",
632
- });
633
- child.unref();
634
- }
635
- catch { /* daily inventory is best-effort */ }
636
- }
637
464
  await runSessionStart(hookInput, process.argv[2] === "session-start-opencode" ? "opencode" : "claude-code");
638
465
  return;
639
466
  }
@@ -643,6 +470,8 @@ async function main() {
643
470
  // pinging (the Sonifi "starts arrive, captures never" loss) — re-wired on
644
471
  // their next Stop/SessionEnd. One read + one write, only when missing.
645
472
  (0, utils_1.ensureAllCodiedevHooksInstalled)();
473
+ if (process.argv[2] === "capture-codex")
474
+ (0, utils_1.ensureCodexLimitHookInstalled)();
646
475
  const { session_id, transcript_path, cwd, workspace_roots } = hookInput;
647
476
  const workspaceRoot = workspace_roots && workspace_roots.length > 0
648
477
  ? workspace_roots[0]
@@ -1,9 +1,12 @@
1
- export type LimitKind = "session" | "org_spend" | "other";
1
+ export type LimitKind = "session" | "user_spend" | "org_spend" | "other";
2
2
  export interface LimitEvent {
3
3
  kind: LimitKind;
4
4
  at: number;
5
5
  resetText: string | null;
6
+ reason?: string;
6
7
  }
7
8
  export declare function classifyLimitKind(text: string): LimitKind;
8
9
  export declare function parseLimitEventFromTranscript(transcript: string): LimitEvent | null;
10
+ /** Parse the structured rate-limit snapshot written to Codex rollout JSONL. */
11
+ export declare function parseCodexLimitEventFromTranscript(transcript: string): LimitEvent | null;
9
12
  export declare function tailForLimitEvent(transcript: string, maxChars?: number): string;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.classifyLimitKind = classifyLimitKind;
4
4
  exports.parseLimitEventFromTranscript = parseLimitEventFromTranscript;
5
+ exports.parseCodexLimitEventFromTranscript = parseCodexLimitEventFromTranscript;
5
6
  exports.tailForLimitEvent = tailForLimitEvent;
6
7
  function classifyLimitKind(text) {
7
8
  const t = text.toLowerCase();
@@ -50,6 +51,59 @@ function parseLimitEventFromTranscript(transcript) {
50
51
  }
51
52
  return found;
52
53
  }
54
+ function codexLimitKind(reason, spendControlReached) {
55
+ if (spendControlReached || reason.startsWith("workspace_owner_"))
56
+ return "org_spend";
57
+ if (reason.startsWith("workspace_member_"))
58
+ return "user_spend";
59
+ return reason === "rate_limit_reached" ? "session" : "other";
60
+ }
61
+ function codexResetText(rateLimits) {
62
+ const windows = [rateLimits?.primary, rateLimits?.secondary]
63
+ .filter((window) => window && typeof window === "object");
64
+ const reached = windows.filter((window) => Number(window.used_percent) >= 100);
65
+ const candidates = reached.length > 0 ? reached : windows;
66
+ const resetAt = candidates
67
+ .map((window) => Number(window.resets_at))
68
+ .filter((value) => Number.isFinite(value) && value > 0)
69
+ .sort((a, b) => a - b)[0];
70
+ return resetAt ? new Date(resetAt * 1000).toISOString() : null;
71
+ }
72
+ /** Parse the structured rate-limit snapshot written to Codex rollout JSONL. */
73
+ function parseCodexLimitEventFromTranscript(transcript) {
74
+ let found = null;
75
+ for (const line of transcript.split("\n")) {
76
+ if (!line.includes('"rate_limit_reached_type"') && !line.includes('"spend_control_reached"'))
77
+ continue;
78
+ let entry;
79
+ try {
80
+ entry = JSON.parse(line);
81
+ }
82
+ catch {
83
+ continue;
84
+ }
85
+ if (entry?.type !== "event_msg" || entry?.payload?.type !== "token_count")
86
+ continue;
87
+ const rateLimits = entry.payload.rate_limits;
88
+ if (!rateLimits || typeof rateLimits !== "object")
89
+ continue;
90
+ const reachedType = typeof rateLimits.rate_limit_reached_type === "string"
91
+ ? rateLimits.rate_limit_reached_type
92
+ : null;
93
+ const spendControlReached = rateLimits.spend_control_reached === true;
94
+ if (!reachedType && !spendControlReached)
95
+ continue;
96
+ const reason = reachedType ?? "spend_control_reached";
97
+ const at = typeof entry.timestamp === "string" ? Date.parse(entry.timestamp) : NaN;
98
+ found = {
99
+ kind: codexLimitKind(reason, spendControlReached),
100
+ at: Number.isNaN(at) ? Date.now() : at,
101
+ resetText: codexResetText(rateLimits),
102
+ reason,
103
+ };
104
+ }
105
+ return found;
106
+ }
53
107
  // Tail of the raw transcript to ship with the limit event so the server can
54
108
  // summarize "what they were working on" immediately — without waiting for the
55
109
  // SessionEnd capture (which can lag hours, or drop entirely on Windows).
@@ -0,0 +1,21 @@
1
+ import { type LimitEvent } from "./limitEvent";
2
+ export type LimitEventSource = "claude-code" | "codex";
3
+ export type LimitEventPayload = LimitEvent & {
4
+ source: LimitEventSource;
5
+ sessionId?: string;
6
+ transcriptTail?: string;
7
+ };
8
+ export declare function postLimitEvent(config: {
9
+ backendUrl: string;
10
+ token: string;
11
+ }, payload: LimitEventPayload, timeoutMs?: number): Promise<void>;
12
+ export declare function reportTranscriptLimitEvent(args: {
13
+ config: {
14
+ backendUrl: string;
15
+ token: string;
16
+ };
17
+ source: LimitEventSource;
18
+ sessionId?: string;
19
+ transcript: string;
20
+ emitMinimalOnMiss?: boolean;
21
+ }): Promise<boolean>;
@@ -0,0 +1,91 @@
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.postLimitEvent = postLimitEvent;
37
+ exports.reportTranscriptLimitEvent = reportTranscriptLimitEvent;
38
+ const http = __importStar(require("node:http"));
39
+ const https = __importStar(require("node:https"));
40
+ const version_1 = require("./version");
41
+ const limitEvent_1 = require("./limitEvent");
42
+ function postLimitEvent(config, payload, timeoutMs = 5_000) {
43
+ return new Promise((resolve, reject) => {
44
+ const target = new URL("/api/cli/limitEvent", config.backendUrl);
45
+ const data = JSON.stringify(payload);
46
+ const transport = target.protocol === "https:" ? https : http;
47
+ const request = transport.request({
48
+ hostname: target.hostname,
49
+ port: target.port || (target.protocol === "https:" ? 443 : 80),
50
+ path: target.pathname + target.search,
51
+ method: "POST",
52
+ headers: {
53
+ "Content-Type": "application/json",
54
+ "Content-Length": Buffer.byteLength(data),
55
+ Authorization: `Bearer ${config.token}`,
56
+ "X-Codiedev-Cli-Version": version_1.CLI_VERSION,
57
+ },
58
+ }, (response) => {
59
+ let responseBody = "";
60
+ response.on("data", (chunk) => { responseBody += chunk; });
61
+ response.on("end", () => {
62
+ if (response.statusCode && response.statusCode >= 200 && response.statusCode < 300) {
63
+ resolve();
64
+ }
65
+ else {
66
+ reject(new Error(`HTTP ${response.statusCode}: ${responseBody}`));
67
+ }
68
+ });
69
+ });
70
+ request.on("error", reject);
71
+ request.setTimeout(timeoutMs, () => request.destroy(new Error(`timeout after ${timeoutMs}ms`)));
72
+ request.write(data);
73
+ request.end();
74
+ });
75
+ }
76
+ async function reportTranscriptLimitEvent(args) {
77
+ const parse = args.source === "codex"
78
+ ? limitEvent_1.parseCodexLimitEventFromTranscript
79
+ : limitEvent_1.parseLimitEventFromTranscript;
80
+ const parsed = parse(args.transcript);
81
+ if (!parsed && !args.emitMinimalOnMiss)
82
+ return false;
83
+ const event = parsed ?? { kind: "other", at: Date.now(), resetText: null };
84
+ await postLimitEvent(args.config, {
85
+ source: args.source,
86
+ ...event,
87
+ sessionId: args.sessionId,
88
+ transcriptTail: args.transcript ? (0, limitEvent_1.tailForLimitEvent)(args.transcript) : undefined,
89
+ });
90
+ return true;
91
+ }
@@ -13,20 +13,14 @@ function parseRemoteUrl(url) {
13
13
  let s = url.trim();
14
14
  if (s.endsWith(".git"))
15
15
  s = s.slice(0, -4);
16
+ const ssh = s.match(/^git@([^:]+):(.+)$/);
17
+ if (ssh) {
18
+ return { host: ssh[1].toLowerCase(), ownerName: ssh[2].toLowerCase() };
19
+ }
16
20
  const https = s.match(/^https?:\/\/([^/]+)\/(.+)$/);
17
21
  if (https) {
18
22
  return { host: https[1].toLowerCase(), ownerName: https[2].toLowerCase() };
19
23
  }
20
- // Codie strips SSH userinfo before putting a remote in the gateway header,
21
- // so `git@gitlab.com:owner/repo.git` arrives here as
22
- // `gitlab.com:owner/repo.git`. Accept both forms; otherwise every SSH-cloned
23
- // Harness checkout loses its repoId even though transcript capture can
24
- // resolve the same raw remote correctly. Check this after URL forms so the
25
- // colon in `https://` is never mistaken for an SCP separator.
26
- const scp = s.match(/^(?:[^@\s]+@)?([^:/\s]+):(.+)$/);
27
- if (scp) {
28
- return { host: scp[1].toLowerCase(), ownerName: scp[2].toLowerCase() };
29
- }
30
24
  return null;
31
25
  }
32
26
  function resolveRepoForCapture(input) {
@@ -13,7 +13,6 @@ export interface OfferOpts {
13
13
  log?: (line: string) => void;
14
14
  isTty?: boolean;
15
15
  }
16
- export declare function promptYesNo(question: string): Promise<boolean>;
17
16
  /**
18
17
  * Prompt-capable sibling of maybePrintUpgradeNudge. Call this before handing
19
18
  * the terminal to a TUI. A successful upgrade relaunches the fresh CLI and
@@ -43,7 +43,6 @@ var __importStar = (this && this.__importStar) || (function () {
43
43
  })();
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.maybePrintUpgradeNudge = maybePrintUpgradeNudge;
46
- exports.promptYesNo = promptYesNo;
47
46
  exports.maybeOfferUpgrade = maybeOfferUpgrade;
48
47
  const fs = __importStar(require("fs"));
49
48
  const path = __importStar(require("path"));
package/dist/utils.d.ts CHANGED
@@ -14,7 +14,6 @@ export interface CodiedevConfig {
14
14
  }>;
15
15
  lastSynced: string;
16
16
  backendUrl: string;
17
- agentSecurityEnabled?: boolean;
18
17
  deviceId?: string;
19
18
  deviceName?: string;
20
19
  captureIgnoredPaths?: string[];
@@ -97,7 +96,7 @@ export declare function openCodeInstalled(): boolean;
97
96
  /** Install a dormant global OpenCode adapter. Installing it unconditionally
98
97
  * keeps `codiedev connect` as the only setup step even when Codie/OpenCode is
99
98
  * installed later. OpenCode loads this file from its documented plugin dir. */
100
- export declare function installOpenCodeCapturePlugin(agentSecurityEnabled?: boolean): void;
99
+ export declare function installOpenCodeCapturePlugin(): void;
101
100
  /**
102
101
  * VS Code with the GitHub Copilot Chat extension installed. The extension
103
102
  * directory pattern is `~/.vscode/extensions/github.copilot-chat-*`. We treat
@@ -105,13 +104,11 @@ export declare function installOpenCodeCapturePlugin(agentSecurityEnabled?: bool
105
104
  */
106
105
  export declare function vscodeCopilotInstalled(): boolean;
107
106
  export declare function upsertCodiedevHook(hooks: Record<string, unknown>, eventName: string, subcommand: string, matcher?: string): void;
108
- /** Remove Codiedev's entries for one event while preserving foreign hooks. */
109
- export declare function removeCodiedevHook(hooks: Record<string, unknown>, eventName: string): boolean;
110
- export declare function installHook(agentSecurityEnabled?: boolean): void;
107
+ export declare function installHook(): void;
111
108
  export declare function ensureDiscoveryHookInstalled(): void;
112
109
  export declare function ensureBackupHookInstalled(): void;
113
110
  export declare function ensureLimitHookInstalled(): void;
114
- export declare function ensureAllCodiedevHooksInstalled(agentSecurityEnabled?: boolean): void;
111
+ export declare function ensureAllCodiedevHooksInstalled(): void;
115
112
  export declare function ensureSessionStartHookInstalled(): void;
116
113
  export declare function installClaudeCodeInstructions(): void;
117
114
  export declare function installCodexInstructions(): void;
@@ -169,7 +166,8 @@ export declare function cleanupCodexMcp(): void;
169
166
  * so we append a well-marked block if one isn't already present. Idempotent.
170
167
  */
171
168
  export declare function installCodexMcp(): boolean;
172
- export declare function installCodexHook(agentSecurityEnabled?: boolean): void;
169
+ export declare function installCodexHook(): void;
170
+ export declare function ensureCodexLimitHookInstalled(): void;
173
171
  export declare function installCursorHook(): void;
174
172
  export interface ParsedStats {
175
173
  messageCount: number;