nolo-cli 0.107.0-alpha.3 → 0.107.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/README.md +1 -1
  2. package/{_executeModel-UDCSOTD2.js → _executeModel-C4EH5TZG.js} +77 -69
  3. package/action-GVA5ARSJ.js +20 -0
  4. package/{addContentAction-MB2HHMDZ.js → addContentAction-M2WOWZCL.js} +13 -12
  5. package/{addDialogAgentAction-MCEABSBA.js → addDialogAgentAction-QMAQ5Z6N.js} +19 -20
  6. package/{agentDeleteCommand-OWYHN6D6.js → agentDeleteCommand-D3RICSHX.js} +118 -43
  7. package/{agentEmailCommands-BQFXQIHF.js → agentEmailCommands-YC2A4USQ.js} +99 -89
  8. package/agentListCommands-UDJELMYP.js +741 -0
  9. package/{agentMachineCommands-6DTI7YSG.js → agentMachineCommands-XH4IBGTO.js} +48 -44
  10. package/{agentProviderCommands-GNPTL5XR.js → agentProviderCommands-CMVTBY5X.js} +97 -2
  11. package/{agentPullCommand-O5MPVJ5E.js → agentPullCommand-B4T2DJ5H.js} +1 -1
  12. package/agentRecordCommands-3EOC2QMN.js +138 -0
  13. package/agentRunCommand-BTHYOVVS.js +167 -0
  14. package/{agentRunControl-AVJR4JW4.js → agentRunControl-3HWA2LLQ.js} +2 -2
  15. package/{agentRuntimeLocal-KCMC2TBD.js → agentRuntimeLocal-N4YGKL7M.js} +44 -40
  16. package/{agentSlice-R7PR6KYM.js → agentSlice-NIN4T6CF.js} +15 -14
  17. package/{appDeleteCommands-JEPA3EWE.js → appDeleteCommands-7DAF33AM.js} +46 -42
  18. package/{appDeployCommands-UTB76XFM.js → appDeployCommands-ZPZSMFGZ.js} +46 -42
  19. package/{appDeployStatusCommands-LGHF2SBD.js → appDeployStatusCommands-FWBDSSS5.js} +46 -42
  20. package/{appGetCommands-PX2BFHOB.js → appGetCommands-SZZOHGEP.js} +46 -42
  21. package/{appListCommands-BSMPTWKC.js → appListCommands-FPMUBDPM.js} +46 -42
  22. package/{authCommand-YW42YZA4.js → authCommand-VL3G26NQ.js} +119 -25
  23. package/{authCommands-RGOGCPP6.js → authCommands-5RTUDYZW.js} +7 -5
  24. package/{builtinSkillRegistry-FUAVTRVZ.js → builtinSkillRegistry-ZK4K67Y5.js} +11 -9
  25. package/{chromeCommands-FZYAU75D.js → chromeCommands-AU5ZJXBG.js} +289 -37
  26. package/{chromeOpsCommands-62P2Q2YB.js → chromeOpsCommands-ZYTCMNHG.js} +156 -22
  27. package/{chunk-JDPFV2JV.js → chunk-2NNRWRM5.js} +20 -11
  28. package/{chunk-JEDZAO7L.js → chunk-2SYTUTLI.js} +257 -37
  29. package/{chunk-HVURBGF3.js → chunk-2U3NHADM.js} +6 -6
  30. package/{chunk-AVTT555J.js → chunk-2ZHBFX5R.js} +29 -9
  31. package/{chunk-QTGQ2LTL.js → chunk-3DQY5IFB.js} +6 -10
  32. package/chunk-4LCOCOR5.js +50 -0
  33. package/{chunk-JPGCXHNM.js → chunk-4LKMZ3RR.js} +1 -1
  34. package/{chunk-2GRGFCF6.js → chunk-4VJAC32I.js} +2 -1
  35. package/{chunk-6D6VBVEW.js → chunk-4W24JM32.js} +80 -14
  36. package/{chunk-MC4XLIJ7.js → chunk-523DITKR.js} +1 -1
  37. package/{chunk-E4MARS3Q.js → chunk-5GVBDNTO.js} +1 -1
  38. package/chunk-5MMAK75Q.js +15 -0
  39. package/{chunk-7S4YWN7P.js → chunk-5UUIGN6I.js} +4 -4
  40. package/{chunk-42V6BK2E.js → chunk-62IL3KQV.js} +4 -34
  41. package/{chunk-C63CN6KA.js → chunk-6IJDSYUA.js} +849 -279
  42. package/{chunk-4MK3I7Y4.js → chunk-6MAW4XOF.js} +5 -5
  43. package/chunk-76HO2DP6.js +15 -0
  44. package/chunk-7AY2H3GA.js +13692 -0
  45. package/{chunk-4CR5I7BD.js → chunk-7HYOPBUW.js} +47 -67
  46. package/{chunk-VLTIYV3X.js → chunk-7I25CBT5.js} +34 -8
  47. package/chunk-AGIM7IMJ.js +147 -0
  48. package/{chunk-5UTFL4QJ.js → chunk-AOTBKDRK.js} +7 -0
  49. package/{chunk-6OZ36FXI.js → chunk-BIPGGSJS.js} +22 -4
  50. package/{chunk-EYAQT56M.js → chunk-BNQMNUKZ.js} +9 -6
  51. package/{chunk-62CATUYY.js → chunk-CBUMFIFE.js} +834 -962
  52. package/{chunk-OIVSQH2O.js → chunk-CDEEFJXN.js} +308 -11
  53. package/chunk-CHOZJMJS.js +78 -0
  54. package/{chunk-ETI2NYVF.js → chunk-CIP3AAMT.js} +4 -4
  55. package/{chunk-GBRG75BQ.js → chunk-D2MDMY2M.js} +1 -0
  56. package/{chunk-IBOZNTG2.js → chunk-DGEUP3DK.js} +23 -28
  57. package/{chunk-Q554LLSI.js → chunk-DMXLXZKL.js} +1 -1
  58. package/{chunk-3VNWWVKB.js → chunk-E3YWHWCR.js} +50 -18
  59. package/{chunk-HKNXJGEC.js → chunk-ECFHBYUW.js} +85 -50
  60. package/{chunk-ACTMNVHM.js → chunk-EHSYSKXV.js} +1 -1
  61. package/{chunk-WV42OMV7.js → chunk-ELUDRSRD.js} +8 -8
  62. package/{chunk-ABQYVJBJ.js → chunk-EV235ILT.js} +5 -5
  63. package/{chunk-TRV2MIDW.js → chunk-EXAJ6DCR.js} +1 -0
  64. package/{chunk-BFDFRLLQ.js → chunk-EZ4VZHAG.js} +5 -5
  65. package/{chunk-GO3Z72MT.js → chunk-FHLMQIU7.js} +65 -95
  66. package/{chunk-2F7RJC6L.js → chunk-FOXQ7BAB.js} +1 -1
  67. package/{chunk-SH43IESU.js → chunk-GGC55Q32.js} +199 -29
  68. package/chunk-GMZLP5DV.js +82 -0
  69. package/{chunk-FDTR4QJ6.js → chunk-H6A6X4EK.js} +32 -1
  70. package/{chunk-DVINR3PL.js → chunk-HDLLCBRF.js} +1 -1
  71. package/{chunk-QOKAKDPE.js → chunk-HIM3FCLF.js} +0 -1
  72. package/{chunk-ONXOABJR.js → chunk-HNGRBWYF.js} +3 -0
  73. package/{chunk-BNGW5ASC.js → chunk-I2RGRVQ7.js} +22 -2
  74. package/{chunk-O6OSWFZQ.js → chunk-IHPDK6UD.js} +6 -0
  75. package/{chunk-QDSINZM3.js → chunk-IXH3HKY2.js} +1 -22
  76. package/{chunk-GQBOR3G6.js → chunk-JBKHQOYK.js} +8 -4
  77. package/{chunk-XD2MJFUG.js → chunk-JEFXP73H.js} +1 -1
  78. package/{chunk-PBA4UJBK.js → chunk-JIXGJTBU.js} +4 -4
  79. package/{chunk-AEOE4Z6Q.js → chunk-K6L5PNXK.js} +39 -15
  80. package/{chunk-Z77CRGTW.js → chunk-KA5O5KQL.js} +1 -1
  81. package/{chunk-GPCZ7UXG.js → chunk-KD6D6BLA.js} +1 -1
  82. package/{chunk-UZGQNTVW.js → chunk-KHKTDMYG.js} +8 -6
  83. package/{chunk-MRIHM22T.js → chunk-LBFL6RZG.js} +58 -9
  84. package/{chunk-C3M4465B.js → chunk-LKIVH2I2.js} +403 -658
  85. package/{chunk-LS2ZANRJ.js → chunk-LOCNXNOU.js} +1 -1
  86. package/{chunk-DHDMC6FO.js → chunk-LUBFABMJ.js} +15 -14
  87. package/{chunk-NA35QHCF.js → chunk-LVBFKW65.js} +1 -1
  88. package/chunk-LWLNLZC5.js +265 -0
  89. package/{chunk-EEGDMATE.js → chunk-M3NMHYRB.js} +86 -81
  90. package/chunk-N4NGJDT6.js +929 -0
  91. package/{chunk-GL3EHUR4.js → chunk-N7NLLRQQ.js} +68 -46
  92. package/{chunk-BFU6C242.js → chunk-NMFV26UG.js} +1 -1
  93. package/{chunk-FZGV4FGK.js → chunk-NUHHBEJC.js} +378 -187
  94. package/chunk-NWENLDMM.js +42 -0
  95. package/chunk-O7SVKKLY.js +875 -0
  96. package/chunk-OCZKATRC.js +129 -0
  97. package/{chunk-6Z7MY3V7.js → chunk-OEJLGVBL.js} +5 -5
  98. package/{chunk-VMFTFOWF.js → chunk-OKGVNZTL.js} +35 -8
  99. package/{chunk-GEPUP7OO.js → chunk-OR2UWD6X.js} +3 -3
  100. package/{chunk-KQHGZWSV.js → chunk-OXME3PEE.js} +1 -1
  101. package/{chunk-J4MOO56S.js → chunk-PNXHPLRU.js} +10 -2
  102. package/{chunk-3BMYXEUW.js → chunk-R5AJODNI.js} +1 -1
  103. package/{chunk-P5EVSOPA.js → chunk-R6EBQEQE.js} +22 -29
  104. package/{chunk-NOF3HDSN.js → chunk-RLSF5CSE.js} +11 -9
  105. package/chunk-RQ6CTX2E.js +164 -0
  106. package/{chunk-3UZPUAES.js → chunk-RSB4PWX4.js} +1 -1
  107. package/{chunk-KQ4ONNQD.js → chunk-RXA3HAVW.js} +190 -8
  108. package/{chunk-IIVGDG4I.js → chunk-SJKULPGX.js} +13 -7
  109. package/chunk-SQHMSDDU.js +8004 -0
  110. package/{chunk-Q67DOFOO.js → chunk-SZC4REES.js} +212 -105
  111. package/{chunk-JRQFUXY7.js → chunk-TBAXFWOS.js} +99 -22
  112. package/{chunk-PQFHGD77.js → chunk-TBM2RS3U.js} +161 -14
  113. package/{chunk-XT35TTFG.js → chunk-TCYU7L6V.js} +18 -20
  114. package/{chunk-BYMV6QZG.js → chunk-TNIX77YM.js} +4 -0
  115. package/chunk-TY3TIUJK.js +89 -0
  116. package/{chunk-I3TQJXQ7.js → chunk-U3C3IA32.js} +9 -2
  117. package/{chunk-YYLHWIW6.js → chunk-UKZX3NHN.js} +1 -1
  118. package/chunk-UM7TA3K4.js +1008 -0
  119. package/{chunk-6YTOJY35.js → chunk-VBG7GWWR.js} +253 -8
  120. package/chunk-VIWDGTDA.js +8 -0
  121. package/chunk-VJVNU77E.js +83 -0
  122. package/{chunk-W7HLWAQ7.js → chunk-VPLUU4IX.js} +118 -90
  123. package/{chunk-UKWGR5SS.js → chunk-VY6VFGKR.js} +194 -1144
  124. package/chunk-VZLO4XJD.js +17 -0
  125. package/{chunk-AHAHBQMU.js → chunk-WDLSZMTA.js} +2 -2
  126. package/{chunk-53HEEOTT.js → chunk-WF5X57VW.js} +83 -16
  127. package/{chunk-VRTTFDBM.js → chunk-WPHY3VPF.js} +35 -22
  128. package/{chunk-ASIFZ2WY.js → chunk-YKZLFY5J.js} +66 -54
  129. package/chunk-YV25TM3R.js +780 -0
  130. package/{chunk-B2C6QTY4.js → chunk-YXKNBVYW.js} +19 -17
  131. package/chunk-ZRAU6T47.js +56 -0
  132. package/commandRegistry-UU6IOTHI.js +833 -0
  133. package/{contentThunks-IV2T3EJC.js → contentThunks-ARD56LKI.js} +39 -36
  134. package/{cooldownCommands-REAWYVTY.js → cooldownCommands-ZLEPIB4A.js} +2 -2
  135. package/{createAgentAutomationAction-JCOIEYY4.js → createAgentAutomationAction-HWJLU6DD.js} +40 -37
  136. package/createDialogAction-CHYA6PZE.js +70 -0
  137. package/{createPageAction-D2VOOLP3.js → createPageAction-TSCYNFX7.js} +47 -43
  138. package/{chunk-VFEJQMH4.js → cursorProvider-YGGNMVWX.js} +2751 -10172
  139. package/{dbSlice-A6C6H32X.js → dbSlice-TG4MZ5EB.js} +12 -11
  140. package/{deleteFile-YG6QMJLU.js → deleteFile-KESP65GC.js} +7 -6
  141. package/{dialogCommands-H7FPUYUQ.js → dialogCommands-IJYQAQMB.js} +49 -46
  142. package/{dialogSlice-6JZOOTDS.js → dialogSlice-H476I77R.js} +57 -37
  143. package/{dist-VODFSGNG.js → dist-NJC2W7I4.js} +61 -54
  144. package/{docCreateCommands-WRG2JLGM.js → docCreateCommands-AZSK4NAG.js} +48 -44
  145. package/{docDeleteCommands-SHWJIBJS.js → docDeleteCommands-DDJMSMFZ.js} +49 -45
  146. package/{docReadCommands-WJN3WT6O.js → docReadCommands-5F5Q6EBZ.js} +47 -43
  147. package/{docStore-HUPQ2FSS.js → docStore-HW2RDZDM.js} +17 -13
  148. package/{docUpdateCommands-IE32IL6O.js → docUpdateCommands-XPNPVZOC.js} +48 -44
  149. package/{fetchMessages-EIU7UFFK.js → fetchMessages-3PXAHKCR.js} +5 -5
  150. package/{getPricing-LUSEABA6.js → getPricing-DOBBZLOK.js} +4 -4
  151. package/{handleSendMessageAction-DFPMQ7YM.js → handleSendMessageAction-IOXE7ILC.js} +109 -42
  152. package/index.js +87 -884
  153. package/{lib-FRRS27KN.js → lib-PU2G2MSO.js} +523 -191
  154. package/{localDialogRead-BTFJ4V2I.js → localDialogRead-UYCKO7YZ.js} +2 -2
  155. package/{localRuntimeAuthority-6IR6BBXE.js → localRuntimeAuthority-TU6OBC76.js} +9 -6
  156. package/{localRuntimeDb-SYQGUF4U.js → localRuntimeDb-VKWVOPPI.js} +1 -1
  157. package/{machineCommands-OL4NLMXG.js → machineCommands-Q7TDGM6P.js} +96 -86
  158. package/machineWsRunDispatch-T5VFOQ3R.js +154 -0
  159. package/{memberThunks-P2ECUJ56.js → memberThunks-5S5D3N4U.js} +20 -18
  160. package/{models-T3SG6MJ5.js → models-PQU25T6Q.js} +2 -2
  161. package/oauthTokenStore-ITEB663N.js +23 -0
  162. package/package.json +1 -1
  163. package/processSpawn-SOAV3FEV.js +11 -0
  164. package/profileConfig-ZGOFORWV.js +43 -0
  165. package/{providerPresetApply-FG6NZ57W.js → providerPresetApply-HAQWVAQX.js} +7 -7
  166. package/{providerRegistry-757OWH3O.js → providerRegistry-5FGWB3X5.js} +11 -7
  167. package/{read-COE3ZYR6.js → read-LSYFLSIM.js} +8 -7
  168. package/{readDocTool-5USN4A6V.js → readDocTool-XWRXV3DR.js} +5 -3
  169. package/{readlineWorkspace-BOENUIW2.js → readlineWorkspace-25U6P3DG.js} +2595 -2031
  170. package/{removeDialogAgentAction-J6X5QMTA.js → removeDialogAgentAction-4TEFC3VL.js} +22 -26
  171. package/{runAgentClientLoop-5ZA62FBA.js → runAgentClientLoop-CZOYWHDN.js} +78 -71
  172. package/{runtimeDoctorCommands-X6WDBEQX.js → runtimeDoctorCommands-PIVSBZRZ.js} +4 -4
  173. package/runtimeModeArgs-2Y7BALTO.js +31 -0
  174. package/{setDialogExtraReferencesAction-WX5VTY6O.js → setDialogExtraReferencesAction-LSEN44OY.js} +16 -14
  175. package/{setPrimaryDialogAgentAction-FN22THHT.js → setPrimaryDialogAgentAction-SR366X5A.js} +23 -27
  176. package/{chunk-GP5LKBUZ.js → settingThunks-CTVPPGIN.js} +226 -940
  177. package/{skillDiscovery-CYUCJMAG.js → skillDiscovery-ZBU76COE.js} +3 -3
  178. package/{slateUtils-RIZES3PV.js → slateUtils-NGV2NMKM.js} +1 -1
  179. package/{spaceCommands-5GLCWZPO.js → spaceCommands-3UK63WPE.js} +51 -47
  180. package/{streamAgentChatTurn-6UZJMYRB.js → streamAgentChatTurn-LV6ZWUD4.js} +214 -114
  181. package/{switchDialogAgentAction-LVTEV3FR.js → switchDialogAgentAction-M6NAFOOU.js} +19 -20
  182. package/{tableCommands-O6NWHFKV.js → tableCommands-6U4BMLRR.js} +10 -10
  183. package/{toolApiClient-QVJ6HDNN.js → toolApiClient-AD7CXTA4.js} +4 -3
  184. package/tools-UJOV6CQZ.js +117 -0
  185. package/{tuiSummaryLlmCaller-OHHFTHMG.js → tuiSummaryLlmCaller-DEH7ZEM6.js} +25 -23
  186. package/{updateCommands-2M74UL7P.js → updateCommands-4EHDLF5J.js} +3 -3
  187. package/{updateDialogTitleAction-QTKNIURB.js → updateDialogTitleAction-JYXKIBNJ.js} +84 -45
  188. package/action-P4TNFGAD.js +0 -31
  189. package/agentListCommands-B2V4YAC4.js +0 -410
  190. package/agentRecordCommands-UTHKMMJY.js +0 -128
  191. package/agentRunCommand-D4OBAOVW.js +0 -156
  192. package/browser-image-compression-MFDTD7K6.js +0 -1763
  193. package/chatQueueReduxAdapter-2P6NFX64.js +0 -186
  194. package/chunk-CQ3WA2NE.js +0 -299
  195. package/chunk-M76ECQRD.js +0 -13658
  196. package/chunk-MEYIRKDN.js +0 -8967
  197. package/chunk-PUOC4YRO.js +0 -394
  198. package/chunk-Q5FJJ6XI.js +0 -103
  199. package/chunk-S7Z2HMCN.js +0 -25
  200. package/chunk-T75QD6AB.js +0 -20923
  201. package/chunk-Y5GCAMGP.js +0 -9
  202. package/chunk-YPREESF4.js +0 -225
  203. package/createDialogAction-NGG7YVNU.js +0 -65
  204. package/machineWsRunDispatch-64SG2RUI.js +0 -144
  205. package/settingSlice-P4ZW7K5X.js +0 -204
  206. package/syncMapping-N6RGXGBW.js +0 -437
  207. package/tools-KMGFWO43.js +0 -110
  208. package/{chunk-X7HHHIOZ.js → chunk-Y6ROFCZO.js} +0 -0
  209. package/{db-WK5MDZVN.js → db-VQSAOWAO.js} +1 -1
