nikcli 0.0.6

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 (602) hide show
  1. package/.turbo/turbo-typecheck.log +1 -0
  2. package/AGENTS.md +27 -0
  3. package/Dockerfile +18 -0
  4. package/README.md +15 -0
  5. package/bin/nikcli +84 -0
  6. package/config.json +13 -0
  7. package/docs/tailscale-mobile/01-tailscale-setup.md +94 -0
  8. package/docs/tailscale-mobile/02-host-setup.md +115 -0
  9. package/docs/tailscale-mobile/03-phone-and-serve.md +134 -0
  10. package/docs/tailscale-mobile/README.md +59 -0
  11. package/examples/README.md +54 -0
  12. package/package.json +147 -0
  13. package/parsers-config.ts +253 -0
  14. package/script/build.ts +179 -0
  15. package/script/postinstall.mjs +125 -0
  16. package/script/publish-registries.ts +187 -0
  17. package/script/publish.ts +100 -0
  18. package/script/schema.ts +47 -0
  19. package/script/seed-e2e.ts +50 -0
  20. package/sequential-prancing-forest.md +373 -0
  21. package/src/acp/README.md +164 -0
  22. package/src/acp/agent.ts +1303 -0
  23. package/src/acp/session.ts +105 -0
  24. package/src/acp/types.ts +22 -0
  25. package/src/agent/agent.ts +528 -0
  26. package/src/agent/generate.txt +32 -0
  27. package/src/agent/prompt/compaction.txt +14 -0
  28. package/src/agent/prompt/explore.txt +18 -0
  29. package/src/agent/prompt/summary.txt +11 -0
  30. package/src/agent/prompt/title.txt +44 -0
  31. package/src/auth/index.ts +73 -0
  32. package/src/bun/index.ts +119 -0
  33. package/src/bun/registry.ts +54 -0
  34. package/src/bus/bus-event.ts +43 -0
  35. package/src/bus/global.ts +10 -0
  36. package/src/bus/index.ts +105 -0
  37. package/src/chatbot/handlers.ts +150 -0
  38. package/src/chatbot/index.ts +132 -0
  39. package/src/cli/bootstrap.ts +17 -0
  40. package/src/cli/cmd/acp.ts +69 -0
  41. package/src/cli/cmd/ads.ts +377 -0
  42. package/src/cli/cmd/agent.ts +259 -0
  43. package/src/cli/cmd/auth.ts +400 -0
  44. package/src/cli/cmd/chatbot.ts +420 -0
  45. package/src/cli/cmd/cmd.ts +7 -0
  46. package/src/cli/cmd/companion.ts +81 -0
  47. package/src/cli/cmd/connectors.ts +593 -0
  48. package/src/cli/cmd/debug/agent.ts +166 -0
  49. package/src/cli/cmd/debug/config.ts +16 -0
  50. package/src/cli/cmd/debug/file.ts +97 -0
  51. package/src/cli/cmd/debug/index.ts +48 -0
  52. package/src/cli/cmd/debug/lsp.ts +52 -0
  53. package/src/cli/cmd/debug/ripgrep.ts +87 -0
  54. package/src/cli/cmd/debug/scrap.ts +16 -0
  55. package/src/cli/cmd/debug/skill.ts +16 -0
  56. package/src/cli/cmd/debug/snapshot.ts +52 -0
  57. package/src/cli/cmd/export.ts +88 -0
  58. package/src/cli/cmd/generate.ts +38 -0
  59. package/src/cli/cmd/github.ts +412 -0
  60. package/src/cli/cmd/image-model.ts +128 -0
  61. package/src/cli/cmd/import.ts +201 -0
  62. package/src/cli/cmd/lovable.ts +128 -0
  63. package/src/cli/cmd/mcp.ts +738 -0
  64. package/src/cli/cmd/mobile.ts +223 -0
  65. package/src/cli/cmd/models.ts +77 -0
  66. package/src/cli/cmd/plug.ts +231 -0
  67. package/src/cli/cmd/pr.ts +104 -0
  68. package/src/cli/cmd/rag-model.ts +167 -0
  69. package/src/cli/cmd/remote.ts +416 -0
  70. package/src/cli/cmd/run.ts +589 -0
  71. package/src/cli/cmd/serve.ts +51 -0
  72. package/src/cli/cmd/session.ts +133 -0
  73. package/src/cli/cmd/speak-model.ts +204 -0
  74. package/src/cli/cmd/stats.ts +402 -0
  75. package/src/cli/cmd/tui/app.tsx +841 -0
  76. package/src/cli/cmd/tui/attach.ts +31 -0
  77. package/src/cli/cmd/tui/component/border.tsx +75 -0
  78. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  79. package/src/cli/cmd/tui/component/dialog-command.tsx +172 -0
  80. package/src/cli/cmd/tui/component/dialog-config.tsx +291 -0
  81. package/src/cli/cmd/tui/component/dialog-connectors.tsx +440 -0
  82. package/src/cli/cmd/tui/component/dialog-image-model.tsx +97 -0
  83. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  84. package/src/cli/cmd/tui/component/dialog-model.tsx +234 -0
  85. package/src/cli/cmd/tui/component/dialog-provider.tsx +260 -0
  86. package/src/cli/cmd/tui/component/dialog-rag-model.tsx +217 -0
  87. package/src/cli/cmd/tui/component/dialog-remote.tsx +489 -0
  88. package/src/cli/cmd/tui/component/dialog-session-list.tsx +170 -0
  89. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  90. package/src/cli/cmd/tui/component/dialog-settings/index.tsx +59 -0
  91. package/src/cli/cmd/tui/component/dialog-settings/prompt.tsx +40 -0
  92. package/src/cli/cmd/tui/component/dialog-settings/sidebar.tsx +39 -0
  93. package/src/cli/cmd/tui/component/dialog-settings/spinner.tsx +62 -0
  94. package/src/cli/cmd/tui/component/dialog-settings/ui.tsx +58 -0
  95. package/src/cli/cmd/tui/component/dialog-skills.tsx +117 -0
  96. package/src/cli/cmd/tui/component/dialog-speak-model.tsx +304 -0
  97. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  98. package/src/cli/cmd/tui/component/dialog-status.tsx +165 -0
  99. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  100. package/src/cli/cmd/tui/component/dialog-theme-create.tsx +717 -0
  101. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +52 -0
  102. package/src/cli/cmd/tui/component/dialog-workspace-list.tsx +350 -0
  103. package/src/cli/cmd/tui/component/error-component.tsx +91 -0
  104. package/src/cli/cmd/tui/component/logo.tsx +103 -0
  105. package/src/cli/cmd/tui/component/plugin-route-missing.tsx +14 -0
  106. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +669 -0
  107. package/src/cli/cmd/tui/component/prompt/frecency.tsx +89 -0
  108. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  109. package/src/cli/cmd/tui/component/prompt/index.tsx +2165 -0
  110. package/src/cli/cmd/tui/component/prompt/stash.tsx +63 -0
  111. package/src/cli/cmd/tui/component/spinner.tsx +24 -0
  112. package/src/cli/cmd/tui/component/startup-loading.tsx +63 -0
  113. package/src/cli/cmd/tui/component/table/markdown-table.tsx +267 -0
  114. package/src/cli/cmd/tui/component/table-db/db/connections.ts +75 -0
  115. package/src/cli/cmd/tui/component/table-db/db/db-connection.ts +223 -0
  116. package/src/cli/cmd/tui/component/table-db/db/db-preview.ts +202 -0
  117. package/src/cli/cmd/tui/component/table-db/db/factory.ts +77 -0
  118. package/src/cli/cmd/tui/component/table-db/db/index.ts +9 -0
  119. package/src/cli/cmd/tui/component/table-db/db/mysql-connection.ts +330 -0
  120. package/src/cli/cmd/tui/component/table-db/db/postgres-connection.ts +338 -0
  121. package/src/cli/cmd/tui/component/table-db/db/sqlite-connection.ts +302 -0
  122. package/src/cli/cmd/tui/component/table-db/db/types.ts +108 -0
  123. package/src/cli/cmd/tui/component/table-db/table/dbedit-hooks.ts +74 -0
  124. package/src/cli/cmd/tui/component/table-db/table/index.ts +15 -0
  125. package/src/cli/cmd/tui/component/table-db/table/table-events.ts +54 -0
  126. package/src/cli/cmd/tui/component/table-db/table/table-formatters.ts +191 -0
  127. package/src/cli/cmd/tui/component/table-db/table/table-hooks.ts +105 -0
  128. package/src/cli/cmd/tui/component/table-db/table/table-keyboard-handler.ts +255 -0
  129. package/src/cli/cmd/tui/component/table-db/table/table-layout-engine.ts +208 -0
  130. package/src/cli/cmd/tui/component/table-db/table/table-renderable.ts +486 -0
  131. package/src/cli/cmd/tui/component/table-db/table/table-selection-manager.ts +136 -0
  132. package/src/cli/cmd/tui/component/table-db/table/table-state.ts +198 -0
  133. package/src/cli/cmd/tui/component/table-db/table/types.ts +69 -0
  134. package/src/cli/cmd/tui/component/table-db/ui/db-visualizer.tsx +71 -0
  135. package/src/cli/cmd/tui/component/table-db/ui/index.ts +2 -0
  136. package/src/cli/cmd/tui/component/table-db/ui/table-renderer.ts +607 -0
  137. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  138. package/src/cli/cmd/tui/component/tips.tsx +195 -0
  139. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  140. package/src/cli/cmd/tui/context/args.tsx +14 -0
  141. package/src/cli/cmd/tui/context/directory.ts +13 -0
  142. package/src/cli/cmd/tui/context/exit.tsx +24 -0
  143. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  144. package/src/cli/cmd/tui/context/keybind.tsx +102 -0
  145. package/src/cli/cmd/tui/context/kv.tsx +52 -0
  146. package/src/cli/cmd/tui/context/local.tsx +458 -0
  147. package/src/cli/cmd/tui/context/plugin-keybinds.ts +41 -0
  148. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  149. package/src/cli/cmd/tui/context/route.tsx +54 -0
  150. package/src/cli/cmd/tui/context/sdk.tsx +128 -0
  151. package/src/cli/cmd/tui/context/server.tsx +8 -0
  152. package/src/cli/cmd/tui/context/sync.tsx +510 -0
  153. package/src/cli/cmd/tui/context/theme/abyss.json +233 -0
  154. package/src/cli/cmd/tui/context/theme/apple.json +235 -0
  155. package/src/cli/cmd/tui/context/theme/arctic.json +232 -0
  156. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  157. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  158. package/src/cli/cmd/tui/context/theme/ayuai.json +229 -0
  159. package/src/cli/cmd/tui/context/theme/blood.json +229 -0
  160. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  161. package/src/cli/cmd/tui/context/theme/catmoe.json +235 -0
  162. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  163. package/src/cli/cmd/tui/context/theme/catppuccin-latte.json +233 -0
  164. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  165. package/src/cli/cmd/tui/context/theme/catppuccin.json +259 -0
  166. package/src/cli/cmd/tui/context/theme/charcoal.json +230 -0
  167. package/src/cli/cmd/tui/context/theme/chromatic.json +235 -0
  168. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  169. package/src/cli/cmd/tui/context/theme/cosmic.json +234 -0
  170. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  171. package/src/cli/cmd/tui/context/theme/cyber.json +235 -0
  172. package/src/cli/cmd/tui/context/theme/dawnfox.json +229 -0
  173. package/src/cli/cmd/tui/context/theme/dimension.json +235 -0
  174. package/src/cli/cmd/tui/context/theme/dracula-official.json +222 -0
  175. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  176. package/src/cli/cmd/tui/context/theme/dream.json +235 -0
  177. package/src/cli/cmd/tui/context/theme/duo.json +235 -0
  178. package/src/cli/cmd/tui/context/theme/dusk.json +235 -0
  179. package/src/cli/cmd/tui/context/theme/ebony.json +232 -0
  180. package/src/cli/cmd/tui/context/theme/equilibrium.json +232 -0
  181. package/src/cli/cmd/tui/context/theme/ethereal.json +235 -0
  182. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  183. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  184. package/src/cli/cmd/tui/context/theme/fusion.json +235 -0
  185. package/src/cli/cmd/tui/context/theme/ghost.json +235 -0
  186. package/src/cli/cmd/tui/context/theme/github-dark.json +229 -0
  187. package/src/cli/cmd/tui/context/theme/github-dimmed.json +231 -0
  188. package/src/cli/cmd/tui/context/theme/github-light.json +229 -0
  189. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  190. package/src/cli/cmd/tui/context/theme/glass.json +235 -0
  191. package/src/cli/cmd/tui/context/theme/gold.json +235 -0
  192. package/src/cli/cmd/tui/context/theme/gone.json +234 -0
  193. package/src/cli/cmd/tui/context/theme/greyscale.json +229 -0
  194. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  195. package/src/cli/cmd/tui/context/theme/hacker.json +229 -0
  196. package/src/cli/cmd/tui/context/theme/holo.json +235 -0
  197. package/src/cli/cmd/tui/context/theme/ink.json +235 -0
  198. package/src/cli/cmd/tui/context/theme/jet.json +233 -0
  199. package/src/cli/cmd/tui/context/theme/kanagawa.json +227 -0
  200. package/src/cli/cmd/tui/context/theme/lavender.json +236 -0
  201. package/src/cli/cmd/tui/context/theme/lightph.json +235 -0
  202. package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
  203. package/src/cli/cmd/tui/context/theme/material-ocean.json +230 -0
  204. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  205. package/src/cli/cmd/tui/context/theme/matrix.json +227 -0
  206. package/src/cli/cmd/tui/context/theme/mercury.json +245 -0
  207. package/src/cli/cmd/tui/context/theme/midnight.json +235 -0
  208. package/src/cli/cmd/tui/context/theme/modern.json +235 -0
  209. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  210. package/src/cli/cmd/tui/context/theme/muted.json +229 -0
  211. package/src/cli/cmd/tui/context/theme/neon.json +229 -0
  212. package/src/cli/cmd/tui/context/theme/neonfusion.json +235 -0
  213. package/src/cli/cmd/tui/context/theme/neutral.json +235 -0
  214. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  215. package/src/cli/cmd/tui/context/theme/nikcli.json +245 -0
  216. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  217. package/src/cli/cmd/tui/context/theme/nordic.json +235 -0
  218. package/src/cli/cmd/tui/context/theme/nova.json +235 -0
  219. package/src/cli/cmd/tui/context/theme/obsidian.json +234 -0
  220. package/src/cli/cmd/tui/context/theme/one-dark.json +231 -0
  221. package/src/cli/cmd/tui/context/theme/one-pro.json +229 -0
  222. package/src/cli/cmd/tui/context/theme/onyx.json +233 -0
  223. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  224. package/src/cli/cmd/tui/context/theme/osaka-jade.json +240 -0
  225. package/src/cli/cmd/tui/context/theme/oxocarbon.json +229 -0
  226. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  227. package/src/cli/cmd/tui/context/theme/poimandres.json +230 -0
  228. package/src/cli/cmd/tui/context/theme/prism.json +235 -0
  229. package/src/cli/cmd/tui/context/theme/radiant.json +235 -0
  230. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  231. package/src/cli/cmd/tui/context/theme/shadow.json +235 -0
  232. package/src/cli/cmd/tui/context/theme/silicon.json +235 -0
  233. package/src/cli/cmd/tui/context/theme/slate.json +233 -0
  234. package/src/cli/cmd/tui/context/theme/soft.json +235 -0
  235. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  236. package/src/cli/cmd/tui/context/theme/spectrum.json +235 -0
  237. package/src/cli/cmd/tui/context/theme/starlight.json +233 -0
  238. package/src/cli/cmd/tui/context/theme/sunrise.json +235 -0
  239. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  240. package/src/cli/cmd/tui/context/theme/tech.json +235 -0
  241. package/src/cli/cmd/tui/context/theme/tokyonight-storm.json +245 -0
  242. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  243. package/src/cli/cmd/tui/context/theme/vapor.json +235 -0
  244. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  245. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  246. package/src/cli/cmd/tui/context/theme/vivid.json +232 -0
  247. package/src/cli/cmd/tui/context/theme/void.json +235 -0
  248. package/src/cli/cmd/tui/context/theme/vscode.json +235 -0
  249. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  250. package/src/cli/cmd/tui/context/theme/zinc.json +236 -0
  251. package/src/cli/cmd/tui/context/theme.tsx +1303 -0
  252. package/src/cli/cmd/tui/event.ts +48 -0
  253. package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +152 -0
  254. package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +50 -0
  255. package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +63 -0
  256. package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +62 -0
  257. package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +93 -0
  258. package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +66 -0
  259. package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +96 -0
  260. package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +48 -0
  261. package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +288 -0
  262. package/src/cli/cmd/tui/plugin/api.tsx +407 -0
  263. package/src/cli/cmd/tui/plugin/index.ts +3 -0
  264. package/src/cli/cmd/tui/plugin/internal.ts +25 -0
  265. package/src/cli/cmd/tui/plugin/runtime.ts +1048 -0
  266. package/src/cli/cmd/tui/plugin/slots.tsx +61 -0
  267. package/src/cli/cmd/tui/routes/home.tsx +153 -0
  268. package/src/cli/cmd/tui/routes/session/dbedit.tsx +474 -0
  269. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +65 -0
  270. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +110 -0
  271. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +105 -0
  272. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  273. package/src/cli/cmd/tui/routes/session/footer.tsx +75 -0
  274. package/src/cli/cmd/tui/routes/session/header.tsx +177 -0
  275. package/src/cli/cmd/tui/routes/session/index.tsx +2280 -0
  276. package/src/cli/cmd/tui/routes/session/permission.tsx +540 -0
  277. package/src/cli/cmd/tui/routes/session/question.tsx +435 -0
  278. package/src/cli/cmd/tui/routes/session/sidebar.tsx +313 -0
  279. package/src/cli/cmd/tui/thread.ts +174 -0
  280. package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
  281. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
  282. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +204 -0
  283. package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
  284. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +102 -0
  285. package/src/cli/cmd/tui/ui/dialog-select.tsx +389 -0
  286. package/src/cli/cmd/tui/ui/dialog.tsx +180 -0
  287. package/src/cli/cmd/tui/ui/link.tsx +34 -0
  288. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  289. package/src/cli/cmd/tui/ui/toast.tsx +138 -0
  290. package/src/cli/cmd/tui/util/clipboard.ts +154 -0
  291. package/src/cli/cmd/tui/util/editor.ts +32 -0
  292. package/src/cli/cmd/tui/util/signal.ts +7 -0
  293. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  294. package/src/cli/cmd/tui/util/transcript.ts +98 -0
  295. package/src/cli/cmd/tui/win32.ts +110 -0
  296. package/src/cli/cmd/tui/worker.ts +156 -0
  297. package/src/cli/cmd/uninstall.ts +357 -0
  298. package/src/cli/cmd/upgrade.ts +72 -0
  299. package/src/cli/cmd/web.ts +87 -0
  300. package/src/cli/cmd/workspace-serve.ts +16 -0
  301. package/src/cli/error.ts +57 -0
  302. package/src/cli/network.ts +55 -0
  303. package/src/cli/remote/index.ts +36 -0
  304. package/src/cli/remote/notifications.ts +104 -0
  305. package/src/cli/remote/qr-renderer.ts +86 -0
  306. package/src/cli/remote/remote-service.ts +757 -0
  307. package/src/cli/remote/session-manager.ts +284 -0
  308. package/src/cli/remote/subagent-hooks.ts +151 -0
  309. package/src/cli/remote/types.ts +121 -0
  310. package/src/cli/ui.ts +96 -0
  311. package/src/cli/upgrade.ts +25 -0
  312. package/src/command/index.ts +174 -0
  313. package/src/command/template/initialize.txt +10 -0
  314. package/src/command/template/review.txt +99 -0
  315. package/src/config/config.ts +1760 -0
  316. package/src/config/markdown.ts +88 -0
  317. package/src/config/migrate-tui-config.ts +155 -0
  318. package/src/config/paths.ts +174 -0
  319. package/src/config/tui-schema.ts +36 -0
  320. package/src/config/tui.ts +209 -0
  321. package/src/connectors/api/base.ts +75 -0
  322. package/src/connectors/api/figma.ts +103 -0
  323. package/src/connectors/api/github.ts +247 -0
  324. package/src/connectors/api/lovable.ts +126 -0
  325. package/src/connectors/api/slack.ts +137 -0
  326. package/src/connectors/auth.ts +68 -0
  327. package/src/connectors/cache.ts +119 -0
  328. package/src/connectors/credentials.ts +81 -0
  329. package/src/connectors/index.ts +202 -0
  330. package/src/connectors/registry.ts +358 -0
  331. package/src/docs/context.ts +120 -0
  332. package/src/docs/library.ts +189 -0
  333. package/src/env/index.ts +26 -0
  334. package/src/file/ignore.ts +83 -0
  335. package/src/file/index.ts +411 -0
  336. package/src/file/ripgrep.ts +402 -0
  337. package/src/file/time.ts +65 -0
  338. package/src/file/watcher.ts +127 -0
  339. package/src/flag/flag.ts +128 -0
  340. package/src/format/formatter.ts +356 -0
  341. package/src/format/index.ts +137 -0
  342. package/src/global/index.ts +57 -0
  343. package/src/id/id.ts +83 -0
  344. package/src/ide/index.ts +76 -0
  345. package/src/index.ts +184 -0
  346. package/src/installation/index.ts +246 -0
  347. package/src/lsp/client.ts +250 -0
  348. package/src/lsp/index.ts +483 -0
  349. package/src/lsp/language.ts +119 -0
  350. package/src/lsp/server.ts +2046 -0
  351. package/src/mcp/auth.ts +121 -0
  352. package/src/mcp/index.ts +860 -0
  353. package/src/mcp/oauth-callback.ts +198 -0
  354. package/src/mcp/oauth-provider.ts +148 -0
  355. package/src/mobile/auth.ts +97 -0
  356. package/src/mobile/github-repo.ts +185 -0
  357. package/src/patch/index.ts +631 -0
  358. package/src/permission/arity.ts +150 -0
  359. package/src/permission/dbedit.ts +236 -0
  360. package/src/permission/index.ts +210 -0
  361. package/src/permission/next.ts +287 -0
  362. package/src/plugin/codex.ts +493 -0
  363. package/src/plugin/copilot.ts +261 -0
  364. package/src/plugin/index.ts +714 -0
  365. package/src/plugin/install.ts +379 -0
  366. package/src/plugin/meta.ts +165 -0
  367. package/src/plugin/shared.ts +188 -0
  368. package/src/project/bootstrap.ts +35 -0
  369. package/src/project/instance.ts +84 -0
  370. package/src/project/project.ts +373 -0
  371. package/src/project/state.ts +66 -0
  372. package/src/project/vcs.ts +76 -0
  373. package/src/prompt/stash-store.ts +93 -0
  374. package/src/provider/auth.ts +147 -0
  375. package/src/provider/models-macro.ts +22 -0
  376. package/src/provider/models.ts +216 -0
  377. package/src/provider/provider.ts +1483 -0
  378. package/src/provider/sdk/openai-compatible/src/README.md +5 -0
  379. package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
  380. package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
  381. package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
  382. package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
  383. package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
  384. package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
  385. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
  386. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1732 -0
  387. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
  388. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
  389. package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
  390. package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
  391. package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
  392. package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
  393. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
  394. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
  395. package/src/provider/transform.ts +828 -0
  396. package/src/pty/index.ts +241 -0
  397. package/src/question/index.ts +171 -0
  398. package/src/rag/chunk.ts +43 -0
  399. package/src/rag/embed.ts +179 -0
  400. package/src/rag/index.ts +376 -0
  401. package/src/rag/storage.ts +76 -0
  402. package/src/scheduler/index.ts +61 -0
  403. package/src/server/error.ts +36 -0
  404. package/src/server/event.ts +7 -0
  405. package/src/server/mdns.ts +59 -0
  406. package/src/server/routes/chatbot.ts +205 -0
  407. package/src/server/routes/companion.ts +729 -0
  408. package/src/server/routes/config.ts +92 -0
  409. package/src/server/routes/connectors.ts +121 -0
  410. package/src/server/routes/dbedit.ts +76 -0
  411. package/src/server/routes/experimental.ts +210 -0
  412. package/src/server/routes/file.ts +197 -0
  413. package/src/server/routes/global.ts +135 -0
  414. package/src/server/routes/mcp.ts +225 -0
  415. package/src/server/routes/mobile.ts +2044 -0
  416. package/src/server/routes/permission.ts +68 -0
  417. package/src/server/routes/project.ts +82 -0
  418. package/src/server/routes/provider.ts +235 -0
  419. package/src/server/routes/pty.ts +169 -0
  420. package/src/server/routes/question.ts +98 -0
  421. package/src/server/routes/session.ts +968 -0
  422. package/src/server/routes/tui.ts +379 -0
  423. package/src/server/routes/workspace.ts +104 -0
  424. package/src/server/server.ts +761 -0
  425. package/src/server/ssh.ts +207 -0
  426. package/src/session/auth.ts +402 -0
  427. package/src/session/compaction.ts +253 -0
  428. package/src/session/generate.ts +38 -0
  429. package/src/session/index.ts +598 -0
  430. package/src/session/llm.ts +273 -0
  431. package/src/session/message-v2.ts +836 -0
  432. package/src/session/message.ts +189 -0
  433. package/src/session/processor.ts +408 -0
  434. package/src/session/prompt/anthropic-20250930.txt +165 -0
  435. package/src/session/prompt/anthropic.txt +105 -0
  436. package/src/session/prompt/anthropic_spoof.txt +1 -0
  437. package/src/session/prompt/beast.txt +147 -0
  438. package/src/session/prompt/build-switch.txt +5 -0
  439. package/src/session/prompt/codex_header.txt +79 -0
  440. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  441. package/src/session/prompt/gemini.txt +155 -0
  442. package/src/session/prompt/max-steps.txt +16 -0
  443. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  444. package/src/session/prompt/plan.txt +25 -0
  445. package/src/session/prompt/qwen.txt +108 -0
  446. package/src/session/prompt.ts +1942 -0
  447. package/src/session/retry.ts +90 -0
  448. package/src/session/revert.ts +120 -0
  449. package/src/session/stats.ts +404 -0
  450. package/src/session/status.ts +84 -0
  451. package/src/session/summary.ts +184 -0
  452. package/src/session/system.ts +195 -0
  453. package/src/session/toast.tsx +105 -0
  454. package/src/session/todo.ts +258 -0
  455. package/src/session/uninstall.ts +357 -0
  456. package/src/share/share-next.ts +421 -0
  457. package/src/share/share.ts +92 -0
  458. package/src/shell/shell.ts +65 -0
  459. package/src/skill/index.ts +1 -0
  460. package/src/skill/skill.ts +232 -0
  461. package/src/snapshot/index.ts +297 -0
  462. package/src/storage/storage.ts +227 -0
  463. package/src/tool/apply_patch.ts +288 -0
  464. package/src/tool/apply_patch.txt +33 -0
  465. package/src/tool/bash.ts +252 -0
  466. package/src/tool/bash.txt +115 -0
  467. package/src/tool/batch.ts +175 -0
  468. package/src/tool/batch.txt +24 -0
  469. package/src/tool/codesearch.ts +132 -0
  470. package/src/tool/codesearch.txt +12 -0
  471. package/src/tool/context_collect.ts +152 -0
  472. package/src/tool/context_collect.txt +9 -0
  473. package/src/tool/context_diagnostics.ts +81 -0
  474. package/src/tool/context_diagnostics.txt +5 -0
  475. package/src/tool/context_related.ts +117 -0
  476. package/src/tool/context_related.txt +5 -0
  477. package/src/tool/context_search.ts +108 -0
  478. package/src/tool/context_search.txt +8 -0
  479. package/src/tool/db-diff.ts +434 -0
  480. package/src/tool/db-table.txt +15 -0
  481. package/src/tool/docs_add.ts +50 -0
  482. package/src/tool/docs_add.txt +5 -0
  483. package/src/tool/docs_context.ts +56 -0
  484. package/src/tool/docs_context.txt +4 -0
  485. package/src/tool/docs_gap_report.ts +79 -0
  486. package/src/tool/docs_gap_report.txt +7 -0
  487. package/src/tool/docs_load.ts +41 -0
  488. package/src/tool/docs_load.txt +4 -0
  489. package/src/tool/docs_request.ts +129 -0
  490. package/src/tool/docs_request.txt +7 -0
  491. package/src/tool/docs_search.ts +51 -0
  492. package/src/tool/docs_search.txt +6 -0
  493. package/src/tool/docs_unload.ts +38 -0
  494. package/src/tool/docs_unload.txt +5 -0
  495. package/src/tool/edit.ts +614 -0
  496. package/src/tool/edit.txt +10 -0
  497. package/src/tool/external-directory.ts +32 -0
  498. package/src/tool/generate_image.ts +174 -0
  499. package/src/tool/generate_image.txt +12 -0
  500. package/src/tool/glob.ts +79 -0
  501. package/src/tool/glob.txt +6 -0
  502. package/src/tool/grep.ts +153 -0
  503. package/src/tool/grep.txt +8 -0
  504. package/src/tool/invalid.ts +17 -0
  505. package/src/tool/ls.ts +116 -0
  506. package/src/tool/ls.txt +1 -0
  507. package/src/tool/lsp.ts +96 -0
  508. package/src/tool/lsp.txt +19 -0
  509. package/src/tool/memory_search.ts +141 -0
  510. package/src/tool/memory_search.txt +8 -0
  511. package/src/tool/multiedit.ts +46 -0
  512. package/src/tool/multiedit.txt +41 -0
  513. package/src/tool/plan-enter.txt +14 -0
  514. package/src/tool/plan-exit.txt +13 -0
  515. package/src/tool/plan.ts +130 -0
  516. package/src/tool/question.ts +33 -0
  517. package/src/tool/question.txt +10 -0
  518. package/src/tool/rag_index.ts +77 -0
  519. package/src/tool/rag_index.txt +10 -0
  520. package/src/tool/rag_reset.ts +26 -0
  521. package/src/tool/rag_reset.txt +4 -0
  522. package/src/tool/rag_search.ts +62 -0
  523. package/src/tool/rag_search.txt +6 -0
  524. package/src/tool/rag_status.ts +45 -0
  525. package/src/tool/rag_status.txt +4 -0
  526. package/src/tool/read.ts +203 -0
  527. package/src/tool/read.txt +12 -0
  528. package/src/tool/registry.ts +214 -0
  529. package/src/tool/skill.ts +169 -0
  530. package/src/tool/skill.txt +3 -0
  531. package/src/tool/smart_docs.ts +74 -0
  532. package/src/tool/smart_docs.txt +7 -0
  533. package/src/tool/speak/elevenlabs.ts +201 -0
  534. package/src/tool/speak/openrouter.ts +240 -0
  535. package/src/tool/speak/provider.ts +83 -0
  536. package/src/tool/speak.ts +440 -0
  537. package/src/tool/task.ts +194 -0
  538. package/src/tool/task.txt +60 -0
  539. package/src/tool/todo.ts +53 -0
  540. package/src/tool/todoread.txt +14 -0
  541. package/src/tool/todowrite.txt +167 -0
  542. package/src/tool/tool.ts +87 -0
  543. package/src/tool/tree.ts +218 -0
  544. package/src/tool/tree.txt +8 -0
  545. package/src/tool/truncation.ts +106 -0
  546. package/src/tool/use-connector.ts +47 -0
  547. package/src/tool/voice.ts +188 -0
  548. package/src/tool/webfetch.ts +205 -0
  549. package/src/tool/webfetch.txt +13 -0
  550. package/src/tool/websearch.ts +150 -0
  551. package/src/tool/websearch.txt +14 -0
  552. package/src/tool/write.ts +80 -0
  553. package/src/tool/write.txt +8 -0
  554. package/src/util/archive.ts +16 -0
  555. package/src/util/color.ts +19 -0
  556. package/src/util/context.ts +25 -0
  557. package/src/util/defer.ts +12 -0
  558. package/src/util/error.ts +77 -0
  559. package/src/util/eventloop.ts +20 -0
  560. package/src/util/filesystem.ts +125 -0
  561. package/src/util/flock.ts +329 -0
  562. package/src/util/fn.ts +11 -0
  563. package/src/util/format.ts +20 -0
  564. package/src/util/hash.ts +7 -0
  565. package/src/util/iife.ts +3 -0
  566. package/src/util/keybind.ts +103 -0
  567. package/src/util/lazy.ts +18 -0
  568. package/src/util/locale.ts +81 -0
  569. package/src/util/lock.ts +98 -0
  570. package/src/util/log.ts +180 -0
  571. package/src/util/network.ts +9 -0
  572. package/src/util/process.ts +15 -0
  573. package/src/util/queue.ts +32 -0
  574. package/src/util/record.ts +3 -0
  575. package/src/util/rpc.ts +66 -0
  576. package/src/util/scrap.ts +10 -0
  577. package/src/util/signal.ts +12 -0
  578. package/src/util/timeout.ts +14 -0
  579. package/src/util/token.ts +7 -0
  580. package/src/util/wildcard.ts +56 -0
  581. package/src/workspace/adaptors/index.ts +271 -0
  582. package/src/workspace/adaptors/types.ts +14 -0
  583. package/src/workspace/adaptors/worktree.ts +31 -0
  584. package/src/workspace/config.ts +19 -0
  585. package/src/workspace/index.ts +223 -0
  586. package/src/workspace/session-proxy-middleware.ts +97 -0
  587. package/src/workspace/sse.ts +66 -0
  588. package/src/workspace/workspace-context.ts +23 -0
  589. package/src/workspace/workspace-server/routes.ts +33 -0
  590. package/src/workspace/workspace-server/server.ts +47 -0
  591. package/src/worktree/index.ts +487 -0
  592. package/sst-env.d.ts +10 -0
  593. package/test/benchmark.test.ts +121 -0
  594. package/test/build-optimizations.test.ts +124 -0
  595. package/test/id-benchmark.test.ts +132 -0
  596. package/test/optimizations.test.ts +302 -0
  597. package/test/preload.ts +1 -0
  598. package/test/solidjs-benchmark.test.ts +262 -0
  599. package/test/solidjs-optimizations.test.ts +259 -0
  600. package/test/tui-benchmark.test.ts +230 -0
  601. package/test/wildcard-benchmark.test.ts +180 -0
  602. package/tsconfig.json +26 -0
