@intutic/cli 1.10.0 → 2.0.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 (357) hide show
  1. package/README.md +1 -0
  2. package/dist/cli.js +868 -30
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/attenuate.d.ts +39 -0
  5. package/dist/commands/attenuate.d.ts.map +1 -0
  6. package/dist/commands/attenuate.js +98 -0
  7. package/dist/commands/attenuate.js.map +1 -0
  8. package/dist/commands/budget.d.ts +80 -0
  9. package/dist/commands/budget.d.ts.map +1 -1
  10. package/dist/commands/budget.js +119 -3
  11. package/dist/commands/budget.js.map +1 -1
  12. package/dist/commands/connect.d.ts +10 -0
  13. package/dist/commands/connect.d.ts.map +1 -1
  14. package/dist/commands/connect.js +358 -48
  15. package/dist/commands/connect.js.map +1 -1
  16. package/dist/commands/credentials.d.ts +31 -0
  17. package/dist/commands/credentials.d.ts.map +1 -0
  18. package/dist/commands/credentials.js +134 -0
  19. package/dist/commands/credentials.js.map +1 -0
  20. package/dist/commands/decision.d.ts +37 -0
  21. package/dist/commands/decision.d.ts.map +1 -0
  22. package/dist/commands/decision.js +56 -0
  23. package/dist/commands/decision.js.map +1 -0
  24. package/dist/commands/doctor.d.ts +38 -1
  25. package/dist/commands/doctor.d.ts.map +1 -1
  26. package/dist/commands/doctor.js +228 -18
  27. package/dist/commands/doctor.js.map +1 -1
  28. package/dist/commands/enforce.d.ts +33 -0
  29. package/dist/commands/enforce.d.ts.map +1 -0
  30. package/dist/commands/enforce.js +150 -0
  31. package/dist/commands/enforce.js.map +1 -0
  32. package/dist/commands/enterprise.d.ts +29 -0
  33. package/dist/commands/enterprise.d.ts.map +1 -0
  34. package/dist/commands/enterprise.js +141 -0
  35. package/dist/commands/enterprise.js.map +1 -0
  36. package/dist/commands/env.d.ts +7 -0
  37. package/dist/commands/env.d.ts.map +1 -0
  38. package/dist/commands/env.js +55 -0
  39. package/dist/commands/env.js.map +1 -0
  40. package/dist/commands/exec.d.ts +38 -2
  41. package/dist/commands/exec.d.ts.map +1 -1
  42. package/dist/commands/exec.js +247 -10
  43. package/dist/commands/exec.js.map +1 -1
  44. package/dist/commands/findings.d.ts +84 -0
  45. package/dist/commands/findings.d.ts.map +1 -0
  46. package/dist/commands/findings.js +329 -0
  47. package/dist/commands/findings.js.map +1 -0
  48. package/dist/commands/gateway.d.ts +62 -0
  49. package/dist/commands/gateway.d.ts.map +1 -0
  50. package/dist/commands/gateway.js +298 -0
  51. package/dist/commands/gateway.js.map +1 -0
  52. package/dist/commands/guardrails.d.ts +81 -0
  53. package/dist/commands/guardrails.d.ts.map +1 -0
  54. package/dist/commands/guardrails.js +735 -0
  55. package/dist/commands/guardrails.js.map +1 -0
  56. package/dist/commands/init.d.ts +8 -0
  57. package/dist/commands/init.d.ts.map +1 -1
  58. package/dist/commands/init.js +14 -2
  59. package/dist/commands/init.js.map +1 -1
  60. package/dist/commands/install-daemon.d.ts +70 -3
  61. package/dist/commands/install-daemon.d.ts.map +1 -1
  62. package/dist/commands/install-daemon.js +482 -47
  63. package/dist/commands/install-daemon.js.map +1 -1
  64. package/dist/commands/integrity.d.ts +205 -0
  65. package/dist/commands/integrity.d.ts.map +1 -0
  66. package/dist/commands/integrity.js +620 -0
  67. package/dist/commands/integrity.js.map +1 -0
  68. package/dist/commands/judge.d.ts +31 -0
  69. package/dist/commands/judge.d.ts.map +1 -0
  70. package/dist/commands/judge.js +118 -0
  71. package/dist/commands/judge.js.map +1 -0
  72. package/dist/commands/org.d.ts +31 -0
  73. package/dist/commands/org.d.ts.map +1 -0
  74. package/dist/commands/org.js +159 -0
  75. package/dist/commands/org.js.map +1 -0
  76. package/dist/commands/policy.d.ts +138 -4
  77. package/dist/commands/policy.d.ts.map +1 -1
  78. package/dist/commands/policy.js +538 -97
  79. package/dist/commands/policy.js.map +1 -1
  80. package/dist/commands/predict-cost.d.ts +45 -0
  81. package/dist/commands/predict-cost.d.ts.map +1 -0
  82. package/dist/commands/predict-cost.js +123 -0
  83. package/dist/commands/predict-cost.js.map +1 -0
  84. package/dist/commands/rollback.d.ts +5 -0
  85. package/dist/commands/rollback.d.ts.map +1 -0
  86. package/dist/commands/rollback.js +142 -0
  87. package/dist/commands/rollback.js.map +1 -0
  88. package/dist/commands/routing.d.ts +62 -0
  89. package/dist/commands/routing.d.ts.map +1 -0
  90. package/dist/commands/routing.js +121 -0
  91. package/dist/commands/routing.js.map +1 -0
  92. package/dist/commands/setup.d.ts +62 -0
  93. package/dist/commands/setup.d.ts.map +1 -0
  94. package/dist/commands/setup.js +238 -0
  95. package/dist/commands/setup.js.map +1 -0
  96. package/dist/commands/skill.d.ts +268 -1
  97. package/dist/commands/skill.d.ts.map +1 -1
  98. package/dist/commands/skill.js +744 -39
  99. package/dist/commands/skill.js.map +1 -1
  100. package/dist/commands/sops.d.ts +95 -0
  101. package/dist/commands/sops.d.ts.map +1 -1
  102. package/dist/commands/sops.js +334 -34
  103. package/dist/commands/sops.js.map +1 -1
  104. package/dist/commands/start.d.ts.map +1 -1
  105. package/dist/commands/start.js +1 -18
  106. package/dist/commands/start.js.map +1 -1
  107. package/dist/commands/status.d.ts.map +1 -1
  108. package/dist/commands/status.js +44 -1
  109. package/dist/commands/status.js.map +1 -1
  110. package/dist/commands/team.d.ts +38 -0
  111. package/dist/commands/team.d.ts.map +1 -0
  112. package/dist/commands/team.js +140 -0
  113. package/dist/commands/team.js.map +1 -0
  114. package/dist/commands/traces.d.ts +8 -1
  115. package/dist/commands/traces.d.ts.map +1 -1
  116. package/dist/commands/traces.js +114 -15
  117. package/dist/commands/traces.js.map +1 -1
  118. package/dist/commands/whoami.d.ts.map +1 -1
  119. package/dist/commands/whoami.js +2 -1
  120. package/dist/commands/whoami.js.map +1 -1
  121. package/dist/config/keychain.d.ts +10 -2
  122. package/dist/config/keychain.d.ts.map +1 -1
  123. package/dist/config/keychain.js +83 -14
  124. package/dist/config/keychain.js.map +1 -1
  125. package/dist/config/paths.d.ts +27 -0
  126. package/dist/config/paths.d.ts.map +1 -1
  127. package/dist/config/paths.js +37 -6
  128. package/dist/config/paths.js.map +1 -1
  129. package/dist/config/store.d.ts.map +1 -1
  130. package/dist/config/store.js +19 -3
  131. package/dist/config/store.js.map +1 -1
  132. package/dist/harness/ag2.d.ts +22 -0
  133. package/dist/harness/ag2.d.ts.map +1 -0
  134. package/dist/harness/ag2.js +34 -0
  135. package/dist/harness/ag2.js.map +1 -0
  136. package/dist/harness/agentcore.d.ts +55 -0
  137. package/dist/harness/agentcore.d.ts.map +1 -0
  138. package/dist/harness/agentcore.js +119 -0
  139. package/dist/harness/agentcore.js.map +1 -0
  140. package/dist/harness/agenticOrchestrator.d.ts +52 -0
  141. package/dist/harness/agenticOrchestrator.d.ts.map +1 -0
  142. package/dist/harness/agenticOrchestrator.js +115 -0
  143. package/dist/harness/agenticOrchestrator.js.map +1 -0
  144. package/dist/harness/aiSdkHarness.d.ts +30 -0
  145. package/dist/harness/aiSdkHarness.d.ts.map +1 -0
  146. package/dist/harness/aiSdkHarness.js +51 -0
  147. package/dist/harness/aiSdkHarness.js.map +1 -0
  148. package/dist/harness/aiSdkWorkflow.d.ts +26 -0
  149. package/dist/harness/aiSdkWorkflow.d.ts.map +1 -0
  150. package/dist/harness/aiSdkWorkflow.js +39 -0
  151. package/dist/harness/aiSdkWorkflow.js.map +1 -0
  152. package/dist/harness/autogen.d.ts +24 -0
  153. package/dist/harness/autogen.d.ts.map +1 -0
  154. package/dist/harness/autogen.js +36 -0
  155. package/dist/harness/autogen.js.map +1 -0
  156. package/dist/harness/base.js.map +1 -1
  157. package/dist/harness/continue.d.ts.map +1 -1
  158. package/dist/harness/continue.js +2 -1
  159. package/dist/harness/continue.js.map +1 -1
  160. package/dist/harness/crewai.d.ts +15 -0
  161. package/dist/harness/crewai.d.ts.map +1 -0
  162. package/dist/harness/crewai.js +26 -0
  163. package/dist/harness/crewai.js.map +1 -0
  164. package/dist/harness/detector.d.ts +1 -1
  165. package/dist/harness/detector.d.ts.map +1 -1
  166. package/dist/harness/detector.js +47 -1
  167. package/dist/harness/detector.js.map +1 -1
  168. package/dist/harness/dsh.d.ts +19 -0
  169. package/dist/harness/dsh.d.ts.map +1 -0
  170. package/dist/harness/dsh.js +93 -0
  171. package/dist/harness/dsh.js.map +1 -0
  172. package/dist/harness/eve.d.ts +40 -0
  173. package/dist/harness/eve.d.ts.map +1 -0
  174. package/dist/harness/eve.js +67 -0
  175. package/dist/harness/eve.js.map +1 -0
  176. package/dist/harness/googleAdk.d.ts +18 -0
  177. package/dist/harness/googleAdk.d.ts.map +1 -0
  178. package/dist/harness/googleAdk.js +29 -0
  179. package/dist/harness/googleAdk.js.map +1 -0
  180. package/dist/harness/goose.d.ts.map +1 -1
  181. package/dist/harness/goose.js +5 -1
  182. package/dist/harness/goose.js.map +1 -1
  183. package/dist/harness/grok.d.ts +16 -0
  184. package/dist/harness/grok.d.ts.map +1 -0
  185. package/dist/harness/grok.js +78 -0
  186. package/dist/harness/grok.js.map +1 -0
  187. package/dist/harness/jsSdkGatedAdapter.d.ts +102 -0
  188. package/dist/harness/jsSdkGatedAdapter.d.ts.map +1 -0
  189. package/dist/harness/jsSdkGatedAdapter.js +139 -0
  190. package/dist/harness/jsSdkGatedAdapter.js.map +1 -0
  191. package/dist/harness/langchain.d.ts +22 -0
  192. package/dist/harness/langchain.d.ts.map +1 -0
  193. package/dist/harness/langchain.js +33 -0
  194. package/dist/harness/langchain.js.map +1 -0
  195. package/dist/harness/langgraph.d.ts +22 -0
  196. package/dist/harness/langgraph.d.ts.map +1 -0
  197. package/dist/harness/langgraph.js +80 -0
  198. package/dist/harness/langgraph.js.map +1 -0
  199. package/dist/harness/mastra.d.ts +21 -0
  200. package/dist/harness/mastra.d.ts.map +1 -0
  201. package/dist/harness/mastra.js +33 -0
  202. package/dist/harness/mastra.js.map +1 -0
  203. package/dist/harness/muse.d.ts +22 -0
  204. package/dist/harness/muse.d.ts.map +1 -0
  205. package/dist/harness/muse.js +92 -0
  206. package/dist/harness/muse.js.map +1 -0
  207. package/dist/harness/n8n.d.ts.map +1 -1
  208. package/dist/harness/n8n.js +36 -11
  209. package/dist/harness/n8n.js.map +1 -1
  210. package/dist/harness/openaiAgents.d.ts +40 -0
  211. package/dist/harness/openaiAgents.d.ts.map +1 -0
  212. package/dist/harness/openaiAgents.js +107 -0
  213. package/dist/harness/openaiAgents.js.map +1 -0
  214. package/dist/harness/opencode.d.ts +24 -0
  215. package/dist/harness/opencode.d.ts.map +1 -0
  216. package/dist/harness/opencode.js +97 -0
  217. package/dist/harness/opencode.js.map +1 -0
  218. package/dist/harness/pydanticAi.d.ts +19 -0
  219. package/dist/harness/pydanticAi.d.ts.map +1 -0
  220. package/dist/harness/pydanticAi.js +30 -0
  221. package/dist/harness/pydanticAi.js.map +1 -0
  222. package/dist/harness/sdkGatedAdapter.d.ts +55 -0
  223. package/dist/harness/sdkGatedAdapter.d.ts.map +1 -0
  224. package/dist/harness/sdkGatedAdapter.js +85 -0
  225. package/dist/harness/sdkGatedAdapter.js.map +1 -0
  226. package/dist/harness/smolagents.d.ts +22 -0
  227. package/dist/harness/smolagents.d.ts.map +1 -0
  228. package/dist/harness/smolagents.js +33 -0
  229. package/dist/harness/smolagents.js.map +1 -0
  230. package/dist/harness/strands.d.ts +22 -0
  231. package/dist/harness/strands.d.ts.map +1 -0
  232. package/dist/harness/strands.js +35 -0
  233. package/dist/harness/strands.js.map +1 -0
  234. package/dist/harness/trueforge.d.ts +25 -0
  235. package/dist/harness/trueforge.d.ts.map +1 -0
  236. package/dist/harness/trueforge.js +40 -0
  237. package/dist/harness/trueforge.js.map +1 -0
  238. package/dist/harness/types.d.ts.map +1 -1
  239. package/dist/harness/types.js +70 -0
  240. package/dist/harness/types.js.map +1 -1
  241. package/dist/harness/vercelAiSdk.d.ts +31 -0
  242. package/dist/harness/vercelAiSdk.d.ts.map +1 -0
  243. package/dist/harness/vercelAiSdk.js +44 -0
  244. package/dist/harness/vercelAiSdk.js.map +1 -0
  245. package/dist/harness/xirp.d.ts +43 -0
  246. package/dist/harness/xirp.d.ts.map +1 -0
  247. package/dist/harness/xirp.js +95 -0
  248. package/dist/harness/xirp.js.map +1 -0
  249. package/dist/lib/api.d.ts +68 -0
  250. package/dist/lib/api.d.ts.map +1 -1
  251. package/dist/lib/api.js +108 -67
  252. package/dist/lib/api.js.map +1 -1
  253. package/dist/lib/authMessages.d.ts +19 -0
  254. package/dist/lib/authMessages.d.ts.map +1 -0
  255. package/dist/lib/authMessages.js +20 -0
  256. package/dist/lib/authMessages.js.map +1 -0
  257. package/dist/lib/binaryChecksum.d.ts +12 -0
  258. package/dist/lib/binaryChecksum.d.ts.map +1 -0
  259. package/dist/lib/binaryChecksum.js +45 -0
  260. package/dist/lib/binaryChecksum.js.map +1 -0
  261. package/dist/lib/binaryOnPath.d.ts +24 -0
  262. package/dist/lib/binaryOnPath.d.ts.map +1 -0
  263. package/dist/lib/binaryOnPath.js +31 -0
  264. package/dist/lib/binaryOnPath.js.map +1 -0
  265. package/dist/lib/caTrust.d.ts +69 -0
  266. package/dist/lib/caTrust.d.ts.map +1 -0
  267. package/dist/lib/caTrust.js +169 -0
  268. package/dist/lib/caTrust.js.map +1 -0
  269. package/dist/lib/ciscoScanner.d.ts +178 -0
  270. package/dist/lib/ciscoScanner.d.ts.map +1 -0
  271. package/dist/lib/ciscoScanner.js +292 -0
  272. package/dist/lib/ciscoScanner.js.map +1 -0
  273. package/dist/lib/decisionsLogRefresh.d.ts +28 -0
  274. package/dist/lib/decisionsLogRefresh.d.ts.map +1 -0
  275. package/dist/lib/decisionsLogRefresh.js +54 -0
  276. package/dist/lib/decisionsLogRefresh.js.map +1 -0
  277. package/dist/lib/deviceReport.d.ts +26 -0
  278. package/dist/lib/deviceReport.d.ts.map +1 -0
  279. package/dist/lib/deviceReport.js +55 -0
  280. package/dist/lib/deviceReport.js.map +1 -0
  281. package/dist/lib/elevation.d.ts +58 -0
  282. package/dist/lib/elevation.d.ts.map +1 -0
  283. package/dist/lib/elevation.js +83 -0
  284. package/dist/lib/elevation.js.map +1 -0
  285. package/dist/lib/enforcementState.d.ts +69 -0
  286. package/dist/lib/enforcementState.d.ts.map +1 -0
  287. package/dist/lib/enforcementState.js +113 -0
  288. package/dist/lib/enforcementState.js.map +1 -0
  289. package/dist/lib/ensureValkey.d.ts.map +1 -1
  290. package/dist/lib/ensureValkey.js +2 -2
  291. package/dist/lib/ensureValkey.js.map +1 -1
  292. package/dist/lib/envInjector.d.ts.map +1 -1
  293. package/dist/lib/envInjector.js +22 -4
  294. package/dist/lib/envInjector.js.map +1 -1
  295. package/dist/lib/gitHooks.d.ts +7 -2
  296. package/dist/lib/gitHooks.d.ts.map +1 -1
  297. package/dist/lib/gitHooks.js +180 -13
  298. package/dist/lib/gitHooks.js.map +1 -1
  299. package/dist/lib/gracefulShutdown.d.ts +37 -0
  300. package/dist/lib/gracefulShutdown.d.ts.map +1 -0
  301. package/dist/lib/gracefulShutdown.js +57 -0
  302. package/dist/lib/gracefulShutdown.js.map +1 -0
  303. package/dist/lib/graphIdentity.d.ts +80 -0
  304. package/dist/lib/graphIdentity.d.ts.map +1 -0
  305. package/dist/lib/graphIdentity.js +120 -0
  306. package/dist/lib/graphIdentity.js.map +1 -0
  307. package/dist/lib/localTraces.d.ts +61 -0
  308. package/dist/lib/localTraces.d.ts.map +1 -0
  309. package/dist/lib/localTraces.js +173 -0
  310. package/dist/lib/localTraces.js.map +1 -0
  311. package/dist/lib/mdmManifest.d.ts +73 -0
  312. package/dist/lib/mdmManifest.d.ts.map +1 -0
  313. package/dist/lib/mdmManifest.js +190 -0
  314. package/dist/lib/mdmManifest.js.map +1 -0
  315. package/dist/lib/onboarding.d.ts.map +1 -1
  316. package/dist/lib/onboarding.js +60 -1
  317. package/dist/lib/onboarding.js.map +1 -1
  318. package/dist/lib/otelPropagation.d.ts +26 -0
  319. package/dist/lib/otelPropagation.d.ts.map +1 -0
  320. package/dist/lib/otelPropagation.js +40 -0
  321. package/dist/lib/otelPropagation.js.map +1 -0
  322. package/dist/lib/policySnapshot.d.ts +92 -0
  323. package/dist/lib/policySnapshot.d.ts.map +1 -0
  324. package/dist/lib/policySnapshot.js +173 -0
  325. package/dist/lib/policySnapshot.js.map +1 -0
  326. package/dist/lib/process.d.ts.map +1 -1
  327. package/dist/lib/process.js +60 -0
  328. package/dist/lib/process.js.map +1 -1
  329. package/dist/lib/providerProbe.d.ts +28 -0
  330. package/dist/lib/providerProbe.d.ts.map +1 -0
  331. package/dist/lib/providerProbe.js +57 -0
  332. package/dist/lib/providerProbe.js.map +1 -0
  333. package/dist/lib/proxyBinary.d.ts +19 -0
  334. package/dist/lib/proxyBinary.d.ts.map +1 -0
  335. package/dist/lib/proxyBinary.js +30 -0
  336. package/dist/lib/proxyBinary.js.map +1 -0
  337. package/dist/lib/sandbox/firecracker.d.ts +89 -0
  338. package/dist/lib/sandbox/firecracker.d.ts.map +1 -0
  339. package/dist/lib/sandbox/firecracker.js +135 -0
  340. package/dist/lib/sandbox/firecracker.js.map +1 -0
  341. package/dist/lib/sandbox/index.d.ts +34 -0
  342. package/dist/lib/sandbox/index.d.ts.map +1 -0
  343. package/dist/lib/sandbox/index.js +54 -0
  344. package/dist/lib/sandbox/index.js.map +1 -0
  345. package/dist/lib/sandbox/oci.d.ts +38 -0
  346. package/dist/lib/sandbox/oci.d.ts.map +1 -0
  347. package/dist/lib/sandbox/oci.js +108 -0
  348. package/dist/lib/sandbox/oci.js.map +1 -0
  349. package/dist/lib/sandbox/types.d.ts +48 -0
  350. package/dist/lib/sandbox/types.d.ts.map +1 -0
  351. package/dist/lib/sandbox/types.js +12 -0
  352. package/dist/lib/sandbox/types.js.map +1 -0
  353. package/dist/lib/sopFrontMatter.d.ts +50 -0
  354. package/dist/lib/sopFrontMatter.d.ts.map +1 -0
  355. package/dist/lib/sopFrontMatter.js +129 -0
  356. package/dist/lib/sopFrontMatter.js.map +1 -0
  357. package/package.json +11 -6
