codex-multi-auth 0.1.0

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 (327) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +162 -0
  3. package/assets/opencode-logo-ornate-dark.svg +18 -0
  4. package/assets/readme-hero.svg +31 -0
  5. package/config/README.md +87 -0
  6. package/config/minimal-opencode.json +13 -0
  7. package/config/opencode-legacy.json +571 -0
  8. package/config/opencode-modern.json +239 -0
  9. package/dist/index.d.ts +45 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +3160 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/lib/accounts/rate-limits.d.ts +22 -0
  14. package/dist/lib/accounts/rate-limits.d.ts.map +1 -0
  15. package/dist/lib/accounts/rate-limits.js +63 -0
  16. package/dist/lib/accounts/rate-limits.js.map +1 -0
  17. package/dist/lib/accounts.d.ts +95 -0
  18. package/dist/lib/accounts.d.ts.map +1 -0
  19. package/dist/lib/accounts.js +668 -0
  20. package/dist/lib/accounts.js.map +1 -0
  21. package/dist/lib/audit.d.ts +45 -0
  22. package/dist/lib/audit.d.ts.map +1 -0
  23. package/dist/lib/audit.js +131 -0
  24. package/dist/lib/audit.js.map +1 -0
  25. package/dist/lib/auth/auth.d.ts +56 -0
  26. package/dist/lib/auth/auth.d.ts.map +1 -0
  27. package/dist/lib/auth/auth.js +214 -0
  28. package/dist/lib/auth/auth.js.map +1 -0
  29. package/dist/lib/auth/browser.d.ts +34 -0
  30. package/dist/lib/auth/browser.d.ts.map +1 -0
  31. package/dist/lib/auth/browser.js +185 -0
  32. package/dist/lib/auth/browser.js.map +1 -0
  33. package/dist/lib/auth/server.d.ts +24 -0
  34. package/dist/lib/auth/server.d.ts.map +1 -0
  35. package/dist/lib/auth/server.js +116 -0
  36. package/dist/lib/auth/server.js.map +1 -0
  37. package/dist/lib/auth/token-utils.d.ts +59 -0
  38. package/dist/lib/auth/token-utils.d.ts.map +1 -0
  39. package/dist/lib/auth/token-utils.js +331 -0
  40. package/dist/lib/auth/token-utils.js.map +1 -0
  41. package/dist/lib/auth-rate-limit.d.ts +20 -0
  42. package/dist/lib/auth-rate-limit.d.ts.map +1 -0
  43. package/dist/lib/auth-rate-limit.js +91 -0
  44. package/dist/lib/auth-rate-limit.js.map +1 -0
  45. package/dist/lib/auto-update-checker.d.ts +10 -0
  46. package/dist/lib/auto-update-checker.d.ts.map +1 -0
  47. package/dist/lib/auto-update-checker.js +216 -0
  48. package/dist/lib/auto-update-checker.js.map +1 -0
  49. package/dist/lib/capability-policy.d.ts +18 -0
  50. package/dist/lib/capability-policy.d.ts.map +1 -0
  51. package/dist/lib/capability-policy.js +150 -0
  52. package/dist/lib/capability-policy.js.map +1 -0
  53. package/dist/lib/circuit-breaker.d.ts +34 -0
  54. package/dist/lib/circuit-breaker.d.ts.map +1 -0
  55. package/dist/lib/circuit-breaker.js +124 -0
  56. package/dist/lib/circuit-breaker.js.map +1 -0
  57. package/dist/lib/cli.d.ts +64 -0
  58. package/dist/lib/cli.d.ts.map +1 -0
  59. package/dist/lib/cli.js +274 -0
  60. package/dist/lib/cli.js.map +1 -0
  61. package/dist/lib/codex-cli/observability.d.ts +22 -0
  62. package/dist/lib/codex-cli/observability.d.ts.map +1 -0
  63. package/dist/lib/codex-cli/observability.js +36 -0
  64. package/dist/lib/codex-cli/observability.js.map +1 -0
  65. package/dist/lib/codex-cli/state.d.ts +86 -0
  66. package/dist/lib/codex-cli/state.d.ts.map +1 -0
  67. package/dist/lib/codex-cli/state.js +470 -0
  68. package/dist/lib/codex-cli/state.js.map +1 -0
  69. package/dist/lib/codex-cli/sync.d.ts +27 -0
  70. package/dist/lib/codex-cli/sync.d.ts.map +1 -0
  71. package/dist/lib/codex-cli/sync.js +325 -0
  72. package/dist/lib/codex-cli/sync.js.map +1 -0
  73. package/dist/lib/codex-cli/writer.d.ts +12 -0
  74. package/dist/lib/codex-cli/writer.d.ts.map +1 -0
  75. package/dist/lib/codex-cli/writer.js +388 -0
  76. package/dist/lib/codex-cli/writer.js.map +1 -0
  77. package/dist/lib/codex-manager.d.ts +2 -0
  78. package/dist/lib/codex-manager.d.ts.map +1 -0
  79. package/dist/lib/codex-manager.js +4841 -0
  80. package/dist/lib/codex-manager.js.map +1 -0
  81. package/dist/lib/config.d.ts +269 -0
  82. package/dist/lib/config.d.ts.map +1 -0
  83. package/dist/lib/config.js +789 -0
  84. package/dist/lib/config.js.map +1 -0
  85. package/dist/lib/constants.d.ts +78 -0
  86. package/dist/lib/constants.d.ts.map +1 -0
  87. package/dist/lib/constants.js +78 -0
  88. package/dist/lib/constants.js.map +1 -0
  89. package/dist/lib/context-overflow.d.ts +27 -0
  90. package/dist/lib/context-overflow.d.ts.map +1 -0
  91. package/dist/lib/context-overflow.js +124 -0
  92. package/dist/lib/context-overflow.js.map +1 -0
  93. package/dist/lib/dashboard-settings.d.ts +90 -0
  94. package/dist/lib/dashboard-settings.d.ts.map +1 -0
  95. package/dist/lib/dashboard-settings.js +327 -0
  96. package/dist/lib/dashboard-settings.js.map +1 -0
  97. package/dist/lib/entitlement-cache.d.ts +41 -0
  98. package/dist/lib/entitlement-cache.d.ts.map +1 -0
  99. package/dist/lib/entitlement-cache.js +137 -0
  100. package/dist/lib/entitlement-cache.js.map +1 -0
  101. package/dist/lib/errors.d.ts +113 -0
  102. package/dist/lib/errors.d.ts.map +1 -0
  103. package/dist/lib/errors.js +103 -0
  104. package/dist/lib/errors.js.map +1 -0
  105. package/dist/lib/forecast.d.ts +42 -0
  106. package/dist/lib/forecast.d.ts.map +1 -0
  107. package/dist/lib/forecast.js +256 -0
  108. package/dist/lib/forecast.js.map +1 -0
  109. package/dist/lib/health.d.ts +33 -0
  110. package/dist/lib/health.d.ts.map +1 -0
  111. package/dist/lib/health.js +70 -0
  112. package/dist/lib/health.js.map +1 -0
  113. package/dist/lib/index.d.ts +32 -0
  114. package/dist/lib/index.d.ts.map +1 -0
  115. package/dist/lib/index.js +32 -0
  116. package/dist/lib/index.js.map +1 -0
  117. package/dist/lib/live-account-sync.d.ts +39 -0
  118. package/dist/lib/live-account-sync.d.ts.map +1 -0
  119. package/dist/lib/live-account-sync.js +196 -0
  120. package/dist/lib/live-account-sync.js.map +1 -0
  121. package/dist/lib/logger.d.ts +40 -0
  122. package/dist/lib/logger.d.ts.map +1 -0
  123. package/dist/lib/logger.js +364 -0
  124. package/dist/lib/logger.js.map +1 -0
  125. package/dist/lib/oauth-success.html +338 -0
  126. package/dist/lib/parallel-probe.d.ts +28 -0
  127. package/dist/lib/parallel-probe.d.ts.map +1 -0
  128. package/dist/lib/parallel-probe.js +97 -0
  129. package/dist/lib/parallel-probe.js.map +1 -0
  130. package/dist/lib/preemptive-quota-scheduler.d.ts +53 -0
  131. package/dist/lib/preemptive-quota-scheduler.d.ts.map +1 -0
  132. package/dist/lib/preemptive-quota-scheduler.js +220 -0
  133. package/dist/lib/preemptive-quota-scheduler.js.map +1 -0
  134. package/dist/lib/proactive-refresh.d.ts +66 -0
  135. package/dist/lib/proactive-refresh.d.ts.map +1 -0
  136. package/dist/lib/proactive-refresh.js +143 -0
  137. package/dist/lib/proactive-refresh.js.map +1 -0
  138. package/dist/lib/prompts/codex-opencode-bridge.d.ts +19 -0
  139. package/dist/lib/prompts/codex-opencode-bridge.d.ts.map +1 -0
  140. package/dist/lib/prompts/codex-opencode-bridge.js +169 -0
  141. package/dist/lib/prompts/codex-opencode-bridge.js.map +1 -0
  142. package/dist/lib/prompts/codex.d.ts +41 -0
  143. package/dist/lib/prompts/codex.d.ts.map +1 -0
  144. package/dist/lib/prompts/codex.js +383 -0
  145. package/dist/lib/prompts/codex.js.map +1 -0
  146. package/dist/lib/prompts/opencode-codex.d.ts +25 -0
  147. package/dist/lib/prompts/opencode-codex.d.ts.map +1 -0
  148. package/dist/lib/prompts/opencode-codex.js +270 -0
  149. package/dist/lib/prompts/opencode-codex.js.map +1 -0
  150. package/dist/lib/quota-cache.d.ts +68 -0
  151. package/dist/lib/quota-cache.d.ts.map +1 -0
  152. package/dist/lib/quota-cache.js +224 -0
  153. package/dist/lib/quota-cache.js.map +1 -0
  154. package/dist/lib/quota-probe.d.ts +49 -0
  155. package/dist/lib/quota-probe.d.ts.map +1 -0
  156. package/dist/lib/quota-probe.js +368 -0
  157. package/dist/lib/quota-probe.js.map +1 -0
  158. package/dist/lib/recovery/constants.d.ts +12 -0
  159. package/dist/lib/recovery/constants.d.ts.map +1 -0
  160. package/dist/lib/recovery/constants.js +31 -0
  161. package/dist/lib/recovery/constants.js.map +1 -0
  162. package/dist/lib/recovery/index.d.ts +12 -0
  163. package/dist/lib/recovery/index.d.ts.map +1 -0
  164. package/dist/lib/recovery/index.js +12 -0
  165. package/dist/lib/recovery/index.js.map +1 -0
  166. package/dist/lib/recovery/storage.d.ts +24 -0
  167. package/dist/lib/recovery/storage.d.ts.map +1 -0
  168. package/dist/lib/recovery/storage.js +362 -0
  169. package/dist/lib/recovery/storage.js.map +1 -0
  170. package/dist/lib/recovery/types.d.ts +116 -0
  171. package/dist/lib/recovery/types.d.ts.map +1 -0
  172. package/dist/lib/recovery/types.js +7 -0
  173. package/dist/lib/recovery/types.js.map +1 -0
  174. package/dist/lib/recovery.d.ts +31 -0
  175. package/dist/lib/recovery.d.ts.map +1 -0
  176. package/dist/lib/recovery.js +313 -0
  177. package/dist/lib/recovery.js.map +1 -0
  178. package/dist/lib/refresh-guardian.d.ts +31 -0
  179. package/dist/lib/refresh-guardian.d.ts.map +1 -0
  180. package/dist/lib/refresh-guardian.js +151 -0
  181. package/dist/lib/refresh-guardian.js.map +1 -0
  182. package/dist/lib/refresh-lease.d.ts +37 -0
  183. package/dist/lib/refresh-lease.d.ts.map +1 -0
  184. package/dist/lib/refresh-lease.js +335 -0
  185. package/dist/lib/refresh-lease.js.map +1 -0
  186. package/dist/lib/refresh-queue.d.ts +117 -0
  187. package/dist/lib/refresh-queue.d.ts.map +1 -0
  188. package/dist/lib/refresh-queue.js +297 -0
  189. package/dist/lib/refresh-queue.js.map +1 -0
  190. package/dist/lib/request/failure-policy.d.ts +42 -0
  191. package/dist/lib/request/failure-policy.d.ts.map +1 -0
  192. package/dist/lib/request/failure-policy.js +133 -0
  193. package/dist/lib/request/failure-policy.js.map +1 -0
  194. package/dist/lib/request/fetch-helpers.d.ts +152 -0
  195. package/dist/lib/request/fetch-helpers.d.ts.map +1 -0
  196. package/dist/lib/request/fetch-helpers.js +704 -0
  197. package/dist/lib/request/fetch-helpers.js.map +1 -0
  198. package/dist/lib/request/helpers/input-utils.d.ts +7 -0
  199. package/dist/lib/request/helpers/input-utils.d.ts.map +1 -0
  200. package/dist/lib/request/helpers/input-utils.js +214 -0
  201. package/dist/lib/request/helpers/input-utils.js.map +1 -0
  202. package/dist/lib/request/helpers/model-map.d.ts +28 -0
  203. package/dist/lib/request/helpers/model-map.d.ts.map +1 -0
  204. package/dist/lib/request/helpers/model-map.js +133 -0
  205. package/dist/lib/request/helpers/model-map.js.map +1 -0
  206. package/dist/lib/request/helpers/tool-utils.d.ts +29 -0
  207. package/dist/lib/request/helpers/tool-utils.d.ts.map +1 -0
  208. package/dist/lib/request/helpers/tool-utils.js +117 -0
  209. package/dist/lib/request/helpers/tool-utils.js.map +1 -0
  210. package/dist/lib/request/rate-limit-backoff.d.ts +17 -0
  211. package/dist/lib/request/rate-limit-backoff.d.ts.map +1 -0
  212. package/dist/lib/request/rate-limit-backoff.js +83 -0
  213. package/dist/lib/request/rate-limit-backoff.js.map +1 -0
  214. package/dist/lib/request/request-transformer.d.ts +107 -0
  215. package/dist/lib/request/request-transformer.d.ts.map +1 -0
  216. package/dist/lib/request/request-transformer.js +814 -0
  217. package/dist/lib/request/request-transformer.js.map +1 -0
  218. package/dist/lib/request/response-handler.d.ts +23 -0
  219. package/dist/lib/request/response-handler.d.ts.map +1 -0
  220. package/dist/lib/request/response-handler.js +155 -0
  221. package/dist/lib/request/response-handler.js.map +1 -0
  222. package/dist/lib/request/stream-failover.d.ts +21 -0
  223. package/dist/lib/request/stream-failover.d.ts.map +1 -0
  224. package/dist/lib/request/stream-failover.js +204 -0
  225. package/dist/lib/request/stream-failover.js.map +1 -0
  226. package/dist/lib/rotation.d.ts +146 -0
  227. package/dist/lib/rotation.d.ts.map +1 -0
  228. package/dist/lib/rotation.js +321 -0
  229. package/dist/lib/rotation.js.map +1 -0
  230. package/dist/lib/runtime-paths.d.ts +58 -0
  231. package/dist/lib/runtime-paths.d.ts.map +1 -0
  232. package/dist/lib/runtime-paths.js +164 -0
  233. package/dist/lib/runtime-paths.js.map +1 -0
  234. package/dist/lib/schemas.d.ts +435 -0
  235. package/dist/lib/schemas.d.ts.map +1 -0
  236. package/dist/lib/schemas.js +268 -0
  237. package/dist/lib/schemas.js.map +1 -0
  238. package/dist/lib/session-affinity.d.ts +23 -0
  239. package/dist/lib/session-affinity.d.ts.map +1 -0
  240. package/dist/lib/session-affinity.js +127 -0
  241. package/dist/lib/session-affinity.js.map +1 -0
  242. package/dist/lib/shutdown.d.ts +7 -0
  243. package/dist/lib/shutdown.d.ts.map +1 -0
  244. package/dist/lib/shutdown.js +43 -0
  245. package/dist/lib/shutdown.js.map +1 -0
  246. package/dist/lib/storage/migrations.d.ts +59 -0
  247. package/dist/lib/storage/migrations.d.ts.map +1 -0
  248. package/dist/lib/storage/migrations.js +41 -0
  249. package/dist/lib/storage/migrations.js.map +1 -0
  250. package/dist/lib/storage/paths.d.ts +51 -0
  251. package/dist/lib/storage/paths.d.ts.map +1 -0
  252. package/dist/lib/storage/paths.js +152 -0
  253. package/dist/lib/storage/paths.js.map +1 -0
  254. package/dist/lib/storage.d.ts +106 -0
  255. package/dist/lib/storage.d.ts.map +1 -0
  256. package/dist/lib/storage.js +896 -0
  257. package/dist/lib/storage.js.map +1 -0
  258. package/dist/lib/table-formatter.d.ts +32 -0
  259. package/dist/lib/table-formatter.d.ts.map +1 -0
  260. package/dist/lib/table-formatter.js +44 -0
  261. package/dist/lib/table-formatter.js.map +1 -0
  262. package/dist/lib/tools/hashline-tools.d.ts +51 -0
  263. package/dist/lib/tools/hashline-tools.d.ts.map +1 -0
  264. package/dist/lib/tools/hashline-tools.js +456 -0
  265. package/dist/lib/tools/hashline-tools.js.map +1 -0
  266. package/dist/lib/types.d.ts +130 -0
  267. package/dist/lib/types.d.ts.map +1 -0
  268. package/dist/lib/types.js +2 -0
  269. package/dist/lib/types.js.map +1 -0
  270. package/dist/lib/ui/ansi.d.ts +40 -0
  271. package/dist/lib/ui/ansi.d.ts.map +1 -0
  272. package/dist/lib/ui/ansi.js +68 -0
  273. package/dist/lib/ui/ansi.js.map +1 -0
  274. package/dist/lib/ui/auth-menu.d.ts +76 -0
  275. package/dist/lib/ui/auth-menu.d.ts.map +1 -0
  276. package/dist/lib/ui/auth-menu.js +590 -0
  277. package/dist/lib/ui/auth-menu.js.map +1 -0
  278. package/dist/lib/ui/confirm.d.ts +11 -0
  279. package/dist/lib/ui/confirm.d.ts.map +1 -0
  280. package/dist/lib/ui/confirm.js +29 -0
  281. package/dist/lib/ui/confirm.js.map +1 -0
  282. package/dist/lib/ui/copy.d.ts +123 -0
  283. package/dist/lib/ui/copy.d.ts.map +1 -0
  284. package/dist/lib/ui/copy.js +127 -0
  285. package/dist/lib/ui/copy.js.map +1 -0
  286. package/dist/lib/ui/format.d.ts +62 -0
  287. package/dist/lib/ui/format.d.ts.map +1 -0
  288. package/dist/lib/ui/format.js +205 -0
  289. package/dist/lib/ui/format.js.map +1 -0
  290. package/dist/lib/ui/runtime.d.ts +43 -0
  291. package/dist/lib/ui/runtime.d.ts.map +1 -0
  292. package/dist/lib/ui/runtime.js +69 -0
  293. package/dist/lib/ui/runtime.js.map +1 -0
  294. package/dist/lib/ui/select.d.ts +60 -0
  295. package/dist/lib/ui/select.d.ts.map +1 -0
  296. package/dist/lib/ui/select.js +467 -0
  297. package/dist/lib/ui/select.js.map +1 -0
  298. package/dist/lib/ui/theme.d.ts +56 -0
  299. package/dist/lib/ui/theme.d.ts.map +1 -0
  300. package/dist/lib/ui/theme.js +186 -0
  301. package/dist/lib/ui/theme.js.map +1 -0
  302. package/dist/lib/unified-settings.d.ts +71 -0
  303. package/dist/lib/unified-settings.d.ts.map +1 -0
  304. package/dist/lib/unified-settings.js +299 -0
  305. package/dist/lib/unified-settings.js.map +1 -0
  306. package/dist/lib/utils.d.ts +29 -0
  307. package/dist/lib/utils.d.ts.map +1 -0
  308. package/dist/lib/utils.js +54 -0
  309. package/dist/lib/utils.js.map +1 -0
  310. package/package.json +115 -0
  311. package/scripts/audit-dev-allowlist.js +128 -0
  312. package/scripts/bench-format/hashline-v2.mjs +642 -0
  313. package/scripts/bench-format/models.mjs +105 -0
  314. package/scripts/bench-format/opencode.mjs +205 -0
  315. package/scripts/bench-format/render.mjs +496 -0
  316. package/scripts/bench-format/stats.mjs +54 -0
  317. package/scripts/bench-format/tasks.mjs +151 -0
  318. package/scripts/benchmark-edit-formats.mjs +1161 -0
  319. package/scripts/benchmark-render-dashboard.mjs +49 -0
  320. package/scripts/codex-multi-auth.js +6 -0
  321. package/scripts/codex-routing.js +34 -0
  322. package/scripts/codex.js +122 -0
  323. package/scripts/copy-oauth-success.js +37 -0
  324. package/scripts/install-opencode-codex-auth.js +193 -0
  325. package/scripts/test-all-models.sh +7 -0
  326. package/scripts/test-model-matrix.js +424 -0
  327. package/scripts/validate-model-map.sh +7 -0