@@ -0,0 +1,757 @@
1
+ import { EventEmitter } from "node:events"
2
+ import fs from "node:fs"
3
+ import path from "node:path"
4
+ import os from "node:os"
5
+ import {
6
+ checkTunnelAvailability,
7
+ createTunnel,
8
+ probeTunnel,
9
+ type TunnelProvider,
10
+ type TunnelResult,
11
+ } from "@nikcli-ai/remote"
12
+ import { SessionManager } from "./session-manager"
13
+ import type {
14
+ BroadcastMessage,
15
+ RemoteNotification,
16
+ RemoteServiceConfig,
17
+ RemoteSession,
18
+ RemoteSessionPersistence,
19
+ ResolvedRemoteSession,
20
+ SessionOptions,
21
+ SessionStatus,
22
+ TaskInfo,
23
+ } from "./types"
24
+
25
+ type SessionSnapshot = {
26
+ id?: string
27
+ name?: string
28
+ status?: SessionStatus
29
+ connectedDevices?: number
30
+ startedAt?: string
31
+ lastActivity?: string
32
+ port?: number
33
+ }
34
+
35
+ const DEFAULT_TUNNEL_PROVIDERS: TunnelProvider[] = ["localtunnel", "cloudflared", "ngrok", "remotosh"]
36
+
37
+ export class RemoteService extends EventEmitter {
38
+ private static instance: RemoteService
39
+ private initialized = false
40
+ private sessionManager: SessionManager | null = null
41
+ private config: RemoteServiceConfig
42
+ private configPath: string
43
+ private sessionsDir: string
44
+ private activeTunnel: TunnelResult | null = null
45
+ private handledStoppedSessions = new Set<string>()
46
+
47
+ private constructor() {
48
+ super()
49
+ this.configPath = path.join(os.homedir(), ".nikcli", "remote-config.json")
50
+ this.sessionsDir = path.join(os.homedir(), ".nikcli", "remote-sessions")
51
+ this.config = this.getDefaultConfig()
52
+ }
53
+
54
+ static getInstance(): RemoteService {
55
+ if (!RemoteService.instance) {
56
+ RemoteService.instance = new RemoteService()
57
+ }
58
+ return RemoteService.instance
59
+ }
60
+
61
+ async init(): Promise<void> {
62
+ if (this.initialized) return
63
+
64
+ try {
65
+ await this.ensureDirectories()
66
+ this.config = await this.loadConfig()
67
+ await this.checkRemotoshAvailable()
68
+
69
+ if (this.config.autoRecoverSession) {
70
+ await this.recoverPreviousSessions()
71
+ }
72
+
73
+ this.initialized = true
74
+ this.emit("ready")
75
+ } catch (error: any) {
76
+ this.emit("error", error)
77
+ }
78
+ }
79
+
80
+ isInitialized(): boolean {
81
+ return this.initialized
82
+ }
83
+
84
+ async startSession(options?: SessionOptions): Promise<RemoteSession> {
85
+ if (!this.initialized) {
86
+ throw new Error("RemoteService not initialized. Call init() first.")
87
+ }
88
+
89
+ if (this.sessionManager?.isActive()) {
90
+ throw new Error('Session already active. Stop it first with "nikcli remote-control stop"')
91
+ }
92
+
93
+ if (this.sessionManager) {
94
+ this.sessionManager.removeAllListeners()
95
+ }
96
+
97
+ this.handledStoppedSessions.clear()
98
+ this.sessionManager = new SessionManager(this.config)
99
+
100
+ this.sessionManager.on("status:change", (session: RemoteSession) => {
101
+ void this.persistSession(session)
102
+ this.emit("session:status", session)
103
+ })
104
+
105
+ this.sessionManager.on("device:connected", (session: RemoteSession, device: any) => {
106
+ void this.persistSession(session)
107
+ this.emit("device:connected", session, device)
108
+ })
109
+
110
+ this.sessionManager.on("device:disconnected", (session: RemoteSession, device: any) => {
111
+ void this.persistSession(session)
112
+ this.emit("device:disconnected", session, device)
113
+ })
114
+
115
+ this.sessionManager.on("error", (error: Error) => {
116
+ this.emit("session:error", error)
117
+ })
118
+
119
+ this.sessionManager.on("terminal:output", (data: string) => {
120
+ this.broadcastToClients(data)
121
+ })
122
+
123
+ this.sessionManager.on("stopped", (session: RemoteSession | null) => {
124
+ void this.handleSessionStopped(session)
125
+ })
126
+
127
+ const session = await this.sessionManager.start(options)
128
+ await this.persistSession(session)
129
+
130
+ this.emit("session:started", session)
131
+ return session
132
+ }
133
+
134
+ async stopSession(): Promise<void> {
135
+ if (!this.sessionManager?.isActive()) {
136
+ throw new Error("No active session")
137
+ }
138
+
139
+ const session = this.sessionManager.getSession()
140
+ await this.sessionManager.stop()
141
+ await this.handleSessionStopped(session)
142
+ }
143
+
144
+ getSession(): RemoteSession | null {
145
+ return this.sessionManager?.getSession() ?? null
146
+ }
147
+
148
+ hasActiveSession(): boolean {
149
+ return this.sessionManager?.isActive() ?? false
150
+ }
151
+
152
+ hasRemoteClients(): boolean {
153
+ const session = this.sessionManager?.getSession()
154
+ return (session?.connectedDevices?.length ?? 0) > 0
155
+ }
156
+
157
+ private sanitizeForTunnel(data: string): string {
158
+ return data
159
+ }
160
+
161
+ writeToTerminal(data: string): void {
162
+ if (!this.sessionManager?.isActive()) return
163
+ const sanitized = this.sanitizeForTunnel(data)
164
+ this.sessionManager.writeToTerminal(sanitized)
165
+ }
166
+
167
+ private broadcastToClients(_data: string): void {
168
+ // The server already broadcasts to clients via WebSocket
169
+ // No action needed - this is just for receiving output from server
170
+ }
171
+
172
+ resizeTerminal(cols: number, rows: number): void {
173
+ if (!this.sessionManager?.isActive()) return
174
+ this.sessionManager.resizeTerminal(cols, rows)
175
+ }
176
+
177
+ getServerPort(): number {
178
+ return this.sessionManager?.getSession()?.port ?? 0
179
+ }
180
+
181
+ getSessionSecret(): string {
182
+ const session = this.sessionManager?.getSession()
183
+ if (!session?.qrUrl) return ""
184
+ return this.extractTokenFromUrl(session.qrUrl)
185
+ }
186
+
187
+ async createSessionTunnel(options: {
188
+ enableTunnel: boolean
189
+ provider?: TunnelProvider
190
+ }): Promise<TunnelProvider | null> {
191
+ const session = this.getSession()
192
+ if (!session) throw new Error("No active session")
193
+ if (!options.enableTunnel) return null
194
+ if (options.provider === "none") return null
195
+
196
+ const port = session.port ?? this.getServerPort()
197
+ if (!port) {
198
+ throw new Error("Tunnel unavailable; missing server port")
199
+ }
200
+
201
+ const providers = await this.getTunnelCandidates(options.provider)
202
+ if (providers.length === 0) {
203
+ return null
204
+ }
205
+
206
+ const sessionToken = this.extractTokenFromUrl(session.qrUrl)
207
+ if (!sessionToken) {
208
+ throw new Error("Tunnel unavailable; missing session token")
209
+ }
210
+
211
+ await this.closeTunnel()
212
+
213
+ for (const provider of providers) {
214
+ const result = await createTunnel(port, provider).catch(() => null)
215
+ if (!result) continue
216
+
217
+ const tunnelUrl = this.buildSessionUrl(result.url, session.id, sessionToken)
218
+ const reachable = await probeTunnel(tunnelUrl)
219
+ if (!reachable) {
220
+ await result.close().catch(() => {})
221
+ continue
222
+ }
223
+
224
+ this.activeTunnel = result
225
+ session.tunnelUrl = tunnelUrl
226
+ session.qrUrl = tunnelUrl
227
+ await this.persistSession(session)
228
+ return provider
229
+ }
230
+
231
+ return null
232
+ }
233
+
234
+ async closeTunnel(): Promise<void> {
235
+ if (this.activeTunnel) {
236
+ await this.activeTunnel.close().catch(() => {})
237
+ this.activeTunnel = null
238
+ }
239
+
240
+ const session = this.getSession()
241
+ if (!session?.tunnelUrl || !session.localUrl) return
242
+
243
+ const token = this.extractTokenFromUrl(session.qrUrl)
244
+ session.tunnelUrl = undefined
245
+ session.qrUrl = this.buildSessionUrl(session.localUrl, session.id, token)
246
+ await this.persistSession(session)
247
+ }
248
+
249
+ broadcast(message: BroadcastMessage): void {
250
+ if (!this.sessionManager?.isActive()) return
251
+ this.sessionManager.broadcast({
252
+ ...message,
253
+ timestamp: message.timestamp ?? Date.now(),
254
+ })
255
+ }
256
+
257
+ notify(notification: RemoteNotification): void {
258
+ if (!this.config.notificationsEnabled) return
259
+ this.sessionManager?.notify(notification)
260
+ }
261
+
262
+ notifyTaskComplete(task: TaskInfo): void {
263
+ if (!this.config.notificationsEnabled) return
264
+ this.notify({
265
+ type: "task_complete",
266
+ title: `Task Completed: ${task.name}`,
267
+ body: task.summary,
268
+ data: task,
269
+ })
270
+ }
271
+
272
+ notifyError(agentName: string, error: string): void {
273
+ if (!this.config.notificationsEnabled) return
274
+ this.notify({
275
+ type: "error",
276
+ title: `Error in ${agentName}`,
277
+ body: error,
278
+ })
279
+ }
280
+
281
+ notifyInputRequired(agentName: string, message: string): void {
282
+ if (!this.config.notificationsEnabled) return
283
+ this.notify({
284
+ type: "action_required",
285
+ title: `${agentName} needs input`,
286
+ body: message,
287
+ })
288
+ }
289
+
290
+ getConfig(): RemoteServiceConfig {
291
+ return { ...this.config }
292
+ }
293
+
294
+ async updateConfig(config: Partial<RemoteServiceConfig>): Promise<void> {
295
+ this.config = { ...this.config, ...config }
296
+ await this.saveConfig()
297
+ }
298
+
299
+ async resolveSession(sessionId?: string): Promise<ResolvedRemoteSession | null> {
300
+ const active = this.getSession()
301
+ if (active && active.status !== "stopped") {
302
+ if (!sessionId || active.id === sessionId) {
303
+ return {
304
+ source: "memory",
305
+ session: active,
306
+ }
307
+ }
308
+ }
309
+
310
+ return this.getLivePersistedSession(sessionId)
311
+ }
312
+
313
+ async listPersistedSessions(): Promise<RemoteSessionPersistence[]> {
314
+ if (!fs.existsSync(this.sessionsDir)) return []
315
+
316
+ const result: RemoteSessionPersistence[] = []
317
+ for (const file of fs.readdirSync(this.sessionsDir)) {
318
+ if (!file.endsWith(".json")) continue
319
+ const filePath = path.join(this.sessionsDir, file)
320
+ const loaded = this.readPersistedSession(filePath)
321
+ if (loaded) {
322
+ result.push(loaded)
323
+ }
324
+ }
325
+
326
+ return result.toSorted((a, b) => {
327
+ const aTs = new Date(a.lastActivity).getTime()
328
+ const bTs = new Date(b.lastActivity).getTime()
329
+ return bTs - aTs
330
+ })
331
+ }
332
+
333
+ async getPersistedSession(sessionId: string): Promise<RemoteSessionPersistence | null> {
334
+ const filePath = this.getSessionFilePath(sessionId)
335
+ if (!fs.existsSync(filePath)) return null
336
+ return this.readPersistedSession(filePath)
337
+ }
338
+
339
+ async getLivePersistedSession(sessionId?: string): Promise<ResolvedRemoteSession | null> {
340
+ const candidates = await (async () => {
341
+ if (sessionId) {
342
+ const match = await this.getPersistedSession(sessionId)
343
+ return match ? [match] : []
344
+ }
345
+ return this.listPersistedSessions()
346
+ })()
347
+
348
+ for (const candidate of candidates) {
349
+ const snapshot = await this.fetchPersistedSessionSnapshot(candidate)
350
+ if (!snapshot) {
351
+ await this.cleanupPersistedSession(candidate.sessionId)
352
+ continue
353
+ }
354
+
355
+ const status = snapshot.status ?? candidate.status
356
+ if (status === "stopped") {
357
+ await this.cleanupPersistedSession(candidate.sessionId)
358
+ continue
359
+ }
360
+
361
+ const now = new Date().toISOString()
362
+ const merged: RemoteSessionPersistence = {
363
+ ...candidate,
364
+ name: snapshot.name ?? candidate.name,
365
+ status,
366
+ startedAt: snapshot.startedAt ?? candidate.startedAt,
367
+ lastActivity: snapshot.lastActivity ?? now,
368
+ port: snapshot.port ?? candidate.port,
369
+ localUrl: candidate.localUrl ?? this.deriveLocalUrl(candidate),
370
+ }
371
+
372
+ const hydrated = this.hydratePersistedSession(merged, snapshot)
373
+ await this.persistSessionRecord(merged)
374
+
375
+ return {
376
+ source: "persisted",
377
+ session: hydrated,
378
+ persisted: merged,
379
+ }
380
+ }
381
+
382
+ return null
383
+ }
384
+
385
+ async stopPersistedSession(sessionId: string): Promise<boolean> {
386
+ const session = await this.getPersistedSession(sessionId)
387
+ if (!session) return false
388
+
389
+ const controlURL = this.buildControlURL(session, "/api/control/stop")
390
+ if (!controlURL) return false
391
+
392
+ const token = this.extractTokenFromUrl(session.qrUrl)
393
+ if (token) {
394
+ controlURL.searchParams.set("t", token)
395
+ }
396
+
397
+ const controller = new AbortController()
398
+ const timeout = setTimeout(() => controller.abort(), 4000)
399
+
400
+ const response = await fetch(controlURL, {
401
+ method: "POST",
402
+ headers: token
403
+ ? {
404
+ Authorization: `Bearer ${token}`,
405
+ }
406
+ : undefined,
407
+ signal: controller.signal,
408
+ }).catch(() => null)
409
+
410
+ clearTimeout(timeout)
411
+
412
+ if (!response) {
413
+ return false
414
+ }
415
+
416
+ if (response.ok) {
417
+ await this.cleanupPersistedSession(sessionId)
418
+ return true
419
+ }
420
+
421
+ if (response.status === 404 || response.status === 410) {
422
+ await this.cleanupPersistedSession(sessionId)
423
+ }
424
+
425
+ return false
426
+ }
427
+
428
+ async removePersistedSession(sessionId: string): Promise<void> {
429
+ await this.cleanupPersistedSession(sessionId)
430
+ }
431
+
432
+ private async handleSessionStopped(session: RemoteSession | null): Promise<void> {
433
+ const sessionId = session?.id
434
+ if (sessionId && this.handledStoppedSessions.has(sessionId)) {
435
+ return
436
+ }
437
+ if (sessionId) {
438
+ this.handledStoppedSessions.add(sessionId)
439
+ }
440
+
441
+ await this.closeTunnel()
442
+
443
+ if (sessionId) {
444
+ await this.cleanupPersistedSession(sessionId)
445
+ }
446
+
447
+ this.emit("session:stopped")
448
+ }
449
+
450
+ private async getTunnelCandidates(preferred?: TunnelProvider): Promise<TunnelProvider[]> {
451
+ if (preferred && preferred !== "none") {
452
+ return (await checkTunnelAvailability(preferred)) ? [preferred] : []
453
+ }
454
+
455
+ const available: TunnelProvider[] = []
456
+ for (const candidate of DEFAULT_TUNNEL_PROVIDERS) {
457
+ if (await checkTunnelAvailability(candidate)) {
458
+ available.push(candidate)
459
+ }
460
+ }
461
+ return available
462
+ }
463
+
464
+ private buildSessionUrl(baseUrl: string, sessionId: string, token?: string): string {
465
+ const url = new URL(baseUrl)
466
+ url.searchParams.set("s", sessionId)
467
+ if (token) {
468
+ url.searchParams.set("t", token)
469
+ }
470
+ return url.toString()
471
+ }
472
+
473
+ private hydratePersistedSession(session: RemoteSessionPersistence, snapshot?: SessionSnapshot): RemoteSession {
474
+ const startedAt = this.safeDate(snapshot?.startedAt ?? session.startedAt)
475
+ const lastActivity = this.safeDate(snapshot?.lastActivity ?? session.lastActivity)
476
+ const connectedCount = Math.max(0, snapshot?.connectedDevices ?? 0)
477
+
478
+ return {
479
+ id: snapshot?.id ?? session.sessionId,
480
+ name: snapshot?.name ?? session.name,
481
+ qrCode: "",
482
+ qrUrl: session.tunnelUrl || session.qrUrl,
483
+ localUrl: session.localUrl ?? this.deriveLocalUrl(session),
484
+ tunnelUrl: session.tunnelUrl,
485
+ tunnelPassword: session.tunnelPassword,
486
+ status: snapshot?.status ?? session.status,
487
+ connectedDevices: Array.from({ length: connectedCount }, (_, index) => ({
488
+ id: `remote-${index + 1}`,
489
+ connectedAt: lastActivity,
490
+ lastActivity,
491
+ })),
492
+ startedAt,
493
+ lastActivity,
494
+ port: snapshot?.port ?? session.port,
495
+ }
496
+ }
497
+
498
+ private safeDate(input: string): Date {
499
+ const parsed = new Date(input)
500
+ return Number.isNaN(parsed.getTime()) ? new Date() : parsed
501
+ }
502
+
503
+ private deriveLocalUrl(session: RemoteSessionPersistence): string | undefined {
504
+ if (session.localUrl) return session.localUrl
505
+
506
+ try {
507
+ const parsed = new URL(session.qrUrl)
508
+ return parsed.origin
509
+ } catch {
510
+ if (session.port) {
511
+ return `http://127.0.0.1:${session.port}`
512
+ }
513
+ return undefined
514
+ }
515
+ }
516
+
517
+ private extractTokenFromUrl(url: string): string {
518
+ try {
519
+ return new URL(url).searchParams.get("t") || ""
520
+ } catch {
521
+ return ""
522
+ }
523
+ }
524
+
525
+ private buildControlURL(session: RemoteSessionPersistence, endpoint: string): URL | null {
526
+ const localUrl = session.localUrl ?? this.deriveLocalUrl(session)
527
+ if (!localUrl) return null
528
+ try {
529
+ return new URL(endpoint, localUrl)
530
+ } catch {
531
+ return null
532
+ }
533
+ }
534
+
535
+ private async fetchPersistedSessionSnapshot(session: RemoteSessionPersistence): Promise<SessionSnapshot | null> {
536
+ const token = this.extractTokenFromUrl(session.qrUrl)
537
+ const headers = token
538
+ ? {
539
+ Authorization: `Bearer ${token}`,
540
+ }
541
+ : undefined
542
+
543
+ const controlURL = this.buildControlURL(session, "/api/control/status")
544
+ if (controlURL) {
545
+ if (token) {
546
+ controlURL.searchParams.set("t", token)
547
+ }
548
+ const control = await this.fetchJSON(controlURL, headers)
549
+ if (control && typeof control === "object") {
550
+ const payload =
551
+ "session" in control && control.session && typeof control.session === "object" ? control.session : control
552
+ const normalized = this.normalizeSessionSnapshot(payload)
553
+ if (normalized) return normalized
554
+ }
555
+ }
556
+
557
+ const legacyURL = this.buildControlURL(session, "/api/session")
558
+ if (!legacyURL) return null
559
+ const legacy = await this.fetchJSON(legacyURL)
560
+ if (!legacy || typeof legacy !== "object") return null
561
+ return this.normalizeSessionSnapshot(legacy)
562
+ }
563
+
564
+ private normalizeSessionSnapshot(input: unknown): SessionSnapshot | null {
565
+ if (!input || typeof input !== "object") return null
566
+ const value = input as Record<string, unknown>
567
+
568
+ const status = typeof value.status === "string" ? (value.status as SessionStatus) : undefined
569
+ const id = typeof value.id === "string" ? value.id : undefined
570
+ const name = typeof value.name === "string" ? value.name : undefined
571
+ const connectedDevicesRaw = value.connectedDevices
572
+ const connectedDevices =
573
+ typeof connectedDevicesRaw === "number" && Number.isFinite(connectedDevicesRaw)
574
+ ? Math.max(0, Math.floor(connectedDevicesRaw))
575
+ : undefined
576
+
577
+ const startedAt = typeof value.startedAt === "string" ? value.startedAt : undefined
578
+ const lastActivity = typeof value.lastActivity === "string" ? value.lastActivity : undefined
579
+ const portRaw = value.port
580
+ const port = typeof portRaw === "number" && Number.isFinite(portRaw) ? Math.max(0, Math.floor(portRaw)) : undefined
581
+
582
+ if (!status && !id && !name && connectedDevices === undefined) return null
583
+
584
+ return {
585
+ id,
586
+ name,
587
+ status,
588
+ connectedDevices,
589
+ startedAt,
590
+ lastActivity,
591
+ port,
592
+ }
593
+ }
594
+
595
+ private async fetchJSON(url: URL, headers?: Record<string, string>): Promise<unknown> {
596
+ const controller = new AbortController()
597
+ const timeout = setTimeout(() => controller.abort(), 3500)
598
+
599
+ try {
600
+ const response = await fetch(url, {
601
+ method: "GET",
602
+ headers,
603
+ signal: controller.signal,
604
+ })
605
+
606
+ if (!response.ok) return null
607
+ return await response.json().catch(() => null)
608
+ } catch {
609
+ return null
610
+ } finally {
611
+ clearTimeout(timeout)
612
+ }
613
+ }
614
+
615
+ private readPersistedSession(filePath: string): RemoteSessionPersistence | null {
616
+ try {
617
+ const data = fs.readFileSync(filePath, "utf-8")
618
+ const parsed = JSON.parse(data) as Partial<RemoteSessionPersistence>
619
+ if (!parsed || typeof parsed !== "object") return null
620
+ if (!parsed.sessionId || !parsed.name || !parsed.qrUrl) return null
621
+ if (!parsed.startedAt || !parsed.lastActivity || !parsed.status) return null
622
+
623
+ return {
624
+ sessionId: parsed.sessionId,
625
+ name: parsed.name,
626
+ qrUrl: parsed.qrUrl,
627
+ localUrl: parsed.localUrl,
628
+ tunnelUrl: parsed.tunnelUrl,
629
+ tunnelPassword: parsed.tunnelPassword,
630
+ startedAt: parsed.startedAt,
631
+ lastActivity: parsed.lastActivity,
632
+ status: parsed.status,
633
+ port: parsed.port,
634
+ }
635
+ } catch {
636
+ return null
637
+ }
638
+ }
639
+
640
+ private async ensureDirectories(): Promise<void> {
641
+ const dirs = [path.dirname(this.configPath), this.sessionsDir]
642
+
643
+ for (const dir of dirs) {
644
+ if (!fs.existsSync(dir)) {
645
+ fs.mkdirSync(dir, { recursive: true })
646
+ }
647
+ }
648
+ }
649
+
650
+ private async loadConfig(): Promise<RemoteServiceConfig> {
651
+ try {
652
+ if (fs.existsSync(this.configPath)) {
653
+ const data = fs.readFileSync(this.configPath, "utf-8")
654
+ const loaded = JSON.parse(data)
655
+ return { ...this.getDefaultConfig(), ...loaded }
656
+ }
657
+ } catch {
658
+ // ignore
659
+ }
660
+ return this.getDefaultConfig()
661
+ }
662
+
663
+ private async saveConfig(): Promise<void> {
664
+ try {
665
+ fs.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2))
666
+ } catch {
667
+ // ignore
668
+ }
669
+ }
670
+
671
+ private getDefaultConfig(): RemoteServiceConfig {
672
+ return {
673
+ notificationsEnabled: true,
674
+ sessionExpiry: 86400,
675
+ maxDevices: 5,
676
+ autoRecoverSession: true,
677
+ }
678
+ }
679
+
680
+ private async checkRemotoshAvailable(): Promise<boolean> {
681
+ try {
682
+ const { execSync } = await import("node:child_process")
683
+ execSync("remoto --version", { stdio: "pipe", timeout: 10000 })
684
+ return true
685
+ } catch {
686
+ return false
687
+ }
688
+ }
689
+
690
+ private async recoverPreviousSessions(): Promise<void> {
691
+ try {
692
+ if (!fs.existsSync(this.sessionsDir)) return
693
+ const files = fs.readdirSync(this.sessionsDir)
694
+ for (const file of files) {
695
+ if (!file.endsWith(".json")) continue
696
+ const filePath = path.join(this.sessionsDir, file)
697
+ const session = this.readPersistedSession(filePath)
698
+ if (!session) {
699
+ fs.unlinkSync(filePath)
700
+ continue
701
+ }
702
+
703
+ const startedAt = new Date(session.startedAt)
704
+ const now = new Date()
705
+ const ageSeconds = (now.getTime() - startedAt.getTime()) / 1000
706
+ if (ageSeconds > this.config.sessionExpiry) {
707
+ fs.unlinkSync(filePath)
708
+ }
709
+ }
710
+ } catch {
711
+ // ignore
712
+ }
713
+ }
714
+
715
+ private getSessionFilePath(sessionId: string): string {
716
+ return path.join(this.sessionsDir, `${sessionId}.json`)
717
+ }
718
+
719
+ private async persistSessionRecord(session: RemoteSessionPersistence): Promise<void> {
720
+ try {
721
+ const filePath = this.getSessionFilePath(session.sessionId)
722
+ fs.writeFileSync(filePath, JSON.stringify(session, null, 2))
723
+ } catch {
724
+ // ignore
725
+ }
726
+ }
727
+
728
+ private async persistSession(session: RemoteSession): Promise<void> {
729
+ const data: RemoteSessionPersistence = {
730
+ sessionId: session.id,
731
+ name: session.name,
732
+ qrUrl: session.qrUrl,
733
+ localUrl: session.localUrl,
734
+ tunnelUrl: session.tunnelUrl,
735
+ tunnelPassword: session.tunnelPassword,
736
+ startedAt: session.startedAt.toISOString(),
737
+ lastActivity: session.lastActivity.toISOString(),
738
+ status: session.status,
739
+ port: session.port,
740
+ }
741
+
742
+ await this.persistSessionRecord(data)
743
+ }
744
+
745
+ private async cleanupPersistedSession(sessionId: string): Promise<void> {
746
+ try {
747
+ const filePath = this.getSessionFilePath(sessionId)
748
+ if (fs.existsSync(filePath)) {
749
+ fs.unlinkSync(filePath)
750
+ }
751
+ } catch {
752
+ // ignore
753
+ }
754
+ }
755
+ }
756
+
757
+ export const remoteService = RemoteService.getInstance()