@@ -1,8 +1,9 @@
1
1
  /**
2
- * install-daemon.ts — System-level sync-daemon & mcp-daemon persistence installer.
2
+ * install-daemon.ts — System-level sync-daemon, mcp-daemon & proxy persistence installer.
3
3
  *
4
- * Registers the Intutic sync-daemon or mcp-daemon as system-level services so they
5
- * auto-start on login/boot and restart automatically on any exit.
4
+ * Registers the Intutic sync-daemon, mcp-daemon, or the standalone `intutic-proxy`
5
+ * binary as system-level services so they auto-start on login/boot and restart
6
+ * automatically on any exit.
6
7
  *
7
8
  * Platform support:
8
9
  * - macOS : LaunchAgent/LaunchDaemon (LaunchDaemon requires root/system flag)
@@ -16,9 +17,8 @@ import * as fs from 'node:fs/promises';
16
17
  import * as path from 'node:path';
17
18
  import * as os from 'node:os';
18
19
  import { execFileSync } from 'node:child_process';
19
- import { createLogger } from '@intutic/logger';
20
20
  import { loadConfig } from '../config/store.js';
21
- const log = createLogger('cli-install-daemon');
21
+ import { localProxyBinary } from '../lib/proxyBinary.js';
22
22
  // ── Error definitions ──────────────────────────────────────────────────
23
23
  export class ElevationRequiredError extends Error {
24
24
  constructor(targetPath) {
@@ -32,9 +32,13 @@ export function checkRootPrivileges(system, targetPath = '/Library/LaunchDaemons
32
32
  throw new ElevationRequiredError(targetPath);
33
33
  }
34
34
  }
35
- export function getPaths(system, isMcp, platform = process.platform) {
36
- const label = isMcp ? 'ai.intutic.mcp-daemon' : 'ai.intutic.sync-daemon';
37
- const unitName = isMcp ? 'intutic-mcp-daemon.service' : 'intutic-sync-daemon.service';
35
+ const SERVICE_NAMES = {
36
+ sync: { label: 'ai.intutic.sync-daemon', unitName: 'intutic-sync-daemon.service', logStem: 'sync-daemon' },
37
+ mcp: { label: 'ai.intutic.mcp-daemon', unitName: 'intutic-mcp-daemon.service', logStem: 'mcp-daemon' },
38
+ proxy: { label: 'ai.intutic.proxy', unitName: 'intutic-proxy.service', logStem: 'proxy' },
39
+ };
40
+ export function getServicePaths(system, target, platform = process.platform) {
41
+ const { label, unitName, logStem } = SERVICE_NAMES[target];
38
42
  if (platform === 'darwin') {
39
43
  const dir = system ? '/Library/LaunchDaemons' : path.join(os.homedir(), 'Library', 'LaunchAgents');
40
44
  const plistPath = path.join(dir, `${label}.plist`);
@@ -44,8 +48,8 @@ export function getPaths(system, isMcp, platform = process.platform) {
44
48
  targetDir: dir,
45
49
  targetPath: plistPath,
46
50
  logsDir,
47
- logPath: path.join(logsDir, isMcp ? 'mcp-daemon.log' : 'sync-daemon.log'),
48
- errPath: path.join(logsDir, isMcp ? 'mcp-daemon.err' : 'sync-daemon.err'),
51
+ logPath: path.join(logsDir, `${logStem}.log`),
52
+ errPath: path.join(logsDir, `${logStem}.err`),
49
53
  };
50
54
  }
51
55
  else {
@@ -58,11 +62,15 @@ export function getPaths(system, isMcp, platform = process.platform) {
58
62
  targetDir: dir,
59
63
  targetPath: unitPath,
60
64
  logsDir,
61
- logPath: path.join(logsDir, isMcp ? 'mcp-daemon.log' : 'sync-daemon.log'),
62
- errPath: path.join(logsDir, isMcp ? 'mcp-daemon.err' : 'sync-daemon.err'),
65
+ logPath: path.join(logsDir, `${logStem}.log`),
66
+ errPath: path.join(logsDir, `${logStem}.err`),
63
67
  };
64
68
  }
65
69
  }
70
+ /** The two-daemon shape every existing caller uses; `getServicePaths` is the general form. */
71
+ export function getPaths(system, isMcp, platform = process.platform) {
72
+ return getServicePaths(system, isMcp ? 'mcp' : 'sync', platform);
73
+ }
66
74
  // ── macOS LaunchAgent & LaunchDaemon ──────────────────────────────────
