@zenalexa/unicli 0.224.1 → 0.225.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 (256) hide show
  1. package/AGENTS.md +5 -5
  2. package/README.md +92 -92
  3. package/README.zh-CN.md +69 -69
  4. package/dist/adapters/_electron/desktop-shared.d.ts.map +1 -1
  5. package/dist/adapters/_electron/desktop-shared.js +2 -1
  6. package/dist/adapters/_electron/desktop-shared.js.map +1 -1
  7. package/dist/adapters/_electron/shared.d.ts +6 -0
  8. package/dist/adapters/_electron/shared.d.ts.map +1 -1
  9. package/dist/adapters/_electron/shared.js +9 -0
  10. package/dist/adapters/_electron/shared.js.map +1 -1
  11. package/dist/adapters/antigravity/extra.js +4 -1
  12. package/dist/adapters/antigravity/extra.js.map +1 -1
  13. package/dist/adapters/chatwise/extra.js +4 -1
  14. package/dist/adapters/chatwise/extra.js.map +1 -1
  15. package/dist/adapters/codex/codex.js +3 -1
  16. package/dist/adapters/codex/codex.js.map +1 -1
  17. package/dist/adapters/codex/extra.js +4 -1
  18. package/dist/adapters/codex/extra.js.map +1 -1
  19. package/dist/adapters/codex/projects.d.ts.map +1 -1
  20. package/dist/adapters/codex/projects.js +3 -1
  21. package/dist/adapters/codex/projects.js.map +1 -1
  22. package/dist/adapters/cursor/cursor.js +6 -1
  23. package/dist/adapters/cursor/cursor.js.map +1 -1
  24. package/dist/adapters/discord-app/discord-app.js +10 -1
  25. package/dist/adapters/discord-app/discord-app.js.map +1 -1
  26. package/dist/adapters/notion-app/notion-app.js +11 -1
  27. package/dist/adapters/notion-app/notion-app.js.map +1 -1
  28. package/dist/adapters/spotify/api.js +36 -6
  29. package/dist/adapters/spotify/api.js.map +1 -1
  30. package/dist/adapters/xiaohongshu/browser-state.d.ts +2 -1
  31. package/dist/adapters/xiaohongshu/browser-state.d.ts.map +1 -1
  32. package/dist/adapters/xiaohongshu/browser-state.js +56 -2
  33. package/dist/adapters/xiaohongshu/browser-state.js.map +1 -1
  34. package/dist/adapters/xiaohongshu/feed.d.ts +24 -0
  35. package/dist/adapters/xiaohongshu/feed.d.ts.map +1 -0
  36. package/dist/adapters/xiaohongshu/feed.js +82 -0
  37. package/dist/adapters/xiaohongshu/feed.js.map +1 -0
  38. package/dist/adapters/xueqiu/extra.js +2 -2
  39. package/dist/adapters/xueqiu/extra.js.map +1 -1
  40. package/dist/browser/cdp-client.d.ts +15 -1
  41. package/dist/browser/cdp-client.d.ts.map +1 -1
  42. package/dist/browser/cdp-client.js +45 -16
  43. package/dist/browser/cdp-client.js.map +1 -1
  44. package/dist/browser/daemon.js +29 -7
  45. package/dist/browser/daemon.js.map +1 -1
  46. package/dist/browser/launcher.d.ts.map +1 -1
  47. package/dist/browser/launcher.js +22 -8
  48. package/dist/browser/launcher.js.map +1 -1
  49. package/dist/browser/local-profiles.d.ts +2 -0
  50. package/dist/browser/local-profiles.d.ts.map +1 -1
  51. package/dist/browser/local-profiles.js +42 -2
  52. package/dist/browser/local-profiles.js.map +1 -1
  53. package/dist/browser/page.d.ts +2 -2
  54. package/dist/browser/page.d.ts.map +1 -1
  55. package/dist/browser/page.js +2 -2
  56. package/dist/browser/page.js.map +1 -1
  57. package/dist/browser/protocol.d.ts +7 -0
  58. package/dist/browser/protocol.d.ts.map +1 -1
  59. package/dist/browser/protocol.js +5 -0
  60. package/dist/browser/protocol.js.map +1 -1
  61. package/dist/cli.d.ts.map +1 -1
  62. package/dist/cli.js +4 -3
  63. package/dist/cli.js.map +1 -1
  64. package/dist/commands/architecture.d.ts +3 -1
  65. package/dist/commands/architecture.d.ts.map +1 -1
  66. package/dist/commands/architecture.js +13 -5
  67. package/dist/commands/architecture.js.map +1 -1
  68. package/dist/commands/browser/index.d.ts.map +1 -1
  69. package/dist/commands/browser/index.js +26 -4
  70. package/dist/commands/browser/index.js.map +1 -1
  71. package/dist/commands/delivery.d.ts.map +1 -1
  72. package/dist/commands/delivery.js.map +1 -1
  73. package/dist/commands/describe.d.ts.map +1 -1
  74. package/dist/commands/describe.js +104 -9
  75. package/dist/commands/describe.js.map +1 -1
  76. package/dist/commands/dispatch.d.ts.map +1 -1
  77. package/dist/commands/dispatch.js +3 -13
  78. package/dist/commands/dispatch.js.map +1 -1
  79. package/dist/commands/do.d.ts +16 -13
  80. package/dist/commands/do.d.ts.map +1 -1
  81. package/dist/commands/do.js +72 -18
  82. package/dist/commands/do.js.map +1 -1
  83. package/dist/commands/social.d.ts.map +1 -1
  84. package/dist/commands/social.js +3 -13
  85. package/dist/commands/social.js.map +1 -1
  86. package/dist/core/architecture-tree.d.ts +26 -6
  87. package/dist/core/architecture-tree.d.ts.map +1 -1
  88. package/dist/core/architecture-tree.js +184 -40
  89. package/dist/core/architecture-tree.js.map +1 -1
  90. package/dist/core/capability-matrix.d.ts +63 -0
  91. package/dist/core/capability-matrix.d.ts.map +1 -0
  92. package/dist/core/capability-matrix.js +316 -0
  93. package/dist/core/capability-matrix.js.map +1 -0
  94. package/dist/core/command-contract-lint.d.ts.map +1 -1
  95. package/dist/core/command-contract-lint.js +3 -1
  96. package/dist/core/command-contract-lint.js.map +1 -1
  97. package/dist/core/command-contract.d.ts +10 -3
  98. package/dist/core/command-contract.d.ts.map +1 -1
  99. package/dist/core/command-contract.js +87 -5
  100. package/dist/core/command-contract.js.map +1 -1
  101. package/dist/discovery/aliases.d.ts.map +1 -1
  102. package/dist/discovery/aliases.js +26 -0
  103. package/dist/discovery/aliases.js.map +1 -1
  104. package/dist/discovery/core-catalog.d.ts +1 -0
  105. package/dist/discovery/core-catalog.d.ts.map +1 -1
  106. package/dist/discovery/core-catalog.js +22 -5
  107. package/dist/discovery/core-catalog.js.map +1 -1
  108. package/dist/discovery/intents.d.ts +31 -4
  109. package/dist/discovery/intents.d.ts.map +1 -1
  110. package/dist/discovery/intents.js +166 -3
  111. package/dist/discovery/intents.js.map +1 -1
  112. package/dist/discovery/loader.d.ts.map +1 -1
  113. package/dist/discovery/loader.js +3 -0
  114. package/dist/discovery/loader.js.map +1 -1
  115. package/dist/discovery/search.d.ts.map +1 -1
  116. package/dist/discovery/search.js +10 -1
  117. package/dist/discovery/search.js.map +1 -1
  118. package/dist/engine/cascade.d.ts.map +1 -1
  119. package/dist/engine/cascade.js +21 -25
  120. package/dist/engine/cascade.js.map +1 -1
  121. package/dist/engine/chromium-cookies.d.ts +27 -0
  122. package/dist/engine/chromium-cookies.d.ts.map +1 -1
  123. package/dist/engine/chromium-cookies.js +53 -14
  124. package/dist/engine/chromium-cookies.js.map +1 -1
  125. package/dist/engine/cookie-capture.d.ts +30 -0
  126. package/dist/engine/cookie-capture.d.ts.map +1 -0
  127. package/dist/engine/cookie-capture.js +104 -0
  128. package/dist/engine/cookie-capture.js.map +1 -0
  129. package/dist/engine/cookie-extractor.d.ts.map +1 -1
  130. package/dist/engine/cookie-extractor.js +4 -12
  131. package/dist/engine/cookie-extractor.js.map +1 -1
  132. package/dist/engine/cookie-refresh.d.ts +59 -8
  133. package/dist/engine/cookie-refresh.d.ts.map +1 -1
  134. package/dist/engine/cookie-refresh.js +80 -58
  135. package/dist/engine/cookie-refresh.js.map +1 -1
  136. package/dist/engine/cookie-source.d.ts +110 -0
  137. package/dist/engine/cookie-source.d.ts.map +1 -0
  138. package/dist/engine/cookie-source.js +207 -0
  139. package/dist/engine/cookie-source.js.map +1 -0
  140. package/dist/engine/cookies.d.ts +37 -25
  141. package/dist/engine/cookies.d.ts.map +1 -1
  142. package/dist/engine/cookies.js +58 -157
  143. package/dist/engine/cookies.js.map +1 -1
  144. package/dist/engine/delivery/index.d.ts +2 -1
  145. package/dist/engine/delivery/index.d.ts.map +1 -1
  146. package/dist/engine/delivery/index.js +2 -1
  147. package/dist/engine/delivery/index.js.map +1 -1
  148. package/dist/engine/delivery/spec.d.ts +44 -0
  149. package/dist/engine/delivery/spec.d.ts.map +1 -0
  150. package/dist/engine/delivery/spec.js +82 -0
  151. package/dist/engine/delivery/spec.js.map +1 -0
  152. package/dist/engine/executor.d.ts +17 -6
  153. package/dist/engine/executor.d.ts.map +1 -1
  154. package/dist/engine/executor.js +131 -68
  155. package/dist/engine/executor.js.map +1 -1
  156. package/dist/engine/kernel/stages.d.ts.map +1 -1
  157. package/dist/engine/kernel/stages.js +14 -4
  158. package/dist/engine/kernel/stages.js.map +1 -1
  159. package/dist/engine/objective/catalog.d.ts +23 -0
  160. package/dist/engine/objective/catalog.d.ts.map +1 -0
  161. package/dist/engine/objective/catalog.js +42 -0
  162. package/dist/engine/objective/catalog.js.map +1 -0
  163. package/dist/engine/objective/delivery.d.ts +18 -0
  164. package/dist/engine/objective/delivery.d.ts.map +1 -0
  165. package/dist/engine/objective/delivery.js +64 -0
  166. package/dist/engine/objective/delivery.js.map +1 -0
  167. package/dist/engine/objective/index.d.ts +20 -0
  168. package/dist/engine/objective/index.d.ts.map +1 -0
  169. package/dist/engine/objective/index.js +20 -0
  170. package/dist/engine/objective/index.js.map +1 -0
  171. package/dist/engine/objective/media-playback.d.ts +17 -0
  172. package/dist/engine/objective/media-playback.d.ts.map +1 -0
  173. package/dist/engine/objective/media-playback.js +186 -0
  174. package/dist/engine/objective/media-playback.js.map +1 -0
  175. package/dist/engine/objective/output.d.ts +20 -0
  176. package/dist/engine/objective/output.d.ts.map +1 -0
  177. package/dist/engine/objective/output.js +88 -0
  178. package/dist/engine/objective/output.js.map +1 -0
  179. package/dist/engine/objective/planner.d.ts +17 -0
  180. package/dist/engine/objective/planner.d.ts.map +1 -0
  181. package/dist/engine/objective/planner.js +60 -0
  182. package/dist/engine/objective/planner.js.map +1 -0
  183. package/dist/engine/objective/types.d.ts +66 -0
  184. package/dist/engine/objective/types.d.ts.map +1 -0
  185. package/dist/engine/objective/types.js +16 -0
  186. package/dist/engine/objective/types.js.map +1 -0
  187. package/dist/engine/runtime.d.ts.map +1 -1
  188. package/dist/engine/runtime.js +9 -4
  189. package/dist/engine/runtime.js.map +1 -1
  190. package/dist/engine/step-observer.d.ts +62 -0
  191. package/dist/engine/step-observer.d.ts.map +1 -0
  192. package/dist/engine/step-observer.js +38 -0
  193. package/dist/engine/step-observer.js.map +1 -0
  194. package/dist/engine/steps/browser-helpers.d.ts.map +1 -1
  195. package/dist/engine/steps/browser-helpers.js +36 -8
  196. package/dist/engine/steps/browser-helpers.js.map +1 -1
  197. package/dist/engine/steps/fetch-text.d.ts.map +1 -1
  198. package/dist/engine/steps/fetch-text.js +106 -33
  199. package/dist/engine/steps/fetch-text.js.map +1 -1
  200. package/dist/engine/steps/fetch.d.ts +20 -0
  201. package/dist/engine/steps/fetch.d.ts.map +1 -1
  202. package/dist/engine/steps/fetch.js.map +1 -1
  203. package/dist/engine/steps/index.d.ts +2 -0
  204. package/dist/engine/steps/index.d.ts.map +1 -1
  205. package/dist/engine/steps/index.js +2 -0
  206. package/dist/engine/steps/index.js.map +1 -1
  207. package/dist/engine/steps/split-text.d.ts +26 -0
  208. package/dist/engine/steps/split-text.d.ts.map +1 -0
  209. package/dist/engine/steps/split-text.js +89 -0
  210. package/dist/engine/steps/split-text.js.map +1 -0
  211. package/dist/engine/steps/to-entries.d.ts +9 -0
  212. package/dist/engine/steps/to-entries.d.ts.map +1 -0
  213. package/dist/engine/steps/to-entries.js +27 -0
  214. package/dist/engine/steps/to-entries.js.map +1 -0
  215. package/dist/fast-path/handlers/discovery.d.ts.map +1 -1
  216. package/dist/fast-path/handlers/discovery.js +7 -7
  217. package/dist/fast-path/handlers/discovery.js.map +1 -1
  218. package/dist/manifest-compact.txt +3 -2
  219. package/dist/manifest.json +183 -17
  220. package/dist/mcp/tools.js +1 -1
  221. package/dist/mcp/tools.js.map +1 -1
  222. package/dist/output/auth-guidance.d.ts +17 -3
  223. package/dist/output/auth-guidance.d.ts.map +1 -1
  224. package/dist/output/auth-guidance.js +27 -3
  225. package/dist/output/auth-guidance.js.map +1 -1
  226. package/dist/output/error-map.d.ts.map +1 -1
  227. package/dist/output/error-map.js +4 -0
  228. package/dist/output/error-map.js.map +1 -1
  229. package/package.json +4 -2
  230. package/server.json +2 -2
  231. package/skills/unicli/SKILL.md +1 -1
  232. package/skills/unicli-claude-code/SKILL.md +1 -1
  233. package/skills/unicli-hermes/SKILL.md +1 -1
  234. package/src/adapters/12306/price.yaml +91 -0
  235. package/src/adapters/12306/stations.yaml +52 -0
  236. package/src/adapters/12306/trains.yaml +129 -0
  237. package/src/adapters/_electron/desktop-shared.ts +5 -1
  238. package/src/adapters/_electron/shared.ts +15 -0
  239. package/src/adapters/antigravity/extra.ts +10 -1
  240. package/src/adapters/chatwise/extra.ts +10 -1
  241. package/src/adapters/codex/codex.ts +6 -0
  242. package/src/adapters/codex/extra.ts +10 -1
  243. package/src/adapters/codex/projects.ts +9 -1
  244. package/src/adapters/cursor/cursor.ts +9 -0
  245. package/src/adapters/discord-app/discord-app.ts +16 -1
  246. package/src/adapters/macos/brightness.yaml +6 -3
  247. package/src/adapters/macos/calendar-list.yaml +9 -11
  248. package/src/adapters/macos/calendar-today.yaml +1 -1
  249. package/src/adapters/macos/safari-url.yaml +8 -4
  250. package/src/adapters/maoyan/hot.yaml +1 -1
  251. package/src/adapters/notion-app/notion-app.ts +17 -1
  252. package/src/adapters/spotify/api.ts +54 -8
  253. package/src/adapters/weibo/trending.yaml +2 -0
  254. package/src/adapters/xiaohongshu/browser-state.ts +59 -2
  255. package/src/adapters/xiaohongshu/feed.ts +103 -0
  256. package/src/adapters/xueqiu/extra.ts +5 -2