@@ -0,0 +1,642 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ const TAG_RE = /^(\d+)#([0-9a-z]{3})$/;
4
+ const LOOSE_TAG_RE = /^L?(\d+)(?:#([0-9a-z]+))?$/i;
5
+
6
+ function normalizeLineForHash(line) {
7
+ let value = line;
8
+ if (value.endsWith("\r")) {
9
+ value = value.slice(0, -1);
10
+ }
11
+ return value.replace(/\s+/g, "");
12
+ }
13
+
14
+ export function computeHashlineV2Hash(line) {
15
+ const normalized = normalizeLineForHash(line);
16
+ const digest = createHash("sha1").update(normalized, "utf8").digest();
17
+ const raw = (digest.readUInt32BE(0) % 46656).toString(36);
18
+ return raw.padStart(3, "0");
19
+ }
20
+
21
+ export function formatFileForHashlineV2(_filePath, content, startLine = 1, endLine) {
22
+ if (content.length === 0) {
23
+ return "";
24
+ }
25
+ const lines = content.split("\n");
26
+ const stop = typeof endLine === "number" ? Math.min(endLine, lines.length) : lines.length;
27
+ const start = Math.max(1, startLine);
28
+ const output = [];
29
+ for (let index = start - 1; index < stop; index += 1) {
30
+ const lineNumber = index + 1;
31
+ const hash = computeHashlineV2Hash(lines[index] ?? "");
32
+ output.push(`${lineNumber}#${hash}:${lines[index] ?? ""}`);
33
+ }
34
+ return output.join("\n");
35
+ }
36
+
37
+ export function extractJsonCodeBlock(text) {
38
+ if (typeof text !== "string" || text.trim().length === 0) {
39
+ return null;
40
+ }
41
+ const sentinel = text.match(/BEGIN_V2_JSON\s*([\s\S]*?)\s*END_V2_JSON/i);
42
+ if (sentinel?.[1]) {
43
+ return sentinel[1].trim();
44
+ }
45
+ const fencedJson = text.match(/```json\s*([\s\S]*?)```/i);
46
+ if (fencedJson?.[1]) {
47
+ return fencedJson[1].trim();
48
+ }
49
+ const fencedJsonLike = text.match(/```(?:jsonc|javascript|js|ts|tsx)?\s*([\s\S]*?)```/i);
50
+ if (fencedJsonLike?.[1] && /["']?\bpath\b["']?\s*:/.test(fencedJsonLike[1]) && /["']?\bedits\b["']?\s*:/.test(fencedJsonLike[1])) {
51
+ return fencedJsonLike[1].trim();
52
+ }
53
+ const fenced = text.match(/```\s*([\s\S]*?)```/);
54
+ if (fenced?.[1] && fenced[1].includes("\"edits\"")) {
55
+ return fenced[1].trim();
56
+ }
57
+ const rawQuoted = text.match(/(\{[\s\S]*?"path"\s*:[\s\S]*?"edits"\s*:\s*\[[\s\S]*?\][\s\S]*?\})/);
58
+ if (rawQuoted?.[1]) {
59
+ return rawQuoted[1].trim();
60
+ }
61
+ const rawLoose = text.match(/(\{[\s\S]*?\bpath\b\s*:[\s\S]*?\bedits\b\s*:\s*\[[\s\S]*?\][\s\S]*?\})/i);
62
+ if (rawLoose?.[1]) {
63
+ return rawLoose[1].trim();
64
+ }
65
+ const balanced = extractBalancedJsonObject(text);
66
+ return balanced?.trim() ?? null;
67
+ }
68
+
69
+ function extractBalancedJsonObject(text) {
70
+ if (typeof text !== "string") {
71
+ return null;
72
+ }
73
+ let start = -1;
74
+ let depth = 0;
75
+ let inString = false;
76
+ let quote = "";
77
+ let escaped = false;
78
+
79
+ for (let index = 0; index < text.length; index += 1) {
80
+ const ch = text[index];
81
+ if (inString) {
82
+ if (escaped) {
83
+ escaped = false;
84
+ continue;
85
+ }
86
+ if (ch === "\\") {
87
+ escaped = true;
88
+ continue;
89
+ }
90
+ if (ch === quote) {
91
+ inString = false;
92
+ quote = "";
93
+ }
94
+ continue;
95
+ }
96
+
97
+ if (ch === "\"" || ch === "'") {
98
+ inString = true;
99
+ quote = ch;
100
+ continue;
101
+ }
102
+
103
+ if (ch === "{") {
104
+ if (depth === 0) {
105
+ start = index;
106
+ }
107
+ depth += 1;
108
+ continue;
109
+ }
110
+ if (ch === "}" && depth > 0) {
111
+ depth -= 1;
112
+ if (depth === 0 && start >= 0) {
113
+ const candidate = text.slice(start, index + 1);
114
+ if (/\bpath\b\s*:/.test(candidate) && /\bedits\b\s*:/.test(candidate)) {
115
+ return candidate;
116
+ }
117
+ }
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+
123
+ function stripJsonComments(text) {
124
+ let output = "";
125
+ let inString = false;
126
+ let quote = "";
127
+ let escaped = false;
128
+
129
+ for (let index = 0; index < text.length; index += 1) {
130
+ const ch = text[index];
131
+ const next = text[index + 1];
132
+
133
+ if (inString) {
134
+ output += ch;
135
+ if (escaped) {
136
+ escaped = false;
137
+ continue;
138
+ }
139
+ if (ch === "\\") {
140
+ escaped = true;
141
+ continue;
142
+ }
143
+ if (ch === quote) {
144
+ inString = false;
145
+ quote = "";
146
+ }
147
+ continue;
148
+ }
149
+
150
+ if (ch === "\"" || ch === "'") {
151
+ inString = true;
152
+ quote = ch;
153
+ output += ch;
154
+ continue;
155
+ }
156
+
157
+ if (ch === "/" && next === "/") {
158
+ while (index < text.length && text[index] !== "\n") {
159
+ index += 1;
160
+ }
161
+ if (index < text.length) {
162
+ output += "\n";
163
+ }
164
+ continue;
165
+ }
166
+
167
+ if (ch === "/" && next === "*") {
168
+ index += 2;
169
+ while (index < text.length) {
170
+ if (text[index] === "*" && text[index + 1] === "/") {
171
+ index += 1;
172
+ break;
173
+ }
174
+ index += 1;
175
+ }
176
+ continue;
177
+ }
178
+
179
+ output += ch;
180
+ }
181
+
182
+ return output;
183
+ }
184
+
185
+ function quoteUnquotedKeys(text) {
186
+ return text.replace(/([{,]\s*)([A-Za-z_][A-Za-z0-9_-]*)(\s*:)/g, '$1"$2"$3');
187
+ }
188
+
189
+ function convertSingleQuotedStrings(text) {
190
+ return text.replace(/'([^'\\]*(?:\\.[^'\\]*)*)'/g, (_match, inner) => `"${inner.replace(/"/g, '\\"')}"`);
191
+ }
192
+
193
+ function trimToJsonObject(text) {
194
+ const start = text.indexOf("{");
195
+ const end = text.lastIndexOf("}");
196
+ if (start === -1 || end === -1 || end < start) {
197
+ return text;
198
+ }
199
+ return text.slice(start, end + 1);
200
+ }
201
+
202
+ function normalizeJsonCandidate(text) {
203
+ let next = String(text ?? "")
204
+ .replace(/^\uFEFF/, "")
205
+ .replace(/[“”]/g, "\"")
206
+ .replace(/[‘’]/g, "'");
207
+ next = next.replace(/```[a-z]*\s*/gi, "").replace(/```/g, "");
208
+ next = next.replace(/BEGIN_V2_JSON/gi, "").replace(/END_V2_JSON/gi, "");
209
+ next = trimToJsonObject(next);
210
+ next = stripJsonComments(next);
211
+ next = quoteUnquotedKeys(next);
212
+ next = convertSingleQuotedStrings(next);
213
+ next = next.replace(/,\s*([}\]])/g, "$1");
214
+ return next.trim();
215
+ }
216
+
217
+ function parseJsonWithRepairs(jsonText) {
218
+ const candidates = [];
219
+ const seen = new Set();
220
+ const push = (value) => {
221
+ const key = String(value ?? "");
222
+ if (key.length === 0 || seen.has(key)) {
223
+ return;
224
+ }
225
+ seen.add(key);
226
+ candidates.push(key);
227
+ };
228
+
229
+ push(String(jsonText ?? "").trim());
230
+ push(normalizeJsonCandidate(jsonText));
231
+ const balanced = extractBalancedJsonObject(String(jsonText ?? ""));
232
+ if (balanced) {
233
+ push(balanced.trim());
234
+ push(normalizeJsonCandidate(balanced));
235
+ }
236
+
237
+ let lastError = null;
238
+ for (const candidate of candidates) {
239
+ try {
240
+ return JSON.parse(candidate);
241
+ } catch (error) {
242
+ lastError = error;
243
+ }
244
+ }
245
+ throw lastError ?? new Error("Invalid JSON");
246
+ }
247
+
248
+ function parseTag(tag) {
249
+ const match = TAG_RE.exec(String(tag ?? "").trim());
250
+ if (!match) {
251
+ throw new Error(`Invalid tag format: ${tag}`);
252
+ }
253
+ const line = Number.parseInt(match[1], 10);
254
+ if (!Number.isFinite(line) || line < 1) {
255
+ throw new Error(`Invalid line number in tag: ${tag}`);
256
+ }
257
+ return { line, hash: match[2] };
258
+ }
259
+
260
+ function validateEdit(edit, index) {
261
+ if (!edit || typeof edit !== "object") {
262
+ throw new Error(`Edit ${index} must be an object`);
263
+ }
264
+ const op = edit.op;
265
+ if (!["set", "ins", "sub"].includes(op)) {
266
+ throw new Error(`Edit ${index} has unsupported op: ${String(op)}`);
267
+ }
268
+ if (op === "set") {
269
+ const hasTag = typeof edit.tag === "string";
270
+ const hasRange = typeof edit.first === "string" && typeof edit.last === "string";
271
+ if (!hasTag && !hasRange) {
272
+ throw new Error(`Edit ${index} set requires tag or first+last`);
273
+ }
274
+ if (!(edit.content === null || (Array.isArray(edit.content) && edit.content.every((v) => typeof v === "string")))) {
275
+ throw new Error(`Edit ${index} set content must be string[] or null`);
276
+ }
277
+ return;
278
+ }
279
+ if (op === "ins") {
280
+ if (!Array.isArray(edit.content) || !edit.content.every((v) => typeof v === "string")) {
281
+ throw new Error(`Edit ${index} ins content must be string[]`);
282
+ }
283
+ const anchors = ["after", "before", "bof", "eof"].filter((key) => key in edit);
284
+ if (anchors.length === 0) {
285
+ throw new Error(`Edit ${index} ins requires after/before/bof/eof`);
286
+ }
287
+ return;
288
+ }
289
+ if (op === "sub") {
290
+ if (typeof edit.tag !== "string" || typeof edit.old !== "string" || typeof edit.new !== "string") {
291
+ throw new Error(`Edit ${index} sub requires tag, old, new strings`);
292
+ }
293
+ }
294
+ }
295
+
296
+ function normalizeEditShape(edit) {
297
+ if (!edit || typeof edit !== "object") {
298
+ return edit;
299
+ }
300
+ const next = { ...edit };
301
+ if ((next.op === "set" || next.op === "ins") && typeof next.content === "string") {
302
+ next.content = [next.content];
303
+ }
304
+ return next;
305
+ }
306
+
307
+ function normalizeHashlineV2Path(pathValue) {
308
+ const normalized = String(pathValue ?? "").trim().replace(/\\/g, "/");
309
+ if (!normalized) {
310
+ return normalized;
311
+ }
312
+ if (normalized === "TodoApp.tsx" || normalized.endsWith("/TodoApp.tsx")) {
313
+ return "src/TodoApp.tsx";
314
+ }
315
+ if (normalized.startsWith("./")) {
316
+ return normalized;
317
+ }
318
+ return normalized;
319
+ }
320
+
321
+ export function parseHashlineV2Call(jsonText) {
322
+ let parsed;
323
+ try {
324
+ parsed = parseJsonWithRepairs(jsonText);
325
+ } catch (error) {
326
+ throw new Error(`Invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
327
+ }
328
+ if (!parsed || typeof parsed !== "object") {
329
+ throw new Error("Edit call must be an object");
330
+ }
331
+ if (typeof parsed.path !== "string" || parsed.path.trim().length === 0) {
332
+ throw new Error("Edit call path is required");
333
+ }
334
+ parsed.path = normalizeHashlineV2Path(parsed.path);
335
+ if (parsed.edits && !Array.isArray(parsed.edits) && typeof parsed.edits === "object") {
336
+ parsed.edits = [parsed.edits];
337
+ }
338
+ if (!Array.isArray(parsed.edits)) {
339
+ throw new Error("Edit call edits must be an array");
340
+ }
341
+ parsed.edits = parsed.edits.map(normalizeEditShape);
342
+ parsed.edits.forEach((edit, index) => validateEdit(edit, index));
343
+ return parsed;
344
+ }
345
+
346
+ function stripTagPrefix(line) {
347
+ const match = /^(\d+#[0-9a-z]{3}:)(.*)$/i.exec(line);
348
+ if (!match) {
349
+ return line;
350
+ }
351
+ return match[2];
352
+ }
353
+
354
+ function resolveUniqueSubTagFromOldText(content, oldText) {
355
+ if (typeof content !== "string" || typeof oldText !== "string" || oldText.length === 0) {
356
+ return null;
357
+ }
358
+ const lines = content.split("\n");
359
+ const matches = [];
360
+ for (let index = 0; index < lines.length; index += 1) {
361
+ const line = lines[index] ?? "";
362
+ if (line.includes(oldText)) {
363
+ matches.push(index);
364
+ }
365
+ }
366
+ if (matches.length !== 1) {
367
+ return null;
368
+ }
369
+ const lineIndex = matches[0];
370
+ const line = lines[lineIndex] ?? "";
371
+ return `${lineIndex + 1}#${computeHashlineV2Hash(line)}`;
372
+ }
373
+
374
+ function canonicalizeLooseTag(tagValue, sourceContent) {
375
+ const raw = String(tagValue ?? "").trim();
376
+ if (!raw || typeof sourceContent !== "string" || sourceContent.length === 0) {
377
+ return null;
378
+ }
379
+ const bareHash = raw.replace(/^#/, "");
380
+ if (/^[0-9a-z]{3}$/i.test(bareHash)) {
381
+ const lines = sourceContent.split("\n");
382
+ const matches = [];
383
+ for (let index = 0; index < lines.length; index += 1) {
384
+ const hash = computeHashlineV2Hash(lines[index] ?? "");
385
+ if (hash === bareHash.toLowerCase()) {
386
+ matches.push(index);
387
+ }
388
+ }
389
+ if (matches.length === 1) {
390
+ const lineNumber = matches[0] + 1;
391
+ return `${lineNumber}#${bareHash.toLowerCase()}`;
392
+ }
393
+ }
394
+ const match = LOOSE_TAG_RE.exec(raw);
395
+ if (!match) {
396
+ return null;
397
+ }
398
+ const lineNumber = Number.parseInt(match[1], 10);
399
+ if (!Number.isFinite(lineNumber) || lineNumber < 1) {
400
+ return null;
401
+ }
402
+ const lines = sourceContent.split("\n");
403
+ if (lineNumber > lines.length) {
404
+ return null;
405
+ }
406
+ const line = lines[lineNumber - 1] ?? "";
407
+ return `${lineNumber}#${computeHashlineV2Hash(line)}`;
408
+ }
409
+
410
+ function tryCanonicalizeTag(tagValue, sourceContent) {
411
+ try {
412
+ parseTag(tagValue);
413
+ return tagValue;
414
+ } catch {
415
+ return canonicalizeLooseTag(tagValue, sourceContent) ?? tagValue;
416
+ }
417
+ }
418
+
419
+ export function autocorrectHashlineV2Call(call, sourceContent) {
420
+ const normalized = {
421
+ path: call.path,
422
+ edits: call.edits.map((edit) => {
423
+ if (edit.op === "set" && Array.isArray(edit.content)) {
424
+ const nextEdit = {
425
+ ...edit,
426
+ content: edit.content.map((line) => stripTagPrefix(line)),
427
+ };
428
+ if (typeof nextEdit.tag === "string") {
429
+ nextEdit.tag = tryCanonicalizeTag(nextEdit.tag, sourceContent);
430
+ }
431
+ if (typeof nextEdit.first === "string") {
432
+ nextEdit.first = tryCanonicalizeTag(nextEdit.first, sourceContent);
433
+ }
434
+ if (typeof nextEdit.last === "string") {
435
+ nextEdit.last = tryCanonicalizeTag(nextEdit.last, sourceContent);
436
+ }
437
+ return nextEdit;
438
+ }
439
+ if (edit.op === "ins" && Array.isArray(edit.content)) {
440
+ const nextEdit = {
441
+ ...edit,
442
+ content: edit.content.map((line) => stripTagPrefix(line)),
443
+ };
444
+ if (typeof nextEdit.after === "string") {
445
+ nextEdit.after = tryCanonicalizeTag(nextEdit.after, sourceContent);
446
+ }
447
+ if (typeof nextEdit.before === "string") {
448
+ nextEdit.before = tryCanonicalizeTag(nextEdit.before, sourceContent);
449
+ }
450
+ return nextEdit;
451
+ }
452
+ if (edit.op === "sub") {
453
+ const nextEdit = { ...edit };
454
+ nextEdit.tag = tryCanonicalizeTag(nextEdit.tag, sourceContent);
455
+ try {
456
+ parseTag(nextEdit.tag);
457
+ } catch {
458
+ const correctedTag = resolveUniqueSubTagFromOldText(sourceContent, nextEdit.old);
459
+ if (correctedTag) {
460
+ nextEdit.tag = correctedTag;
461
+ }
462
+ }
463
+ return nextEdit;
464
+ }
465
+ return { ...edit };
466
+ }),
467
+ };
468
+ return normalized;
469
+ }
470
+
471
+ function resolveTag(tag, lines) {
472
+ let parsed;
473
+ try {
474
+ parsed = parseTag(tag);
475
+ } catch (error) {
476
+ return {
477
+ ok: false,
478
+ index: -1,
479
+ error: error instanceof Error ? error.message : `Invalid tag format: ${String(tag)}`,
480
+ };
481
+ }
482
+ const index = parsed.line - 1;
483
+ if (index < 0 || index >= lines.length) {
484
+ return { ok: false, index, error: `Line ${parsed.line} out of range (file has ${lines.length} lines)` };
485
+ }
486
+ const actualHash = computeHashlineV2Hash(lines[index] ?? "");
487
+ if (actualHash !== parsed.hash) {
488
+ return {
489
+ ok: false,
490
+ index,
491
+ error: `Hash mismatch at line ${parsed.line}: expected ${parsed.hash}, got ${actualHash}`,
492
+ };
493
+ }
494
+ return { ok: true, index, error: null };
495
+ }
496
+
497
+ export function applyHashlineV2Edits(content, call) {
498
+ const lines = content.split("\n");
499
+ const errors = [];
500
+ const resolved = [];
501
+
502
+ for (const edit of call.edits) {
503
+ if (edit.op === "set") {
504
+ if (typeof edit.tag === "string") {
505
+ const target = resolveTag(edit.tag, lines);
506
+ if (!target.ok) {
507
+ errors.push(target.error);
508
+ continue;
509
+ }
510
+ resolved.push({ edit, start: target.index, end: target.index });
511
+ } else {
512
+ const first = resolveTag(edit.first, lines);
513
+ const last = resolveTag(edit.last, lines);
514
+ if (!first.ok) {
515
+ errors.push(first.error);
516
+ continue;
517
+ }
518
+ if (!last.ok) {
519
+ errors.push(last.error);
520
+ continue;
521
+ }
522
+ if (first.index > last.index) {
523
+ errors.push(`set range invalid: first ${edit.first} is after last ${edit.last}`);
524
+ continue;
525
+ }
526
+ resolved.push({ edit, start: first.index, end: last.index });
527
+ }
528
+ continue;
529
+ }
530
+
531
+ if (edit.op === "ins") {
532
+ let anchorIndex;
533
+ if (edit.bof) {
534
+ anchorIndex = -1;
535
+ } else if (edit.eof) {
536
+ anchorIndex = lines.length;
537
+ } else if (typeof edit.after === "string") {
538
+ const after = resolveTag(edit.after, lines);
539
+ if (!after.ok) {
540
+ errors.push(after.error);
541
+ continue;
542
+ }
543
+ anchorIndex = after.index;
544
+ if (typeof edit.before === "string") {
545
+ const before = resolveTag(edit.before, lines);
546
+ if (!before.ok) {
547
+ errors.push(before.error);
548
+ continue;
549
+ }
550
+ }
551
+ } else if (typeof edit.before === "string") {
552
+ const before = resolveTag(edit.before, lines);
553
+ if (!before.ok) {
554
+ errors.push(before.error);
555
+ continue;
556
+ }
557
+ anchorIndex = before.index - 1;
558
+ } else {
559
+ errors.push("ins requires after, before, bof, or eof");
560
+ continue;
561
+ }
562
+ resolved.push({ edit, start: anchorIndex, end: anchorIndex });
563
+ continue;
564
+ }
565
+
566
+ if (edit.op === "sub") {
567
+ const target = resolveTag(edit.tag, lines);
568
+ if (!target.ok) {
569
+ errors.push(target.error);
570
+ continue;
571
+ }
572
+ const line = lines[target.index] ?? "";
573
+ const firstOccurrence = line.indexOf(edit.old);
574
+ if (firstOccurrence === -1) {
575
+ errors.push(`sub old text not found at line ${target.index + 1}`);
576
+ continue;
577
+ }
578
+ if (line.indexOf(edit.old, firstOccurrence + 1) !== -1) {
579
+ errors.push(`sub old text occurs multiple times at line ${target.index + 1}`);
580
+ continue;
581
+ }
582
+ resolved.push({ edit, start: target.index, end: target.index });
583
+ }
584
+ }
585
+
586
+ if (errors.length > 0) {
587
+ return { ok: false, errors, content, applied: 0 };
588
+ }
589
+
590
+ resolved.sort((left, right) => right.start - left.start);
591
+
592
+ for (const item of resolved) {
593
+ const edit = item.edit;
594
+ if (edit.op === "set") {
595
+ if (typeof edit.tag === "string") {
596
+ const lineIndex = parseTag(edit.tag).line - 1;
597
+ if (edit.content === null) {
598
+ lines.splice(lineIndex, 1);
599
+ } else {
600
+ lines.splice(lineIndex, 1, ...edit.content);
601
+ }
602
+ } else {
603
+ const firstIndex = parseTag(edit.first).line - 1;
604
+ const lastIndex = parseTag(edit.last).line - 1;
605
+ const count = lastIndex - firstIndex + 1;
606
+ if (edit.content === null) {
607
+ lines.splice(firstIndex, count);
608
+ } else {
609
+ lines.splice(firstIndex, count, ...edit.content);
610
+ }
611
+ }
612
+ continue;
613
+ }
614
+
615
+ if (edit.op === "ins") {
616
+ let insertAt;
617
+ if (edit.bof) {
618
+ insertAt = 0;
619
+ } else if (edit.eof) {
620
+ insertAt = lines.length;
621
+ } else if (typeof edit.after === "string") {
622
+ insertAt = parseTag(edit.after).line;
623
+ } else {
624
+ insertAt = parseTag(edit.before).line - 1;
625
+ }
626
+ lines.splice(insertAt, 0, ...edit.content);
627
+ continue;
628
+ }
629
+
630
+ if (edit.op === "sub") {
631
+ const lineIndex = parseTag(edit.tag).line - 1;
632
+ lines[lineIndex] = (lines[lineIndex] ?? "").replace(edit.old, edit.new);
633
+ }
634
+ }
635
+
636
+ return {
637
+ ok: true,
638
+ errors: [],
639
+ content: lines.join("\n"),
640
+ applied: resolved.length,
641
+ };
642
+ }