dexto 1.14.6 → 2.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/dist/agents/coding-agent/coding-agent.yml +0 -2
  2. package/dist/cli/agent-audit.d.ts +12 -0
  3. package/dist/cli/agent-audit.d.ts.map +1 -0
  4. package/dist/cli/agent-audit.js +52 -0
  5. package/dist/cli/auth/billing.d.ts +0 -6
  6. package/dist/cli/auth/billing.d.ts.map +1 -1
  7. package/dist/cli/auth/billing.js +0 -23
  8. package/dist/cli/auth/constants.d.ts +3 -31
  9. package/dist/cli/auth/constants.d.ts.map +1 -1
  10. package/dist/cli/auth/constants.js +9 -33
  11. package/dist/cli/auth/index.d.ts +2 -9
  12. package/dist/cli/auth/index.d.ts.map +1 -1
  13. package/dist/cli/auth/index.js +2 -7
  14. package/dist/cli/auth/sign-in-runtime.d.ts +12 -0
  15. package/dist/cli/auth/sign-in-runtime.d.ts.map +1 -0
  16. package/dist/cli/auth/sign-in-runtime.js +59 -0
  17. package/dist/cli/cloud/app-scopes.d.ts +4 -0
  18. package/dist/cli/cloud/app-scopes.d.ts.map +1 -0
  19. package/dist/cli/cloud/app-scopes.js +19 -0
  20. package/dist/cli/cloud/command.d.ts +107 -0
  21. package/dist/cli/cloud/command.d.ts.map +1 -0
  22. package/dist/cli/cloud/command.js +154 -0
  23. package/dist/cli/cloud/commands/account.d.ts +11 -0
  24. package/dist/cli/cloud/commands/account.d.ts.map +1 -0
  25. package/dist/cli/cloud/commands/account.js +129 -0
  26. package/dist/cli/cloud/commands/agents.d.ts +3 -0
  27. package/dist/cli/cloud/commands/agents.d.ts.map +1 -0
  28. package/dist/cli/cloud/commands/agents.js +526 -0
  29. package/dist/cli/cloud/commands/api.d.ts +3 -0
  30. package/dist/cli/cloud/commands/api.d.ts.map +1 -0
  31. package/dist/cli/cloud/commands/api.js +26 -0
  32. package/dist/cli/cloud/commands/app-files.d.ts +29 -0
  33. package/dist/cli/cloud/commands/app-files.d.ts.map +1 -0
  34. package/dist/cli/cloud/commands/app-files.js +72 -0
  35. package/dist/cli/cloud/commands/artifacts.d.ts +3 -0
  36. package/dist/cli/cloud/commands/artifacts.d.ts.map +1 -0
  37. package/dist/cli/cloud/commands/artifacts.js +267 -0
  38. package/dist/cli/cloud/commands/automations.d.ts +3 -0
  39. package/dist/cli/cloud/commands/automations.d.ts.map +1 -0
  40. package/dist/cli/cloud/commands/automations.js +233 -0
  41. package/dist/cli/cloud/commands/chats.d.ts +3 -0
  42. package/dist/cli/cloud/commands/chats.d.ts.map +1 -0
  43. package/dist/cli/cloud/commands/chats.js +204 -0
  44. package/dist/cli/cloud/commands/data-deletions.d.ts +3 -0
  45. package/dist/cli/cloud/commands/data-deletions.d.ts.map +1 -0
  46. package/dist/cli/cloud/commands/data-deletions.js +55 -0
  47. package/dist/cli/cloud/commands/deploys.d.ts +10 -0
  48. package/dist/cli/cloud/commands/deploys.d.ts.map +1 -0
  49. package/dist/cli/cloud/commands/deploys.js +225 -0
  50. package/dist/cli/cloud/commands/drive.d.ts +12 -0
  51. package/dist/cli/cloud/commands/drive.d.ts.map +1 -0
  52. package/dist/cli/cloud/commands/drive.js +321 -0
  53. package/dist/cli/cloud/commands/files.d.ts +24 -0
  54. package/dist/cli/cloud/commands/files.d.ts.map +1 -0
  55. package/dist/cli/cloud/commands/files.js +284 -0
  56. package/dist/cli/cloud/commands/find.d.ts +3 -0
  57. package/dist/cli/cloud/commands/find.d.ts.map +1 -0
  58. package/dist/cli/cloud/commands/find.js +63 -0
  59. package/dist/cli/cloud/commands/models.d.ts +3 -0
  60. package/dist/cli/cloud/commands/models.d.ts.map +1 -0
  61. package/dist/cli/cloud/commands/models.js +250 -0
  62. package/dist/cli/cloud/commands/profile.d.ts +3 -0
  63. package/dist/cli/cloud/commands/profile.d.ts.map +1 -0
  64. package/dist/cli/cloud/commands/profile.js +120 -0
  65. package/dist/cli/cloud/commands/projects.d.ts +5 -0
  66. package/dist/cli/cloud/commands/projects.d.ts.map +1 -0
  67. package/dist/cli/cloud/commands/projects.js +66 -0
  68. package/dist/cli/cloud/commands/publish.d.ts +3 -0
  69. package/dist/cli/cloud/commands/publish.d.ts.map +1 -0
  70. package/dist/cli/cloud/commands/publish.js +139 -0
  71. package/dist/cli/cloud/commands/sandboxes.d.ts +3 -0
  72. package/dist/cli/cloud/commands/sandboxes.d.ts.map +1 -0
  73. package/dist/cli/cloud/commands/sandboxes.js +266 -0
  74. package/dist/cli/cloud/commands/secrets.d.ts +3 -0
  75. package/dist/cli/cloud/commands/secrets.d.ts.map +1 -0
  76. package/dist/cli/cloud/commands/secrets.js +111 -0
  77. package/dist/cli/cloud/commands/skills.d.ts +3 -0
  78. package/dist/cli/cloud/commands/skills.d.ts.map +1 -0
  79. package/dist/cli/cloud/commands/skills.js +203 -0
  80. package/dist/cli/cloud/commands/traces.d.ts +3 -0
  81. package/dist/cli/cloud/commands/traces.d.ts.map +1 -0
  82. package/dist/cli/cloud/commands/traces.js +142 -0
  83. package/dist/cli/cloud/commands/web.d.ts +3 -0
  84. package/dist/cli/cloud/commands/web.d.ts.map +1 -0
  85. package/dist/cli/cloud/commands/web.js +53 -0
  86. package/dist/cli/cloud/commands/workspace-apps.d.ts +58 -0
  87. package/dist/cli/cloud/commands/workspace-apps.d.ts.map +1 -0
  88. package/dist/cli/cloud/commands/workspace-apps.js +657 -0
  89. package/dist/cli/cloud/commands/workspace.d.ts +36 -0
  90. package/dist/cli/cloud/commands/workspace.d.ts.map +1 -0
  91. package/dist/cli/cloud/commands/workspace.js +281 -0
  92. package/dist/cli/cloud/connection.d.ts +33 -0
  93. package/dist/cli/cloud/connection.d.ts.map +1 -0
  94. package/dist/cli/cloud/connection.js +38 -0
  95. package/dist/cli/cloud/events.d.ts +63 -0
  96. package/dist/cli/cloud/events.d.ts.map +1 -0
  97. package/dist/cli/cloud/events.js +72 -0
  98. package/dist/cli/cloud/host.d.ts +9 -0
  99. package/dist/cli/cloud/host.d.ts.map +1 -0
  100. package/dist/cli/cloud/host.js +109 -0
  101. package/dist/cli/cloud/output.d.ts +34 -0
  102. package/dist/cli/cloud/output.d.ts.map +1 -0
  103. package/dist/cli/cloud/output.js +77 -0
  104. package/dist/cli/cloud/run.d.ts +26 -0
  105. package/dist/cli/cloud/run.d.ts.map +1 -0
  106. package/dist/cli/cloud/run.js +444 -0
  107. package/dist/cli/cloud/runtime.d.ts +33 -0
  108. package/dist/cli/cloud/runtime.d.ts.map +1 -0
  109. package/dist/cli/cloud/runtime.js +86 -0
  110. package/dist/cli/cloud/tar.d.ts +9 -0
  111. package/dist/cli/cloud/tar.d.ts.map +1 -0
  112. package/dist/cli/cloud/tar.js +69 -0
  113. package/dist/cli/cloud/transfer.d.ts +13 -0
  114. package/dist/cli/cloud/transfer.d.ts.map +1 -0
  115. package/dist/cli/cloud/transfer.js +36 -0
  116. package/dist/cli/cloud/v1.d.ts +20 -0
  117. package/dist/cli/cloud/v1.d.ts.map +1 -0
  118. package/dist/cli/cloud/v1.js +66 -0
  119. package/dist/cli/cloud/zip.d.ts +8 -0
  120. package/dist/cli/cloud/zip.d.ts.map +1 -0
  121. package/dist/cli/cloud/zip.js +66 -0
  122. package/dist/cli/commands/auth/sign-in-commands.d.ts +62 -0
  123. package/dist/cli/commands/auth/sign-in-commands.d.ts.map +1 -0
  124. package/dist/cli/commands/auth/sign-in-commands.js +248 -0
  125. package/dist/cli/commands/auth/sign-in-context.d.ts +16 -0
  126. package/dist/cli/commands/auth/sign-in-context.d.ts.map +1 -0
  127. package/dist/cli/commands/auth/sign-in-context.js +68 -0
  128. package/dist/cli/commands/cloud/register.d.ts +5 -0
  129. package/dist/cli/commands/cloud/register.d.ts.map +1 -1
  130. package/dist/cli/commands/cloud/register.js +44 -24
  131. package/dist/cli/commands/create-app.js +5 -6
  132. package/dist/cli/commands/create-image.d.ts.map +1 -1
  133. package/dist/cli/commands/create-image.js +9 -10
  134. package/dist/cli/commands/index.d.ts +0 -2
  135. package/dist/cli/commands/index.d.ts.map +1 -1
  136. package/dist/cli/commands/index.js +0 -2
  137. package/dist/cli/commands/init.d.ts +0 -2
  138. package/dist/cli/commands/init.d.ts.map +1 -1
  139. package/dist/cli/commands/init.js +5 -26
  140. package/dist/cli/commands/run/headless.d.ts.map +1 -1
  141. package/dist/cli/commands/run/headless.js +57 -2
  142. package/dist/cli/commands/run/output.d.ts +29 -10
  143. package/dist/cli/commands/run/output.d.ts.map +1 -1
  144. package/dist/cli/commands/run/output.js +44 -22
  145. package/dist/cli/commands/run/register.d.ts.map +1 -1
  146. package/dist/cli/commands/run/register.js +21 -9
  147. package/dist/cli/commands/run/step-log.d.ts +11 -0
  148. package/dist/cli/commands/run/step-log.d.ts.map +1 -0
  149. package/dist/cli/commands/run/step-log.js +42 -0
  150. package/dist/cli/commands/session/register.d.ts.map +1 -1
  151. package/dist/cli/commands/session/register.js +51 -21
  152. package/dist/cli/commands/session-commands.js +1 -1
  153. package/dist/cli/commands/setup.js +17 -14
  154. package/dist/cli/failure.d.ts +29 -0
  155. package/dist/cli/failure.d.ts.map +1 -0
  156. package/dist/cli/failure.js +31 -0
  157. package/dist/cli/help-json.d.ts +22 -0
  158. package/dist/cli/help-json.d.ts.map +1 -0
  159. package/dist/cli/help-json.js +20 -0
  160. package/dist/cli/import/claude-code.d.ts +10 -0
  161. package/dist/cli/import/claude-code.d.ts.map +1 -0
  162. package/dist/cli/import/claude-code.js +131 -0
  163. package/dist/cli/import/codex.d.ts +27 -0
  164. package/dist/cli/import/codex.d.ts.map +1 -0
  165. package/dist/cli/import/codex.js +148 -0
  166. package/dist/cli/import/git.d.ts +19 -0
  167. package/dist/cli/import/git.d.ts.map +1 -0
  168. package/dist/cli/import/git.js +95 -0
  169. package/dist/cli/import/local-chats.d.ts +28 -0
  170. package/dist/cli/import/local-chats.d.ts.map +1 -0
  171. package/dist/cli/import/local-chats.js +129 -0
  172. package/dist/cli/import/redaction.d.ts +37 -0
  173. package/dist/cli/import/redaction.d.ts.map +1 -0
  174. package/dist/cli/import/redaction.js +148 -0
  175. package/dist/cli/import/test-utils.d.ts +16 -0
  176. package/dist/cli/import/test-utils.d.ts.map +1 -0
  177. package/dist/cli/import/test-utils.js +223 -0
  178. package/dist/cli/import/transcript.d.ts +75 -0
  179. package/dist/cli/import/transcript.d.ts.map +1 -0
  180. package/dist/cli/import/transcript.js +123 -0
  181. package/dist/cli/interaction.d.ts +18 -0
  182. package/dist/cli/interaction.d.ts.map +1 -0
  183. package/dist/cli/interaction.js +30 -0
  184. package/dist/cli/modes/cli.d.ts.map +1 -1
  185. package/dist/cli/modes/cli.js +42 -9
  186. package/dist/cli/modes/server.js +8 -8
  187. package/dist/cli/modes/web.js +3 -3
  188. package/dist/cli/output.d.ts +9 -0
  189. package/dist/cli/output.d.ts.map +1 -0
  190. package/dist/cli/output.js +6 -0
  191. package/dist/cli/utils/dexto-auth-check.d.ts +1 -1
  192. package/dist/cli/utils/dexto-auth-check.d.ts.map +1 -1
  193. package/dist/cli/utils/dexto-auth-check.js +4 -5
  194. package/dist/cli/utils/dexto-setup.d.ts +4 -5
  195. package/dist/cli/utils/dexto-setup.d.ts.map +1 -1
  196. package/dist/cli/utils/dexto-setup.js +17 -14
  197. package/dist/cli/utils/provider-setup.js +1 -1
  198. package/dist/cli/utils/scaffolding-utils.d.ts +9 -3
  199. package/dist/cli/utils/scaffolding-utils.d.ts.map +1 -1
  200. package/dist/cli/utils/scaffolding-utils.js +48 -40
  201. package/dist/cli/utils/version-check.d.ts.map +1 -1
  202. package/dist/cli/utils/version-check.js +2 -1
  203. package/dist/index-main.js +44 -65
  204. package/dist/webui/assets/index-D9xiOs62.js +1649 -0
  205. package/dist/webui/index.html +1 -1
  206. package/package.json +15 -14
  207. package/dist/cli/auth/api-client.d.ts +0 -125
  208. package/dist/cli/auth/api-client.d.ts.map +0 -1
  209. package/dist/cli/auth/api-client.js +0 -585
  210. package/dist/cli/auth/device.d.ts +0 -14
  211. package/dist/cli/auth/device.d.ts.map +0 -1
  212. package/dist/cli/auth/device.js +0 -84
  213. package/dist/cli/auth/dexto-api-key.d.ts +0 -34
  214. package/dist/cli/auth/dexto-api-key.d.ts.map +0 -1
  215. package/dist/cli/auth/dexto-api-key.js +0 -160
  216. package/dist/cli/auth/login-persistence.d.ts +0 -15
  217. package/dist/cli/auth/login-persistence.d.ts.map +0 -1
  218. package/dist/cli/auth/login-persistence.js +0 -72
  219. package/dist/cli/auth/oauth.d.ts +0 -39
  220. package/dist/cli/auth/oauth.d.ts.map +0 -1
  221. package/dist/cli/auth/oauth.js +0 -434
  222. package/dist/cli/auth/service.d.ts +0 -24
  223. package/dist/cli/auth/service.d.ts.map +0 -1
  224. package/dist/cli/auth/service.js +0 -161
  225. package/dist/cli/auth/types.d.ts +0 -11
  226. package/dist/cli/auth/types.d.ts.map +0 -1
  227. package/dist/cli/auth/types.js +0 -1
  228. package/dist/cli/cloud-chat.d.ts +0 -18
  229. package/dist/cli/cloud-chat.d.ts.map +0 -1
  230. package/dist/cli/cloud-chat.js +0 -734
  231. package/dist/cli/commands/auth/index.d.ts +0 -4
  232. package/dist/cli/commands/auth/index.d.ts.map +0 -1
  233. package/dist/cli/commands/auth/index.js +0 -4
  234. package/dist/cli/commands/auth/login.d.ts +0 -12
  235. package/dist/cli/commands/auth/login.d.ts.map +0 -1
  236. package/dist/cli/commands/auth/login.js +0 -153
  237. package/dist/cli/commands/auth/logout.d.ts +0 -5
  238. package/dist/cli/commands/auth/logout.d.ts.map +0 -1
  239. package/dist/cli/commands/auth/logout.js +0 -81
  240. package/dist/cli/commands/auth/register.d.ts +0 -3
  241. package/dist/cli/commands/auth/register.d.ts.map +0 -1
  242. package/dist/cli/commands/auth/register.js +0 -96
  243. package/dist/cli/commands/auth/status.d.ts +0 -2
  244. package/dist/cli/commands/auth/status.d.ts.map +0 -1
  245. package/dist/cli/commands/auth/status.js +0 -22
  246. package/dist/cli/commands/billing/index.d.ts +0 -2
  247. package/dist/cli/commands/billing/index.d.ts.map +0 -1
  248. package/dist/cli/commands/billing/index.js +0 -2
  249. package/dist/cli/commands/billing/register.d.ts +0 -3
  250. package/dist/cli/commands/billing/register.d.ts.map +0 -1
  251. package/dist/cli/commands/billing/register.js +0 -20
  252. package/dist/cli/commands/billing/status.d.ts +0 -8
  253. package/dist/cli/commands/billing/status.d.ts.map +0 -1
  254. package/dist/cli/commands/billing/status.js +0 -71
  255. package/dist/cli/commands/cloud/discovery.d.ts +0 -17
  256. package/dist/cli/commands/cloud/discovery.d.ts.map +0 -1
  257. package/dist/cli/commands/cloud/discovery.js +0 -88
  258. package/dist/cli/commands/deploy/client.d.ts +0 -113
  259. package/dist/cli/commands/deploy/client.d.ts.map +0 -1
  260. package/dist/cli/commands/deploy/client.js +0 -519
  261. package/dist/cli/commands/deploy/config.d.ts +0 -37
  262. package/dist/cli/commands/deploy/config.d.ts.map +0 -1
  263. package/dist/cli/commands/deploy/config.js +0 -144
  264. package/dist/cli/commands/deploy/entry-agent.d.ts +0 -3
  265. package/dist/cli/commands/deploy/entry-agent.d.ts.map +0 -1
  266. package/dist/cli/commands/deploy/entry-agent.js +0 -70
  267. package/dist/cli/commands/deploy/index.d.ts +0 -12
  268. package/dist/cli/commands/deploy/index.d.ts.map +0 -1
  269. package/dist/cli/commands/deploy/index.js +0 -370
  270. package/dist/cli/commands/deploy/links.d.ts +0 -3
  271. package/dist/cli/commands/deploy/links.d.ts.map +0 -1
  272. package/dist/cli/commands/deploy/links.js +0 -53
  273. package/dist/cli/commands/deploy/register.d.ts +0 -6
  274. package/dist/cli/commands/deploy/register.d.ts.map +0 -1
  275. package/dist/cli/commands/deploy/register.js +0 -128
  276. package/dist/cli/commands/deploy/snapshot.d.ts +0 -12
  277. package/dist/cli/commands/deploy/snapshot.d.ts.map +0 -1
  278. package/dist/cli/commands/deploy/snapshot.js +0 -76
  279. package/dist/cli/commands/deploy/state.d.ts +0 -13
  280. package/dist/cli/commands/deploy/state.d.ts.map +0 -1
  281. package/dist/cli/commands/deploy/state.js +0 -122
  282. package/dist/cli/commands/span/register.d.ts +0 -6
  283. package/dist/cli/commands/span/register.d.ts.map +0 -1
  284. package/dist/cli/commands/span/register.js +0 -25
  285. package/dist/cli/commands/trace/client.d.ts +0 -98
  286. package/dist/cli/commands/trace/client.d.ts.map +0 -1
  287. package/dist/cli/commands/trace/client.js +0 -150
  288. package/dist/cli/commands/trace/format.d.ts +0 -5
  289. package/dist/cli/commands/trace/format.d.ts.map +0 -1
  290. package/dist/cli/commands/trace/format.js +0 -58
  291. package/dist/cli/commands/trace/index.d.ts +0 -21
  292. package/dist/cli/commands/trace/index.d.ts.map +0 -1
  293. package/dist/cli/commands/trace/index.js +0 -49
  294. package/dist/cli/commands/trace/register.d.ts +0 -6
  295. package/dist/cli/commands/trace/register.d.ts.map +0 -1
  296. package/dist/cli/commands/trace/register.js +0 -71
  297. package/dist/webui/assets/index-DEfRFmFT.js +0 -1667