@@ -1,9 +1,12 @@
1
1
  import {
2
2
  NOLO_CHROME_CONNECTOR_EXTENSION_ID,
3
3
  NOLO_CHROME_CONNECTOR_PROTOCOL_VERSION,
4
+ NOLO_CONNECTOR_EXTENSION_IDS,
5
+ NOLO_FIREFOX_CONNECTOR_EXTENSION_ID,
4
6
  REQUIRED_CHROME_CONNECTOR_FEATURES,
5
- createChromeConnectorClient
6
- } from "./chunk-SH43IESU.js";
7
+ createChromeConnectorClient,
8
+ resolveUploadRoots
9
+ } from "./chunk-GGC55Q32.js";
7
10
  import {
8
11
  toErrorMessage
9
12
  } from "./chunk-HNMXYYJR.js";
@@ -11,16 +14,18 @@ import "./chunk-KUG52IAP.js";
11
14
  import "./chunk-4VNS5WPM.js";
12
15
 
13
16
  // packages/cli/chromeCommands.ts
14
- import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "node:fs";
17
+ import { spawnSync } from "node:child_process";
18
+ import { existsSync as existsSync2, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
15
19
  import { resolve as resolve2 } from "node:path";
16
20
 
17
21
  // packages/desktop-chrome-connector/nativeHostInstall.mjs
18
- import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
22
+ import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
19
23
  import { createHash, randomBytes } from "node:crypto";
20
24
  import { dirname, resolve } from "node:path";
21
25
  import { execFileSync } from "node:child_process";
22
26
  import { fileURLToPath } from "node:url";
23
27
  var DEFAULT_CONNECTOR_ROOT = dirname(fileURLToPath(import.meta.url));
28
+ var FIREFOX_EXTENSION_ID = "nolo-browser-connector@nolo.chat";
24
29
  function extensionIdFromPublicKey(publicKeyBase64) {
25
30
  const digest = createHash("sha256").update(Buffer.from(publicKeyBase64, "base64")).digest();
26
31
  return Array.from(
@@ -40,8 +45,23 @@ function resolveNodePath() {
40
45
  }
41
46
  function nativeMessagingHostsDir({
42
47
  home = process.env.HOME || "",
43
- platform = process.platform
48
+ platform = process.platform,
49
+ browser = "chrome"
44
50
  } = {}) {
51
+ if (browser === "firefox") {
52
+ if (platform === "darwin") {
53
+ return resolve(home, "Library/Application Support/Mozilla/NativeMessagingHosts");
54
+ }
55
+ if (platform === "linux") {
56
+ return resolve(home, ".mozilla/native-messaging-hosts");
57
+ }
58
+ throw new Error(
59
+ `Firefox native host installation is not implemented for platform "${platform}". Windows needs a registry value under HKCU\\Software\\Mozilla\\NativeMessagingHosts pointing at the manifest file.`
60
+ );
61
+ }
62
+ if (browser !== "chrome") {
63
+ throw new Error(`Unknown native messaging browser target: "${browser}".`);
64
+ }
45
65
  if (platform === "darwin") {
46
66
  return resolve(home, "Library/Application Support/Google/Chrome/NativeMessagingHosts");
47
67
  }
@@ -55,54 +75,71 @@ function nativeMessagingHostsDir({
55
75
  function resolveNativeHostInstallPaths({
56
76
  home = process.env.HOME || "",
57
77
  connectorRoot = DEFAULT_CONNECTOR_ROOT,
58
- platform = process.platform
78
+ platform = process.platform,
79
+ browser = "chrome"
59
80
  } = {}) {
60
81
  const supportDir = resolve(home, "Library/Application Support/Nolo/ChromeConnector");
82
+ const wrapperName = browser === "firefox" ? "nolo-firefox-native-host" : "nolo-chrome-native-host";
61
83
  return {
62
84
  connectorRoot,
85
+ browser,
63
86
  extensionManifestPath: resolve(connectorRoot, "extension", "manifest.json"),
64
87
  hostPath: resolve(connectorRoot, "native-host", "nolo-chrome-native-host.mjs"),
65
88
  templatePath: resolve(connectorRoot, "native-host", "com.nolo.chrome_connector.json"),
66
89
  nativeManifestPath: resolve(
67
- nativeMessagingHostsDir({ home, platform }),
90
+ nativeMessagingHostsDir({ home, platform, browser }),
68
91
  "com.nolo.chrome_connector.json"
69
92
  ),
70
93
  supportDir,
71
94
  tokenPath: resolve(supportDir, "token"),
72
- wrapperPath: resolve(supportDir, "nolo-chrome-native-host")
95
+ wrapperPath: resolve(supportDir, wrapperName)
73
96
  };
74
97
  }
98
+ function connectorPortForBrowser({ browser = "chrome", env = process.env } = {}) {
99
+ if (browser === "firefox") {
100
+ return Number(env.NOLO_FIREFOX_CONNECTOR_PORT || 38948);
101
+ }
102
+ return Number(env.NOLO_CHROME_CONNECTOR_PORT || 38947);
103
+ }
75
104
  function installNativeHostManifest({
76
105
  home = process.env.HOME || "",
77
106
  connectorRoot = DEFAULT_CONNECTOR_ROOT,
78
107
  platform = process.platform,
79
108
  extensionId,
80
- nodePath
109
+ nodePath,
110
+ browser = "chrome"
81
111
  } = {}) {
82
- const paths = resolveNativeHostInstallPaths({ home, connectorRoot, platform });
112
+ const paths = resolveNativeHostInstallPaths({ home, connectorRoot, platform, browser });
83
113
  const manifest = JSON.parse(readFileSync(paths.templatePath, "utf8"));
84
114
  const extensionManifest = JSON.parse(readFileSync(paths.extensionManifestPath, "utf8"));
85
- const resolvedExtensionId = extensionId || extensionIdFromPublicKey(extensionManifest.key);
115
+ const resolvedExtensionId = extensionId || (browser === "firefox" ? FIREFOX_EXTENSION_ID : extensionIdFromPublicKey(extensionManifest.key));
86
116
  const resolvedNodePath = nodePath || resolveNodePath();
87
117
  mkdirSync(paths.supportDir, { recursive: true });
88
118
  const existingToken = existsSync(paths.tokenPath) ? readFileSync(paths.tokenPath, "utf8").trim() : "";
89
119
  const token = existingToken || randomBytes(32).toString("hex");
90
120
  writeFileSync(paths.tokenPath, `${token}
91
121
  `, { mode: 384 });
122
+ const port = connectorPortForBrowser({ browser });
92
123
  writeFileSync(
93
124
  paths.wrapperPath,
94
125
  `#!/bin/sh
95
- NOLO_CHROME_CONNECTOR_TOKEN=${JSON.stringify(token)} exec ${JSON.stringify(resolvedNodePath)} ${JSON.stringify(paths.hostPath)}
126
+ NOLO_CHROME_CONNECTOR_BROWSER=${JSON.stringify(browser)} NOLO_CHROME_CONNECTOR_TOKEN=${JSON.stringify(token)} ${browser === "firefox" ? `NOLO_FIREFOX_CONNECTOR_PORT=${JSON.stringify(String(port))} ` : ""}exec ${JSON.stringify(resolvedNodePath)} ${JSON.stringify(paths.hostPath)}
96
127
  `
97
128
  );
98
129
  chmodSync(paths.wrapperPath, 493);
99
130
  manifest.path = paths.wrapperPath;
100
- manifest.allowed_origins = [`chrome-extension://${resolvedExtensionId}/`];
131
+ if (browser === "firefox") {
132
+ delete manifest.allowed_origins;
133
+ manifest.allowed_extensions = [FIREFOX_EXTENSION_ID];
134
+ } else {
135
+ manifest.allowed_origins = [`chrome-extension://${resolvedExtensionId}/`];
136
+ }
101
137
  mkdirSync(dirname(paths.nativeManifestPath), { recursive: true });
102
138
  writeFileSync(paths.nativeManifestPath, `${JSON.stringify(manifest, null, 2)}
103
139
  `);
104
140
  return {
105
141
  extensionId: resolvedExtensionId,
142
+ browser,
106
143
  nodePath: resolvedNodePath,
107
144
  tokenPath: paths.tokenPath,
108
145
  ...paths
@@ -119,6 +156,79 @@ var CLI_CONNECTOR_ROOT_CANDIDATES = [
119
156
  var FULL_SUPPORT_CHROME_FEATURES = [
120
157
  ...new Set(Object.values(REQUIRED_CHROME_CONNECTOR_FEATURES).flat())
121
158
  ].sort();
159
+ var FIREFOX_UNAVAILABLE_FEATURES = ["browser_debug", "file_upload"];
160
+ function resolveConnectorBrowser(extensionId) {
161
+ if (!extensionId) return null;
162
+ if (extensionId === NOLO_FIREFOX_CONNECTOR_EXTENSION_ID) return "firefox";
163
+ if (NOLO_CONNECTOR_EXTENSION_IDS.includes(extensionId)) return "chrome";
164
+ return null;
165
+ }
166
+ function expectedFeaturesForBrowser(browser) {
167
+ return browser === "firefox" ? FULL_SUPPORT_CHROME_FEATURES.filter(
168
+ (feature) => !FIREFOX_UNAVAILABLE_FEATURES.includes(feature)
169
+ ) : FULL_SUPPORT_CHROME_FEATURES;
170
+ }
171
+ function browserLabel(browser) {
172
+ return browser === "firefox" ? "Firefox" : browser === "chrome" ? "Chrome" : "browser";
173
+ }
174
+ async function fetchOnlineBuildSha(fetchImpl = fetch, timeoutMs = 3e3) {
175
+ try {
176
+ const controller = new AbortController();
177
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
178
+ const res = await fetchImpl("https://us.nolo.chat/public/latest-assets.json", {
179
+ signal: controller.signal
180
+ }).finally(() => clearTimeout(timer));
181
+ if (!res.ok) return null;
182
+ const data = await res.json();
183
+ return typeof data?.buildSha === "string" ? data.buildSha : null;
184
+ } catch {
185
+ return null;
186
+ }
187
+ }
188
+ function resolveLocalHeadSha() {
189
+ try {
190
+ const result = spawnSync("git", ["rev-parse", "HEAD"], {
191
+ encoding: "utf8",
192
+ timeout: 3e3,
193
+ stdio: ["ignore", "pipe", "ignore"]
194
+ });
195
+ if (result.status === 0 && typeof result.stdout === "string") {
196
+ const sha = result.stdout.trim();
197
+ return sha.length >= 7 ? sha : null;
198
+ }
199
+ return null;
200
+ } catch {
201
+ return null;
202
+ }
203
+ }
204
+ function compareDeployShas(localSha, onlineSha) {
205
+ if (!localSha || !onlineSha) return "unknown";
206
+ if (localSha === onlineSha || localSha.slice(0, 7) === onlineSha.slice(0, 7)) {
207
+ return "in sync";
208
+ }
209
+ try {
210
+ const localAhead = spawnSync("git", ["merge-base", "--is-ancestor", onlineSha, localSha], {
211
+ timeout: 3e3,
212
+ stdio: ["ignore", "ignore", "ignore"]
213
+ });
214
+ if (localAhead.status === 0) return "local ahead";
215
+ const onlineAhead = spawnSync("git", ["merge-base", "--is-ancestor", localSha, onlineSha], {
216
+ timeout: 3e3,
217
+ stdio: ["ignore", "ignore", "ignore"]
218
+ });
219
+ if (onlineAhead.status === 0) return "online ahead";
220
+ const objCheck = spawnSync("git", ["cat-file", "-e", `${onlineSha}^{commit}`], {
221
+ timeout: 3e3,
222
+ stdio: ["ignore", "ignore", "ignore"]
223
+ });
224
+ if (objCheck.status === 0) {
225
+ return "diverged";
226
+ }
227
+ return "unknown";
228
+ } catch {
229
+ return "unknown";
230
+ }
231
+ }
122
232
  function pickCliConnectorRoot(candidates) {
123
233
  for (const candidate of candidates) {
124
234
  if (existsSync2(resolve2(candidate, "extension", "manifest.json"))) return candidate;
@@ -158,7 +268,7 @@ function detectWorktreeCheckout(connectorRoot) {
158
268
  if (existsSync2(gitEntry)) {
159
269
  let isGitFile = null;
160
270
  try {
161
- isGitFile = statSync(gitEntry).isFile();
271
+ isGitFile = statSync2(gitEntry).isFile();
162
272
  } catch {
163
273
  }
164
274
  if (isGitFile === true) {
@@ -233,9 +343,13 @@ async function probeConnectorRpc(requestChrome) {
233
343
  }
234
344
  }
235
345
  function resolveStatusNextStep(report) {
346
+ const targetBrowser = report.rpc.browser ?? report.nativeHost.browser;
347
+ const installCmd = targetBrowser === "firefox" ? "nolo chrome install --browser firefox" : "nolo chrome install";
348
+ const connectedLabel = browserLabel(targetBrowser);
349
+ const browserText = connectedLabel === "browser" ? "the browser" : connectedLabel;
236
350
  if (!report.connectorRoot) {
237
351
  if (report.rpc.online && report.rpc.protocolVersion === report.rpc.expectedProtocolVersion) {
238
- return "Chrome connector is online, but this CLI cannot see a connector checkout, so `nolo chrome install` / manifest checks are unavailable here. Set NOLO_CHROME_CONNECTOR_ROOT (or run the CLI from a repo checkout) to enable them.";
352
+ return "The Nolo Browser Connector is online, but this CLI cannot see a connector checkout, so `nolo chrome install` / manifest checks are unavailable here. Set NOLO_CHROME_CONNECTOR_ROOT (or run the CLI from a repo checkout) to enable them.";
239
353
  }
240
354
  return "Run the CLI from a repo checkout that contains packages/desktop-chrome-connector, or set NOLO_CHROME_CONNECTOR_ROOT to one, then rerun `nolo chrome status`.";
241
355
  }
@@ -243,20 +357,20 @@ function resolveStatusNextStep(report) {
243
357
  return `This platform has no supported native messaging host install (Chrome looks the host up through a registry value on Windows), so non-interactive install is not available: ${report.nativeHost.platformError ?? "unsupported platform"}`;
244
358
  }
245
359
  if (!report.nativeHost.installed || !report.token.present) {
246
- return "nolo chrome install";
360
+ return installCmd;
247
361
  }
248
362
  if (!report.nativeHost.wrapperPathMatches || !report.nativeHost.allowedOriginMatches) {
249
363
  const installedWrapperPath = report.nativeHost.installedWrapperPath;
250
364
  if (!report.nativeHost.wrapperPathMatches && installedWrapperPath && installedWrapperPath !== report.nativeHost.wrapperPath && existsSync2(installedWrapperPath)) {
251
- return `Native host is installed for a different checkout (${installedWrapperPath}); this CLI is looking at ${report.connectorRoot}. Run \`nolo chrome status\` from the checkout that installed it, or run \`nolo chrome install\` from here only if Chrome should switch to this one.`;
365
+ return `Native host is installed for a different checkout (${installedWrapperPath}); this CLI is looking at ${report.connectorRoot}. Run \`nolo chrome status\` from the checkout that installed it, or run \`${installCmd}\` from here only if ${browserText} should switch to this one.`;
252
366
  }
253
- return "nolo chrome install (the installed manifest does not point at this checkout's wrapper / extension id, which Chrome silently ignores)";
367
+ return `${installCmd} (the installed manifest does not point at this checkout's wrapper / extension id, which ${browserText} silently ignores)`;
254
368
  }
255
369
  if (!report.rpc.online) {
256
- return `Load the unpacked extension from ${report.extension.path} in chrome://extensions (Developer mode) or install it from the Chrome Web Store, then rerun \`nolo chrome status\`.`;
370
+ return `Load the unpacked extension from ${report.extension.path} in chrome://extensions (Chrome) or about:debugging#/runtime/this-firefox (Firefox), or install the Nolo Browser Connector from https://nolo.chat/downloads, then rerun \`nolo chrome status\`.`;
257
371
  }
258
- if (report.rpc.extensionId && report.rpc.extensionId !== report.extension.expectedId) {
259
- return `The connected extension (${report.rpc.extensionId}) is not the connector at ${report.extension.path}; remove the other copy and rerun \`nolo chrome status\`.`;
372
+ if (report.rpc.extensionId && report.rpc.browser === null) {
373
+ return `The connected extension (${report.rpc.extensionId}) is not a recognized Nolo Browser Connector build; reload it (\`nolo chrome reload\`) or remove the other copy, then rerun \`nolo chrome status\`.`;
260
374
  }
261
375
  if (report.rpc.protocolVersion !== report.rpc.expectedProtocolVersion) {
262
376
  return `nolo chrome reload (the running extension does not speak protocol ${report.rpc.expectedProtocolVersion}; a reload restarts the worker with the files on disk)`;
@@ -264,12 +378,13 @@ function resolveStatusNextStep(report) {
264
378
  if (report.rpc.missingFeatures.length > 0) {
265
379
  return `nolo chrome reload (the running extension is missing ${report.rpc.missingFeatures.join(", ")}); if that persists, the checkout's extension is older than the CLI expects.`;
266
380
  }
267
- return "Chrome connector is online: chrome_* tools execute against this Chrome profile.";
381
+ return `The Nolo Browser Connector is online (${connectedLabel}): connector tools execute against this ${connectedLabel} profile.`;
268
382
  }
269
383
  async function buildChromeConnectorStatus(args = {}) {
270
384
  const env = args.env ?? process.env;
271
385
  const home = args.home ?? env.HOME ?? "";
272
386
  const platform = args.platform ?? process.platform;
387
+ const browserTarget = args.browser;
273
388
  let connectorRoot = args.connectorRoot ?? null;
274
389
  let rootError = null;
275
390
  if (!connectorRoot) {
@@ -295,38 +410,112 @@ async function buildChromeConnectorStatus(args = {}) {
295
410
  wrapperPath: null,
296
411
  installedWrapperPath: null,
297
412
  wrapperPathMatches: false,
298
- allowedOriginMatches: false
413
+ allowedOriginMatches: false,
414
+ browser: null
299
415
  },
300
416
  token: { path: null, present: false },
417
+ uploadRoots: { roots: [], isDefault: false },
418
+ deploy: {
419
+ localSha: null,
420
+ onlineSha: null,
421
+ sync: "unknown"
422
+ },
301
423
  rpc: {
302
424
  online: false,
303
425
  extensionId: null,
304
426
  protocolVersion: null,
305
427
  features: [],
428
+ browser: null,
306
429
  missingFeatures: [...FULL_SUPPORT_CHROME_FEATURES],
307
430
  expectedProtocolVersion: NOLO_CHROME_CONNECTOR_PROTOCOL_VERSION
308
431
  },
309
432
  nextStep: ""
310
433
  };
311
- const requestChrome = args.requestChrome ?? createChromeConnectorClient().request;
434
+ try {
435
+ const rootsInfo = resolveUploadRoots({ ...env, ...home ? { HOME: home } : {} });
436
+ report.uploadRoots = {
437
+ roots: rootsInfo.roots,
438
+ isDefault: rootsInfo.isDefault
439
+ };
440
+ } catch (error) {
441
+ report.uploadRoots = {
442
+ roots: [],
443
+ isDefault: false,
444
+ error: toErrorMessage(error)
445
+ };
446
+ }
447
+ let localHeadSha = null;
448
+ try {
449
+ localHeadSha = (args.resolveLocalHeadSha ?? resolveLocalHeadSha)();
450
+ } catch {
451
+ localHeadSha = null;
452
+ }
453
+ let onlineBuildSha = null;
454
+ try {
455
+ onlineBuildSha = await (args.fetchOnlineBuildSha ?? fetchOnlineBuildSha)();
456
+ } catch {
457
+ onlineBuildSha = null;
458
+ }
459
+ let sync = "unknown";
460
+ try {
461
+ const compareShas = args.compareDeployShas ?? compareDeployShas;
462
+ sync = compareShas(localHeadSha, onlineBuildSha);
463
+ } catch {
464
+ sync = "unknown";
465
+ }
466
+ report.deploy = {
467
+ localSha: localHeadSha,
468
+ onlineSha: onlineBuildSha,
469
+ sync
470
+ };
471
+ const requestChrome = args.requestChrome ?? createChromeConnectorClient({ browser: browserTarget }).request;
312
472
  const probe = await probeConnectorRpc(requestChrome);
313
473
  report.rpc.online = probe.online;
314
474
  report.rpc.extensionId = probe.extensionId;
315
475
  report.rpc.protocolVersion = probe.protocolVersion;
316
476
  report.rpc.features = probe.features;
317
- report.rpc.missingFeatures = FULL_SUPPORT_CHROME_FEATURES.filter(
477
+ report.rpc.browser = probe.online ? resolveConnectorBrowser(probe.extensionId) ?? (probe.extensionId === expectedId ? "chrome" : null) : null;
478
+ report.rpc.missingFeatures = expectedFeaturesForBrowser(report.rpc.browser).filter(
318
479
  (feature) => !probe.features.includes(feature)
319
480
  );
320
481
  if (!probe.online) report.rpc.error = probe.error;
321
482
  if (!connectorRoot) {
322
483
  if (rootError) report.connectorRootError = rootError;
323
- report.ok = report.rpc.online && probe.extensionId === expectedId && probe.protocolVersion === NOLO_CHROME_CONNECTOR_PROTOCOL_VERSION && report.rpc.missingFeatures.length === 0;
484
+ report.nativeHost.browser = report.rpc.browser;
485
+ report.ok = report.rpc.online && report.rpc.browser !== null && probe.protocolVersion === NOLO_CHROME_CONNECTOR_PROTOCOL_VERSION && report.rpc.missingFeatures.length === 0;
324
486
  report.nextStep = resolveStatusNextStep(report);
325
487
  return report;
326
488
  }
489
+ let manifestBrowser = browserTarget ?? (report.rpc.browser === "firefox" ? "firefox" : "chrome");
490
+ if (!report.rpc.browser) {
491
+ try {
492
+ const chromeManifestPath = resolveNativeHostInstallPaths({
493
+ home,
494
+ connectorRoot,
495
+ platform,
496
+ browser: "chrome"
497
+ }).nativeManifestPath;
498
+ const firefoxManifestPath = resolveNativeHostInstallPaths({
499
+ home,
500
+ connectorRoot,
501
+ platform,
502
+ browser: "firefox"
503
+ }).nativeManifestPath;
504
+ if (!existsSync2(chromeManifestPath) && existsSync2(firefoxManifestPath)) {
505
+ manifestBrowser = "firefox";
506
+ }
507
+ } catch {
508
+ }
509
+ }
510
+ report.nativeHost.browser = manifestBrowser;
327
511
  let paths = null;
328
512
  try {
329
- paths = resolveNativeHostInstallPaths({ home, connectorRoot, platform });
513
+ paths = resolveNativeHostInstallPaths({
514
+ home,
515
+ connectorRoot,
516
+ platform,
517
+ browser: manifestBrowser
518
+ });
330
519
  } catch (error) {
331
520
  report.nativeHost.supported = false;
332
521
  report.nativeHost.platformError = toErrorMessage(error);
@@ -340,8 +529,7 @@ async function buildChromeConnectorStatus(args = {}) {
340
529
  report.nativeHost.installed = installedManifest !== null;
341
530
  report.nativeHost.installedWrapperPath = typeof installedManifest?.path === "string" ? installedManifest.path : null;
342
531
  report.nativeHost.wrapperPathMatches = report.nativeHost.installedWrapperPath === paths.wrapperPath;
343
- const expectedOrigin = `chrome-extension://${expectedId}/`;
344
- report.nativeHost.allowedOriginMatches = Array.isArray(installedManifest?.allowed_origins) && installedManifest.allowed_origins.includes(expectedOrigin);
532
+ report.nativeHost.allowedOriginMatches = manifestBrowser === "firefox" ? Array.isArray(installedManifest?.allowed_extensions) && installedManifest.allowed_extensions.includes(NOLO_FIREFOX_CONNECTOR_EXTENSION_ID) : Array.isArray(installedManifest?.allowed_origins) && installedManifest.allowed_origins.includes(`chrome-extension://${expectedId}/`);
345
533
  if (existsSync2(paths.tokenPath)) {
346
534
  try {
347
535
  report.token.present = readFileSync2(paths.tokenPath, "utf8").trim().length > 0;
@@ -349,7 +537,7 @@ async function buildChromeConnectorStatus(args = {}) {
349
537
  report.token.present = false;
350
538
  }
351
539
  }
352
- report.ok = report.nativeHost.supported && report.nativeHost.installed && report.nativeHost.wrapperPathMatches && report.nativeHost.allowedOriginMatches && report.token.present && probe.online && probe.extensionId === expectedId && probe.protocolVersion === NOLO_CHROME_CONNECTOR_PROTOCOL_VERSION && report.rpc.missingFeatures.length === 0;
540
+ report.ok = report.nativeHost.supported && report.nativeHost.installed && report.nativeHost.wrapperPathMatches && report.nativeHost.allowedOriginMatches && report.token.present && probe.online && report.rpc.browser !== null && probe.protocolVersion === NOLO_CHROME_CONNECTOR_PROTOCOL_VERSION && report.rpc.missingFeatures.length === 0;
353
541
  report.nextStep = resolveStatusNextStep(report);
354
542
  return report;
355
543
  }
@@ -357,7 +545,7 @@ function yesNo(value) {
357
545
  return value ? "ok" : "mismatch";
358
546
  }
359
547
  function formatChromeConnectorStatus(report) {
360
- const lines = ["Nolo Chrome connector"];
548
+ const lines = ["Nolo Browser Connector"];
361
549
  lines.push(
362
550
  ` connector source : ${report.connectorRoot ?? "(not found)"}`
363
551
  );
@@ -374,10 +562,21 @@ function formatChromeConnectorStatus(report) {
374
562
  lines.push(
375
563
  ` token : ${report.token.present ? "present" : "missing"}${report.token.path ? ` ${report.token.path}` : ""}`
376
564
  );
565
+ if (report.uploadRoots.error) {
566
+ lines.push(` upload roots : error (${report.uploadRoots.error})`);
567
+ } else if (report.uploadRoots.roots.length > 0) {
568
+ const originLabel = report.uploadRoots.isDefault ? "default" : "configured";
569
+ lines.push(` upload roots : ${report.uploadRoots.roots.join(", ")} (${originLabel})`);
570
+ } else {
571
+ lines.push(` upload roots : (none)`);
572
+ }
573
+ const localShort = report.deploy.localSha ? report.deploy.localSha.slice(0, 7) : "unknown";
574
+ const onlineShort = report.deploy.onlineSha ? report.deploy.onlineSha.slice(0, 7) : "unknown";
575
+ lines.push(` deploy status : local ${localShort} online ${onlineShort} (${report.deploy.sync})`);
377
576
  const rpc = report.rpc;
378
577
  if (rpc.online) {
379
578
  lines.push(
380
- ` RPC : online extension ${rpc.extensionId ?? "unknown"} protocolVersion ${rpc.protocolVersion ?? "unknown"} features ${rpc.features.length > 0 ? rpc.features.join(", ") : "(none)"}`
579
+ ` RPC : online browser ${rpc.browser ?? "unrecognized"} extension ${rpc.extensionId ?? "unknown"} protocolVersion ${rpc.protocolVersion ?? "unknown"} features ${rpc.features.length > 0 ? rpc.features.join(", ") : "(none)"}`
381
580
  );
382
581
  } else {
383
582
  lines.push(
@@ -390,9 +589,14 @@ function formatChromeConnectorStatus(report) {
390
589
  }
391
590
  async function runChromeStatusCommand(args, deps = {}) {
392
591
  const output = deps.output ?? process.stdout;
592
+ const browser = readBrowserOption(args);
593
+ if (browser === null) {
594
+ output.write('[nolo] Chrome connector status failed: --browser must be "chrome" or "firefox".\n');
595
+ return 1;
596
+ }
393
597
  let report;
394
598
  try {
395
- report = await buildChromeConnectorStatus(deps);
599
+ report = await buildChromeConnectorStatus({ ...deps, browser });
396
600
  } catch (error) {
397
601
  output.write(`[nolo] Chrome connector status failed: ${toErrorMessage(error)}
398
602
  `);
@@ -406,9 +610,26 @@ async function runChromeStatusCommand(args, deps = {}) {
406
610
  }
407
611
  return report.ok ? 0 : 1;
408
612
  }
613
+ function readBrowserOption(args) {
614
+ const equalsForm = args.find((arg) => arg.startsWith("--browser="));
615
+ const index = args.indexOf("--browser");
616
+ const raw = equalsForm !== void 0 ? equalsForm.slice("--browser=".length) : index >= 0 ? args[index + 1] : void 0;
617
+ if (raw === void 0) return void 0;
618
+ const value = raw.trim();
619
+ if (value === "chrome" || value === "firefox") return value;
620
+ return null;
621
+ }
409
622
  async function runChromeInstallCommand(args, deps = {}) {
410
623
  const env = deps.env ?? process.env;
411
624
  const output = deps.output ?? process.stdout;
625
+ const parsed = readBrowserOption(args);
626
+ if (parsed === null) {
627
+ output.write(
628
+ '[nolo] Chrome native host install failed: --browser must be "chrome" or "firefox".\n'
629
+ );
630
+ return 1;
631
+ }
632
+ const browser = parsed ?? "chrome";
412
633
  const install = deps.installNativeHost ?? installNativeHostManifest;
413
634
  let connectorRoot;
414
635
  try {
@@ -432,6 +653,7 @@ async function runChromeInstallCommand(args, deps = {}) {
432
653
  result = install({
433
654
  home: deps.home ?? env.HOME ?? "",
434
655
  connectorRoot,
656
+ browser,
435
657
  ...deps.platform ? { platform: deps.platform } : {},
436
658
  ...deps.nodePath ? { nodePath: deps.nodePath } : {}
437
659
  });
@@ -446,12 +668,12 @@ async function runChromeInstallCommand(args, deps = {}) {
446
668
  } else {
447
669
  output.write(
448
670
  [
449
- "Nolo Chrome connector native host installed.",
671
+ `Nolo Browser Connector native host installed (${browser}).`,
450
672
  ` manifest : ${result.nativeManifestPath}`,
451
673
  ` wrapper : ${result.wrapperPath} -> ${result.nodePath}`,
452
674
  ` token : ${result.tokenPath} (an existing token is kept, so reruns are idempotent)`,
453
675
  ` extension: ${result.extensionId}`,
454
- `Next step: load the unpacked extension from ${resolve2(connectorRoot, "extension")} in chrome://extensions (Developer mode) or install it from the Chrome Web Store, then run \`nolo chrome status\`.`,
676
+ `Next step: load the unpacked extension from ${resolve2(connectorRoot, "extension")} in ` + (browser === "firefox" ? "about:debugging#/runtime/this-firefox, or install the signed Nolo Browser Connector add-on from https://nolo.chat/downloads, then run `nolo chrome status`." : "chrome://extensions (Developer mode), or install the Nolo Browser Connector from https://nolo.chat/downloads (Chrome or Firefox), then run `nolo chrome status`."),
455
677
  ""
456
678
  ].join("\n")
457
679
  );
@@ -460,8 +682,32 @@ async function runChromeInstallCommand(args, deps = {}) {
460
682
  }
461
683
  async function runChromeReloadCommand(args, deps = {}) {
462
684
  const output = deps.output ?? process.stdout;
463
- const requestChrome = deps.requestChrome ?? createChromeConnectorClient().request;
685
+ const browser = readBrowserOption(args);
686
+ if (browser === null) {
687
+ output.write('[nolo] Chrome connector reload failed: --browser must be "chrome" or "firefox".\n');
688
+ return 1;
689
+ }
690
+ const requestChrome = deps.requestChrome ?? createChromeConnectorClient({ browser }).request;
464
691
  try {
692
+ if (browser) {
693
+ try {
694
+ const info = await requestChrome("connector_info", {});
695
+ const connectedBrowser = resolveConnectorBrowser(
696
+ typeof info?.extensionId === "string" ? info.extensionId : null
697
+ );
698
+ if (connectedBrowser !== null && connectedBrowser !== browser) {
699
+ const mismatch = new Error(
700
+ `the connector on this endpoint answers as ${browserLabel(connectedBrowser)}, not ${browserLabel(browser)} \u2014 refusing to reload the wrong browser. Check NOLO_CHROME_CONNECTOR_RPC_URL / the per-browser port overrides.`
701
+ );
702
+ mismatch.code = "CHROME_CONNECTOR_EXTENSION_MISMATCH";
703
+ throw mismatch;
704
+ }
705
+ } catch (error) {
706
+ if (error?.code === "CHROME_CONNECTOR_EXTENSION_MISMATCH") {
707
+ throw error;
708
+ }
709
+ }
710
+ }
465
711
  const result = await requestChrome("reload_extension", {});
466
712
  if (args.includes("--json")) {
467
713
  output.write(`${JSON.stringify({ ok: true, result }, null, 2)}
@@ -480,7 +726,7 @@ The connector service worker restarts within ~1s; verify with \`nolo chrome stat
480
726
  `);
481
727
  if (code === "CHROME_CONNECTOR_UNAVAILABLE") {
482
728
  output.write(
483
- "The native host is not reachable: load the unpacked connector extension in chrome://extensions (or start Chrome), then rerun `nolo chrome reload`.\n"
729
+ "The native host is not reachable: open Chrome or Firefox with the Nolo Browser Connector extension enabled, or install it from https://nolo.chat/downloads (or load the unpacked connector extension in chrome://extensions), then rerun `nolo chrome reload`.\n"
484
730
  );
485
731
  }
486
732
  return 1;
@@ -488,11 +734,17 @@ The connector service worker restarts within ~1s; verify with \`nolo chrome stat
488
734
  }
489
735
  export {
490
736
  CLI_CONNECTOR_ROOT_CANDIDATES,
737
+ FIREFOX_UNAVAILABLE_FEATURES,
491
738
  FULL_SUPPORT_CHROME_FEATURES,
492
739
  buildChromeConnectorStatus,
740
+ compareDeployShas,
493
741
  detectWorktreeCheckout,
742
+ expectedFeaturesForBrowser,
743
+ fetchOnlineBuildSha,
494
744
  formatChromeConnectorStatus,
495
745
  resolveCliConnectorRoot,
746
+ resolveConnectorBrowser,
747
+ resolveLocalHeadSha,
496
748
  runChromeInstallCommand,
497
749
  runChromeReloadCommand,
498
750
  runChromeStatusCommand