67
75
  export function buildPlist(opts, system = false) {
68
76
  const paths = getPaths(system, false, 'darwin');
@@ -122,19 +130,41 @@ ${argStrings.join('\n')}
122
130
  </plist>
123
131
  `;
124
132
  }
133
+ /**
134
+ * Resolve the argv used to launch the MCP daemon from a service definition.
135
+ *
136
+ * Both launchd and systemd exec the program directly rather than through a
137
+ * shell, and neither searches PATH: launchd requires an absolute path in
138
+ * `ProgramArguments[0]`, and systemd rejects a unit whose `ExecStart` does not
139
+ * start with an absolute path. So `which` is run for its *output* — the
140
+ * absolute location — not merely as a presence check.
141
+ *
142
+ * Falls back to running the bundled daemon entry point under the current Node
143
+ * when `intutic-mcp-daemon` is not installed on PATH.
144
+ */
145
+ function resolveMcpDaemonExec(daemonPath) {
146
+ const fallback = [process.execPath, daemonPath];
147
+ try {
148
+ // stderr is dropped so a "not found" message never reaches the CLI output;
149
+ // `which` exits non-zero in that case and the catch below handles it.
150
+ const resolved = execFileSync('which', ['intutic-mcp-daemon'], {
151
+ encoding: 'utf-8',
152
+ stdio: ['ignore', 'pipe', 'ignore'],
153
+ })
154
+ .split('\n')[0]
155
+ .trim();
156
+ return path.isAbsolute(resolved) ? [resolved] : fallback;
157
+ }
158
+ catch {
159
+ return fallback;
160
+ }
161
+ }
125
162
  export function buildMcpPlist(opts, system = false) {
126
163
  const paths = getPaths(system, true, 'darwin');
127
164
  const config = loadConfig();
128
165
  const workspaceRoot = config?.workspaceRoot ?? process.cwd();
129
166
  const daemonPath = path.join(workspaceRoot, 'packages', 'mcp-proxy', 'dist', 'daemon', 'index.js');
130
- let execArgs = [];
131
- try {
132
- execFileSync('which', ['intutic-mcp-daemon'], { stdio: 'ignore' });
133
- execArgs = ['intutic-mcp-daemon'];
134
- }
135
- catch {
136
- execArgs = [process.execPath, daemonPath];
137
- }
167
+ const execArgs = resolveMcpDaemonExec(daemonPath);
138
168
  const programArgsXml = execArgs.map(arg => ` <string>${arg}</string>`).join('\n');
139
169
  const runAtLoad = system ? '' : '\n <key>RunAtLoad</key>\n <true/>';
140
170
  return `<?xml version="1.0" encoding="UTF-8"?>
@@ -198,7 +228,13 @@ async function installMacos(opts, system = false, dryRun = false) {
198
228
  try {
199
229
  execFileSync('launchctl', ['unload', '-w', paths.targetPath], { stdio: 'ignore' });
200
230
  }
201
- catch { }
231
+ catch {
232
+ // Best-effort: this unload exists only to release a previously installed
233
+ // agent before its plist is overwritten. On a first install there is no
234
+ // plist and nothing loaded, so launchctl exits non-zero — the expected
235
+ // case, not a failure. A real problem surfaces at the `load` below, which
236
+ // is deliberately not wrapped.
237
+ }
202
238
  await fs.writeFile(paths.targetPath, plist, 'utf-8');
203
239
  execFileSync('launchctl', ['load', '-w', paths.targetPath]);
204
240
  console.log(`\n✅ Launch${system ? 'Daemon' : 'Agent'} installed and started.`);
@@ -206,7 +242,7 @@ async function installMacos(opts, system = false, dryRun = false) {
206
242
  // ── Linux systemd ─────────────────────────────────────────────────────
207
243
  export function buildUnit(opts, system = false) {
208
244
  const paths = getPaths(system, false, 'linux');
209
- let execStart = '';
245
+ let execStart;
210
246
  if (opts.binaryPath.endsWith('.js') || opts.binaryPath.endsWith('.ts')) {
211
247
  execStart = `${process.execPath} ${opts.binaryPath}`;
212
248
  }
@@ -249,14 +285,7 @@ export function buildMcpUnit(opts, system = false) {
249
285
  const config = loadConfig();
250
286
  const workspaceRoot = config?.workspaceRoot ?? process.cwd();
251
287
  const daemonPath = path.join(workspaceRoot, 'packages', 'mcp-proxy', 'dist', 'daemon', 'index.js');
252
- let execStart = '';
253
- try {
254
- execFileSync('which', ['intutic-mcp-daemon'], { stdio: 'ignore' });
255
- execStart = 'intutic-mcp-daemon';
256
- }
257
- catch {
258
- execStart = `${process.execPath} ${daemonPath}`;
259
- }
288
+ const execStart = resolveMcpDaemonExec(daemonPath).join(' ');
260
289
  return `[Unit]
261
290
  Description=Intutic MCP Daemon — Persistent policy and telemetry cache for MCP harnesses
262
291
  Documentation=https://docs.intutic.ai/daemon
@@ -278,6 +307,119 @@ Environment=CONTROL_PLANE_URL=${opts.controlPlaneUrl}
278
307
  StandardOutput=append:${paths.logPath}
279
308
  StandardError=append:${paths.errPath}
280
309
 
310
+ [Install]
311
+ WantedBy=${system ? 'multi-user.target' : 'default.target'}
312
+ `;
313
+ }
314
+ // ── Standalone proxy (TD-465) ─────────────────────────────────────────
315
+ /**
316
+ * The environment `intutic start` hands the proxy, as a service file has to
317
+ * fix it: `PORT` always; `VALKEY_URL` when a Valkey was named, else
318
+ * `INTUTIC_STANDALONE=1` so the proxy skips the probe and says why;
319
+ * `UPSTREAM_URL` only when given. Insertion order is the order it is written.
320
+ */
321
+ export function proxyServiceEnv(opts) {
322
+ return {
323
+ PORT: opts.port,
324
+ ...(opts.valkeyUrl ? { VALKEY_URL: opts.valkeyUrl } : { INTUTIC_STANDALONE: '1' }),
325
+ ...(opts.upstreamUrl ? { UPSTREAM_URL: opts.upstreamUrl } : {}),
326
+ };
327
+ }
328
+ /**
329
+ * Resolve the absolute `intutic-proxy` path a service file may reference.
330
+ *
331
+ * Same rule as `resolveMcpDaemonExec`: launchd and systemd exec the program
332
+ * directly, do not search PATH, and refuse a relative `ExecStart`, so the PATH
333
+ * shim `intutic start` falls back to is not usable here. An explicit path is
334
+ * taken as given but must be absolute; otherwise the launcher's version-pinned
335
+ * binary, then `which`. Nothing found is an error rather than a unit that
336
+ * fails on its first start with a message only the log file sees.
337
+ */
338
+ export function resolveProxyServiceBinary(explicit) {
339
+ if (explicit) {
340
+ if (!path.isAbsolute(explicit)) {
341
+ throw new Error(`--binary-path must be absolute (a service file cannot search PATH): ${explicit}`);
342
+ }
343
+ return explicit;
344
+ }
345
+ const pinned = localProxyBinary();
346
+ if (pinned)
347
+ return pinned;
348
+ try {
349
+ const resolved = execFileSync('which', ['intutic-proxy'], {
350
+ encoding: 'utf-8',
351
+ stdio: ['ignore', 'pipe', 'ignore'],
352
+ })
353
+ .split('\n')[0]
354
+ .trim();
355
+ if (path.isAbsolute(resolved))
356
+ return resolved;
357
+ }
358
+ catch {
359
+ // `which` exits non-zero when nothing is on PATH; handled by the throw below.
360
+ }
361
+ throw new Error('Could not find the intutic-proxy binary. Install it with `npm install -g @intutic/proxy` ' +
362
+ 'or pass --binary-path /absolute/path/to/intutic-proxy.');
363
+ }
364
+ export function buildProxyPlist(opts, system = false) {
365
+ const paths = getServicePaths(system, 'proxy', 'darwin');
366
+ const runAtLoad = system ? '' : '\n <key>RunAtLoad</key>\n <true/>';
367
+ const envXml = Object.entries(proxyServiceEnv(opts))
368
+ .map(([k, v]) => ` <key>${k}</key>\n <string>${v}</string>`)
369
+ .join('\n');
370
+ return `<?xml version="1.0" encoding="UTF-8"?>
371
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
372
+ <plist version="1.0">
373
+ <dict>
374
+ <key>Label</key>
375
+ <string>${paths.label}</string>
376
+
377
+ <key>ProgramArguments</key>
378
+ <array>
379
+ <string>${opts.binaryPath}</string>
380
+ </array>
381
+
382
+ <key>KeepAlive</key>
383
+ <true/>
384
+ <key>ThrottleInterval</key>
385
+ <integer>5</integer>${runAtLoad}
386
+
387
+ <key>StandardOutPath</key>
388
+ <string>${paths.logPath}</string>
389
+ <key>StandardErrorPath</key>
390
+ <string>${paths.errPath}</string>
391
+
392
+ <key>EnvironmentVariables</key>
393
+ <dict>
394
+ ${envXml}
395
+ </dict>
396
+ </dict>
397
+ </plist>
398
+ `;
399
+ }
400
+ export function buildProxyUnit(opts, system = false) {
401
+ const paths = getServicePaths(system, 'proxy', 'linux');
402
+ const envLines = Object.entries(proxyServiceEnv(opts))
403
+ .map(([k, v]) => `Environment=${k}=${v}`)
404
+ .join('\n');
405
+ return `[Unit]
406
+ Description=Intutic Proxy — Standalone governed LLM gateway
407
+ Documentation=https://docs.intutic.ai/integrations/standalone
408
+ After=network-online.target
409
+ Wants=network-online.target
410
+
411
+ [Service]
412
+ Type=simple
413
+ ExecStart=${opts.binaryPath}
414
+
415
+ Restart=always
416
+ RestartSec=5
417
+
418
+ ${envLines}
419
+
420
+ StandardOutput=append:${paths.logPath}
421
+ StandardError=append:${paths.errPath}
422
+
281
423
  [Install]
282
424
  WantedBy=${system ? 'multi-user.target' : 'default.target'}
283
425
  `;
@@ -337,11 +479,21 @@ export async function uninstallDaemon(opts = {}) {
337
479
  try {
338
480
  execFileSync('launchctl', ['unload', '-w', paths.targetPath], { stdio: 'ignore' });
339
481
  }
340
- catch { }
482
+ catch {
483
+ // Best-effort: uninstall is idempotent. launchctl exits non-zero when
484
+ // the agent is already unloaded or was never loaded, and in both cases
485
+ // the desired end state is reached. The plist removal below is what
486
+ // actually has to happen, so it must not be skipped on this failure.
487
+ }
341
488
  try {
342
489
  await fs.unlink(paths.targetPath);
343
490
  }
344
- catch { }
491
+ catch {
492
+ // Best-effort: ENOENT means an earlier uninstall already removed the
493
+ // plist, which is success for this command. Other errors (EACCES on a
494
+ // /Library/LaunchDaemons plist) are already pre-empted by the
495
+ // checkRootPrivileges call above.
496
+ }
345
497
  }
346
498
  console.log(`✅ Launch${system ? 'Daemon' : 'Agent'} removed.`);
347
499
  break;
@@ -353,12 +505,21 @@ export async function uninstallDaemon(opts = {}) {
353
505
  try {
354
506
  execFileSync('systemctl', [...cmdArgs, 'disable', '--now', paths.unitName], { stdio: 'ignore' });
355
507
  }
356
- catch { }
508
+ catch {
509
+ // Best-effort: `disable --now` exits non-zero when the unit is not
510
+ // enabled, not loaded, or absent entirely — all of which mean the work
511
+ // this call was going to do is already done. The unit file removal
512
+ // below is the part that must still run.
513
+ }
357
514
  try {
358
515
  await fs.unlink(paths.targetPath);
359
516
  execFileSync('systemctl', [...cmdArgs, 'daemon-reload'], { stdio: 'ignore' });
360
517
  }
361
- catch { }
518
+ catch {
519
+ // Best-effort: ENOENT means the unit file is already gone, and the
520
+ // reload is deliberately inside the same block — if nothing was
521
+ // removed there is no change for systemd to pick up.
522
+ }
362
523
  }
363
524
  console.log(`✅ systemd ${system ? 'system' : 'user'} unit removed.`);
364
525
  break;
@@ -419,7 +580,7 @@ export async function daemonStop() {
419
580
  console.log(`✅ Successfully stopped ${system ? 'system' : 'user'} sync-daemon.`);
420
581
  }
421
582
  catch (e) {
422
- console.log(`❌ Failed to stop ${system ? 'system' : 'user'} sync-daemon: ${e.message}`);
583
+ console.log(`❌ Failed to stop ${system ? 'system' : 'user'} sync-daemon: ${e instanceof Error ? e.message : String(e)}`);
423
584
  }
424
585
  }
425
586
  }
@@ -436,7 +597,7 @@ export async function daemonStop() {
436
597
  console.log(`✅ Successfully stopped ${system ? 'system' : 'user'} sync-daemon unit.`);
437
598
  }
438
599
  catch (e) {
439
- console.log(`❌ Failed to stop ${system ? 'system' : 'user'} sync-daemon unit: ${e.message}`);
600
+ console.log(`❌ Failed to stop ${system ? 'system' : 'user'} sync-daemon unit: ${e instanceof Error ? e.message : String(e)}`);
440
601
  }
441
602
  }
442
603
  }
@@ -456,7 +617,7 @@ export async function daemonStart() {
456
617
  console.log(`✅ Successfully started ${system ? 'system' : 'user'} sync-daemon.`);
457
618
  }
458
619
  catch (e) {
459
- console.log(`❌ Failed to start ${system ? 'system' : 'user'} sync-daemon: ${e.message}`);
620
+ console.log(`❌ Failed to start ${system ? 'system' : 'user'} sync-daemon: ${e instanceof Error ? e.message : String(e)}`);
460
621
  }
461
622
  }
