@symerian/symi 3.0.17 → 3.0.19

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 (259) hide show
  1. package/dist/{audio-preflight-CBDFctZN.js → audio-preflight-BfmZbg4Y.js} +4 -4
  2. package/dist/{audio-preflight-gsZSpG-6.js → audio-preflight-DcuC-liM.js} +4 -4
  3. package/dist/build-info.json +3 -3
  4. package/dist/bundled/boot-md/handler.js +8 -8
  5. package/dist/bundled/session-memory/handler.js +7 -7
  6. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  7. package/dist/{chrome-nPMY1XTJ.js → chrome-Bo7cbvFK.js} +5 -5
  8. package/dist/{chrome-BjVab8gM.js → chrome-DYp18Q0t.js} +5 -5
  9. package/dist/{deliver-D-QFqm31.js → deliver-ChSIbiMM.js} +1 -1
  10. package/dist/{deliver-B4-bcot9.js → deliver-DEgRQM4J.js} +1 -1
  11. package/dist/extensionAPI.js +7 -7
  12. package/dist/{image-CDwtQjmt.js → image-Bx-hvoNJ.js} +1 -1
  13. package/dist/{image-CcS-vzTA.js → image-CQl_mjWk.js} +1 -1
  14. package/dist/llm-slug-generator.js +7 -7
  15. package/dist/{manager-BnEdHzmO.js → manager-D_pn0urG.js} +1 -1
  16. package/dist/{manager-09r0qPze.js → manager-YQxK2t0C.js} +1 -1
  17. package/dist/{pi-embedded-CWsY69-4.js → pi-embedded-CLw_ZzEZ.js} +16 -16
  18. package/dist/{pi-embedded-helpers-BBMy-lqr.js → pi-embedded-helpers-B5I53aw6.js} +4 -4
  19. package/dist/{pi-embedded-helpers-ChEYbgVj.js → pi-embedded-helpers-sUAEIC9X.js} +4 -4
  20. package/dist/plugin-sdk/{accounts-BfyWsC_i.js → accounts-CWFytwbR.js} +3 -3
  21. package/dist/plugin-sdk/{active-listener-DcJW7xAT.js → active-listener-BkZ4jHrL.js} +2 -2
  22. package/dist/plugin-sdk/{agent-scope-ChbGV6of.js → agent-scope-C9gfY_Gk.js} +2 -2
  23. package/dist/plugin-sdk/{audio-preflight-D3GtNLqW.js → audio-preflight-HKbdzXLZ.js} +21 -21
  24. package/dist/plugin-sdk/{bindings-CN2Qmefj.js → bindings-BaKIqPPy.js} +2 -2
  25. package/dist/plugin-sdk/{channel-web-DTyqujjA.js → channel-web-D5nWiTH1.js} +18 -18
  26. package/dist/plugin-sdk/{chrome-BKzAKr3K.js → chrome-klTSnz-9.js} +3 -3
  27. package/dist/plugin-sdk/{chunk-DhDkBujV.js → chunk-BbrYSny_.js} +1 -1
  28. package/dist/plugin-sdk/{command-format-CVrYFyZS.js → command-format-BN6tyZt6.js} +1 -1
  29. package/dist/plugin-sdk/{commands-registry-17yfZkHZ.js → commands-registry-CTzKKtY6.js} +4 -4
  30. package/dist/plugin-sdk/{config-7wk65zKC.js → config-Crv2qEdJ.js} +9 -9
  31. package/dist/plugin-sdk/{consolidate-exbAW0ml.js → consolidate-DT1QH65Q.js} +2 -2
  32. package/dist/plugin-sdk/{deliver-TxAcw7J5.js → deliver-7rOvAlrc.js} +12 -12
  33. package/dist/plugin-sdk/{diagnostic-Debx4frd.js → diagnostic-0nsxhWp7.js} +1 -1
  34. package/dist/plugin-sdk/{fs-safe-wBYbAkJF.js → fs-safe-DfWYBeWF.js} +1 -1
  35. package/dist/plugin-sdk/{gemini-auth-7U2pm2Ky.js → gemini-auth-C0N0_u49.js} +1 -1
  36. package/dist/plugin-sdk/{image-BtDVmYA5.js → image-WOSl2apK.js} +4 -4
  37. package/dist/plugin-sdk/index.js +43 -43
  38. package/dist/plugin-sdk/{ir-CKMvRrGW.js → ir-9J84MTls.js} +4 -4
  39. package/dist/plugin-sdk/{local-roots-c_gaPs01.js → local-roots-OLRDbvyY.js} +3 -3
  40. package/dist/plugin-sdk/{login-DUym1Jy0.js → login-C7x4q0i2.js} +7 -7
  41. package/dist/plugin-sdk/{login-qr-B-WBdvrX.js → login-qr-Dv5_MoAW.js} +9 -9
  42. package/dist/plugin-sdk/{manager-B71SCzos.js → manager-C83tK17x.js} +8 -8
  43. package/dist/plugin-sdk/{manifest-registry-Dnic6Chh.js → manifest-registry-CJMV-PI7.js} +1 -1
  44. package/dist/plugin-sdk/{markdown-tables-Dur7OTlM.js → markdown-tables-DXNKz5y_.js} +1 -1
  45. package/dist/plugin-sdk/{message-channel-BrAhJJV_.js → message-channel-aGy1HbQQ.js} +1 -1
  46. package/dist/plugin-sdk/{model-selection-B9qaVQSJ.js → model-selection-C-3-tpe7.js} +4 -4
  47. package/dist/plugin-sdk/{outbound-DB1wDM8b.js → outbound-DquCeSy5.js} +6 -6
  48. package/dist/plugin-sdk/{pi-auth-json-ZO118hoy.js → pi-auth-json-D9PDCXGn.js} +1 -1
  49. package/dist/plugin-sdk/{pi-embedded-helpers-s_U0Un7j.js → pi-embedded-helpers-D3ygfH7l.js} +16 -16
  50. package/dist/plugin-sdk/{plugins-DF81oSaI.js → plugins-DOwnSg9D.js} +4 -4
  51. package/dist/plugin-sdk/{pw-ai-CTwP02uv.js → pw-ai-rlengLjb.js} +8 -8
  52. package/dist/plugin-sdk/{qmd-manager-CBaSGant.js → qmd-manager-BzxFjRFa.js} +4 -4
  53. package/dist/plugin-sdk/{registry-CZVURNhF.js → registry-5iFfixlB.js} +2 -2
  54. package/dist/plugin-sdk/{replies-hwRbkU3z.js → replies-BXOzO_H5.js} +7 -7
  55. package/dist/plugin-sdk/{reply-prefix-CaXmzZlx.js → reply-prefix-INAKTqCU.js} +1 -1
  56. package/dist/plugin-sdk/{resolve-outbound-target-fxVSOBmk.js → resolve-outbound-target-DvbxHtqp.js} +2 -2
  57. package/dist/plugin-sdk/{resolve-route-ClCyiOeu.js → resolve-route-URXlY3AK.js} +3 -3
  58. package/dist/plugin-sdk/{runner-Cq5jvwQ7.js → runner-Bv0_DWoH.js} +9 -9
  59. package/dist/plugin-sdk/{session-B_TkB65Y.js → session-C3r8l7ou.js} +4 -4
  60. package/dist/plugin-sdk/{skill-commands-0LF9HTGr.js → skill-commands-KjLUGIdZ.js} +5 -5
  61. package/dist/plugin-sdk/{skills-BIT_O7J0.js → skills-BrsD4L5c.js} +7 -7
  62. package/dist/plugin-sdk/{sqlite-Bx5Y5U5X.js → sqlite-CjW7ME1H.js} +1 -1
  63. package/dist/plugin-sdk/{subsystem-CXqYeDy-.js → subsystem-DcOg1xJr.js} +1 -1
  64. package/dist/plugin-sdk/{synthesis-DtsYAj1E.js → synthesis-CY7YAasV.js} +38 -38
  65. package/dist/plugin-sdk/{target-errors-B8mokOeH.js → target-errors-BVWJGWFq.js} +2 -2
  66. package/dist/plugin-sdk/{thinking-Ca0DhqzO.js → thinking-CtsTDPOi.js} +3 -3
  67. package/dist/plugin-sdk/{tokens-CvlONEqh.js → tokens-8lqOTZCB.js} +1 -1
  68. package/dist/plugin-sdk/{tool-images-DpBaWEHT.js → tool-images-Cl_rGIUZ.js} +2 -2
  69. package/dist/plugin-sdk/{tool-loop-detection-BOvUFa0f.js → tool-loop-detection-Da4WUT_P.js} +2 -2
  70. package/dist/plugin-sdk/{unified-runner-CnM7lyNd.js → unified-runner-nwMnsZyj.js} +60 -60
  71. package/dist/plugin-sdk/web-BlweOZDp.js +54 -0
  72. package/dist/plugin-sdk/{whatsapp-actions-CvnfsFJm.js → whatsapp-actions-DpfaGYs7.js} +21 -21
  73. package/dist/{pw-ai-BW8_KeDf.js → pw-ai-BqxJG-Wh.js} +1 -1
  74. package/dist/{pw-ai-j9IE1K0-.js → pw-ai-C-NSGye0.js} +1 -1
  75. package/dist/{runner-8ALr2UII.js → runner-COGFTeDw.js} +1 -1
  76. package/dist/{runner-C4-9kFdR.js → runner-DhCi2lT1.js} +1 -1
  77. package/dist/{synthesis-Cph3LhA1.js → synthesis-CXZu24Vx.js} +7 -7
  78. package/dist/{synthesis-Cus0A2dL.js → synthesis-DrPxcMlQ.js} +7 -7
  79. package/dist/{unified-runner-CX80YMTk.js → unified-runner-iByUazvW.js} +16 -16
  80. package/dist/{web-ChozvJ7I.js → web-EsMQBIYf.js} +7 -7
  81. package/dist/{web-DFlsbXmQ.js → web-PPg5y6xI.js} +7 -7
  82. package/package.json +1 -1
  83. package/dist/plugin-sdk/web-CIPJBHAU.js +0 -54
  84. package/extensions/copilot-proxy/README.md +0 -24
  85. package/extensions/copilot-proxy/index.ts +0 -154
  86. package/extensions/copilot-proxy/node_modules/.bin/symi +0 -21
  87. package/extensions/copilot-proxy/package.json +0 -15
  88. package/extensions/copilot-proxy/symi.plugin.json +0 -9
  89. package/extensions/device-pair/index.ts +0 -642
  90. package/extensions/device-pair/symi.plugin.json +0 -20
  91. package/extensions/diagnostics-otel/index.ts +0 -15
  92. package/extensions/diagnostics-otel/node_modules/.bin/acorn +0 -21
  93. package/extensions/diagnostics-otel/node_modules/.bin/symi +0 -21
  94. package/extensions/diagnostics-otel/package.json +0 -27
  95. package/extensions/diagnostics-otel/src/service.test.ts +0 -290
  96. package/extensions/diagnostics-otel/src/service.ts +0 -666
  97. package/extensions/diagnostics-otel/symi.plugin.json +0 -8
  98. package/extensions/google-antigravity-auth/README.md +0 -24
  99. package/extensions/google-antigravity-auth/index.ts +0 -424
  100. package/extensions/google-antigravity-auth/node_modules/.bin/symi +0 -21
  101. package/extensions/google-antigravity-auth/package.json +0 -15
  102. package/extensions/google-antigravity-auth/symi.plugin.json +0 -9
  103. package/extensions/google-gemini-cli-auth/README.md +0 -35
  104. package/extensions/google-gemini-cli-auth/index.ts +0 -75
  105. package/extensions/google-gemini-cli-auth/node_modules/.bin/symi +0 -21
  106. package/extensions/google-gemini-cli-auth/oauth.test.ts +0 -162
  107. package/extensions/google-gemini-cli-auth/oauth.ts +0 -636
  108. package/extensions/google-gemini-cli-auth/package.json +0 -15
  109. package/extensions/google-gemini-cli-auth/symi.plugin.json +0 -9
  110. package/extensions/learning-loop/index.ts +0 -159
  111. package/extensions/learning-loop/node_modules/.bin/symi +0 -21
  112. package/extensions/learning-loop/package.json +0 -18
  113. package/extensions/learning-loop/src/analytics/gateway-methods.ts +0 -230
  114. package/extensions/learning-loop/src/analytics/metrics-aggregator.ts +0 -153
  115. package/extensions/learning-loop/src/capture/run-tracker.ts +0 -181
  116. package/extensions/learning-loop/src/capture/serializer.ts +0 -74
  117. package/extensions/learning-loop/src/db.ts +0 -583
  118. package/extensions/learning-loop/src/feedback/explicit-feedback.ts +0 -58
  119. package/extensions/learning-loop/src/feedback/implicit-signals.ts +0 -89
  120. package/extensions/learning-loop/src/graph/edge-inference.ts +0 -189
  121. package/extensions/learning-loop/src/graph/graph-retrieval.ts +0 -144
  122. package/extensions/learning-loop/src/graph/graph-store.ts +0 -183
  123. package/extensions/learning-loop/src/hooks.ts +0 -244
  124. package/extensions/learning-loop/src/injection/cache.ts +0 -73
  125. package/extensions/learning-loop/src/injection/context-injector.ts +0 -104
  126. package/extensions/learning-loop/src/injection/prompt-builder.ts +0 -43
  127. package/extensions/learning-loop/src/learning/embedding-bridge.ts +0 -54
  128. package/extensions/learning-loop/src/learning/learning-extractor.ts +0 -217
  129. package/extensions/learning-loop/src/learning/learning-store.ts +0 -158
  130. package/extensions/learning-loop/src/learning/retrieval.ts +0 -87
  131. package/extensions/learning-loop/src/math/confidence-intervals.ts +0 -62
  132. package/extensions/learning-loop/src/math/ewma.ts +0 -51
  133. package/extensions/learning-loop/src/math/weighted-scorer.ts +0 -42
  134. package/extensions/learning-loop/src/schema.ts +0 -176
  135. package/extensions/learning-loop/src/scoring/normalization.ts +0 -32
  136. package/extensions/learning-loop/src/scoring/quality-engine.ts +0 -78
  137. package/extensions/learning-loop/src/scoring/signal-extractors.ts +0 -155
  138. package/extensions/learning-loop/src/test/context-injector.test.ts +0 -142
  139. package/extensions/learning-loop/src/test/fixes.test.ts +0 -1286
  140. package/extensions/learning-loop/src/test/graph.test.ts +0 -711
  141. package/extensions/learning-loop/src/test/integration.test.ts +0 -312
  142. package/extensions/learning-loop/src/test/learning-store.test.ts +0 -191
  143. package/extensions/learning-loop/src/test/math.test.ts +0 -148
  144. package/extensions/learning-loop/src/test/quality-engine.test.ts +0 -231
  145. package/extensions/learning-loop/src/test/run-tracker.test.ts +0 -143
  146. package/extensions/learning-loop/src/types.ts +0 -281
  147. package/extensions/learning-loop/symi.plugin.json +0 -46
  148. package/extensions/llm-task/README.md +0 -97
  149. package/extensions/llm-task/index.ts +0 -6
  150. package/extensions/llm-task/package.json +0 -12
  151. package/extensions/llm-task/src/llm-task-tool.test.ts +0 -138
  152. package/extensions/llm-task/src/llm-task-tool.ts +0 -249
  153. package/extensions/llm-task/symi.plugin.json +0 -21
  154. package/extensions/memory-lancedb/config.ts +0 -161
  155. package/extensions/memory-lancedb/index.test.ts +0 -330
  156. package/extensions/memory-lancedb/index.ts +0 -670
  157. package/extensions/memory-lancedb/node_modules/.bin/arrow2csv +0 -21
  158. package/extensions/memory-lancedb/node_modules/.bin/openai +0 -21
  159. package/extensions/memory-lancedb/node_modules/.bin/symi +0 -21
  160. package/extensions/memory-lancedb/package.json +0 -20
  161. package/extensions/memory-lancedb/symi.plugin.json +0 -71
  162. package/extensions/minimax-portal-auth/README.md +0 -33
  163. package/extensions/minimax-portal-auth/index.ts +0 -161
  164. package/extensions/minimax-portal-auth/node_modules/.bin/symi +0 -21
  165. package/extensions/minimax-portal-auth/oauth.ts +0 -247
  166. package/extensions/minimax-portal-auth/package.json +0 -15
  167. package/extensions/minimax-portal-auth/symi.plugin.json +0 -9
  168. package/extensions/model-equalizer/index.ts +0 -80
  169. package/extensions/model-equalizer/skills/model-equalizer/SKILL.md +0 -58
  170. package/extensions/model-equalizer/src/detection.ts +0 -62
  171. package/extensions/model-equalizer/src/enhancer.ts +0 -63
  172. package/extensions/model-equalizer/src/test/detection.test.ts +0 -218
  173. package/extensions/model-equalizer/src/test/enhancer.test.ts +0 -137
  174. package/extensions/model-equalizer/src/test/integration.test.ts +0 -185
  175. package/extensions/model-equalizer/src/types.ts +0 -24
  176. package/extensions/model-equalizer/symi.plugin.json +0 -12
  177. package/extensions/phone-control/index.ts +0 -421
  178. package/extensions/phone-control/symi.plugin.json +0 -10
  179. package/extensions/pipeline/README.md +0 -75
  180. package/extensions/pipeline/SKILL.md +0 -97
  181. package/extensions/pipeline/index.ts +0 -18
  182. package/extensions/pipeline/package.json +0 -11
  183. package/extensions/pipeline/src/pipeline-tool.test.ts +0 -345
  184. package/extensions/pipeline/src/pipeline-tool.ts +0 -266
  185. package/extensions/pipeline/src/windows-spawn.test.ts +0 -148
  186. package/extensions/pipeline/src/windows-spawn.ts +0 -193
  187. package/extensions/pipeline/symi.plugin.json +0 -10
  188. package/extensions/qwen-portal-auth/README.md +0 -24
  189. package/extensions/qwen-portal-auth/index.ts +0 -134
  190. package/extensions/qwen-portal-auth/oauth.ts +0 -190
  191. package/extensions/qwen-portal-auth/symi.plugin.json +0 -9
  192. package/extensions/talk-voice/index.ts +0 -150
  193. package/extensions/talk-voice/symi.plugin.json +0 -10
  194. package/extensions/thread-ownership/index.test.ts +0 -180
  195. package/extensions/thread-ownership/index.ts +0 -133
  196. package/extensions/thread-ownership/symi.plugin.json +0 -28
  197. package/skills/1password/SKILL.md +0 -71
  198. package/skills/1password/references/cli-examples.md +0 -29
  199. package/skills/1password/references/get-started.md +0 -17
  200. package/skills/apple-notes/SKILL.md +0 -78
  201. package/skills/apple-reminders/SKILL.md +0 -119
  202. package/skills/bear-notes/SKILL.md +0 -108
  203. package/skills/blogwatcher/SKILL.md +0 -70
  204. package/skills/blucli/SKILL.md +0 -48
  205. package/skills/bluebubbles/SKILL.md +0 -132
  206. package/skills/camsnap/SKILL.md +0 -46
  207. package/skills/canvas/SKILL.md +0 -204
  208. package/skills/connect-email/SKILL.md +0 -142
  209. package/skills/document-generation/SKILL.md +0 -83
  210. package/skills/eightctl/SKILL.md +0 -51
  211. package/skills/food-order/SKILL.md +0 -49
  212. package/skills/gemini/SKILL.md +0 -44
  213. package/skills/gh-issues/SKILL.md +0 -865
  214. package/skills/gifgrep/SKILL.md +0 -80
  215. package/skills/github/SKILL.md +0 -164
  216. package/skills/gog/SKILL.md +0 -117
  217. package/skills/goplaces/SKILL.md +0 -53
  218. package/skills/healthcheck/SKILL.md +0 -246
  219. package/skills/himalaya/SKILL.md +0 -258
  220. package/skills/himalaya/references/configuration.md +0 -184
  221. package/skills/himalaya/references/message-composition.md +0 -199
  222. package/skills/imsg/SKILL.md +0 -122
  223. package/skills/long-task/SKILL.md +0 -58
  224. package/skills/long-task/scripts/detach-task.sh +0 -187
  225. package/skills/nano-banana-pro/SKILL.md +0 -59
  226. package/skills/nano-banana-pro/scripts/generate_image.py +0 -184
  227. package/skills/nano-pdf/SKILL.md +0 -39
  228. package/skills/notion/SKILL.md +0 -173
  229. package/skills/obsidian/SKILL.md +0 -82
  230. package/skills/openai-image-gen/SKILL.md +0 -90
  231. package/skills/openai-image-gen/scripts/gen.py +0 -240
  232. package/skills/openai-whisper/SKILL.md +0 -39
  233. package/skills/openai-whisper-api/SKILL.md +0 -53
  234. package/skills/openai-whisper-api/scripts/transcribe.sh +0 -85
  235. package/skills/openhue/SKILL.md +0 -113
  236. package/skills/oracle/SKILL.md +0 -126
  237. package/skills/ordercli/SKILL.md +0 -79
  238. package/skills/peekaboo/SKILL.md +0 -191
  239. package/skills/reactions-extensive/SKILL.md +0 -30
  240. package/skills/reactions-minimal/SKILL.md +0 -31
  241. package/skills/safe-edit/SKILL.md +0 -51
  242. package/skills/sag/SKILL.md +0 -88
  243. package/skills/sherpa-onnx-tts/SKILL.md +0 -104
  244. package/skills/sherpa-onnx-tts/bin/sherpa-onnx-tts +0 -178
  245. package/skills/songsee/SKILL.md +0 -50
  246. package/skills/sonoscli/SKILL.md +0 -66
  247. package/skills/spotify-player/SKILL.md +0 -65
  248. package/skills/symihub/SKILL.md +0 -78
  249. package/skills/things-mac/SKILL.md +0 -87
  250. package/skills/tmux/SKILL.md +0 -153
  251. package/skills/tmux/scripts/find-sessions.sh +0 -112
  252. package/skills/tmux/scripts/wait-for-text.sh +0 -83
  253. package/skills/trello/SKILL.md +0 -96
  254. package/skills/video-frames/SKILL.md +0 -47
  255. package/skills/video-frames/scripts/frame.sh +0 -81
  256. package/skills/voice-call/SKILL.md +0 -46
  257. package/skills/wacli/SKILL.md +0 -73
  258. package/skills/weather/SKILL.md +0 -113
  259. package/skills/xurl/SKILL.md +0 -462