@@ -0,0 +1,104 @@
1
+ //! @owner src::engine::cookie_capture
2
+ //! @does Parses response Set-Cookie headers and merges them into a request Cookie header, with same-host scoping
3
+ //! @needs none (pure string/URL logic)
4
+ //! @feeds ./steps/fetch-text, ./steps/fetch (capture_cookies option)
5
+ //! @breaks never throws; unparseable input yields conservative (empty / false) results
6
+ //! @invariants capture is host-scoped; a cross-site final URL never contributes cookies
7
+ //! @side-effects none (pure)
8
+ //! @perf O(n) over the Set-Cookie line count
9
+ //! @concurrency pure; safe to call concurrently
10
+ //! @test tests/unit/engine/cookie-capture.test.ts
11
+ //! @stability stable
12
+ //! @since 2026-05-30
13
+ /**
14
+ * Parse an array of raw `Set-Cookie` header lines into a `name → value` map.
15
+ * Only the leading `name=value` pair of each line is kept; cookie attributes
16
+ * (Path, Domain, HttpOnly, …) after the first `;` are intentionally dropped —
17
+ * we re-send the pair on same-host requests and do not honor attribute scoping
18
+ * beyond the host check performed by the caller.
19
+ */
20
+ export function parseSetCookiePairs(lines) {
21
+ const out = {};
22
+ for (const line of lines) {
23
+ const head = line.split(";", 1)[0]?.trim() ?? "";
24
+ const eq = head.indexOf("=");
25
+ if (eq <= 0)
26
+ continue;
27
+ const name = head.slice(0, eq).trim();
28
+ const value = head.slice(eq + 1).trim();
29
+ if (name)
30
+ out[name] = value;
31
+ }
32
+ return out;
33
+ }
34
+ /**
35
+ * Merge captured cookie pairs onto an existing `Cookie` request header.
36
+ * Existing cookies are preserved; a captured cookie of the same name wins and
37
+ * moves to the end. Returns the original header unchanged when nothing was
38
+ * captured.
39
+ */
40
+ export function mergeCookieHeader(existing, captured) {
41
+ const capturedNames = Object.keys(captured);
42
+ if (capturedNames.length === 0)
43
+ return existing ?? "";
44
+ const pairs = new Map();
45
+ if (existing) {
46
+ for (const part of existing.split(";")) {
47
+ const seg = part.trim();
48
+ if (!seg)
49
+ continue;
50
+ const eq = seg.indexOf("=");
51
+ if (eq <= 0)
52
+ continue;
53
+ pairs.set(seg.slice(0, eq).trim(), seg.slice(eq + 1).trim());
54
+ }
55
+ }
56
+ // Drop survivors that a captured cookie overrides, so captured wins and
57
+ // appears at the end in capture order.
58
+ for (const name of capturedNames) {
59
+ pairs.delete(name);
60
+ }
61
+ const merged = [];
62
+ for (const [name, value] of pairs)
63
+ merged.push(`${name}=${value}`);
64
+ for (const name of capturedNames)
65
+ merged.push(`${name}=${captured[name]}`);
66
+ return merged.join("; ");
67
+ }
68
+ /**
69
+ * Conservative same-host check for cookie capture. Returns true only when the
70
+ * final response URL shares a registrable domain with the original request URL.
71
+ *
72
+ * Limitation (stated honestly): without a Public Suffix List we approximate the
73
+ * registrable domain as the last two dot-labels. This is correct for
74
+ * single-label TLDs (12306.cn, example.com) and ERRS ON THE SIDE OF REJECTION
75
+ * for multi-label eTLDs (foo.co.uk vs bar.co.uk both reduce to "co.uk" → would
76
+ * be treated as same; to avoid the FALSE-ACCEPT that introduces, we additionally
77
+ * require the full hostnames to be equal OR one to be a dot-suffix of the other).
78
+ * Rejecting is safe (cookies simply are not captured); false-accept would leak
79
+ * cookies, which we must not do.
80
+ */
81
+ export function sameRegistrableHost(requestUrl, finalUrl) {
82
+ let reqHost;
83
+ let finHost;
84
+ try {
85
+ reqHost = new URL(requestUrl).hostname.toLowerCase();
86
+ finHost = new URL(finalUrl).hostname.toLowerCase();
87
+ }
88
+ catch {
89
+ return false;
90
+ }
91
+ if (!reqHost || !finHost)
92
+ return false;
93
+ if (reqHost === finHost)
94
+ return true;
95
+ // Accept a subdomain relationship on the same registrable domain only when
96
+ // one host is a dot-suffix of the other (www.12306.cn ↔ kyfw.12306.cn share
97
+ // the suffix 12306.cn, and one is not a suffix of the other), so we compare
98
+ // the last-two-labels registrable domain AND require it to be non-trivial.
99
+ const reg = (h) => h.split(".").slice(-2).join(".");
100
+ const reqReg = reg(reqHost);
101
+ const finReg = reg(finHost);
102
+ return reqReg.length > 0 && reqReg === finReg;
103
+ }
104
+ //# sourceMappingURL=cookie-capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookie-capture.js","sourceRoot":"","sources":["../../src/engine/cookie-capture.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"cookie-extractor.d.ts","sourceRoot":"","sources":["../../src/engine/cookie-extractor.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CA+BjC;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,MAAM,CAaR"}
1
+ {"version":3,"file":"cookie-extractor.d.ts","sourceRoot":"","sources":["../../src/engine/cookie-extractor.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAiBH;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAwBjC;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,MAAM,CAWR"}
@@ -8,22 +8,15 @@
8
8
  */
9
9
  import { writeFileSync, mkdirSync } from "node:fs";
10
10
  import { join } from "node:path";
11
+ import { cookieDir } from "./cookie-source.js";
11
12
  /**
12
13
  * Extract cookies for a domain from a running Chrome instance.
13
14
  * Connects to Chrome's CDP debug port, calls Network.getCookies,
14
15
  * and returns cookies as a flat key-value record.
15
16
  */
16
17
  export async function extractCookiesViaCDP(domain, port) {
17
- const { CDPClient } = await import("../browser/cdp-client.js");
18
- const rawPort = process.env.UNICLI_CDP_PORT;
19
- let cdpPort = port ?? 9222;
20
- if (!port && rawPort) {
21
- const p = parseInt(rawPort, 10);
22
- if (!Number.isInteger(p) || p < 1 || p > 65535) {
23
- throw new Error(`Invalid UNICLI_CDP_PORT: "${rawPort}"`);
24
- }
25
- cdpPort = p;
26
- }
18
+ const { CDPClient, resolveCdpPort } = await import("../browser/cdp-client.js");
19
+ const cdpPort = resolveCdpPort(port);
27
20
  const client = await CDPClient.connectToChrome(cdpPort);
28
21
  try {
29
22
  const { cookies } = (await client.send("Network.getCookies", {
@@ -52,8 +45,7 @@ export function saveCookies(site, cookies) {
52
45
  if (!/^[a-zA-Z0-9._-]+$/.test(site)) {
53
46
  throw new Error(`Invalid site name: "${site}" — only alphanumeric, dot, dash, underscore allowed`);
54
47
  }
55
- const dir = process.env.UNICLI_COOKIE_DIR ??
56
- join(process.env.HOME ?? "~", ".unicli", "cookies");
48
+ const dir = cookieDir();
57
49
  mkdirSync(dir, { recursive: true });
58
50
  const filePath = join(dir, `${site}.json`);
59
51
  writeFileSync(filePath, JSON.stringify(cookies, null, 2), "utf-8");
@@ -1 +1 @@
1
- {"version":3,"file":"cookie-extractor.js","sourceRoot":"","sources":["../../src/engine/cookie-extractor.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAYjC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAAc,EACd,IAAa;IAEb,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAC;IAC/D,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAC5C,IAAI,OAAO,GAAG,IAAI,IAAI,IAAI,CAAC;IAC3B,IAAI,CAAC,IAAI,IAAI,OAAO,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAChC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC;YAC/C,MAAM,IAAI,KAAK,CAAC,6BAA6B,OAAO,GAAG,CAAC,CAAC;QAC3D,CAAC;QACD,OAAO,GAAG,CAAC,CAAC;IACd,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IAExD,IAAI,CAAC;QACH,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE;YAC3D,IAAI,EAAE;gBACJ,WAAW,MAAM,EAAE;gBACnB,eAAe,MAAM,EAAE;gBACvB,UAAU,MAAM,EAAE;gBAClB,cAAc,MAAM,EAAE;aACvB;SACF,CAAC,CAA6B,CAAC;QAEhC,MAAM,MAAM,GAA2B,EAAE,CAAC;QAC1C,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC;QAC3B,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACvC,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,IAAY,EACZ,OAA+B;IAE/B,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CACb,uBAAuB,IAAI,sDAAsD,CAClF,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GACP,OAAO,CAAC,GAAG,CAAC,iBAAiB;QAC7B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;IACtD,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;IAC3C,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACnE,OAAO,QAAQ,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"cookie-extractor.js","sourceRoot":"","sources":["../../src/engine/cookie-extractor.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAY/C;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAAc,EACd,IAAa;IAEb,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,GACjC,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IAExD,IAAI,CAAC;QACH,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE;YAC3D,IAAI,EAAE;gBACJ,WAAW,MAAM,EAAE;gBACnB,eAAe,MAAM,EAAE;gBACvB,UAAU,MAAM,EAAE;gBAClB,cAAc,MAAM,EAAE;aACvB;SACF,CAAC,CAA6B,CAAC;QAEhC,MAAM,MAAM,GAA2B,EAAE,CAAC;QAC1C,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC;QAC3B,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACvC,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,IAAY,EACZ,OAA+B;IAE/B,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CACb,uBAAuB,IAAI,sDAAsD,CAClF,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;IACxB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;IAC3C,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACnE,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -1,13 +1,64 @@
1
1
  /**
2
- * Smart Cookie Refresh — automatically refresh expired cookies via CDP.
3
- *
4
- * When an adapter gets a 401/403, this module attempts to navigate Chrome
5
- * to the site's main page (refreshing the session), extract fresh cookies
6
- * via CDP, and write them to the cookie file for subsequent requests.
2
+ * @owner src::engine::cookie-refresh
3
+ * @does Refreshes a site's login session by navigating Chrome to it over
4
+ * CDP and re-reading cookies, surfacing the real cause of a miss
5
+ * (no browser / no cookies / error) instead of a bare boolean.
6
+ * @needs ./cookie-extractor (saveCookies), ../browser/page (lazy),
7
+ * ../browser/cdp-client (resolveCdpPort, lazy)
8
+ * @feeds src::engine::runtime (maybeRefreshCookies — the auto 401/403 path)
9
+ * @breaks never throws — every failure becomes a typed SessionRefreshOutcome
10
+ * @invariants navigation (page.goto) happens BEFORE reading cookies so the live
11
+ * server session is rebuilt; this is the navigate-then-read refresh,
12
+ * deliberately distinct from cookies.ts acquireCookies(skipDisk)
13
+ * which is a pure DB/CDP read with no navigation
14
+ * @side-effects connects to Chrome via CDP, navigates a page, persists cookies to disk
15
+ * @concurrency stateless; the page is opened and closed within one call
16
+ * @test tests/unit/engine/cookie-refresh.test.ts
17
+ * @stability stable
18
+ * @since 2026-05-30
19
+ */
20
+ /** The slice of a live browser page a session refresh needs. */
21
+ export interface PageSession {
22
+ goto(url: string, opts: {
23
+ settleMs: number;
24
+ }): Promise<void>;
25
+ cookies(): Promise<Record<string, string>>;
26
+ close(): Promise<void>;
27
+ }
28
+ /** Injectable IO so the refresh policy is testable without Chrome or a network. */
29
+ export interface SessionRefreshDeps {
30
+ connect(port: number): Promise<PageSession>;
31
+ persist(site: string, cookies: Record<string, string>): void;
32
+ }
33
+ /**
34
+ * Typed result of a refresh attempt. Names WHY a refresh did not happen instead
35
+ * of collapsing connect failure, an empty cookie jar, and unexpected errors all
36
+ * to `false` (the old behavior, which left runtime.ts unable to tell the agent
37
+ * what actually went wrong).
7
38
  */
39
+ export type SessionRefreshOutcome = {
40
+ status: "refreshed";
41
+ site: string;
42
+ cookieCount: number;
43
+ } | {
44
+ status: "no-browser";
45
+ detail: string;
46
+ } | {
47
+ status: "no-cookies";
48
+ detail: string;
49
+ } | {
50
+ status: "error";
51
+ detail: string;
52
+ };
53
+ export declare const defaultSessionRefreshDeps: SessionRefreshDeps;
8
54
  /**
9
- * Attempt to refresh cookies for a site by navigating Chrome to it.
10
- * Returns true if cookies were refreshed, false if not possible.
55
+ * Attempt to refresh cookies for a site by navigating Chrome to it and reading
56
+ * the resulting session cookies. Returns a typed outcome; never throws.
57
+ *
58
+ * Persistence goes through the canonical writer (saveCookies) so the on-disk
59
+ * format always matches what the loaders expect ({name: value}); hand-rolling
60
+ * the write here once emitted an array format the loaders silently rejected,
61
+ * leaving the adapter unauthenticated after a "successful" refresh.
11
62
  */
12
- export declare function refreshCookies(site: string, domain?: string): Promise<boolean>;
63
+ export declare function refreshCookies(site: string, domain?: string, deps?: SessionRefreshDeps): Promise<SessionRefreshOutcome>;
13
64
  //# sourceMappingURL=cookie-refresh.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cookie-refresh.d.ts","sourceRoot":"","sources":["../../src/engine/cookie-refresh.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH;;;GAGG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,CAAC,CA4DlB"}
1
+ {"version":3,"file":"cookie-refresh.d.ts","sourceRoot":"","sources":["../../src/engine/cookie-refresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,gEAAgE;AAChE,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,mFAAmF;AACnF,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC5C,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;CAC9D;AAED;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,GAC7B;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAOxC,eAAO,MAAM,yBAAyB,EAAE,kBAGvC,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,GAAE,kBAA8C,GACnD,OAAO,CAAC,qBAAqB,CAAC,CAuDhC"}
@@ -1,71 +1,93 @@
1
1
  /**
2
- * Smart Cookie Refresh — automatically refresh expired cookies via CDP.
3
- *
4
- * When an adapter gets a 401/403, this module attempts to navigate Chrome
5
- * to the site's main page (refreshing the session), extract fresh cookies
6
- * via CDP, and write them to the cookie file for subsequent requests.
2
+ * @owner src::engine::cookie-refresh
3
+ * @does Refreshes a site's login session by navigating Chrome to it over
4
+ * CDP and re-reading cookies, surfacing the real cause of a miss
5
+ * (no browser / no cookies / error) instead of a bare boolean.
6
+ * @needs ./cookie-extractor (saveCookies), ../browser/page (lazy),
7
+ * ../browser/cdp-client (resolveCdpPort, lazy)
8
+ * @feeds src::engine::runtime (maybeRefreshCookies — the auto 401/403 path)
9
+ * @breaks never throws — every failure becomes a typed SessionRefreshOutcome
10
+ * @invariants navigation (page.goto) happens BEFORE reading cookies so the live
11
+ * server session is rebuilt; this is the navigate-then-read refresh,
12
+ * deliberately distinct from cookies.ts acquireCookies(skipDisk)
13
+ * which is a pure DB/CDP read with no navigation
14
+ * @side-effects connects to Chrome via CDP, navigates a page, persists cookies to disk
15
+ * @concurrency stateless; the page is opened and closed within one call
16
+ * @test tests/unit/engine/cookie-refresh.test.ts
17
+ * @stability stable
18
+ * @since 2026-05-30
7
19
  */
8
- import { mkdirSync, writeFileSync } from "node:fs";
9
- import { join } from "node:path";
10
- import { homedir } from "node:os";
20
+ import { saveCookies } from "./cookie-extractor.js";
21
+ async function defaultConnect(port) {
22
+ const { BrowserPage } = await import("../browser/page.js");
23
+ return BrowserPage.connect(port);
24
+ }
25
+ export const defaultSessionRefreshDeps = {
26
+ connect: defaultConnect,
27
+ persist: saveCookies,
28
+ };
11
29
  /**
12
- * Attempt to refresh cookies for a site by navigating Chrome to it.
13
- * Returns true if cookies were refreshed, false if not possible.
30
+ * Attempt to refresh cookies for a site by navigating Chrome to it and reading
31
+ * the resulting session cookies. Returns a typed outcome; never throws.
32
+ *
33
+ * Persistence goes through the canonical writer (saveCookies) so the on-disk
34
+ * format always matches what the loaders expect ({name: value}); hand-rolling
35
+ * the write here once emitted an array format the loaders silently rejected,
36
+ * leaving the adapter unauthenticated after a "successful" refresh.
14
37
  */
15
- export async function refreshCookies(site, domain) {
38
+ export async function refreshCookies(site, domain, deps = defaultSessionRefreshDeps) {
39
+ let port;
40
+ try {
41
+ const { resolveCdpPort } = await import("../browser/cdp-client.js");
42
+ port = resolveCdpPort();
43
+ }
44
+ catch (err) {
45
+ return {
46
+ status: "error",
47
+ detail: err instanceof Error ? err.message : String(err),
48
+ };
49
+ }
50
+ let page;
16
51
  try {
17
- const { BrowserPage: BP } = await import("../browser/page.js");
18
- // Try connecting to an existing Chrome instance
19
- let port = 9222;
20
- const rawPort = process.env.UNICLI_CDP_PORT;
21
- if (rawPort) {
22
- const p = parseInt(rawPort, 10);
23
- if (Number.isInteger(p) && p >= 1 && p <= 65535) {
24
- port = p;
25
- }
52
+ page = await deps.connect(port);
53
+ }
54
+ catch (err) {
55
+ return {
56
+ status: "no-browser",
57
+ detail: `Could not connect to Chrome on CDP port ${port}: ${err instanceof Error ? err.message : String(err)}`,
58
+ };
59
+ }
60
+ try {
61
+ const targetUrl = domain ? `https://${domain}` : `https://www.${site}.com`;
62
+ await page.goto(targetUrl, { settleMs: 3000 });
63
+ const cookies = await page.cookies();
64
+ if (Object.keys(cookies).length === 0) {
65
+ return {
66
+ status: "no-cookies",
67
+ detail: `No cookies present for ${site} after navigating to ${targetUrl}`,
68
+ };
26
69
  }
27
- let page;
70
+ deps.persist(site, cookies);
71
+ return {
72
+ status: "refreshed",
73
+ site,
74
+ cookieCount: Object.keys(cookies).length,
75
+ };
76
+ }
77
+ catch (err) {
78
+ return {
79
+ status: "error",
80
+ detail: err instanceof Error ? err.message : String(err),
81
+ };
82
+ }
83
+ finally {
28
84
  try {
29
- page = await BP.connect(port);
85
+ await page.close();
30
86
  }
31
87
  catch {
32
- // No Chrome CDP available cannot refresh
33
- return false;
88
+ // REASON: page close is best-effort cleanup; a close failure must not
89
+ // override the refresh outcome already determined above.
34
90
  }
35
- try {
36
- // Navigate to the site's main page to refresh session
37
- const targetUrl = domain
38
- ? `https://${domain}`
39
- : `https://www.${site}.com`;
40
- await page.goto(targetUrl, { settleMs: 3000 });
41
- // Extract cookies
42
- const cookies = await page.cookies();
43
- if (Object.keys(cookies).length === 0) {
44
- return false;
45
- }
46
- // Write cookies to file
47
- const cookiesDir = join(homedir(), ".unicli", "cookies");
48
- mkdirSync(cookiesDir, { recursive: true });
49
- const cookieArray = Object.entries(cookies).map(([name, value]) => ({
50
- name,
51
- value,
52
- domain: domain ?? `${site}.com`,
53
- }));
54
- const cookiePath = join(cookiesDir, `${site}.json`);
55
- writeFileSync(cookiePath, JSON.stringify(cookieArray, null, 2), "utf-8");
56
- return true;
57
- }
58
- finally {
59
- try {
60
- await page.close();
61
- }
62
- catch {
63
- /* best effort */
64
- }
65
- }
66
- }
67
- catch {
68
- return false;
69
91
  }
70
92
  }
71
93
  //# sourceMappingURL=cookie-refresh.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cookie-refresh.js","sourceRoot":"","sources":["../../src/engine/cookie-refresh.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGlC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAY,EACZ,MAAe;IAEf,IAAI,CAAC;QACH,MAAM,EAAE,WAAW,EAAE,EAAE,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;QAE/D,gDAAgD;QAChD,IAAI,IAAI,GAAG,IAAI,CAAC;QAChB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;QAC5C,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YAChC,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC;gBAChD,IAAI,GAAG,CAAC,CAAC;YACX,CAAC;QACH,CAAC;QAED,IAAI,IAA6B,CAAC;QAClC,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,2CAA2C;YAC3C,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,CAAC;YACH,sDAAsD;YACtD,MAAM,SAAS,GAAG,MAAM;gBACtB,CAAC,CAAC,WAAW,MAAM,EAAE;gBACrB,CAAC,CAAC,eAAe,IAAI,MAAM,CAAC;YAE9B,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;YAE/C,kBAAkB;YAClB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACrC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtC,OAAO,KAAK,CAAC;YACf,CAAC;YAED,wBAAwB;YACxB,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;YACzD,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAE3C,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;gBAClE,IAAI;gBACJ,KAAK;gBACL,MAAM,EAAE,MAAM,IAAI,GAAG,IAAI,MAAM;aAChC,CAAC,CAAC,CAAC;YAEJ,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;YACpD,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;YAEzE,OAAO,IAAI,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YACrB,CAAC;YAAC,MAAM,CAAC;gBACP,iBAAiB;YACnB,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"cookie-refresh.js","sourceRoot":"","sources":["../../src/engine/cookie-refresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AA2BpD,KAAK,UAAU,cAAc,CAAC,IAAY;IACxC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAC3D,OAAO,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,CAAC,MAAM,yBAAyB,GAAuB;IAC3D,OAAO,EAAE,cAAc;IACvB,OAAO,EAAE,WAAW;CACrB,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAY,EACZ,MAAe,EACf,OAA2B,yBAAyB;IAEpD,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAC;QACpE,IAAI,GAAG,cAAc,EAAE,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,MAAM,EAAE,OAAO;YACf,MAAM,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACzD,CAAC;IACJ,CAAC;IAED,IAAI,IAAiB,CAAC;IACtB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,MAAM,EAAE,YAAY;YACpB,MAAM,EAAE,2CAA2C,IAAI,KACrD,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CACjD,EAAE;SACH,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,MAAM,EAAE,CAAC,CAAC,CAAC,eAAe,IAAI,MAAM,CAAC;QAC3E,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAE/C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACrC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtC,OAAO;gBACL,MAAM,EAAE,YAAY;gBACpB,MAAM,EAAE,0BAA0B,IAAI,wBAAwB,SAAS,EAAE;aAC1E,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC5B,OAAO;YACL,MAAM,EAAE,WAAW;YACnB,IAAI;YACJ,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM;SACzC,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,MAAM,EAAE,OAAO;YACf,MAAM,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACzD,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;QAAC,MAAM,CAAC;YACP,sEAAsE;YACtE,yDAAyD;QAC3D,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * @owner src::engine::cookie-source
3
+ * @does Multi-source cookie acquisition that surfaces the REAL cause of a
4
+ * miss (keychain denied / corrupt file / v20 encryption / CDP
5
+ * unavailable) as a typed outcome, instead of collapsing every
6
+ * failure to null.
7
+ * @needs node:fs, node:path, ./chromium-cookies (lazy), ./cookie-extractor (lazy)
8
+ * @feeds src::engine::cookies (loadCookies/loadCookiesWithCDP/acquireCookies
9
+ * projections), src::engine::executor (auth error detail)
10
+ * @breaks never throws from loadCookiesWithDiagnostics — failures become
11
+ * CookieLoadOutcome {status:"error", reasons}; readDiskCookies is total
12
+ * @invariants exactly one of loaded|absent|error; "absent" ⇒ no source errored
13
+ * (genuinely not logged in); "error" ⇒ ≥1 source had a real failure
14
+ * @side-effects readDiskCookies reads fs; default sources read browser DB / CDP
15
+ * @perf disk O(file); browser tries installed browsers in order, stops on hit
16
+ * @concurrency stateless; sources own their own IO
17
+ * @test tests/unit/engine/cookie-source.test.ts
18
+ * @stability experimental
19
+ * @since 2026-05-30
20
+ */
21
+ export type CookieSourceName = "disk" | "browser" | "cdp";
22
+ /** A typed failure cause from one acquisition source. */
23
+ export interface CookieReason {
24
+ source: CookieSourceName;
25
+ /** Stable code, e.g. corrupt_file | keychain_denied | encryption_unsupported | cdp_unavailable. */
26
+ code: string;
27
+ detail: string;
28
+ }
29
+ /**
30
+ * Result of trying every cookie source for a site. Discriminated so callers
31
+ * cannot confuse "genuinely not logged in" (absent) with "Keychain denied /
32
+ * file corrupt / Chrome v20" (error) — the distinction the old null collapse
33
+ * destroyed.
34
+ */
35
+ export type CookieLoadOutcome = {
36
+ status: "loaded";
37
+ source: CookieSourceName;
38
+ cookies: Record<string, string>;
39
+ } | {
40
+ status: "absent";
41
+ } | {
42
+ status: "error";
43
+ reasons: CookieReason[];
44
+ };
45
+ /** Disk read distinguishes absent (fine) from corrupt (a real, surfaceable fault). */
46
+ export type DiskRead = {
47
+ kind: "ok";
48
+ cookies: Record<string, string>;
49
+ } | {
50
+ kind: "absent";
51
+ } | {
52
+ kind: "corrupt";
53
+ detail: string;
54
+ };
55
+ /** Browser read across installed browsers: a hit, a clean miss, or real errors. */
56
+ export type BrowserAttempt = {
57
+ kind: "ok";
58
+ cookies: Record<string, string>;
59
+ } | {
60
+ kind: "none";
61
+ } | {
62
+ kind: "error";
63
+ reasons: CookieReason[];
64
+ };
65
+ /**
66
+ * Injectable acquisition sources. The default wires the real fs / browser DB /
67
+ * CDP; tests pass fakes so the whole policy is verifiable without a network,
68
+ * Keychain, or browser.
69
+ */
70
+ export interface CookieSources {
71
+ readDisk(site: string): DiskRead;
72
+ readBrowser(domain: string): Promise<BrowserAttempt>;
73
+ readCdp(domain: string): Promise<Record<string, string>>;
74
+ }
75
+ export declare function cookieDir(): string;
76
+ /**
77
+ * Read the on-disk cookie file, distinguishing absent from corrupt. The old
78
+ * loadCookies collapsed a truncated / wrong-shaped file to null ("no auth"),
79
+ * hiding the real cause; this returns a typed corrupt with a detail.
80
+ */
81
+ export declare function readDiskCookies(site: string): DiskRead;
82
+ /** Resolve the cookie domain the same way the legacy loader did. */
83
+ export declare function resolveCookieDomain(site: string, domain?: string): string;
84
+ export declare const defaultCookieSources: CookieSources;
85
+ /**
86
+ * Acquire cookies across disk → browser → CDP, collecting the real cause of
87
+ * each source's failure. Never throws: a miss with no underlying fault is
88
+ * "absent"; a miss where a source actually errored (Keychain denial, corrupt
89
+ * file, v20 encryption, CDP down) is "error" with the reasons.
90
+ *
91
+ * Domain precedence and the disk-first / browser / CDP order match the legacy
92
+ * loadCookiesWithCDP exactly, so the projection wrappers preserve behavior.
93
+ */
94
+ export declare function loadCookiesWithDiagnostics(site: string, domain?: string, sources?: CookieSources, opts?: {
95
+ skipDisk?: boolean;
96
+ }): Promise<CookieLoadOutcome>;
97
+ /**
98
+ * Turn a non-loaded outcome into an agent-actionable message + suggestion.
99
+ * This is where the no-silent-failure win is spent: Keychain denial, v20
100
+ * encryption, and a corrupt file each get a DISTINCT next step instead of the
101
+ * legacy one-size-fits-all "run unicli auth setup".
102
+ */
103
+ export declare function describeCookieFailure(outcome: Exclude<CookieLoadOutcome, {
104
+ status: "loaded";
105
+ }>, site: string, domain?: string): {
106
+ message: string;
107
+ suggestion: string;
108
+ retryable: boolean;
109
+ };
110
+ //# sourceMappingURL=cookie-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookie-source.d.ts","sourceRoot":"","sources":["../../src/engine/cookie-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,SAAS,GAAG,KAAK,CAAC;AAE1D,yDAAyD;AACzD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,gBAAgB,CAAC;IACzB,mGAAmG;IACnG,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GACzB;IACE,MAAM,EAAE,QAAQ,CAAC;IACjB,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC,GACD;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GACpB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,YAAY,EAAE,CAAA;CAAE,CAAC;AAEjD,sFAAsF;AACtF,MAAM,MAAM,QAAQ,GAChB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC,mFAAmF;AACnF,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,YAAY,EAAE,CAAA;CAAE,CAAC;AAE/C;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;IACjC,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACrD,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAC1D;AAID,wBAAgB,SAAS,IAAI,MAAM,CAKlC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAyBtD;AAED,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAIzE;AA8CD,eAAO,MAAM,oBAAoB,EAAE,aAIlC,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,GAAE,aAAoC,EAC7C,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,GAChC,OAAO,CAAC,iBAAiB,CAAC,CA8C5B;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,CAAC,iBAAiB,EAAE;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,CAAC,EACzD,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,GACd;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CA0B7D"}