462
623
  }
@@ -473,7 +634,7 @@ export async function daemonStart() {
473
634
  console.log(`✅ Successfully started ${system ? 'system' : 'user'} sync-daemon unit.`);
474
635
  }
475
636
  catch (e) {
476
- console.log(`❌ Failed to start ${system ? 'system' : 'user'} sync-daemon unit: ${e.message}`);
637
+ console.log(`❌ Failed to start ${system ? 'system' : 'user'} sync-daemon unit: ${e instanceof Error ? e.message : String(e)}`);
477
638
  }
478
639
  }
479
640
  }
@@ -507,7 +668,12 @@ export async function installMcpDaemon(opts) {
507
668
  try {
508
669
  execFileSync('launchctl', ['unload', '-w', paths.targetPath], { stdio: 'ignore' });
509
670
  }
510
- catch { }
671
+ catch {
672
+ // Best-effort: releases a previously installed MCP agent before its
673
+ // plist is overwritten. Nothing is loaded on a first install, so a
674
+ // non-zero exit here is the normal path. The `load` below is unwrapped
675
+ // and will report a genuine failure.
676
+ }
511
677
  await fs.writeFile(paths.targetPath, plist, 'utf-8');
512
678
  execFileSync('launchctl', ['load', '-w', paths.targetPath]);
513
679
  console.log(`\n✅ MCP Launch${system ? 'Daemon' : 'Agent'} installed and started.`);
@@ -551,11 +717,21 @@ export async function uninstallMcpDaemon(opts = {}) {
551
717
  try {
552
718
  execFileSync('launchctl', ['unload', '-w', paths.targetPath], { stdio: 'ignore' });
553
719
  }
554
- catch { }
720
+ catch {
721
+ // Best-effort: uninstall is idempotent. An MCP agent that is already
722
+ // unloaded (or never was) makes launchctl exit non-zero while leaving
723
+ // the desired end state in place, and the plist removal below still
724
+ // has to run.
725
+ }
555
726
  try {
556
727
  await fs.unlink(paths.targetPath);
557
728
  }
558
- catch { }
729
+ catch {
730
+ // Best-effort: ENOENT means a previous uninstall already removed the
731
+ // plist, which is the outcome this command wants. Permission failures
732
+ // on the /Library/LaunchDaemons copy are pre-empted by the
733
+ // checkRootPrivileges call above.
734
+ }
559
735
  }
560
736
  console.log(`✅ MCP Launch${system ? 'Daemon' : 'Agent'} removed.`);
561
737
  break;
@@ -567,12 +743,21 @@ export async function uninstallMcpDaemon(opts = {}) {
567
743
  try {
568
744
  execFileSync('systemctl', [...cmdArgs, 'disable', '--now', paths.unitName], { stdio: 'ignore' });
569
745
  }
570
- catch { }
746
+ catch {
747
+ // Best-effort: a unit that is not enabled, not loaded, or absent makes
748
+ // `disable --now` exit non-zero, and each of those already satisfies
749
+ // what this call was for. Removal of the unit file below must not be
750
+ // skipped because of it.
751
+ }
571
752
  try {
572
753
  await fs.unlink(paths.targetPath);
573
754
  execFileSync('systemctl', [...cmdArgs, 'daemon-reload'], { stdio: 'ignore' });
574
755
  }
575
- catch { }
756
+ catch {
757
+ // Best-effort: ENOENT means the unit file is already gone. The reload
758
+ // shares this block on purpose — with nothing removed there is no
759
+ // change for systemd to reload.
760
+ }
576
761
  }
577
762
  console.log(`✅ MCP systemd ${system ? 'system' : 'user'} unit removed.`);
578
763
  break;
@@ -631,7 +816,7 @@ export async function mcpDaemonStop() {
631
816
  console.log(`✅ Successfully stopped MCP Launch${system ? 'Daemon' : 'Agent'}.`);
632
817
  }
633
818
  catch (e) {
634
- console.log(`❌ Failed to stop MCP Launch${system ? 'Daemon' : 'Agent'}: ${e.message}`);
819
+ console.log(`❌ Failed to stop MCP Launch${system ? 'Daemon' : 'Agent'}: ${e instanceof Error ? e.message : String(e)}`);
635
820
  }
636
821
  }