@@ -0,0 +1,148 @@
1
+ // Reads Codex sessions: rollouts in `$CODEX_HOME/sessions/YYYY/MM/DD/`, titled from `state_5.sqlite`.
2
+ import { access } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { z } from 'zod';
5
+ import { createTurns, finishChat, isRecord, jsonLines, stringField, toolCallText, toolResultText, validTime, } from './transcript.js';
6
+ /** Context Codex sends as `user` messages that the person never typed. */
7
+ const CODEX_INJECTED = /^\s*(<environment_context>|<user_instructions>|<permissions instructions>|<turn_aborted>|# AGENTS\.md instructions)/u;
8
+ /**
9
+ * A Codex rollout: `{timestamp, type, payload}` lines, as `codex-rs/history` writes them. The
10
+ * `session_meta` line names the thread and its git remote; `response_item` lines carry messages and
11
+ * tool calls. Developer and system messages, injected context, reasoning and compaction are left out.
12
+ * `titleOf` gives the title Codex keeps for a thread, or null when it keeps none.
13
+ */
14
+ export function parseCodexRollout(text, file, titleOf) {
15
+ const turns = createTurns();
16
+ let id = null;
17
+ let cwd = null;
18
+ let gitBranch = null;
19
+ let remoteUrl = null;
20
+ for (const line of jsonLines(text)) {
21
+ const payload = line.payload;
22
+ if (!isRecord(payload))
23
+ continue;
24
+ if (line.type === 'session_meta') {
25
+ id ??= stringField(payload, 'id');
26
+ cwd ??= stringField(payload, 'cwd');
27
+ if (isRecord(payload.git)) {
28
+ gitBranch ??= stringField(payload.git, 'branch');
29
+ remoteUrl ??= stringField(payload.git, 'repository_url');
30
+ }
31
+ continue;
32
+ }
33
+ const at = validTime(stringField(line, 'timestamp'));
34
+ if (line.type !== 'response_item' || at === null)
35
+ continue;
36
+ switch (payload.type) {
37
+ case 'message': {
38
+ const said = codexContentText(payload.content);
39
+ if (payload.role === 'user' && !CODEX_INJECTED.test(said))
40
+ turns.user(said, at);
41
+ if (payload.role === 'assistant')
42
+ turns.assistant(said, at);
43
+ break;
44
+ }
45
+ case 'function_call':
46
+ turns.assistant(toolCallText(stringField(payload, 'name') ?? 'tool', payload.arguments ?? ''), at);
47
+ break;
48
+ case 'custom_tool_call':
49
+ turns.assistant(toolCallText(stringField(payload, 'name') ?? 'tool', payload.input ?? ''), at);
50
+ break;
51
+ case 'local_shell_call': {
52
+ const action = isRecord(payload.action) ? payload.action : {};
53
+ const command = Array.isArray(action.command) ? action.command : [];
54
+ turns.assistant(toolCallText('shell', { command }), at);
55
+ break;
56
+ }
57
+ case 'function_call_output':
58
+ case 'custom_tool_call_output':
59
+ turns.assistant(toolResultText(codexOutputText(payload.output)), at);
60
+ break;
61
+ }
62
+ }
63
+ const threadId = id ?? path.basename(file, '.jsonl');
64
+ return finishChat({
65
+ cwd,
66
+ gitBranch,
67
+ id: threadId,
68
+ path: file,
69
+ pullRequests: [],
70
+ recordedRemoteUrl: remoteUrl,
71
+ source: 'codex',
72
+ title: titleOf(threadId),
73
+ }, turns.done());
74
+ }
75
+ function codexContentText(content) {
76
+ if (!Array.isArray(content))
77
+ return '';
78
+ return content
79
+ .map((part) => (isRecord(part) && typeof part.text === 'string' ? part.text : ''))
80
+ .filter((part) => part !== '')
81
+ .join('\n');
82
+ }
83
+ function codexOutputText(output) {
84
+ if (typeof output === 'string')
85
+ return output;
86
+ if (isRecord(output) && typeof output.content === 'string')
87
+ return output.content;
88
+ return codexContentText(output);
89
+ }
90
+ /** `$CODEX_HOME`, or `~/.codex`. */
91
+ export function codexHome(environment, homeDirectory) {
92
+ return environment.CODEX_HOME ?? path.join(homeDirectory, '.codex');
93
+ }
94
+ /** The state database Codex 0.1xx keeps beside its rollouts. */
95
+ const CODEX_STATE_DB = 'state_5.sqlite';
96
+ const ThreadRowsSchema = z.array(z.object({
97
+ archived: z.number(),
98
+ cwd: z.string(),
99
+ id: z.string(),
100
+ name: z.string().nullable(),
101
+ rollout_path: z.string().nullable(),
102
+ title: z.string(),
103
+ updated_at: z.number(),
104
+ }));
105
+ /**
106
+ * The threads Codex lists, read from its state database without writing to it; null when there is
107
+ * no such database. A present database that cannot be read is reported to the caller.
108
+ * Archived threads are left out, as their rollouts are not where sessions are read from.
109
+ */
110
+ export async function readCodexThreads(home) {
111
+ const file = path.join(home, CODEX_STATE_DB);
112
+ if (!(await exists(file)))
113
+ return null;
114
+ let rows;
115
+ try {
116
+ const { default: Database } = await import('better-sqlite3');
117
+ const db = new Database(file, { fileMustExist: true, readonly: true });
118
+ try {
119
+ rows = ThreadRowsSchema.parse(db
120
+ .prepare('SELECT id, rollout_path, cwd, title, name, archived, updated_at FROM threads')
121
+ .all());
122
+ }
123
+ finally {
124
+ db.close();
125
+ }
126
+ }
127
+ catch (error) {
128
+ throw new Error(`Cannot read Codex state database; titles and migrated history are unavailable. Use --dir to read rollout files without it. ${error instanceof Error ? error.message : 'Unknown error.'}`, { cause: error });
129
+ }
130
+ const threads = [];
131
+ for (const row of rows) {
132
+ if (row.archived !== 0)
133
+ continue;
134
+ const name = row.name?.trim() ?? '';
135
+ const title = row.title.trim();
136
+ threads.push({
137
+ cwd: row.cwd,
138
+ hasRollout: row.rollout_path !== null && (await exists(row.rollout_path)),
139
+ id: row.id,
140
+ title: name !== '' ? name : title !== '' ? title : null,
141
+ updatedAt: new Date(row.updated_at * 1_000).toISOString(),
142
+ });
143
+ }
144
+ return threads;
145
+ }
146
+ async function exists(file) {
147
+ return await access(file).then(() => true, () => false);
148
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The repository `folder` is in: the main checkout's folder, so every worktree of one repository
3
+ * names the same one, and its `origin` remote. Null when the folder is gone or in no repository.
4
+ */
5
+ export declare function findRepository(folder: string): Promise<{
6
+ readonly remoteUrl: string | null;
7
+ readonly root: string;
8
+ } | null>;
9
+ type FoundRepository = ReturnType<typeof findRepository>;
10
+ /** A lookup of `findRepository` that reads each folder once. */
11
+ export declare function createRepositoryFinder(): (folder: string | null) => FoundRepository;
12
+ /** `[remote "origin"] url = …` in a git config, without credentials. */
13
+ export declare function originUrl(config: string): string | null;
14
+ /**
15
+ * A remote URL with userinfo, query and fragment removed, including scp-style userinfo.
16
+ */
17
+ export declare function withoutCredentials(url: string): string;
18
+ export {};
19
+ //# sourceMappingURL=git.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../../src/cli/import/git.ts"],"names":[],"mappings":"AAKA;;;GAGG;AACH,wBAAsB,cAAc,CAChC,MAAM,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAW9E;AAED,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AAEzD,gEAAgE;AAChE,wBAAgB,sBAAsB,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,eAAe,CAWnF;AA6BD,wEAAwE;AACxE,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAavD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAatD"}
@@ -0,0 +1,95 @@
1
+ // Finds the git repository a folder is in by reading `.git` on disk; git itself is never run.
2
+ import { readFile, stat } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ /**
5
+ * The repository `folder` is in: the main checkout's folder, so every worktree of one repository
6
+ * names the same one, and its `origin` remote. Null when the folder is gone or in no repository.
7
+ */
8
+ export async function findRepository(folder) {
9
+ const resolved = path.resolve(folder);
10
+ if (!(await stat(resolved).catch(() => null))?.isDirectory())
11
+ return null;
12
+ const found = await findDotGit(resolved);
13
+ if (found === null)
14
+ return null;
15
+ const commonDir = await commonGitDir(found);
16
+ if (commonDir === null)
17
+ return null;
18
+ const root = path.basename(commonDir) === '.git' ? path.dirname(commonDir) : path.dirname(found.path);
19
+ const config = await readFile(path.join(commonDir, 'config'), 'utf8').catch(() => null);
20
+ return { remoteUrl: config === null ? null : originUrl(config), root };
21
+ }
22
+ /** A lookup of `findRepository` that reads each folder once. */
23
+ export function createRepositoryFinder() {
24
+ const found = new Map();
25
+ return async (folder) => {
26
+ if (folder === null)
27
+ return null;
28
+ let repository = found.get(folder);
29
+ if (repository === undefined) {
30
+ repository = findRepository(folder);
31
+ found.set(folder, repository);
32
+ }
33
+ return await repository;
34
+ };
35
+ }
36
+ /** The nearest `.git` at or above `folder`: a directory, or a worktree's `gitdir:` file. */
37
+ async function findDotGit(folder) {
38
+ for (let current = folder;; current = path.dirname(current)) {
39
+ const candidate = path.join(current, '.git');
40
+ const stats = await stat(candidate).catch(() => null);
41
+ if (stats?.isDirectory())
42
+ return { kind: 'dir', path: candidate };
43
+ if (stats?.isFile())
44
+ return { kind: 'file', path: candidate };
45
+ if (path.dirname(current) === current)
46
+ return null;
47
+ }
48
+ }
49
+ /** The git directory shared by every worktree: a worktree's `gitdir`, then its `commondir`. */
50
+ async function commonGitDir(dotGit) {
51
+ if (dotGit.kind === 'dir')
52
+ return dotGit.path;
53
+ const pointer = await readFile(dotGit.path, 'utf8').catch(() => null);
54
+ const gitDir = /^gitdir:\s*(.+)$/mu.exec(pointer ?? '')?.[1]?.trim();
55
+ if (gitDir === undefined)
56
+ return null;
57
+ const worktreeDir = path.resolve(path.dirname(dotGit.path), gitDir);
58
+ const common = await readFile(path.join(worktreeDir, 'commondir'), 'utf8').catch(() => null);
59
+ return common === null ? worktreeDir : path.resolve(worktreeDir, common.trim());
60
+ }
61
+ /** `[remote "origin"] url = …` in a git config, without credentials. */
62
+ export function originUrl(config) {
63
+ let inOrigin = false;
64
+ for (const raw of config.split(/\r?\n/u)) {
65
+ const line = raw.trim();
66
+ if (line.startsWith('[')) {
67
+ inOrigin = /^\[remote\s+"origin"\]$/u.test(line);
68
+ continue;
69
+ }
70
+ if (!inOrigin)
71
+ continue;
72
+ const url = /^url\s*=\s*(.+)$/u.exec(line)?.[1]?.trim();
73
+ if (url !== undefined && url !== '')
74
+ return withoutCredentials(url);
75
+ }
76
+ return null;
77
+ }
78
+ /**
79
+ * A remote URL with userinfo, query and fragment removed, including scp-style userinfo.
80
+ */
81
+ export function withoutCredentials(url) {
82
+ if (!/^[a-z][a-z0-9+.-]*:\/\//iu.test(url))
83
+ return url.replace(/^(\/\/)?[^\s]*@(?=[^/]+(?:\/|:|$))/u, '$1').split(/[?#]/u, 1)[0] ?? '';
84
+ try {
85
+ const parsed = new URL(url);
86
+ parsed.username = '';
87
+ parsed.password = '';
88
+ parsed.search = '';
89
+ parsed.hash = '';
90
+ return parsed.toString();
91
+ }
92
+ catch {
93
+ return url.replace(/\/\/[^/@]*@/u, '//').split(/[?#]/u, 1)[0] ?? '';
94
+ }
95
+ }
@@ -0,0 +1,28 @@
1
+ import type { CloudRuntime } from '../cloud/runtime.js';
2
+ import type { LocalChat, LocalChatSource } from './transcript.js';
3
+ export interface LocalChatQuery {
4
+ /** Another sessions folder to read instead of the source's own. */
5
+ readonly dir: string | null;
6
+ /** Keep sessions whose working directory is this folder or inside it. */
7
+ readonly folder: string | null;
8
+ /** Keep sessions with a turn at or after this date. */
9
+ readonly since: string | null;
10
+ readonly source: LocalChatSource;
11
+ }
12
+ /** Something about this machine's sessions that the chats found don't show. */
13
+ export interface LocalChatNotice {
14
+ readonly code: 'codex_history_migrated';
15
+ /** How many sessions it is about. */
16
+ readonly count: number;
17
+ readonly message: string;
18
+ }
19
+ export interface LocalChatScan {
20
+ /** Newest first. */
21
+ readonly chats: readonly LocalChat[];
22
+ readonly notices: readonly LocalChatNotice[];
23
+ }
24
+ type FinderRuntime = Pick<CloudRuntime, 'environment' | 'homeDirectory'>;
25
+ /** The source's sessions on this machine that match the query, newest first. */
26
+ export declare function findLocalChats(runtime: FinderRuntime, query: LocalChatQuery): Promise<LocalChatScan>;
27
+ export {};
28
+ //# sourceMappingURL=local-chats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-chats.d.ts","sourceRoot":"","sources":["../../../src/cli/import/local-chats.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAIxD,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAc,MAAM,iBAAiB,CAAC;AAK9E,MAAM,WAAW,cAAc;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,uDAAuD;IACvD,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;CACpC;AAED,+EAA+E;AAC/E,MAAM,WAAW,eAAe;IAC5B,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;IACxC,qCAAqC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC1B,oBAAoB;IACpB,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;CAChD;AAED,KAAK,aAAa,GAAG,IAAI,CAAC,YAAY,EAAE,aAAa,GAAG,eAAe,CAAC,CAAC;AAEzE,gFAAgF;AAChF,wBAAsB,cAAc,CAChC,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CA+BxB"}
@@ -0,0 +1,129 @@
1
+ // Finds the Claude Code and Codex chats on this machine that a query asks for.
2
+ import { readdir, readFile, stat } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { UsageError } from '../cloud/command.js';
5
+ import { claudeCodeFolder, parseClaudeCodeTranscript } from './claude-code.js';
6
+ import { codexHome, parseCodexRollout, readCodexThreads } from './codex.js';
7
+ import { createRepositoryFinder, withoutCredentials } from './git.js';
8
+ /** A transcript file larger than this is not read. */
9
+ const MAX_FILE_BYTES = 64 * 1024 * 1024;
10
+ /** The source's sessions on this machine that match the query, newest first. */
11
+ export async function findLocalChats(runtime, query) {
12
+ if (query.dir !== null)
13
+ await readdir(query.dir).catch(() => {
14
+ throw new UsageError(`--dir ${query.dir} is not a folder this can read.`);
15
+ });
16
+ const since = query.since === null ? null : new Date(query.since);
17
+ const within = query.folder === null ? null : path.resolve(query.folder);
18
+ const matches = (updatedAt, cwd) => (since === null || new Date(updatedAt) >= since) &&
19
+ (within === null || insideFolder(cwd, within));
20
+ const sessions = query.source === 'claude-code'
21
+ ? await claudeCodeSessions(runtime, query.dir)
22
+ : await codexSessions(runtime, query.dir);
23
+ const repositoryOf = createRepositoryFinder();
24
+ const chats = [];
25
+ for (const file of sessions.files) {
26
+ const stats = await stat(file).catch(() => null);
27
+ if (stats === null || stats.size > MAX_FILE_BYTES)
28
+ continue;
29
+ const text = await readFile(file, 'utf8').catch(() => null);
30
+ if (text === null)
31
+ continue;
32
+ const parsed = sessions.parse(text, file);
33
+ if (parsed === null || !matches(parsed.updatedAt, parsed.cwd))
34
+ continue;
35
+ chats.push(await withRepository(parsed, repositoryOf));
36
+ }
37
+ const notices = sessions.notices(matches);
38
+ return {
39
+ chats: chats.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)),
40
+ notices,
41
+ };
42
+ }
43
+ async function claudeCodeSessions(runtime, dir) {
44
+ const folder = dir ?? claudeCodeFolder(runtime.environment, runtime.homeDirectory);
45
+ return {
46
+ files: await claudeCodeFiles(folder),
47
+ notices: () => [],
48
+ parse: parseClaudeCodeTranscript,
49
+ };
50
+ }
51
+ /**
52
+ * Codex rollouts, titled as Codex lists them. Its own state database is read only for its own
53
+ * sessions folder: a `--dir` is a copy, whose threads that database doesn't describe.
54
+ */
55
+ async function codexSessions(runtime, dir) {
56
+ const home = codexHome(runtime.environment, runtime.homeDirectory);
57
+ const threads = dir === null ? await readCodexThreads(home) : null;
58
+ const titles = new Map((threads ?? []).map((thread) => [thread.id, thread.title]));
59
+ return {
60
+ files: await codexFiles(dir ?? path.join(home, 'sessions')),
61
+ notices: (matches) => migratedNotice(threads ?? [], matches),
62
+ parse: (text, file) => parseCodexRollout(text, file, (id) => titles.get(id) ?? null),
63
+ };
64
+ }
65
+ /** Codex threads whose history moved out of rollout files, which this can't read yet. */
66
+ function migratedNotice(threads, matches) {
67
+ const count = threads.filter((thread) => !thread.hasRollout && matches(thread.updatedAt, thread.cwd)).length;
68
+ if (count === 0)
69
+ return [];
70
+ const chats = count === 1 ? '1 Codex chat has' : `${count} Codex chats have`;
71
+ return [
72
+ {
73
+ code: 'codex_history_migrated',
74
+ count,
75
+ message: `${chats} no rollout file on this machine, so it can't be imported yet: Codex keeps its history in its own database after \`codex migrate-rollouts\`.`,
76
+ },
77
+ ];
78
+ }
79
+ async function withRepository(parsed, repositoryOf) {
80
+ const { recordedRemoteUrl, ...chat } = parsed;
81
+ const onDisk = await repositoryOf(chat.cwd);
82
+ // The remote Codex recorded when the session ran outlives a checkout that has since moved.
83
+ const remoteUrl = recordedRemoteUrl === null
84
+ ? (onDisk?.remoteUrl ?? null)
85
+ : withoutCredentials(recordedRemoteUrl);
86
+ const root = onDisk?.root ?? null;
87
+ return {
88
+ ...chat,
89
+ repository: root === null && remoteUrl === null ? null : { remoteUrl, root },
90
+ };
91
+ }
92
+ function insideFolder(cwd, folder) {
93
+ if (cwd === null)
94
+ return false;
95
+ const relative = path.relative(folder, cwd);
96
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
97
+ }
98
+ async function entries(folder) {
99
+ return await readdir(folder, { withFileTypes: true }).catch(() => []);
100
+ }
101
+ /** `projects/<project>/<session>.jsonl`; deeper files are a session's subagents. */
102
+ async function claudeCodeFiles(folder) {
103
+ const files = [];
104
+ for (const project of await entries(folder)) {
105
+ if (!project.isDirectory())
106
+ continue;
107
+ for (const file of await entries(path.join(folder, project.name)))
108
+ if (file.isFile() && file.name.endsWith('.jsonl'))
109
+ files.push(path.join(folder, project.name, file.name));
110
+ }
111
+ return files;
112
+ }
113
+ /** `sessions/YYYY/MM/DD/rollout-*.jsonl`; compressed rollouts are left out. */
114
+ async function codexFiles(folder) {
115
+ const files = [];
116
+ const visit = async (dir, depth) => {
117
+ for (const entry of await entries(dir)) {
118
+ const full = path.join(dir, entry.name);
119
+ if (entry.isDirectory() && depth < 3)
120
+ await visit(full, depth + 1);
121
+ else if (entry.isFile() &&
122
+ entry.name.startsWith('rollout-') &&
123
+ entry.name.endsWith('.jsonl'))
124
+ files.push(full);
125
+ }
126
+ };
127
+ await visit(folder, 0);
128
+ return files;
129
+ }
@@ -0,0 +1,37 @@
1
+ import type { LocalChat, LocalChatMessage, LocalChatSource } from './transcript.js';
2
+ /** What `dexto_chat_import` takes; its bounds are the server's too. */
3
+ export interface ChatImportInput {
4
+ readonly cwd?: string;
5
+ readonly gitBranch?: string;
6
+ readonly messages: readonly LocalChatMessage[];
7
+ readonly source: LocalChatSource;
8
+ readonly sourceSessionId: string;
9
+ readonly title: string;
10
+ }
11
+ export declare const MAX_IMPORT_MESSAGES = 2000;
12
+ export declare const MAX_MESSAGE_CHARS = 64000;
13
+ export declare const MAX_IMPORT_CHARS = 4000000;
14
+ export declare function truncate(text: string, max: number): string;
15
+ export declare function redactSecrets(text: string): {
16
+ counts: Record<string, number>;
17
+ text: string;
18
+ };
19
+ export type PreparedImport = {
20
+ readonly input: ChatImportInput;
21
+ readonly kind: 'ready';
22
+ /** How many of the session's oldest messages were left out to fit the import's bounds. */
23
+ readonly leftOut: number;
24
+ /** How many of each kind of secret were redacted, counted from the markers sent. */
25
+ readonly redactions: Readonly<Record<string, number>>;
26
+ /** How many messages were cut to the size limit. */
27
+ readonly truncated: number;
28
+ } | {
29
+ readonly kind: 'skipped';
30
+ readonly reason: string;
31
+ };
32
+ /**
33
+ * The chat as it is sent: redacted, and each message within the size limit. A session longer than
34
+ * the import's bounds keeps its newest messages, after a note that says how many were left out.
35
+ */
36
+ export declare function toImportInput(chat: Pick<LocalChat, 'cwd' | 'gitBranch' | 'id' | 'messages' | 'source' | 'title'>): PreparedImport;
37
+ //# sourceMappingURL=redaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../../../src/cli/import/redaction.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEpF,uEAAuE;AACvE,MAAM,WAAW,eAAe;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC/C,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,mBAAmB,OAAQ,CAAC;AACzC,eAAO,MAAM,iBAAiB,QAAS,CAAC;AACxC,eAAO,MAAM,gBAAgB,UAAY,CAAC;AAM1C,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAI1D;AAsBD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAuC5F;AAED,MAAM,MAAM,cAAc,GACpB;IACI,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,0FAA0F;IAC1F,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,oDAAoD;IACpD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC9B,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAgB5D;;;GAGG;AACH,wBAAgB,aAAa,CACzB,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,GAAG,WAAW,GAAG,IAAI,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAC,GACpF,cAAc,CA0BhB"}
@@ -0,0 +1,148 @@
1
+ // Turns a local chat into what `dexto_chat_import` takes: redacted here, and within its bounds.
2
+ export const MAX_IMPORT_MESSAGES = 2_000;
3
+ export const MAX_MESSAGE_CHARS = 64_000;
4
+ export const MAX_IMPORT_CHARS = 4_000_000;
5
+ const MAX_TITLE_CHARS = 200;
6
+ const MAX_CWD_CHARS = 1_024;
7
+ const MAX_BRANCH_CHARS = 255;
8
+ const SOURCE_SESSION_ID = /^[A-Za-z0-9._:-]{1,200}$/u;
9
+ export function truncate(text, max) {
10
+ if (text.length <= max)
11
+ return text;
12
+ const marker = `\n… [${text.length - max} more characters left out]`;
13
+ return `${text.slice(0, Math.max(0, max - marker.length))}${marker}`;
14
+ }
15
+ /** Credential shapes, most specific first; each match becomes `[redacted:<kind>]`. */
16
+ const SECRET_PATTERNS = [
17
+ ['private_key', /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/gu],
18
+ ['secret_key', /\bsk-[A-Za-z0-9_-]{20,}/gu],
19
+ ['secret_key', /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9_-]{8,}/gu],
20
+ ['gitlab_token', /\bglpat-[A-Za-z0-9_-]{8,}/gu],
21
+ ['github_token', /\b(?:gh[pousr]_[A-Za-z0-9]{36,}|github_pat_[A-Za-z0-9_]{22,})/gu],
22
+ ['aws_access_key', /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/gu],
23
+ ['slack_token', /\bxox[abprs]-[A-Za-z0-9-]{10,}/gu],
24
+ ['google_api_key', /\bAIza[0-9A-Za-z_-]{35}/gu],
25
+ ['jwt', /\beyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/gu],
26
+ ['bearer_token', /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/gu],
27
+ ['auth_token', /\bAuthorization\s*:\s*(?:Basic|Token)\s+[A-Za-z0-9._~+/=-]{8,}/giu],
28
+ ];
29
+ /** `password = value` and the like: the name stays, the value goes. */
30
+ const SECRET_ASSIGNMENT = /\b([A-Za-z_][A-Za-z0-9_-]*)(\\*["']?\s*[:=]\s*\\*["']?)([^\s"'[]{8,})/gu;
31
+ const REDACTED_MARKER = /\[redacted:([a-z_]+)\]/gu;
32
+ export function redactSecrets(text) {
33
+ const counts = {};
34
+ const count = (kind) => {
35
+ counts[kind] = (counts[kind] ?? 0) + 1;
36
+ };
37
+ let redacted = text;
38
+ for (const [kind, pattern] of SECRET_PATTERNS)
39
+ redacted = redacted.replace(pattern, () => {
40
+ count(kind);
41
+ return `[redacted:${kind}]`;
42
+ });
43
+ redacted = redacted.replace(/(:\/\/[^\s/:@]+:)([^\s/]+)(@)/gu, (_match, prefix, _password, suffix) => {
44
+ count('url_password');
45
+ return `${prefix}[redacted:url_password]${suffix}`;
46
+ });
47
+ redacted = redacted.replace(SECRET_ASSIGNMENT, (match, name, separator, value) => {
48
+ const words = name
49
+ .replace(/([a-z0-9])([A-Z])/gu, '$1_$2')
50
+ .toLowerCase()
51
+ .split(/[_-]+/u);
52
+ const secret = words.some((word) => ['password', 'passwd', 'pass', 'secret', 'token'].includes(word)) ||
53
+ words.some((word, index) => ['api', 'private', 'access'].includes(word) && words[index + 1] === 'key');
54
+ if (!secret || /^z\.\w+\(/u.test(value))
55
+ return match;
56
+ count('assignment');
57
+ return `${name}${separator}[redacted:assignment]`;
58
+ });
59
+ return { counts, text: redacted };
60
+ }
61
+ const SOURCE_NAMES = {
62
+ 'claude-code': 'Claude Code',
63
+ codex: 'Codex',
64
+ };
65
+ /** The first message of a chat whose oldest messages were left out, saying so. */
66
+ function leftOutNote(source, leftOut) {
67
+ const what = leftOut === 1
68
+ ? 'the first message of this session was'
69
+ : `the first ${leftOut.toLocaleString('en-US')} messages of this session were`;
70
+ return `[Imported from ${SOURCE_NAMES[source]}: ${what} left out. Dexto imports up to a session's newest ${MAX_IMPORT_MESSAGES.toLocaleString('en-US')} messages.]`;
71
+ }
72
+ /**
73
+ * The chat as it is sent: redacted, and each message within the size limit. A session longer than
74
+ * the import's bounds keeps its newest messages, after a note that says how many were left out.
75
+ */
76
+ export function toImportInput(chat) {
77
+ if (!SOURCE_SESSION_ID.test(chat.id))
78
+ return { kind: 'skipped', reason: "Its session id isn't one Dexto takes." };
79
+ let truncated = 0;
80
+ const bounded = chat.messages.map((message) => {
81
+ const { text } = redactSecrets(message.text);
82
+ if (text.length > MAX_MESSAGE_CHARS)
83
+ truncated += 1;
84
+ return {
85
+ createdAt: message.createdAt,
86
+ role: message.role,
87
+ text: truncate(text, MAX_MESSAGE_CHARS),
88
+ };
89
+ });
90
+ const { leftOut, messages } = newestWithin(bounded, chat.source);
91
+ const title = redactSecrets(chat.title).text.slice(0, MAX_TITLE_CHARS);
92
+ const cwd = chat.cwd === null ? null : redactSecrets(chat.cwd).text;
93
+ const gitBranch = chat.gitBranch === null ? null : redactSecrets(chat.gitBranch).text;
94
+ const input = {
95
+ ...(cwd === null || cwd.length > MAX_CWD_CHARS ? {} : { cwd }),
96
+ ...(gitBranch === null || gitBranch.length > MAX_BRANCH_CHARS ? {} : { gitBranch }),
97
+ messages,
98
+ source: chat.source,
99
+ sourceSessionId: chat.id,
100
+ title: title.trim() === '' ? 'Imported chat' : title,
101
+ };
102
+ return { input, kind: 'ready', leftOut, redactions: redactionCounts(input), truncated };
103
+ }
104
+ /**
105
+ * The messages as they are when they fit the import's message and character bounds; otherwise a
106
+ * note saying how many were left out, then the newest messages that fit alongside it.
107
+ */
108
+ function newestWithin(messages, source) {
109
+ const chars = messages.reduce((sum, message) => sum + message.text.length, 0);
110
+ if (messages.length <= MAX_IMPORT_MESSAGES && chars <= MAX_IMPORT_CHARS)
111
+ return { leftOut: 0, messages: [...messages] };
112
+ // The note's own length depends on the count it states; the widest count fits every count.
113
+ const noteChars = leftOutNote(source, messages.length).length;
114
+ const kept = [];
115
+ let total = noteChars;
116
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
117
+ const message = messages[index];
118
+ if (message === undefined)
119
+ break;
120
+ if (kept.length === MAX_IMPORT_MESSAGES - 1)
121
+ break;
122
+ if (total + message.text.length > MAX_IMPORT_CHARS)
123
+ break;
124
+ kept.push(message);
125
+ total += message.text.length;
126
+ }
127
+ kept.reverse();
128
+ const leftOut = messages.length - kept.length;
129
+ // Date the note before all retained messages, including when source timestamps tie or regress.
130
+ const lastLeftOut = messages[leftOut - 1];
131
+ if (lastLeftOut === undefined)
132
+ return { leftOut: 0, messages: kept };
133
+ const note = {
134
+ createdAt: new Date(Math.min(Date.parse(lastLeftOut.createdAt), ...kept.map((message) => Date.parse(message.createdAt) - 1))).toISOString(),
135
+ role: 'assistant',
136
+ text: leftOutNote(source, leftOut),
137
+ };
138
+ return { leftOut, messages: [note, ...kept] };
139
+ }
140
+ /** Each `[redacted:<kind>]` in what is sent, including those made while reading tool output. */
141
+ function redactionCounts(input) {
142
+ const counts = {};
143
+ const sent = [input.title, input.cwd ?? '', input.gitBranch ?? ''];
144
+ for (const text of [...sent, ...input.messages.map((message) => message.text)])
145
+ for (const [, kind = ''] of text.matchAll(REDACTED_MARKER))
146
+ counts[kind] = (counts[kind] ?? 0) + 1;
147
+ return counts;
148
+ }
@@ -0,0 +1,16 @@
1
+ export declare const jsonl: (lines: readonly unknown[]) => string;
2
+ /** A synthetic Claude Code session, shaped as the Agent SDK's messages. */
3
+ export declare function claudeSession(sessionId: string): string;
4
+ /** A synthetic Codex rollout, shaped as `RolloutItemWire` lines. */
5
+ export declare function codexRollout(id: string): string;
6
+ /** A Codex `state_5.sqlite` with the `threads` columns an import reads, holding these rows. */
7
+ export declare function writeCodexState(home: string, threads: readonly {
8
+ readonly archived?: boolean;
9
+ readonly cwd: string;
10
+ readonly id: string;
11
+ readonly name?: string | null;
12
+ readonly rolloutPath: string | null;
13
+ readonly title: string;
14
+ readonly updatedAt: string;
15
+ }[]): Promise<void>;
16
+ //# sourceMappingURL=test-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../../../src/cli/import/test-utils.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,KAAK,GAAI,OAAO,SAAS,OAAO,EAAE,WACS,CAAC;AAEzD,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,UAsG9C;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,UAoGtC;AAED,+FAA+F;AAC/F,wBAAsB,eAAe,CACjC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,SAAS;IACd,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC9B,EAAE,GACJ,OAAO,CAAC,IAAI,CAAC,CAuBf"}