@@ -1,636 +0,0 @@
1
- import { createHash, randomBytes } from "node:crypto";
2
- import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
3
- import { createServer } from "node:http";
4
- import { delimiter, dirname, join } from "node:path";
5
- import { isWSL2Sync } from "symi/plugin-sdk";
6
-
7
- const CLIENT_ID_KEYS = ["SYMI_GEMINI_OAUTH_CLIENT_ID", "GEMINI_CLI_OAUTH_CLIENT_ID"];
8
- const CLIENT_SECRET_KEYS = ["SYMI_GEMINI_OAUTH_CLIENT_SECRET", "GEMINI_CLI_OAUTH_CLIENT_SECRET"];
9
- const REDIRECT_URI = "http://localhost:8085/oauth2callback";
10
- const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
11
- const TOKEN_URL = "https://oauth2.googleapis.com/token";
12
- const USERINFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json";
13
- const CODE_ASSIST_ENDPOINT = "https://cloudcode-pa.googleapis.com";
14
- const SCOPES = [
15
- "https://www.googleapis.com/auth/cloud-platform",
16
- "https://www.googleapis.com/auth/userinfo.email",
17
- "https://www.googleapis.com/auth/userinfo.profile",
18
- ];
19
-
20
- const TIER_FREE = "free-tier";
21
- const TIER_LEGACY = "legacy-tier";
22
- const TIER_STANDARD = "standard-tier";
23
-
24
- export type GeminiCliOAuthCredentials = {
25
- access: string;
26
- refresh: string;
27
- expires: number;
28
- email?: string;
29
- projectId: string;
30
- };
31
-
32
- export type GeminiCliOAuthContext = {
33
- isRemote: boolean;
34
- openUrl: (url: string) => Promise<void>;
35
- log: (msg: string) => void;
36
- note: (message: string, title?: string) => Promise<void>;
37
- prompt: (message: string) => Promise<string>;
38
- progress: { update: (msg: string) => void; stop: (msg?: string) => void };
39
- };
40
-
41
- function resolveEnv(keys: string[]): string | undefined {
42
- for (const key of keys) {
43
- const value = process.env[key]?.trim();
44
- if (value) {
45
- return value;
46
- }
47
- }
48
- return undefined;
49
- }
50
-
51
- let cachedGeminiCliCredentials: { clientId: string; clientSecret: string } | null = null;
52
-
53
- /** @internal */
54
- export function clearCredentialsCache(): void {
55
- cachedGeminiCliCredentials = null;
56
- }
57
-
58
- /** Extracts OAuth credentials from the installed Gemini CLI's bundled oauth2.js. */
59
- export function extractGeminiCliCredentials(): { clientId: string; clientSecret: string } | null {
60
- if (cachedGeminiCliCredentials) {
61
- return cachedGeminiCliCredentials;
62
- }
63
-
64
- try {
65
- const geminiPath = findInPath("gemini");
66
- if (!geminiPath) {
67
- return null;
68
- }
69
-
70
- const resolvedPath = realpathSync(geminiPath);
71
- const geminiCliDir = dirname(dirname(resolvedPath));
72
-
73
- const searchPaths = [
74
- join(
75
- geminiCliDir,
76
- "node_modules",
77
- "@google",
78
- "gemini-cli-core",
79
- "dist",
80
- "src",
81
- "code_assist",
82
- "oauth2.js",
83
- ),
84
- join(
85
- geminiCliDir,
86
- "node_modules",
87
- "@google",
88
- "gemini-cli-core",
89
- "dist",
90
- "code_assist",
91
- "oauth2.js",
92
- ),
93
- ];
94
-
95
- let content: string | null = null;
96
- for (const p of searchPaths) {
97
- if (existsSync(p)) {
98
- content = readFileSync(p, "utf8");
99
- break;
100
- }
101
- }
102
- if (!content) {
103
- const found = findFile(geminiCliDir, "oauth2.js", 10);
104
- if (found) {
105
- content = readFileSync(found, "utf8");
106
- }
107
- }
108
- if (!content) {
109
- return null;
110
- }
111
-
112
- const idMatch = content.match(/(\d+-[a-z0-9]+\.apps\.googleusercontent\.com)/);
113
- const secretMatch = content.match(/(GOCSPX-[A-Za-z0-9_-]+)/);
114
- if (idMatch && secretMatch) {
115
- cachedGeminiCliCredentials = { clientId: idMatch[1], clientSecret: secretMatch[1] };
116
- return cachedGeminiCliCredentials;
117
- }
118
- } catch {
119
- // Gemini CLI not installed or extraction failed
120
- }
121
- return null;
122
- }
123
-
124
- function findInPath(name: string): string | null {
125
- const exts = process.platform === "win32" ? [".cmd", ".bat", ".exe", ""] : [""];
126
- for (const dir of (process.env.PATH ?? "").split(delimiter)) {
127
- for (const ext of exts) {
128
- const p = join(dir, name + ext);
129
- if (existsSync(p)) {
130
- return p;
131
- }
132
- }
133
- }
134
- return null;
135
- }
136
-
137
- function findFile(dir: string, name: string, depth: number): string | null {
138
- if (depth <= 0) {
139
- return null;
140
- }
141
- try {
142
- for (const e of readdirSync(dir, { withFileTypes: true })) {
143
- const p = join(dir, e.name);
144
- if (e.isFile() && e.name === name) {
145
- return p;
146
- }
147
- if (e.isDirectory() && !e.name.startsWith(".")) {
148
- const found = findFile(p, name, depth - 1);
149
- if (found) {
150
- return found;
151
- }
152
- }
153
- }
154
- } catch {}
155
- return null;
156
- }
157
-
158
- function resolveOAuthClientConfig(): { clientId: string; clientSecret?: string } {
159
- // 1. Check env vars first (user override)
160
- const envClientId = resolveEnv(CLIENT_ID_KEYS);
161
- const envClientSecret = resolveEnv(CLIENT_SECRET_KEYS);
162
- if (envClientId) {
163
- return { clientId: envClientId, clientSecret: envClientSecret };
164
- }
165
-
166
- // 2. Try to extract from installed Gemini CLI
167
- const extracted = extractGeminiCliCredentials();
168
- if (extracted) {
169
- return extracted;
170
- }
171
-
172
- // 3. No credentials available
173
- throw new Error(
174
- "Gemini CLI not found. Install it first: brew install gemini-cli (or npm install -g @google/gemini-cli), or set GEMINI_CLI_OAUTH_CLIENT_ID.",
175
- );
176
- }
177
-
178
- function shouldUseManualOAuthFlow(isRemote: boolean): boolean {
179
- return isRemote || isWSL2Sync();
180
- }
181
-
182
- function generatePkce(): { verifier: string; challenge: string } {
183
- const verifier = randomBytes(32).toString("hex");
184
- const challenge = createHash("sha256").update(verifier).digest("base64url");
185
- return { verifier, challenge };
186
- }
187
-
188
- function buildAuthUrl(challenge: string, verifier: string): string {
189
- const { clientId } = resolveOAuthClientConfig();
190
- const params = new URLSearchParams({
191
- client_id: clientId,
192
- response_type: "code",
193
- redirect_uri: REDIRECT_URI,
194
- scope: SCOPES.join(" "),
195
- code_challenge: challenge,
196
- code_challenge_method: "S256",
197
- state: verifier,
198
- access_type: "offline",
199
- prompt: "consent",
200
- });
201
- return `${AUTH_URL}?${params.toString()}`;
202
- }
203
-
204
- function parseCallbackInput(
205
- input: string,
206
- expectedState: string,
207
- ): { code: string; state: string } | { error: string } {
208
- const trimmed = input.trim();
209
- if (!trimmed) {
210
- return { error: "No input provided" };
211
- }
212
-
213
- try {
214
- const url = new URL(trimmed);
215
- const code = url.searchParams.get("code");
216
- const state = url.searchParams.get("state") ?? expectedState;
217
- if (!code) {
218
- return { error: "Missing 'code' parameter in URL" };
219
- }
220
- if (!state) {
221
- return { error: "Missing 'state' parameter. Paste the full URL." };
222
- }
223
- return { code, state };
224
- } catch {
225
- if (!expectedState) {
226
- return { error: "Paste the full redirect URL, not just the code." };
227
- }
228
- return { code: trimmed, state: expectedState };
229
- }
230
- }
231
-
232
- async function waitForLocalCallback(params: {
233
- expectedState: string;
234
- timeoutMs: number;
235
- onProgress?: (message: string) => void;
236
- }): Promise<{ code: string; state: string }> {
237
- const port = 8085;
238
- const hostname = "localhost";
239
- const expectedPath = "/oauth2callback";
240
-
241
- return new Promise<{ code: string; state: string }>((resolve, reject) => {
242
- let timeout: NodeJS.Timeout | null = null;
243
- const server = createServer((req, res) => {
244
- try {
245
- const requestUrl = new URL(req.url ?? "/", `http://${hostname}:${port}`);
246
- if (requestUrl.pathname !== expectedPath) {
247
- res.statusCode = 404;
248
- res.setHeader("Content-Type", "text/plain");
249
- res.end("Not found");
250
- return;
251
- }
252
-
253
- const error = requestUrl.searchParams.get("error");
254
- const code = requestUrl.searchParams.get("code")?.trim();
255
- const state = requestUrl.searchParams.get("state")?.trim();
256
-
257
- if (error) {
258
- res.statusCode = 400;
259
- res.setHeader("Content-Type", "text/plain");
260
- res.end(`Authentication failed: ${error}`);
261
- finish(new Error(`OAuth error: ${error}`));
262
- return;
263
- }
264
-
265
- if (!code || !state) {
266
- res.statusCode = 400;
267
- res.setHeader("Content-Type", "text/plain");
268
- res.end("Missing code or state");
269
- finish(new Error("Missing OAuth code or state"));
270
- return;
271
- }
272
-
273
- if (state !== params.expectedState) {
274
- res.statusCode = 400;
275
- res.setHeader("Content-Type", "text/plain");
276
- res.end("Invalid state");
277
- finish(new Error("OAuth state mismatch"));
278
- return;
279
- }
280
-
281
- res.statusCode = 200;
282
- res.setHeader("Content-Type", "text/html; charset=utf-8");
283
- res.end(
284
- "<!doctype html><html><head><meta charset='utf-8'/></head>" +
285
- "<body><h2>Gemini CLI OAuth complete</h2>" +
286
- "<p>You can close this window and return to Symi.</p></body></html>",
287
- );
288
-
289
- finish(undefined, { code, state });
290
- } catch (err) {
291
- finish(err instanceof Error ? err : new Error("OAuth callback failed"));
292
- }
293
- });
294
-
295
- const finish = (err?: Error, result?: { code: string; state: string }) => {
296
- if (timeout) {
297
- clearTimeout(timeout);
298
- }
299
- try {
300
- server.close();
301
- } catch {
302
- // ignore close errors
303
- }
304
- if (err) {
305
- reject(err);
306
- } else if (result) {
307
- resolve(result);
308
- }
309
- };
310
-
311
- server.once("error", (err) => {
312
- finish(err instanceof Error ? err : new Error("OAuth callback server error"));
313
- });
314
-
315
- server.listen(port, hostname, () => {
316
- params.onProgress?.(`Waiting for OAuth callback on ${REDIRECT_URI}…`);
317
- });
318
-
319
- timeout = setTimeout(() => {
320
- finish(new Error("OAuth callback timeout"));
321
- }, params.timeoutMs);
322
- });
323
- }
324
-
325
- async function exchangeCodeForTokens(
326
- code: string,
327
- verifier: string,
328
- ): Promise<GeminiCliOAuthCredentials> {
329
- const { clientId, clientSecret } = resolveOAuthClientConfig();
330
- const body = new URLSearchParams({
331
- client_id: clientId,
332
- code,
333
- grant_type: "authorization_code",
334
- redirect_uri: REDIRECT_URI,
335
- code_verifier: verifier,
336
- });
337
- if (clientSecret) {
338
- body.set("client_secret", clientSecret);
339
- }
340
-
341
- const response = await fetch(TOKEN_URL, {
342
- method: "POST",
343
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
344
- body,
345
- });
346
-
347
- if (!response.ok) {
348
- const errorText = await response.text();
349
- throw new Error(`Token exchange failed: ${errorText}`);
350
- }
351
-
352
- const data = (await response.json()) as {
353
- access_token: string;
354
- refresh_token: string;
355
- expires_in: number;
356
- };
357
-
358
- if (!data.refresh_token) {
359
- throw new Error("No refresh token received. Please try again.");
360
- }
361
-
362
- const email = await getUserEmail(data.access_token);
363
- const projectId = await discoverProject(data.access_token);
364
- const expiresAt = Date.now() + data.expires_in * 1000 - 5 * 60 * 1000;
365
-
366
- return {
367
- refresh: data.refresh_token,
368
- access: data.access_token,
369
- expires: expiresAt,
370
- projectId,
371
- email,
372
- };
373
- }
374
-
375
- async function getUserEmail(accessToken: string): Promise<string | undefined> {
376
- try {
377
- const response = await fetch(USERINFO_URL, {
378
- headers: { Authorization: `Bearer ${accessToken}` },
379
- });
380
- if (response.ok) {
381
- const data = (await response.json()) as { email?: string };
382
- return data.email;
383
- }
384
- } catch {
385
- // ignore
386
- }
387
- return undefined;
388
- }
389
-
390
- async function discoverProject(accessToken: string): Promise<string> {
391
- const envProject = process.env.GOOGLE_CLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT_ID;
392
- const headers = {
393
- Authorization: `Bearer ${accessToken}`,
394
- "Content-Type": "application/json",
395
- "User-Agent": "google-api-nodejs-client/9.15.1",
396
- "X-Goog-Api-Client": "gl-node/symi",
397
- };
398
-
399
- const loadBody = {
400
- cloudaicompanionProject: envProject,
401
- metadata: {
402
- ideType: "IDE_UNSPECIFIED",
403
- platform: "PLATFORM_UNSPECIFIED",
404
- pluginType: "GEMINI",
405
- duetProject: envProject,
406
- },
407
- };
408
-
409
- let data: {
410
- currentTier?: { id?: string };
411
- cloudaicompanionProject?: string | { id?: string };
412
- allowedTiers?: Array<{ id?: string; isDefault?: boolean }>;
413
- } = {};
414
-
415
- try {
416
- const response = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`, {
417
- method: "POST",
418
- headers,
419
- body: JSON.stringify(loadBody),
420
- });
421
-
422
- if (!response.ok) {
423
- const errorPayload = await response.json().catch(() => null);
424
- if (isVpcScAffected(errorPayload)) {
425
- data = { currentTier: { id: TIER_STANDARD } };
426
- } else {
427
- throw new Error(`loadCodeAssist failed: ${response.status} ${response.statusText}`);
428
- }
429
- } else {
430
- data = (await response.json()) as typeof data;
431
- }
432
- } catch (err) {
433
- if (err instanceof Error) {
434
- throw err;
435
- }
436
- throw new Error("loadCodeAssist failed", { cause: err });
437
- }
438
-
439
- if (data.currentTier) {
440
- const project = data.cloudaicompanionProject;
441
- if (typeof project === "string" && project) {
442
- return project;
443
- }
444
- if (typeof project === "object" && project?.id) {
445
- return project.id;
446
- }
447
- if (envProject) {
448
- return envProject;
449
- }
450
- throw new Error(
451
- "This account requires GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID to be set.",
452
- );
453
- }
454
-
455
- const tier = getDefaultTier(data.allowedTiers);
456
- const tierId = tier?.id || TIER_FREE;
457
- if (tierId !== TIER_FREE && !envProject) {
458
- throw new Error(
459
- "This account requires GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID to be set.",
460
- );
461
- }
462
-
463
- const onboardBody: Record<string, unknown> = {
464
- tierId,
465
- metadata: {
466
- ideType: "IDE_UNSPECIFIED",
467
- platform: "PLATFORM_UNSPECIFIED",
468
- pluginType: "GEMINI",
469
- },
470
- };
471
- if (tierId !== TIER_FREE && envProject) {
472
- onboardBody.cloudaicompanionProject = envProject;
473
- (onboardBody.metadata as Record<string, unknown>).duetProject = envProject;
474
- }
475
-
476
- const onboardResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`, {
477
- method: "POST",
478
- headers,
479
- body: JSON.stringify(onboardBody),
480
- });
481
-
482
- if (!onboardResponse.ok) {
483
- throw new Error(`onboardUser failed: ${onboardResponse.status} ${onboardResponse.statusText}`);
484
- }
485
-
486
- let lro = (await onboardResponse.json()) as {
487
- done?: boolean;
488
- name?: string;
489
- response?: { cloudaicompanionProject?: { id?: string } };
490
- };
491
-
492
- if (!lro.done && lro.name) {
493
- lro = await pollOperation(lro.name, headers);
494
- }
495
-
496
- const projectId = lro.response?.cloudaicompanionProject?.id;
497
- if (projectId) {
498
- return projectId;
499
- }
500
- if (envProject) {
501
- return envProject;
502
- }
503
-
504
- throw new Error(
505
- "Could not discover or provision a Google Cloud project. Set GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID.",
506
- );
507
- }
508
-
509
- function isVpcScAffected(payload: unknown): boolean {
510
- if (!payload || typeof payload !== "object") {
511
- return false;
512
- }
513
- const error = (payload as { error?: unknown }).error;
514
- if (!error || typeof error !== "object") {
515
- return false;
516
- }
517
- const details = (error as { details?: unknown[] }).details;
518
- if (!Array.isArray(details)) {
519
- return false;
520
- }
521
- return details.some(
522
- (item) =>
523
- typeof item === "object" &&
524
- item &&
525
- (item as { reason?: string }).reason === "SECURITY_POLICY_VIOLATED",
526
- );
527
- }
528
-
529
- function getDefaultTier(
530
- allowedTiers?: Array<{ id?: string; isDefault?: boolean }>,
531
- ): { id?: string } | undefined {
532
- if (!allowedTiers?.length) {
533
- return { id: TIER_LEGACY };
534
- }
535
- return allowedTiers.find((tier) => tier.isDefault) ?? { id: TIER_LEGACY };
536
- }
537
-
538
- async function pollOperation(
539
- operationName: string,
540
- headers: Record<string, string>,
541
- ): Promise<{ done?: boolean; response?: { cloudaicompanionProject?: { id?: string } } }> {
542
- for (let attempt = 0; attempt < 24; attempt += 1) {
543
- await new Promise((resolve) => setTimeout(resolve, 5000));
544
- const response = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal/${operationName}`, {
545
- headers,
546
- });
547
- if (!response.ok) {
548
- continue;
549
- }
550
- const data = (await response.json()) as {
551
- done?: boolean;
552
- response?: { cloudaicompanionProject?: { id?: string } };
553
- };
554
- if (data.done) {
555
- return data;
556
- }
557
- }
558
- throw new Error("Operation polling timeout");
559
- }
560
-
561
- export async function loginGeminiCliOAuth(
562
- ctx: GeminiCliOAuthContext,
563
- ): Promise<GeminiCliOAuthCredentials> {
564
- const needsManual = shouldUseManualOAuthFlow(ctx.isRemote);
565
- await ctx.note(
566
- needsManual
567
- ? [
568
- "You are running in a remote/VPS environment.",
569
- "A URL will be shown for you to open in your LOCAL browser.",
570
- "After signing in, copy the redirect URL and paste it back here.",
571
- ].join("\n")
572
- : [
573
- "Browser will open for Google authentication.",
574
- "Sign in with your Google account for Gemini CLI access.",
575
- "The callback will be captured automatically on localhost:8085.",
576
- ].join("\n"),
577
- "Gemini CLI OAuth",
578
- );
579
-
580
- const { verifier, challenge } = generatePkce();
581
- const authUrl = buildAuthUrl(challenge, verifier);
582
-
583
- if (needsManual) {
584
- ctx.progress.update("OAuth URL ready");
585
- ctx.log(`\nOpen this URL in your LOCAL browser:\n\n${authUrl}\n`);
586
- ctx.progress.update("Waiting for you to paste the callback URL...");
587
- const callbackInput = await ctx.prompt("Paste the redirect URL here: ");
588
- const parsed = parseCallbackInput(callbackInput, verifier);
589
- if ("error" in parsed) {
590
- throw new Error(parsed.error);
591
- }
592
- if (parsed.state !== verifier) {
593
- throw new Error("OAuth state mismatch - please try again");
594
- }
595
- ctx.progress.update("Exchanging authorization code for tokens...");
596
- return exchangeCodeForTokens(parsed.code, verifier);
597
- }
598
-
599
- ctx.progress.update("Complete sign-in in browser...");
600
- try {
601
- await ctx.openUrl(authUrl);
602
- } catch {
603
- ctx.log(`\nOpen this URL in your browser:\n\n${authUrl}\n`);
604
- }
605
-
606
- try {
607
- const { code } = await waitForLocalCallback({
608
- expectedState: verifier,
609
- timeoutMs: 5 * 60 * 1000,
610
- onProgress: (msg) => ctx.progress.update(msg),
611
- });
612
- ctx.progress.update("Exchanging authorization code for tokens...");
613
- return await exchangeCodeForTokens(code, verifier);
614
- } catch (err) {
615
- if (
616
- err instanceof Error &&
617
- (err.message.includes("EADDRINUSE") ||
618
- err.message.includes("port") ||
619
- err.message.includes("listen"))
620
- ) {
621
- ctx.progress.update("Local callback server failed. Switching to manual mode...");
622
- ctx.log(`\nOpen this URL in your LOCAL browser:\n\n${authUrl}\n`);
623
- const callbackInput = await ctx.prompt("Paste the redirect URL here: ");
624
- const parsed = parseCallbackInput(callbackInput, verifier);
625
- if ("error" in parsed) {
626
- throw new Error(parsed.error, { cause: err });
627
- }
628
- if (parsed.state !== verifier) {
629
- throw new Error("OAuth state mismatch - please try again", { cause: err });
630
- }
631
- ctx.progress.update("Exchanging authorization code for tokens...");
632
- return exchangeCodeForTokens(parsed.code, verifier);
633
- }
634
- throw err;
635
- }
636
- }
@@ -1,15 +0,0 @@
1
- {
2
- "name": "@symi/google-gemini-cli-auth",
3
- "version": "3.0.9",
4
- "private": true,
5
- "description": "Symi Gemini CLI OAuth provider plugin",
6
- "type": "module",
7
- "devDependencies": {
8
- "@symerian/symi": "workspace:*"
9
- },
10
- "symi": {
11
- "extensions": [
12
- "./index.ts"
13
- ]
14
- }
15
- }
@@ -1,9 +0,0 @@
1
- {
2
- "id": "google-gemini-cli-auth",
3
- "providers": ["google-gemini-cli"],
4
- "configSchema": {
5
- "type": "object",
6
- "additionalProperties": false,
7
- "properties": {}
8
- }
9
- }