637
822
  }
@@ -648,7 +833,7 @@ export async function mcpDaemonStop() {
648
833
  console.log(`✅ Successfully stopped MCP ${system ? 'system' : 'user'} unit.`);
649
834
  }
650
835
  catch (e) {
651
- console.log(`❌ Failed to stop MCP ${system ? 'system' : 'user'} unit: ${e.message}`);
836
+ console.log(`❌ Failed to stop MCP ${system ? 'system' : 'user'} unit: ${e instanceof Error ? e.message : String(e)}`);
652
837
  }
653
838
  }
654
839
  }
@@ -668,7 +853,7 @@ export async function mcpDaemonStart() {
668
853
  console.log(`✅ Successfully started MCP Launch${system ? 'Daemon' : 'Agent'}.`);
669
854
  }
670
855
  catch (e) {
671
- console.log(`❌ Failed to start MCP Launch${system ? 'Daemon' : 'Agent'}: ${e.message}`);
856
+ console.log(`❌ Failed to start MCP Launch${system ? 'Daemon' : 'Agent'}: ${e instanceof Error ? e.message : String(e)}`);
672
857
  }
673
858
  }
674
859
  }
@@ -685,12 +870,262 @@ export async function mcpDaemonStart() {
685
870
  console.log(`✅ Successfully started MCP ${system ? 'system' : 'user'} unit.`);
686
871
  }
687
872
  catch (e) {
688
- console.log(`❌ Failed to start MCP ${system ? 'system' : 'user'} unit: ${e.message}`);
873
+ console.log(`❌ Failed to start MCP ${system ? 'system' : 'user'} unit: ${e instanceof Error ? e.message : String(e)}`);
874
+ }
875
+ }
876
+ }
877
+ break;
878
+ }
879
+ }
880
+ }
881
+ /**
882
+ * `intutic daemon status|stop|start --proxy` — the same three verbs the
883
+ * sync daemon and the MCP daemon have, for the standalone proxy service
884
+ * `installProxyService` writes. Both scopes are checked, as for the others:
885
+ * a user-level LaunchAgent and a system-level LaunchDaemon can coexist.
886
+ */
887
+ export async function proxyServiceStatus() {
888
+ switch (process.platform) {
889
+ case 'darwin': {
890
+ for (const system of [false, true]) {
891
+ const paths = getServicePaths(system, 'proxy', 'darwin');
892
+ const plistExists = await fs.access(paths.targetPath).then(() => true).catch(() => false);
893
+ const scopeStr = system ? 'System-level (LaunchDaemon)' : 'User-level (LaunchAgent)';
894
+ if (plistExists) {
895
+ console.log(`✅ Proxy: ${scopeStr} installed at ${paths.targetPath}`);
896
+ try {
897
+ execFileSync('launchctl', ['list', paths.label], { stdio: 'inherit' });
898
+ }
899
+ catch {
900
+ console.log(` ⚠️ Proxy ${scopeStr} plist exists but is not loaded.`);
901
+ }
902
+ }
903
+ else {
904
+ console.log(`⬜ Proxy: ${scopeStr} not installed`);
905
+ }
906
+ }
907
+ break;
908
+ }
909
+ case 'linux': {
910
+ for (const system of [false, true]) {
911
+ const paths = getServicePaths(system, 'proxy', 'linux');
912
+ const scopeStr = system ? 'System-level' : 'User-level';
913
+ const cmdArgs = system ? [] : ['--user'];
914
+ try {
915
+ console.log(`\n--- Proxy ${scopeStr} Status ---`);
916
+ execFileSync('systemctl', [...cmdArgs, 'status', paths.unitName], { stdio: 'inherit' });
917
+ }
918
+ catch {
919
+ console.log(`⬜ Proxy: ${scopeStr} not running`);
920
+ }
921
+ }
922
+ break;
923
+ }
924
+ }
925
+ }
926
+ export async function proxyServiceStop() {
927
+ switch (process.platform) {
928
+ case 'darwin': {
929
+ for (const system of [false, true]) {
930
+ const paths = getServicePaths(system, 'proxy', 'darwin');
931
+ const plistExists = await fs.access(paths.targetPath).then(() => true).catch(() => false);
932
+ if (plistExists) {
933
+ console.log(`\n🛑 Stopping proxy Launch${system ? 'Daemon' : 'Agent'}: ${paths.label}`);
934
+ try {
935
+ execFileSync('launchctl', ['unload', paths.targetPath], { stdio: 'ignore' });
936
+ console.log(`✅ Successfully stopped proxy Launch${system ? 'Daemon' : 'Agent'}.`);
937
+ }
938
+ catch (e) {
939
+ console.log(`❌ Failed to stop proxy Launch${system ? 'Daemon' : 'Agent'}: ${e instanceof Error ? e.message : String(e)}`);
689
940
  }
690
941
  }
691
942
  }
692
943
  break;
693
944
  }
945
+ case 'linux': {
946
+ for (const system of [false, true]) {
947
+ const paths = getServicePaths(system, 'proxy', 'linux');
948
+ const unitExists = await fs.access(paths.targetPath).then(() => true).catch(() => false);
949
+ if (unitExists) {
950
+ try {
951
+ const cmdArgs = system ? [] : ['--user'];
952
+ execFileSync('systemctl', [...cmdArgs, 'stop', paths.unitName], { stdio: 'ignore' });
953
+ console.log(`✅ Successfully stopped proxy ${system ? 'system' : 'user'} unit.`);
954
+ }
955
+ catch (e) {
956
+ console.log(`❌ Failed to stop proxy ${system ? 'system' : 'user'} unit: ${e instanceof Error ? e.message : String(e)}`);
957
+ }
958
+ }
959
+ }
960
+ break;
961
+ }
962
+ }
963
+ }
964
+ export async function proxyServiceStart() {
965
+ switch (process.platform) {
966
+ case 'darwin': {
967
+ for (const system of [false, true]) {
968
+ const paths = getServicePaths(system, 'proxy', 'darwin');
969
+ const plistExists = await fs.access(paths.targetPath).then(() => true).catch(() => false);
970
+ if (plistExists) {
971
+ try {
972
+ execFileSync('launchctl', ['load', '-w', paths.targetPath], { stdio: 'ignore' });
973
+ console.log(`✅ Successfully started proxy Launch${system ? 'Daemon' : 'Agent'}.`);
974
+ }
975
+ catch (e) {
976
+ console.log(`❌ Failed to start proxy Launch${system ? 'Daemon' : 'Agent'}: ${e instanceof Error ? e.message : String(e)}`);
977
+ }
978
+ }
979
+ }
980
+ break;
981
+ }
982
+ case 'linux': {
983
+ for (const system of [false, true]) {
984
+ const paths = getServicePaths(system, 'proxy', 'linux');
985
+ const unitExists = await fs.access(paths.targetPath).then(() => true).catch(() => false);
986
+ if (unitExists) {
987
+ try {
988
+ const cmdArgs = system ? [] : ['--user'];
989
+ execFileSync('systemctl', [...cmdArgs, 'start', paths.unitName], { stdio: 'ignore' });
990
+ console.log(`✅ Successfully started proxy ${system ? 'system' : 'user'} unit.`);
991
+ }
992
+ catch (e) {
993
+ console.log(`❌ Failed to start proxy ${system ? 'system' : 'user'} unit: ${e instanceof Error ? e.message : String(e)}`);
994
+ }
995
+ }
996
+ }
997
+ break;
998
+ }
999
+ }
1000
+ }
1001
+ // ── Public API (standalone proxy, TD-465) ────────────────────────────
1002
+ export async function installProxyService(opts = {}) {
1003
+ const resolved = {
1004
+ binaryPath: resolveProxyServiceBinary(opts.binaryPath),
1005
+ port: opts.port ?? '4000',
1006
+ valkeyUrl: opts.valkeyUrl,
1007
+ upstreamUrl: opts.upstreamUrl,
1008
+ };
1009
+ const system = !!opts.system;
1010
+ const mode = resolved.valkeyUrl ? `Valkey ${resolved.valkeyUrl}` : 'standalone (INTUTIC_STANDALONE=1)';
1011
+ switch (process.platform) {
1012
+ case 'darwin': {
1013
+ const paths = getServicePaths(system, 'proxy', 'darwin');
1014
+ if (!opts.dryRun) {
1015
+ checkRootPrivileges(system, paths.targetPath);
1016
+ }
1017
+ const plist = buildProxyPlist(resolved, system);
1018
+ console.log(`\n📦 Installing proxy macOS Launch${system ? 'Daemon' : 'Agent'}: ${paths.targetPath}`);
1019
+ console.log(` Binary : ${resolved.binaryPath}`);
1020
+ console.log(` Port : ${resolved.port} — ${mode}`);
1021
+ if (opts.dryRun) {
1022
+ console.log('\n[dry-run] Would write plist:');
1023
+ console.log(plist);
1024
+ return;
1025
+ }
1026
+ await fs.mkdir(paths.targetDir, { recursive: true });
1027
+ await fs.mkdir(paths.logsDir, { recursive: true });
1028
+ try {
1029
+ execFileSync('launchctl', ['unload', '-w', paths.targetPath], { stdio: 'ignore' });
1030
+ }
1031
+ catch {
1032
+ // Best-effort: releases a previously installed proxy agent before its
1033
+ // plist is overwritten. Nothing is loaded on a first install, so a
1034
+ // non-zero exit here is the normal path. The `load` below is unwrapped
1035
+ // and will report a genuine failure.
1036
+ }
1037
+ await fs.writeFile(paths.targetPath, plist, 'utf-8');
1038
+ execFileSync('launchctl', ['load', '-w', paths.targetPath]);
1039
+ console.log(`\n✅ Proxy Launch${system ? 'Daemon' : 'Agent'} installed and started.`);
1040
+ break;
1041
+ }
1042
+ case 'linux': {
1043
+ const paths = getServicePaths(system, 'proxy', 'linux');
1044
+ if (!opts.dryRun) {
1045
+ checkRootPrivileges(system, paths.targetPath);
1046
+ }
1047
+ const unit = buildProxyUnit(resolved, system);
1048
+ console.log(`\n📦 Installing proxy systemd ${system ? 'system' : 'user'} unit: ${paths.targetPath}`);
1049
+ console.log(` Binary : ${resolved.binaryPath}`);
1050
+ console.log(` Port : ${resolved.port} — ${mode}`);
1051
+ if (opts.dryRun) {
1052
+ console.log('\n[dry-run] Would write unit:');
1053
+ console.log(unit);
1054
+ return;
1055
+ }
1056
+ await fs.mkdir(paths.targetDir, { recursive: true });
1057
+ await fs.mkdir(paths.logsDir, { recursive: true });
1058
+ await fs.writeFile(paths.targetPath, unit, 'utf-8');
1059
+ const cmdArgs = system ? [] : ['--user'];
1060
+ execFileSync('systemctl', [...cmdArgs, 'daemon-reload']);
1061
+ execFileSync('systemctl', [...cmdArgs, 'enable', '--now', paths.unitName]);
1062
+ console.log(`\n✅ Proxy systemd ${system ? 'system' : 'user'} unit installed and started.`);
1063
+ break;
1064
+ }
1065
+ default:
1066
+ console.log(`\n⚠️ Platform '${process.platform}' not supported.`);
1067
+ }
1068
+ }
1069
+ export async function uninstallProxyService(opts = {}) {
1070
+ const system = !!opts.system;
1071
+ const paths = getServicePaths(system, 'proxy');
1072
+ if (!opts.dryRun) {
1073
+ checkRootPrivileges(system, paths.targetPath);
1074
+ }
1075
+ switch (process.platform) {
1076
+ case 'darwin': {
1077
+ console.log(`\n🗑 Removing proxy Launch${system ? 'Daemon' : 'Agent'}: ${paths.targetPath}`);
1078
+ if (!opts.dryRun) {
1079
+ try {
1080
+ execFileSync('launchctl', ['unload', '-w', paths.targetPath], { stdio: 'ignore' });
1081
+ }
1082
+ catch {
1083
+ // Best-effort: uninstall is idempotent. An agent that is already
1084
+ // unloaded (or never was) makes launchctl exit non-zero while leaving
1085
+ // the desired end state in place, and the plist removal below still
1086
+ // has to run.
1087
+ }
1088
+ try {
1089
+ await fs.unlink(paths.targetPath);
1090
+ }
1091
+ catch {
1092
+ // Best-effort: ENOENT means a previous uninstall already removed the
1093
+ // plist, which is the outcome this command wants. Permission failures
1094
+ // on the /Library/LaunchDaemons copy are pre-empted by the
1095
+ // checkRootPrivileges call above.
1096
+ }
1097
+ }
1098
+ console.log(`✅ Proxy Launch${system ? 'Daemon' : 'Agent'} removed.`);
1099
+ break;
1100
+ }
1101
+ case 'linux': {
1102
+ console.log(`\n🗑 Removing proxy systemd ${system ? 'system' : 'user'} unit: ${paths.targetPath}`);
1103
+ if (!opts.dryRun) {
1104
+ const cmdArgs = system ? [] : ['--user'];
1105
+ try {
1106
+ execFileSync('systemctl', [...cmdArgs, 'disable', '--now', paths.unitName], { stdio: 'ignore' });
1107
+ }
1108
+ catch {
1109
+ // Best-effort: a unit that is not enabled, not loaded, or absent makes
1110
+ // `disable --now` exit non-zero, and each of those already satisfies
1111
+ // what this call was for. Removal of the unit file below must not be
1112
+ // skipped because of it.
1113
+ }
1114
+ try {
1115
+ await fs.unlink(paths.targetPath);
1116
+ execFileSync('systemctl', [...cmdArgs, 'daemon-reload'], { stdio: 'ignore' });
1117
+ }
1118
+ catch {
1119
+ // Best-effort: ENOENT means the unit file is already gone. The reload
1120
+ // shares this block on purpose — with nothing removed there is no
1121
+ // change for systemd to reload.
1122
+ }
1123
+ }
1124
+ console.log(`✅ Proxy systemd ${system ? 'system' : 'user'} unit removed.`);
1125
+ break;
1126
+ }
1127
+ default:
1128
+ console.log(`\n⚠️ Platform '${process.platform}' is not supported.`);
694
1129
  }
695
1130
  }
696
1131
  //# sourceMappingURL=install-daemon.js.map