p-spore 0.1.3 → 0.1.42

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 (396) hide show
  1. package/README.md +110 -15
  2. package/dist/agents/agent-manager.d.ts +3 -3
  3. package/dist/agents/agent-manager.d.ts.map +1 -1
  4. package/dist/agents/agent-manager.js +3 -2
  5. package/dist/agents/agent-manager.js.map +1 -1
  6. package/dist/application/agent-application.d.ts +29 -5
  7. package/dist/application/agent-application.d.ts.map +1 -1
  8. package/dist/application/agent-application.js +444 -14
  9. package/dist/application/agent-application.js.map +1 -1
  10. package/dist/articles/diff.d.ts +70 -0
  11. package/dist/articles/diff.d.ts.map +1 -0
  12. package/dist/articles/diff.js +330 -0
  13. package/dist/articles/diff.js.map +1 -0
  14. package/dist/articles/docx.d.ts +10 -0
  15. package/dist/articles/docx.d.ts.map +1 -0
  16. package/dist/articles/docx.js +219 -0
  17. package/dist/articles/docx.js.map +1 -0
  18. package/dist/articles/draft.d.ts +18 -0
  19. package/dist/articles/draft.d.ts.map +1 -0
  20. package/dist/articles/draft.js +88 -0
  21. package/dist/articles/draft.js.map +1 -0
  22. package/dist/articles/edits.d.ts +24 -0
  23. package/dist/articles/edits.d.ts.map +1 -0
  24. package/dist/articles/edits.js +54 -0
  25. package/dist/articles/edits.js.map +1 -0
  26. package/dist/articles/html.d.ts +10 -0
  27. package/dist/articles/html.d.ts.map +1 -0
  28. package/dist/articles/html.js +122 -0
  29. package/dist/articles/html.js.map +1 -0
  30. package/dist/articles/plain.d.ts +8 -0
  31. package/dist/articles/plain.d.ts.map +1 -0
  32. package/dist/articles/plain.js +69 -0
  33. package/dist/articles/plain.js.map +1 -0
  34. package/dist/articles/preview.d.ts +43 -0
  35. package/dist/articles/preview.d.ts.map +1 -0
  36. package/dist/articles/preview.js +90 -0
  37. package/dist/articles/preview.js.map +1 -0
  38. package/dist/articles/publish.d.ts +10 -0
  39. package/dist/articles/publish.d.ts.map +1 -0
  40. package/dist/articles/publish.js +54 -0
  41. package/dist/articles/publish.js.map +1 -0
  42. package/dist/articles/receipt.d.ts +16 -0
  43. package/dist/articles/receipt.d.ts.map +1 -0
  44. package/dist/articles/receipt.js +27 -0
  45. package/dist/articles/receipt.js.map +1 -0
  46. package/dist/articles/store.d.ts +78 -0
  47. package/dist/articles/store.d.ts.map +1 -0
  48. package/dist/articles/store.js +308 -0
  49. package/dist/articles/store.js.map +1 -0
  50. package/dist/articles/tool.d.ts +14 -0
  51. package/dist/articles/tool.d.ts.map +1 -0
  52. package/dist/articles/tool.js +175 -0
  53. package/dist/articles/tool.js.map +1 -0
  54. package/dist/articles/writer-context.d.ts +5 -0
  55. package/dist/articles/writer-context.d.ts.map +1 -0
  56. package/dist/articles/writer-context.js +56 -0
  57. package/dist/articles/writer-context.js.map +1 -0
  58. package/dist/articles/writer.d.ts +18 -0
  59. package/dist/articles/writer.d.ts.map +1 -0
  60. package/dist/articles/writer.js +79 -0
  61. package/dist/articles/writer.js.map +1 -0
  62. package/dist/articles/zip-write.d.ts +11 -0
  63. package/dist/articles/zip-write.d.ts.map +1 -0
  64. package/dist/articles/zip-write.js +63 -0
  65. package/dist/articles/zip-write.js.map +1 -0
  66. package/dist/bundled-skills/README.md +17 -0
  67. package/dist/bundled-skills/aegis-search/SKILL.md +59 -0
  68. package/dist/bundled-skills/aegis-search/bin/.gitkeep +0 -0
  69. package/dist/bundled-skills/article-canvas/SKILL.md +28 -0
  70. package/dist/bundled-skills/binaries.json +23 -0
  71. package/dist/bundled-skills/doc-extract/SKILL.md +56 -0
  72. package/dist/cli/main.d.ts.map +1 -1
  73. package/dist/cli/main.js +407 -54
  74. package/dist/cli/main.js.map +1 -1
  75. package/dist/config/config.d.ts +43 -0
  76. package/dist/config/config.d.ts.map +1 -1
  77. package/dist/config/config.js +150 -6
  78. package/dist/config/config.js.map +1 -1
  79. package/dist/daemon/daemon.d.ts.map +1 -1
  80. package/dist/daemon/daemon.js +80 -11
  81. package/dist/daemon/daemon.js.map +1 -1
  82. package/dist/desktop/application.d.ts +4 -0
  83. package/dist/desktop/application.d.ts.map +1 -0
  84. package/dist/desktop/application.js +1688 -0
  85. package/dist/desktop/application.js.map +1 -0
  86. package/dist/desktop/article-export.d.ts +12 -0
  87. package/dist/desktop/article-export.d.ts.map +1 -0
  88. package/dist/desktop/article-export.js +87 -0
  89. package/dist/desktop/article-export.js.map +1 -0
  90. package/dist/desktop/assets/pspore.png +0 -0
  91. package/dist/desktop/assets/pspore.svg +24 -0
  92. package/dist/desktop/conversation-smoke.d.ts +5 -0
  93. package/dist/desktop/conversation-smoke.d.ts.map +1 -0
  94. package/dist/desktop/conversation-smoke.js +93 -0
  95. package/dist/desktop/conversation-smoke.js.map +1 -0
  96. package/dist/desktop/daemon-bridge.d.ts.map +1 -1
  97. package/dist/desktop/daemon-bridge.js +62 -17
  98. package/dist/desktop/daemon-bridge.js.map +1 -1
  99. package/dist/desktop/ipc-contract.d.ts +124 -0
  100. package/dist/desktop/ipc-contract.d.ts.map +1 -1
  101. package/dist/desktop/ipc-contract.js +191 -12
  102. package/dist/desktop/ipc-contract.js.map +1 -1
  103. package/dist/desktop/local-path.d.ts +15 -0
  104. package/dist/desktop/local-path.d.ts.map +1 -0
  105. package/dist/desktop/local-path.js +59 -0
  106. package/dist/desktop/local-path.js.map +1 -0
  107. package/dist/desktop/main.d.ts +10 -1
  108. package/dist/desktop/main.d.ts.map +1 -1
  109. package/dist/desktop/main.js +122 -530
  110. package/dist/desktop/main.js.map +1 -1
  111. package/dist/desktop/native-startup.d.ts +4 -0
  112. package/dist/desktop/native-startup.d.ts.map +1 -0
  113. package/dist/desktop/native-startup.js +57 -0
  114. package/dist/desktop/native-startup.js.map +1 -0
  115. package/dist/desktop/preload.cjs +28 -1
  116. package/dist/desktop/renderer/article-panel.d.ts +45 -0
  117. package/dist/desktop/renderer/article-panel.d.ts.map +1 -0
  118. package/dist/desktop/renderer/article-panel.js +1126 -0
  119. package/dist/desktop/renderer/article-panel.js.map +1 -0
  120. package/dist/desktop/renderer/article-split.d.ts +32 -0
  121. package/dist/desktop/renderer/article-split.d.ts.map +1 -0
  122. package/dist/desktop/renderer/article-split.js +48 -0
  123. package/dist/desktop/renderer/article-split.js.map +1 -0
  124. package/dist/desktop/renderer/composer-file-references.d.ts +23 -0
  125. package/dist/desktop/renderer/composer-file-references.d.ts.map +1 -0
  126. package/dist/desktop/renderer/composer-file-references.js +51 -0
  127. package/dist/desktop/renderer/composer-file-references.js.map +1 -0
  128. package/dist/desktop/renderer/conversation.d.ts +23 -0
  129. package/dist/desktop/renderer/conversation.d.ts.map +1 -0
  130. package/dist/desktop/renderer/conversation.js +66 -0
  131. package/dist/desktop/renderer/conversation.js.map +1 -0
  132. package/dist/desktop/renderer/dom-reconcile.d.ts +15 -0
  133. package/dist/desktop/renderer/dom-reconcile.d.ts.map +1 -0
  134. package/dist/desktop/renderer/dom-reconcile.js +33 -0
  135. package/dist/desktop/renderer/dom-reconcile.js.map +1 -0
  136. package/dist/desktop/renderer/index.html +211 -34
  137. package/dist/desktop/renderer/indexing-view.d.ts +22 -0
  138. package/dist/desktop/renderer/indexing-view.d.ts.map +1 -0
  139. package/dist/desktop/renderer/indexing-view.js +115 -0
  140. package/dist/desktop/renderer/indexing-view.js.map +1 -0
  141. package/dist/desktop/renderer/markdown-view.d.ts +23 -0
  142. package/dist/desktop/renderer/markdown-view.d.ts.map +1 -0
  143. package/dist/desktop/renderer/markdown-view.js +436 -0
  144. package/dist/desktop/renderer/markdown-view.js.map +1 -0
  145. package/dist/desktop/renderer/motion.d.ts +67 -0
  146. package/dist/desktop/renderer/motion.d.ts.map +1 -0
  147. package/dist/desktop/renderer/motion.js +296 -0
  148. package/dist/desktop/renderer/motion.js.map +1 -0
  149. package/dist/desktop/renderer/renderer.js +1793 -155
  150. package/dist/desktop/renderer/renderer.js.map +1 -1
  151. package/dist/desktop/renderer/session-find.d.ts +18 -0
  152. package/dist/desktop/renderer/session-find.d.ts.map +1 -0
  153. package/dist/desktop/renderer/session-find.js +29 -0
  154. package/dist/desktop/renderer/session-find.js.map +1 -0
  155. package/dist/desktop/renderer/session-search.d.ts +30 -0
  156. package/dist/desktop/renderer/session-search.d.ts.map +1 -0
  157. package/dist/desktop/renderer/session-search.js +296 -0
  158. package/dist/desktop/renderer/session-search.js.map +1 -0
  159. package/dist/desktop/renderer/store.d.ts +35 -8
  160. package/dist/desktop/renderer/store.d.ts.map +1 -1
  161. package/dist/desktop/renderer/store.js +95 -78
  162. package/dist/desktop/renderer/store.js.map +1 -1
  163. package/dist/desktop/renderer/styles.css +910 -268
  164. package/dist/desktop/renderer/vendor/gsap.min.js +11 -0
  165. package/dist/desktop/renderer-diagnostics.d.ts +5 -0
  166. package/dist/desktop/renderer-diagnostics.d.ts.map +1 -0
  167. package/dist/desktop/renderer-diagnostics.js +47 -0
  168. package/dist/desktop/renderer-diagnostics.js.map +1 -0
  169. package/dist/desktop/security.d.ts +26 -1
  170. package/dist/desktop/security.d.ts.map +1 -1
  171. package/dist/desktop/security.js +26 -6
  172. package/dist/desktop/security.js.map +1 -1
  173. package/dist/desktop/smoke-fixture.d.ts.map +1 -1
  174. package/dist/desktop/smoke-fixture.js +77 -3
  175. package/dist/desktop/smoke-fixture.js.map +1 -1
  176. package/dist/desktop/splash/index.html +25 -0
  177. package/dist/desktop/splash/splash.d.ts +2 -0
  178. package/dist/desktop/splash/splash.d.ts.map +1 -0
  179. package/dist/desktop/splash/splash.js +8 -0
  180. package/dist/desktop/splash/splash.js.map +1 -0
  181. package/dist/desktop/splash/styles.css +24 -0
  182. package/dist/desktop/system-open.d.ts +16 -0
  183. package/dist/desktop/system-open.d.ts.map +1 -0
  184. package/dist/desktop/system-open.js +90 -0
  185. package/dist/desktop/system-open.js.map +1 -0
  186. package/dist/desktop/validation.d.ts.map +1 -1
  187. package/dist/desktop/validation.js +99 -21
  188. package/dist/desktop/validation.js.map +1 -1
  189. package/dist/desktop/window-security.d.ts +4 -0
  190. package/dist/desktop/window-security.d.ts.map +1 -0
  191. package/dist/desktop/window-security.js +26 -0
  192. package/dist/desktop/window-security.js.map +1 -0
  193. package/dist/documents/errors.d.ts +12 -0
  194. package/dist/documents/errors.d.ts.map +1 -0
  195. package/dist/documents/errors.js +12 -0
  196. package/dist/documents/errors.js.map +1 -0
  197. package/dist/documents/extract.d.ts +37 -0
  198. package/dist/documents/extract.d.ts.map +1 -0
  199. package/dist/documents/extract.js +90 -0
  200. package/dist/documents/extract.js.map +1 -0
  201. package/dist/documents/legacy-office.d.ts +23 -0
  202. package/dist/documents/legacy-office.d.ts.map +1 -0
  203. package/dist/documents/legacy-office.js +439 -0
  204. package/dist/documents/legacy-office.js.map +1 -0
  205. package/dist/documents/ole.d.ts +20 -0
  206. package/dist/documents/ole.d.ts.map +1 -0
  207. package/dist/documents/ole.js +189 -0
  208. package/dist/documents/ole.js.map +1 -0
  209. package/dist/documents/ooxml.d.ts +23 -0
  210. package/dist/documents/ooxml.d.ts.map +1 -0
  211. package/dist/documents/ooxml.js +197 -0
  212. package/dist/documents/ooxml.js.map +1 -0
  213. package/dist/documents/pdf.d.ts +20 -0
  214. package/dist/documents/pdf.d.ts.map +1 -0
  215. package/dist/documents/pdf.js +95 -0
  216. package/dist/documents/pdf.js.map +1 -0
  217. package/dist/documents/plain-text.d.ts +22 -0
  218. package/dist/documents/plain-text.d.ts.map +1 -0
  219. package/dist/documents/plain-text.js +57 -0
  220. package/dist/documents/plain-text.js.map +1 -0
  221. package/dist/documents/zip.d.ts +15 -0
  222. package/dist/documents/zip.d.ts.map +1 -0
  223. package/dist/documents/zip.js +137 -0
  224. package/dist/documents/zip.js.map +1 -0
  225. package/dist/domain/indexing.d.ts +60 -0
  226. package/dist/domain/indexing.d.ts.map +1 -0
  227. package/dist/domain/indexing.js +13 -0
  228. package/dist/domain/indexing.js.map +1 -0
  229. package/dist/domain/runtime.d.ts +133 -5
  230. package/dist/domain/runtime.d.ts.map +1 -1
  231. package/dist/gateway/local-gateway.d.ts +8 -0
  232. package/dist/gateway/local-gateway.d.ts.map +1 -1
  233. package/dist/gateway/local-gateway.js +105 -21
  234. package/dist/gateway/local-gateway.js.map +1 -1
  235. package/dist/indexing/index-service.d.ts +56 -0
  236. package/dist/indexing/index-service.d.ts.map +1 -0
  237. package/dist/indexing/index-service.js +262 -0
  238. package/dist/indexing/index-service.js.map +1 -0
  239. package/dist/kernel/loop.d.ts +10 -0
  240. package/dist/kernel/loop.d.ts.map +1 -1
  241. package/dist/kernel/loop.js +6 -2
  242. package/dist/kernel/loop.js.map +1 -1
  243. package/dist/kernel/reducer.d.ts.map +1 -1
  244. package/dist/kernel/reducer.js +17 -1
  245. package/dist/kernel/reducer.js.map +1 -1
  246. package/dist/kernel/state.d.ts +17 -4
  247. package/dist/kernel/state.d.ts.map +1 -1
  248. package/dist/kernel/state.js +2 -0
  249. package/dist/kernel/state.js.map +1 -1
  250. package/dist/memory/memory-tools.d.ts.map +1 -1
  251. package/dist/memory/memory-tools.js +25 -5
  252. package/dist/memory/memory-tools.js.map +1 -1
  253. package/dist/pi/context-budget.d.ts +8 -0
  254. package/dist/pi/context-budget.d.ts.map +1 -0
  255. package/dist/pi/context-budget.js +62 -0
  256. package/dist/pi/context-budget.js.map +1 -0
  257. package/dist/pi/pi-runtime-adapter.d.ts +38 -3
  258. package/dist/pi/pi-runtime-adapter.d.ts.map +1 -1
  259. package/dist/pi/pi-runtime-adapter.js +551 -56
  260. package/dist/pi/pi-runtime-adapter.js.map +1 -1
  261. package/dist/plugins/bundled-skills.d.ts +56 -0
  262. package/dist/plugins/bundled-skills.d.ts.map +1 -0
  263. package/dist/plugins/bundled-skills.js +243 -0
  264. package/dist/plugins/bundled-skills.js.map +1 -0
  265. package/dist/plugins/extension-manager.d.ts +23 -1
  266. package/dist/plugins/extension-manager.d.ts.map +1 -1
  267. package/dist/plugins/extension-manager.js +117 -0
  268. package/dist/plugins/extension-manager.js.map +1 -1
  269. package/dist/plugins/types.d.ts +29 -0
  270. package/dist/plugins/types.d.ts.map +1 -1
  271. package/dist/plugins/workspace-skills.d.ts +36 -0
  272. package/dist/plugins/workspace-skills.d.ts.map +1 -0
  273. package/dist/plugins/workspace-skills.js +309 -0
  274. package/dist/plugins/workspace-skills.js.map +1 -0
  275. package/dist/policy/policy.d.ts.map +1 -1
  276. package/dist/policy/policy.js +26 -4
  277. package/dist/policy/policy.js.map +1 -1
  278. package/dist/protocol/client.d.ts +1 -0
  279. package/dist/protocol/client.d.ts.map +1 -1
  280. package/dist/protocol/client.js +49 -24
  281. package/dist/protocol/client.js.map +1 -1
  282. package/dist/protocol/limits.d.ts +69 -0
  283. package/dist/protocol/limits.d.ts.map +1 -0
  284. package/dist/protocol/limits.js +166 -0
  285. package/dist/protocol/limits.js.map +1 -0
  286. package/dist/protocol/schema.d.ts +6 -0
  287. package/dist/protocol/schema.d.ts.map +1 -1
  288. package/dist/protocol/schema.js.map +1 -1
  289. package/dist/protocol/server-frame-codec.d.ts +18 -0
  290. package/dist/protocol/server-frame-codec.d.ts.map +1 -0
  291. package/dist/protocol/server-frame-codec.js +211 -0
  292. package/dist/protocol/server-frame-codec.js.map +1 -0
  293. package/dist/service/legacy-daemon.d.ts +6 -0
  294. package/dist/service/legacy-daemon.d.ts.map +1 -0
  295. package/dist/service/legacy-daemon.js +42 -0
  296. package/dist/service/legacy-daemon.js.map +1 -0
  297. package/dist/service/service-launch.d.ts +10 -0
  298. package/dist/service/service-launch.d.ts.map +1 -0
  299. package/dist/service/service-launch.js +56 -0
  300. package/dist/service/service-launch.js.map +1 -0
  301. package/dist/service/system-service.d.ts +57 -0
  302. package/dist/service/system-service.d.ts.map +1 -0
  303. package/dist/service/system-service.js +263 -0
  304. package/dist/service/system-service.js.map +1 -0
  305. package/dist/sessions/session-catalog.d.ts +45 -0
  306. package/dist/sessions/session-catalog.d.ts.map +1 -0
  307. package/dist/sessions/session-catalog.js +270 -0
  308. package/dist/sessions/session-catalog.js.map +1 -0
  309. package/dist/sessions/session-file.d.ts +12 -0
  310. package/dist/sessions/session-file.d.ts.map +1 -0
  311. package/dist/sessions/session-file.js +47 -0
  312. package/dist/sessions/session-file.js.map +1 -0
  313. package/dist/settings/compaction-settings.d.ts +10 -0
  314. package/dist/settings/compaction-settings.d.ts.map +1 -0
  315. package/dist/settings/compaction-settings.js +8 -0
  316. package/dist/settings/compaction-settings.js.map +1 -0
  317. package/dist/settings/indexing-settings-service.d.ts +16 -0
  318. package/dist/settings/indexing-settings-service.d.ts.map +1 -0
  319. package/dist/settings/indexing-settings-service.js +57 -0
  320. package/dist/settings/indexing-settings-service.js.map +1 -0
  321. package/dist/settings/indexing-settings.d.ts +30 -0
  322. package/dist/settings/indexing-settings.d.ts.map +1 -0
  323. package/dist/settings/indexing-settings.js +58 -0
  324. package/dist/settings/indexing-settings.js.map +1 -0
  325. package/dist/settings/llm-settings-service.d.ts +6 -0
  326. package/dist/settings/llm-settings-service.d.ts.map +1 -1
  327. package/dist/settings/llm-settings-service.js +137 -12
  328. package/dist/settings/llm-settings-service.js.map +1 -1
  329. package/dist/settings/llm-settings.d.ts +68 -2
  330. package/dist/settings/llm-settings.d.ts.map +1 -1
  331. package/dist/settings/llm-settings.js +119 -17
  332. package/dist/settings/llm-settings.js.map +1 -1
  333. package/dist/settings/model-api.d.ts +10 -0
  334. package/dist/settings/model-api.d.ts.map +1 -0
  335. package/dist/settings/model-api.js +10 -0
  336. package/dist/settings/model-api.js.map +1 -0
  337. package/dist/storage/home.d.ts +1 -0
  338. package/dist/storage/home.d.ts.map +1 -1
  339. package/dist/storage/home.js +37 -2
  340. package/dist/storage/home.js.map +1 -1
  341. package/dist/tools/core-tools.d.ts +2 -0
  342. package/dist/tools/core-tools.d.ts.map +1 -1
  343. package/dist/tools/core-tools.js +91 -1
  344. package/dist/tools/core-tools.js.map +1 -1
  345. package/dist/ui/shared/activity.d.ts +27 -0
  346. package/dist/ui/shared/activity.d.ts.map +1 -0
  347. package/dist/ui/shared/activity.js +384 -0
  348. package/dist/ui/shared/activity.js.map +1 -0
  349. package/dist/ui/shared/full-message.d.ts +22 -0
  350. package/dist/ui/shared/full-message.d.ts.map +1 -0
  351. package/dist/ui/shared/full-message.js +40 -0
  352. package/dist/ui/shared/full-message.js.map +1 -0
  353. package/dist/ui/shared/local-links.d.ts +7 -0
  354. package/dist/ui/shared/local-links.d.ts.map +1 -0
  355. package/dist/ui/shared/local-links.js +24 -0
  356. package/dist/ui/shared/local-links.js.map +1 -0
  357. package/dist/ui/shared/markdown-stream.d.ts +25 -0
  358. package/dist/ui/shared/markdown-stream.d.ts.map +1 -0
  359. package/dist/ui/shared/markdown-stream.js +35 -0
  360. package/dist/ui/shared/markdown-stream.js.map +1 -0
  361. package/dist/ui/shared/markdown.d.ts +70 -0
  362. package/dist/ui/shared/markdown.d.ts.map +1 -0
  363. package/dist/ui/shared/markdown.js +434 -0
  364. package/dist/ui/shared/markdown.js.map +1 -0
  365. package/dist/ui/shared/presentation.d.ts +10 -2
  366. package/dist/ui/shared/presentation.d.ts.map +1 -1
  367. package/dist/ui/shared/presentation.js +83 -22
  368. package/dist/ui/shared/presentation.js.map +1 -1
  369. package/dist/ui/shared/protocol-state.d.ts +19 -0
  370. package/dist/ui/shared/protocol-state.d.ts.map +1 -1
  371. package/dist/ui/shared/protocol-state.js +188 -26
  372. package/dist/ui/shared/protocol-state.js.map +1 -1
  373. package/dist/ui/shared/virtual-list.d.ts +34 -0
  374. package/dist/ui/shared/virtual-list.d.ts.map +1 -0
  375. package/dist/ui/shared/virtual-list.js +143 -0
  376. package/dist/ui/shared/virtual-list.js.map +1 -0
  377. package/dist/ui/tui/commands.d.ts +1 -1
  378. package/dist/ui/tui/commands.d.ts.map +1 -1
  379. package/dist/ui/tui/commands.js +9 -3
  380. package/dist/ui/tui/commands.js.map +1 -1
  381. package/dist/ui/tui/store.d.ts +26 -0
  382. package/dist/ui/tui/store.d.ts.map +1 -1
  383. package/dist/ui/tui/store.js +67 -3
  384. package/dist/ui/tui/store.js.map +1 -1
  385. package/dist/ui/tui/tui-app.d.ts.map +1 -1
  386. package/dist/ui/tui/tui-app.js +316 -38
  387. package/dist/ui/tui/tui-app.js.map +1 -1
  388. package/dist/ui/tui/view.d.ts +10 -0
  389. package/dist/ui/tui/view.d.ts.map +1 -1
  390. package/dist/ui/tui/view.js +253 -49
  391. package/dist/ui/tui/view.js.map +1 -1
  392. package/dist/version.d.ts +1 -1
  393. package/dist/version.d.ts.map +1 -1
  394. package/dist/version.js +1 -1
  395. package/dist/version.js.map +1 -1
  396. package/package.json +13 -2
package/dist/cli/main.js CHANGED
@@ -1,20 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  import { execFile, spawn } from "node:child_process";
3
3
  import { realpathSync } from "node:fs";
4
- import { access, readFile } from "node:fs/promises";
4
+ import { access, readFile, stat } from "node:fs/promises";
5
5
  import { createRequire } from "node:module";
6
- import { basename } from "node:path";
6
+ import { basename, isAbsolute, normalize, resolve } from "node:path";
7
7
  import { createInterface } from "node:readline/promises";
8
8
  import { fileURLToPath } from "node:url";
9
9
  import { promisify } from "node:util";
10
10
  import { PsporeDaemon } from "../daemon/daemon.js";
11
- import { DaemonSupervisor } from "../daemon/supervisor.js";
11
+ import { parseConfig } from "../config/config.js";
12
12
  import { ProtocolClient } from "../protocol/client.js";
13
13
  import { readResourceTrustSummary } from "../plugins/extension-manager.js";
14
14
  import { resolvePsporeHome } from "../config/config.js";
15
15
  import { ShellService } from "../shell/shell-service.js";
16
+ import { stopLegacyDaemon } from "../service/legacy-daemon.js";
17
+ import { serviceLaunchSpec } from "../service/service-launch.js";
18
+ import { SystemServiceManager } from "../service/system-service.js";
16
19
  import { LlmSettingsService } from "../settings/llm-settings-service.js";
17
- import { defaultModelFor, MODEL_APIS, PROVIDER_OPTIONS, THINKING_LEVELS, } from "../settings/llm-settings.js";
20
+ import {} from "../config/config.js";
21
+ import { defaultModelFor, MODEL_APIS, PERMISSION_MODES, permissionModeSummary, PROVIDER_OPTIONS, THINKING_LEVELS, } from "../settings/llm-settings.js";
22
+ import { atomicWriteJson } from "../storage/atomic.js";
18
23
  import { initializePsporeHome, loadConfig, psporePaths } from "../storage/home.js";
19
24
  import { acceptsModelSetupOffer, PsporeTuiApp } from "../ui/tui/tui-app.js";
20
25
  import { PI_VERSION, PSPORE_VERSION } from "../version.js";
@@ -55,6 +60,8 @@ export async function runCli(argv = process.argv.slice(2)) {
55
60
  case "daemon":
56
61
  requireNoArgs(args, "daemon");
57
62
  return runDaemon(home);
63
+ case "service":
64
+ return runServiceCommand(home, args);
58
65
  case "status":
59
66
  requireNoArgs(args, "status");
60
67
  return printStatus(home);
@@ -81,6 +88,11 @@ export async function runCli(argv = process.argv.slice(2)) {
81
88
  case "agents":
82
89
  requireNoArgs(args, "agents");
83
90
  return printProtocolList(home, "agents.list");
91
+ case "sessions":
92
+ requireNoArgs(args, "sessions");
93
+ return printSessions(home);
94
+ case "session":
95
+ return runSessionCommand(home, args);
84
96
  case "agent":
85
97
  return runAgentCommand(home, args);
86
98
  case "mcp":
@@ -90,7 +102,7 @@ export async function runCli(argv = process.argv.slice(2)) {
90
102
  return printResourceList(home, "resources.list");
91
103
  case "skills":
92
104
  requireNoArgs(args, "skills");
93
- return printResourceList(home, "skills.list");
105
+ return printResourceList(home, "skills.catalog");
94
106
  case "plugins":
95
107
  requireNoArgs(args, "plugins");
96
108
  return printResourceList(home, "plugins.list");
@@ -109,16 +121,14 @@ export async function runCli(argv = process.argv.slice(2)) {
109
121
  throw new CliError("cancel requires exactly one run ID");
110
122
  return cancelRun(home, args[0]);
111
123
  case "prompt":
112
- if (args.length === 0 || !args.join(" ").trim())
113
- throw new CliError("prompt requires text");
114
- return runPrompt(home, args.join(" "));
124
+ return runPromptCommand(home, args);
115
125
  default:
116
126
  throw new CliError(`Unknown command: ${command}\n\n${helpText()}`);
117
127
  }
118
128
  }
119
129
  async function runDaemon(home) {
120
130
  const daemon = await PsporeDaemon.start({ home });
121
- process.stdout.write(`pSpore daemon ${daemon.kernel.snapshot().daemon.phase} (PID ${process.pid})\nSocket: ${daemon.paths.socket}\n`);
131
+ process.stdout.write(`pSpore service process ${daemon.kernel.snapshot().daemon.phase} (PID ${process.pid})\nSocket: ${daemon.paths.socket}\n`);
122
132
  let stopping = false;
123
133
  const stop = () => {
124
134
  if (stopping)
@@ -126,6 +136,13 @@ async function runDaemon(home) {
126
136
  stopping = true;
127
137
  void daemon.stop().catch(() => undefined);
128
138
  };
139
+ // The service holds every live session. Ending the process over a stray
140
+ // rejection loses them all and, once systemd's start limit is reached, leaves
141
+ // clients unable to reconnect at all; a journal entry is the better trade.
142
+ const reportRejection = (reason) => {
143
+ process.stderr.write(`pSpore daemon: unhandled rejection: ${describeUnknownError(reason)}\n`);
144
+ };
145
+ process.on("unhandledRejection", reportRejection);
129
146
  process.once("SIGINT", stop);
130
147
  process.once("SIGTERM", stop);
131
148
  try {
@@ -133,10 +150,16 @@ async function runDaemon(home) {
133
150
  return 0;
134
151
  }
135
152
  finally {
153
+ process.off("unhandledRejection", reportRejection);
136
154
  process.off("SIGINT", stop);
137
155
  process.off("SIGTERM", stop);
138
156
  }
139
157
  }
158
+ function describeUnknownError(reason) {
159
+ if (reason instanceof Error)
160
+ return reason.stack ?? reason.message;
161
+ return String(reason);
162
+ }
140
163
  async function printStatus(home) {
141
164
  const client = await connectFromEndpoint(home, "status");
142
165
  try {
@@ -218,31 +241,89 @@ async function runTui(home) {
218
241
  throw new CliError(`tui requires an interactive TTY; use \`${resolveInvokedName(process.argv[1])} status\` for a non-interactive check`);
219
242
  }
220
243
  const { paths } = await initializePsporeHome(home);
221
- const supervisor = new DaemonSupervisor({ home });
222
- await supervisor.start();
244
+ const service = await cliSystemService(home, paths.workspace);
245
+ const ensured = await service.manager.ensureRunning(service.spec);
246
+ if (ensured.lingerWarning)
247
+ process.stderr.write(`pSpore service warning: ${ensured.lingerWarning}\n`);
248
+ if (ensured.recoveredFromFailure) {
249
+ process.stderr.write("pSpore service: cleared a failed unit state before starting the daemon\n");
250
+ }
223
251
  const settings = new LlmSettingsService(home, paths);
224
- try {
225
- await new PsporeTuiApp({
226
- socketPath: paths.socket,
227
- settings,
228
- onSettingsSaved: async () => {
229
- if (supervisor.ownership !== "owned") {
230
- return {
231
- applied: false,
232
- restartRequired: true,
233
- message: "Settings saved; restart the external daemon to apply them",
234
- };
235
- }
236
- await supervisor.stop();
237
- await supervisor.start();
238
- return { applied: true, restartRequired: false, message: "Settings saved; the TUI daemon is restarting" };
239
- },
240
- }).run();
252
+ await new PsporeTuiApp({
253
+ socketPath: paths.socket,
254
+ settings,
255
+ onSettingsSaved: async () => {
256
+ await service.manager.restart();
257
+ return { applied: true, restartRequired: false, message: "Settings saved; the system service is restarting" };
258
+ },
259
+ }).run();
260
+ return 0;
261
+ }
262
+ async function runServiceCommand(home, args) {
263
+ const action = args[0] ?? "status";
264
+ const { paths } = await initializePsporeHome(home);
265
+ const service = await cliSystemService(home, paths.workspace);
266
+ if (action === "install" || action === "start") {
267
+ requireNoArgs(args.slice(1), `service ${action}`);
268
+ const status = await service.manager.ensureRunning(service.spec);
269
+ process.stdout.write(serviceStatusText(status));
270
+ if (status.lingerWarning)
271
+ process.stderr.write(`Warning: ${status.lingerWarning}\n`);
241
272
  return 0;
242
273
  }
243
- finally {
244
- await supervisor.stop();
274
+ if (action === "restart") {
275
+ requireNoArgs(args.slice(1), "service restart");
276
+ if (!(await service.manager.status()).installed)
277
+ await service.manager.ensureRunning(service.spec);
278
+ else
279
+ await service.manager.restart();
280
+ process.stdout.write(serviceStatusText(await service.manager.status()));
281
+ return 0;
282
+ }
283
+ if (action === "stop") {
284
+ requireNoArgs(args.slice(1), "service stop");
285
+ await service.manager.stop();
286
+ process.stdout.write(serviceStatusText(await service.manager.status()));
287
+ return 0;
245
288
  }
289
+ if (action === "uninstall") {
290
+ requireNoArgs(args.slice(1), "service uninstall");
291
+ await service.manager.uninstall();
292
+ process.stdout.write(`Removed ${service.manager.unitName}\n`);
293
+ return 0;
294
+ }
295
+ if (action === "logs") {
296
+ if (args.length > 2)
297
+ throw new CliError("service logs accepts an optional line count");
298
+ const lines = args[1] === undefined ? 100 : Number(args[1]);
299
+ process.stdout.write(await service.manager.logs(lines));
300
+ return 0;
301
+ }
302
+ if (action === "status") {
303
+ requireNoArgs(args.slice(1), "service status");
304
+ process.stdout.write(serviceStatusText(await service.manager.status()));
305
+ return 0;
306
+ }
307
+ throw new CliError("service usage: service install|start|restart|stop|status|logs [LINES]|uninstall");
308
+ }
309
+ async function cliSystemService(home, workspace) {
310
+ const manager = new SystemServiceManager({ home, beforeStart: async () => stopLegacyDaemon(home) });
311
+ const spec = await serviceLaunchSpec(home, workspace, {
312
+ execPath: process.execPath,
313
+ ...(process.argv[1] === undefined ? {} : { argvEntry: process.argv[1] }),
314
+ moduleUrl: import.meta.url,
315
+ });
316
+ return { manager, spec };
317
+ }
318
+ function serviceStatusText(status) {
319
+ return [
320
+ `Service: ${status.unitName}`,
321
+ `Unit: ${status.unitPath}`,
322
+ `Installed: ${status.installed ? "yes" : "no"}`,
323
+ `Enabled: ${status.enabled ? "yes" : "no"}`,
324
+ `Active: ${status.active ? "yes" : "no"}`,
325
+ `Boot without login: ${status.linger === undefined ? "unknown" : status.linger ? "yes" : "no"}`,
326
+ ].join("\n") + "\n";
246
327
  }
247
328
  async function runDesktop(home) {
248
329
  const entry = fileURLToPath(new URL("../desktop/main.js", import.meta.url));
@@ -258,7 +339,7 @@ async function runDesktop(home) {
258
339
  electron = require("electron");
259
340
  }
260
341
  catch {
261
- throw new CliError("Electron is not included in the npm CLI package; use the pSpore AppImage for the desktop client");
342
+ throw new CliError("Electron is not included in the npm CLI package; use the pSpore desktop zip from GitHub Releases");
262
343
  }
263
344
  if (typeof electron !== "string")
264
345
  throw new CliError("The local Electron executable could not be resolved");
@@ -314,35 +395,46 @@ async function runSetupWizard(home) {
314
395
  const service = new LlmSettingsService(home, paths);
315
396
  const current = await service.get();
316
397
  process.stdout.write("\nModel setup — press Enter to accept each default, Ctrl+C to abort.\n\n");
317
- const providerAnswer = await readLineInput(`1/5 provider [${current.provider}] (deepseek · openai · custom-openai): `);
398
+ const providerAnswer = await readLineInput(`1/6 provider [${current.provider}] (deepseek · openai · custom-openai): `);
318
399
  const provider = providerAnswer.trim() ? parseConfigProvider(providerAnswer.trim()) : current.provider;
319
400
  const suggestedModel = provider === current.provider ? current.model : defaultModelFor(provider);
320
- const modelAnswer = await readLineInput(`2/5 model [${suggestedModel}]: `);
401
+ const modelAnswer = await readLineInput(`2/6 model [${suggestedModel}]: `);
321
402
  const model = modelAnswer.trim() || suggestedModel;
322
403
  let baseUrl = "";
323
404
  let api = "provider-default";
324
405
  if (provider === "custom-openai") {
325
406
  const suggestedUrl = current.provider === provider ? current.baseUrl : "";
326
- const urlAnswer = await readLineInput(`3/5 Base URL${suggestedUrl ? ` [${suggestedUrl}]` : " (required)"}: `);
407
+ const urlAnswer = await readLineInput(`3/6 Base URL${suggestedUrl ? ` [${suggestedUrl}]` : " (required)"}: `);
327
408
  baseUrl = urlAnswer.trim() || suggestedUrl;
328
409
  if (!baseUrl)
329
410
  throw new CliError("A Base URL is required for custom OpenAI");
330
- const apiAnswer = await readLineInput("3/5 API [chat] (chat · responses): ");
411
+ const apiAnswer = await readLineInput("3/6 API [chat] (chat · responses): ");
331
412
  api = parseConfigApi(apiAnswer.trim() || "chat");
332
413
  }
333
- const thinkingAnswer = await readLineInput(`4/5 thinking [${current.thinking}] (${THINKING_LEVELS.join(" · ")}): `);
414
+ const thinkingAnswer = await readLineInput(`4/6 thinking [${current.thinking}] (${THINKING_LEVELS.join(" · ")}): `);
334
415
  const thinking = parseConfigThinking(thinkingAnswer.trim() || current.thinking);
335
416
  const keyDefault = current.hasApiKey ? "keep" : "set";
336
- const keyAnswer = await readLineInput(`5/5 API key [${keyDefault}] (set · keep · clear): `);
417
+ const keyAnswer = await readLineInput(`5/6 API key [${keyDefault}] (set · keep · clear): `);
337
418
  const keyAction = (keyAnswer.trim().toLowerCase() || keyDefault);
338
419
  if (!["set", "keep", "clear"].includes(keyAction))
339
420
  throw new CliError("Key action must be set, keep, or clear");
340
421
  let apiKey;
341
422
  if (keyAction === "set") {
342
- apiKey = await readSecretInput();
423
+ apiKey = await readSecretInput("API key");
343
424
  if (!apiKey)
344
425
  throw new CliError("API key cannot be empty");
345
426
  }
427
+ const cookieDefault = current.hasCookie ? "keep" : "skip";
428
+ const cookieAnswer = await readLineInput(`6/6 Cookie [${cookieDefault}] (set · keep · clear · skip): `);
429
+ const cookieAction = (cookieAnswer.trim().toLowerCase() || cookieDefault);
430
+ if (!["set", "keep", "clear", "skip"].includes(cookieAction))
431
+ throw new CliError("Cookie action must be set, keep, clear, or skip");
432
+ let cookie;
433
+ if (cookieAction === "set") {
434
+ cookie = await readSecretInput("Cookie");
435
+ if (!cookie)
436
+ throw new CliError("Cookie cannot be empty");
437
+ }
346
438
  const saved = await service.save(settingsUpdate(current, {
347
439
  provider,
348
440
  model,
@@ -351,7 +443,9 @@ async function runSetupWizard(home) {
351
443
  thinking,
352
444
  customModel: provider === "custom-openai",
353
445
  ...(apiKey ? { apiKey } : {}),
446
+ ...(cookie ? { cookie } : {}),
354
447
  clearApiKey: keyAction === "clear",
448
+ clearCookie: cookieAction === "clear",
355
449
  }));
356
450
  process.stdout.write("\n");
357
451
  printModelSettings(saved);
@@ -375,6 +469,10 @@ async function runConfigCommand(home, args) {
375
469
  printModelSettings(await service.get());
376
470
  return 0;
377
471
  }
472
+ // Indexing is its own config section rather than an LLM setting, so it is
473
+ // written directly instead of going through the settings service.
474
+ if (action === "indexing")
475
+ return runIndexingConfig(home, paths, args.slice(1));
378
476
  const current = await service.get();
379
477
  let update;
380
478
  if (action === "set") {
@@ -444,10 +542,33 @@ async function runConfigCommand(home, args) {
444
542
  throw new CliError(`config thinking usage: config thinking ${THINKING_LEVELS.join("|")}`);
445
543
  update = settingsUpdate(current, { thinking: parseConfigThinking(args[1]) });
446
544
  }
545
+ else if (action === "permissions") {
546
+ if (args.length !== 2) {
547
+ throw new CliError(`config permissions usage: config permissions ${PERMISSION_MODES.join("|")}|bypass`);
548
+ }
549
+ update = settingsUpdate(current, { permissionMode: parsePermissionMode(args[1]) });
550
+ }
551
+ else if (action === "compact") {
552
+ if (args.length < 2 || args.length > 5) {
553
+ throw new CliError("config compact usage: config compact on|off [RESERVE_TOKENS] [KEEP_RECENT_TOKENS] [TRIGGER_TOKENS]");
554
+ }
555
+ update = settingsUpdate(current, {
556
+ autoCompactionEnabled: parseConfigOnOff(args[1]),
557
+ autoCompactionReserveTokens: args[2] === undefined
558
+ ? current.autoCompactionReserveTokens
559
+ : parseConfigCompactionTokens(args[2], "reserve tokens"),
560
+ autoCompactionKeepRecentTokens: args[3] === undefined
561
+ ? current.autoCompactionKeepRecentTokens
562
+ : parseConfigCompactionTokens(args[3], "keep recent tokens"),
563
+ autoCompactionTriggerTokens: args[4] === undefined
564
+ ? current.autoCompactionTriggerTokens
565
+ : parseConfigCompactionTokens(args[4], "trigger tokens"),
566
+ });
567
+ }
447
568
  else if (action === "key") {
448
569
  if (args.length !== 1)
449
570
  throw new CliError("API keys are never accepted as command arguments; use `config key` and enter it securely");
450
- const apiKey = await readSecretInput();
571
+ const apiKey = await readSecretInput("API key");
451
572
  if (!apiKey)
452
573
  throw new CliError("API key cannot be empty");
453
574
  update = settingsUpdate(current, { apiKey });
@@ -457,14 +578,131 @@ async function runConfigCommand(home, args) {
457
578
  throw new CliError("config clear-key does not accept arguments");
458
579
  update = settingsUpdate(current, { clearApiKey: true });
459
580
  }
581
+ else if (action === "cookie") {
582
+ if (args.length !== 1)
583
+ throw new CliError("Cookies are never accepted as command arguments; use `config cookie` and enter it securely");
584
+ const cookie = await readSecretInput("Cookie");
585
+ if (!cookie)
586
+ throw new CliError("Cookie cannot be empty");
587
+ update = settingsUpdate(current, { cookie });
588
+ }
589
+ else if (action === "clear-cookie") {
590
+ if (args.length !== 1)
591
+ throw new CliError("config clear-cookie does not accept arguments");
592
+ update = settingsUpdate(current, { clearCookie: true });
593
+ }
594
+ else if (action === "header") {
595
+ const name = args[1];
596
+ if (!name || args.length !== 2)
597
+ throw new CliError("config header usage: config header NAME (the value is read securely)");
598
+ const value = await readSecretInput(`${name} value`);
599
+ if (!value)
600
+ throw new CliError("Header value cannot be empty");
601
+ const others = current.customHeaders.filter((header) => header.name.toLowerCase() !== name.toLowerCase());
602
+ update = settingsUpdate(current, {
603
+ customHeaders: [...others.map((header) => ({ name: header.name })), { name, value }],
604
+ });
605
+ }
606
+ else if (action === "clear-header") {
607
+ const name = args[1];
608
+ if (!name || args.length !== 2)
609
+ throw new CliError("config clear-header usage: config clear-header NAME");
610
+ if (!current.customHeaders.some((header) => header.name.toLowerCase() === name.toLowerCase())) {
611
+ throw new CliError(`No custom header named ${name}`);
612
+ }
613
+ update = settingsUpdate(current, {
614
+ customHeaders: current.customHeaders
615
+ .filter((header) => header.name.toLowerCase() !== name.toLowerCase())
616
+ .map((header) => ({ name: header.name })),
617
+ });
618
+ }
460
619
  else {
461
- throw new CliError("config usage: show | set | provider | model | endpoint | thinking | key | clear-key");
620
+ throw new CliError("config usage: show | set | provider | model | endpoint | thinking | permissions | compact | key | clear-key | cookie | clear-cookie | header | clear-header");
462
621
  }
463
622
  const saved = await service.save(update);
464
623
  printModelSettings(saved);
465
624
  process.stdout.write("Saved. Restart an existing daemon to apply the new model settings.\n");
466
625
  return 0;
467
626
  }
627
+ /**
628
+ * Reads and edits `config.indexing`. Directories are resolved to absolute paths
629
+ * and checked to exist, because a typo here silently indexes nothing.
630
+ */
631
+ async function runIndexingConfig(home, paths, args) {
632
+ const config = await loadConfig(home);
633
+ const indexing = config.indexing;
634
+ const action = args[0] ?? "show";
635
+ if (action === "show") {
636
+ requireNoArgs(args.slice(1), "config indexing show");
637
+ printIndexingSettings(indexing, paths.config);
638
+ return 0;
639
+ }
640
+ let next = { ...indexing, directories: [...indexing.directories], extensions: [...indexing.extensions] };
641
+ if (action === "on" || action === "off") {
642
+ requireNoArgs(args.slice(1), `config indexing ${action}`);
643
+ next.enabled = action === "on";
644
+ }
645
+ else if (action === "add" || action === "remove") {
646
+ if (args.length < 2)
647
+ throw new CliError(`config indexing ${action} usage: config indexing ${action} DIR [DIR...]`);
648
+ const targets = await Promise.all(args.slice(1).map(async (value) => resolveIndexDirectory(value, action === "add")));
649
+ next.directories = action === "add"
650
+ ? [...new Set([...next.directories, ...targets])].sort()
651
+ : next.directories.filter((existing) => !targets.includes(existing));
652
+ }
653
+ else if (action === "interval") {
654
+ if (args.length !== 2)
655
+ throw new CliError("config indexing interval usage: config indexing interval MINUTES");
656
+ const minutes = Number(args[1]);
657
+ if (!Number.isSafeInteger(minutes) || minutes < 1 || minutes > 10_080) {
658
+ throw new CliError("Indexing interval must be a whole number of minutes between 1 and 10080");
659
+ }
660
+ next.intervalMinutes = minutes;
661
+ }
662
+ else if (action === "ext") {
663
+ if (args.length !== 2)
664
+ throw new CliError("config indexing ext usage: config indexing ext txt,md,pdf");
665
+ const extensions = (args[1] ?? "").split(",").map((value) => value.trim()).filter(Boolean);
666
+ if (extensions.length === 0)
667
+ throw new CliError("At least one extension is required");
668
+ next.extensions = extensions;
669
+ }
670
+ else {
671
+ throw new CliError("config indexing usage: show | on | off | add DIR | remove DIR | interval MINUTES | ext a,b,c");
672
+ }
673
+ // Re-parsing validates the edit exactly as the daemon will read it.
674
+ const updated = parseConfig({ ...config, indexing: next }, home);
675
+ await atomicWriteJson(paths.config, updated, 0o600);
676
+ printIndexingSettings(updated.indexing, paths.config);
677
+ process.stdout.write("\nSaved. Restart the service to apply: `pspore service restart`.\n");
678
+ return 0;
679
+ }
680
+ async function resolveIndexDirectory(value, mustExist) {
681
+ const resolved = isAbsolute(value) ? normalize(value) : resolve(process.cwd(), value);
682
+ if (!mustExist)
683
+ return resolved;
684
+ try {
685
+ if (!(await stat(resolved)).isDirectory())
686
+ throw new CliError(`Not a directory: ${resolved}`);
687
+ }
688
+ catch (error) {
689
+ if (error instanceof CliError)
690
+ throw error;
691
+ throw new CliError(`Directory does not exist: ${resolved}`);
692
+ }
693
+ return resolved;
694
+ }
695
+ function printIndexingSettings(indexing, configPath) {
696
+ process.stdout.write([
697
+ `Indexing: ${indexing.enabled ? "on" : "off"}`,
698
+ `Interval: every ${indexing.intervalMinutes} minutes`,
699
+ `Extensions: ${indexing.extensions.join(", ")}`,
700
+ indexing.directories.length > 0
701
+ ? `Directories:\n ${indexing.directories.join("\n ")}`
702
+ : "Directories: none configured (nothing will be indexed)",
703
+ `Config file: ${configPath}`,
704
+ ].join("\n") + "\n");
705
+ }
468
706
  function printModelSettings(settings) {
469
707
  process.stdout.write([
470
708
  `Provider: ${settings.provider}`,
@@ -472,7 +710,18 @@ function printModelSettings(settings) {
472
710
  `Base URL: ${settings.baseUrl || "provider default"}`,
473
711
  `API: ${settings.api}`,
474
712
  `Thinking: ${settings.thinking}`,
475
- `API key: ${settings.hasApiKey ? "configured (hidden)" : "not configured"}`,
713
+ `Permissions: ${settings.permissionMode} — ${permissionModeSummary(settings.permissionMode)}`,
714
+ `Auto compact: ${settings.autoCompactionEnabled ? "on" : "off"}`,
715
+ `Compact trigger tokens: ${settings.autoCompactionTriggerTokens}`,
716
+ `Compact reserve tokens: ${settings.autoCompactionReserveTokens}`,
717
+ `Compact keep recent tokens: ${settings.autoCompactionKeepRecentTokens}`,
718
+ `API key: ${settings.hasApiKey ? "configured (hidden)" : "not configured"} — ${settings.apiKeyPath}`,
719
+ `Cookie: ${settings.hasCookie ? "configured (hidden)" : "not configured"} — ${settings.cookiePath}`,
720
+ settings.customHeaders.length === 0
721
+ ? "Custom headers: none"
722
+ : `Custom headers: ${settings.customHeaders.map((header) => `${header.name} (${header.hasValue ? "configured" : "missing value"})`).join(", ")}`,
723
+ `Config file: ${settings.configPath}`,
724
+ `Derived model catalog: ${settings.modelsPath}`,
476
725
  ].join("\n") + "\n");
477
726
  }
478
727
  /** Visible single-line prompt; secrets must go through readSecretInput instead. */
@@ -485,17 +734,17 @@ async function readLineInput(prompt) {
485
734
  rl.close();
486
735
  }
487
736
  }
488
- async function readSecretInput() {
737
+ async function readSecretInput(label = "API key") {
489
738
  if (!process.stdin.isTTY) {
490
739
  let value = "";
491
740
  for await (const chunk of process.stdin) {
492
741
  value += String(chunk);
493
742
  if (value.length > 16_386)
494
- throw new CliError("API key input exceeds 16384 characters");
743
+ throw new CliError(`${label} input exceeds 16384 characters`);
495
744
  }
496
745
  return value.replace(/\r?\n$/, "");
497
746
  }
498
- process.stdout.write("API key (hidden): ");
747
+ process.stdout.write(`${label} (hidden): `);
499
748
  process.stdin.setRawMode(true);
500
749
  process.stdin.resume();
501
750
  return new Promise((resolve, reject) => {
@@ -513,7 +762,7 @@ async function readSecretInput() {
513
762
  const onData = (chunk) => {
514
763
  const data = String(chunk);
515
764
  if (data.includes("\u0003"))
516
- return finish(new CliError("API key input cancelled"));
765
+ return finish(new CliError(`${label} input cancelled`));
517
766
  if (data.includes("\r") || data.includes("\n"))
518
767
  return finish();
519
768
  if (data === "\u007f" || data === "\b") {
@@ -525,7 +774,7 @@ async function readSecretInput() {
525
774
  : data;
526
775
  const printable = [...pasted].filter((character) => character >= " " && character <= "~").join("");
527
776
  if (value.length + printable.length > 16_384)
528
- return finish(new CliError("API key input exceeds 16384 characters"));
777
+ return finish(new CliError(`${label} input exceeds 16384 characters`));
529
778
  value += printable;
530
779
  };
531
780
  process.stdin.on("data", onData);
@@ -545,6 +794,29 @@ function parseConfigThinking(value) {
545
794
  }
546
795
  return normalized;
547
796
  }
797
+ function parsePermissionMode(value) {
798
+ const normalized = value?.trim().toLowerCase();
799
+ // `bypass` is the name users reach for; it maps onto the existing allow_all policy.
800
+ const mode = normalized === "bypass" ? "allow_all" : normalized;
801
+ if (!mode || !PERMISSION_MODES.includes(mode)) {
802
+ throw new CliError(`Permission mode must be one of: ${PERMISSION_MODES.join(", ")} (or bypass)`);
803
+ }
804
+ return mode;
805
+ }
806
+ function parseConfigOnOff(value) {
807
+ if (value === "on")
808
+ return true;
809
+ if (value === "off")
810
+ return false;
811
+ throw new CliError("Auto compact must be on or off");
812
+ }
813
+ function parseConfigCompactionTokens(value, label) {
814
+ const parsed = Number(value);
815
+ if (!Number.isSafeInteger(parsed) || parsed <= 0 || parsed > 1_000_000) {
816
+ throw new CliError(`${label} must be an integer from 1 to 1000000`);
817
+ }
818
+ return parsed;
819
+ }
548
820
  function parseConfigApi(value) {
549
821
  const normalized = value.trim().toLowerCase();
550
822
  const api = normalized === "chat" ? "openai-completions" : normalized === "responses" ? "openai-responses" : normalized;
@@ -561,11 +833,34 @@ function settingsUpdate(settings, changes) {
561
833
  api: changes.api ?? settings.api,
562
834
  thinking: changes.thinking ?? settings.thinking,
563
835
  customModel: changes.customModel ?? settings.customModel,
836
+ permissionMode: changes.permissionMode ?? settings.permissionMode,
837
+ autoCompactionEnabled: changes.autoCompactionEnabled ?? settings.autoCompactionEnabled,
838
+ autoCompactionTriggerTokens: changes.autoCompactionTriggerTokens ?? settings.autoCompactionTriggerTokens,
839
+ autoCompactionReserveTokens: changes.autoCompactionReserveTokens ?? settings.autoCompactionReserveTokens,
840
+ autoCompactionKeepRecentTokens: changes.autoCompactionKeepRecentTokens ?? settings.autoCompactionKeepRecentTokens,
564
841
  ...(changes.apiKey ? { apiKey: changes.apiKey } : {}),
842
+ ...(changes.cookie ? { cookie: changes.cookie } : {}),
843
+ ...(changes.customHeaders ? { customHeaders: changes.customHeaders } : {}),
565
844
  clearApiKey: changes.clearApiKey ?? false,
845
+ clearCookie: changes.clearCookie ?? false,
566
846
  };
567
847
  }
568
- async function runPrompt(home, text) {
848
+ async function runPromptCommand(home, args) {
849
+ let sessionId = "main";
850
+ let textArgs = args;
851
+ if (args[0] === "--session") {
852
+ const selected = args[1];
853
+ if (!selected)
854
+ throw new CliError("prompt --session requires a session ID");
855
+ sessionId = selected;
856
+ textArgs = args.slice(2);
857
+ }
858
+ const text = textArgs.join(" ").trim();
859
+ if (!text)
860
+ throw new CliError("prompt requires text");
861
+ return runPrompt(home, text, sessionId);
862
+ }
863
+ async function runPrompt(home, text, sessionId) {
569
864
  const client = await connectFromEndpoint(home, "prompt");
570
865
  let resolveSettled;
571
866
  let printedDelta = false;
@@ -573,19 +868,22 @@ async function runPrompt(home, text) {
573
868
  resolveSettled = resolve;
574
869
  });
575
870
  const unsubscribe = client.subscribe((event) => {
576
- if (event.type === "message.delta" && typeof event.payload["delta"] === "string") {
871
+ const payload = event.payload;
872
+ if (payload["sessionId"] !== sessionId)
873
+ return;
874
+ if (event.type === "transcript.delta" && payload["kind"] !== "thinking" && typeof payload["delta"] === "string") {
577
875
  printedDelta = true;
578
- process.stdout.write(event.payload["delta"]);
876
+ process.stdout.write(payload["delta"]);
579
877
  }
580
878
  if (event.type === "run.settled")
581
879
  resolveSettled();
582
880
  });
583
- const abort = () => void client.command("session.abort", { reason: "CLI interrupted" }, { sessionId: "main" }).catch(() => undefined);
881
+ const abort = () => void client.command("session.abort", { reason: "CLI interrupted" }, { sessionId }).catch(() => undefined);
584
882
  process.once("SIGINT", abort);
585
883
  try {
586
- await client.command("session.prompt", { text, mode: "prompt" }, { sessionId: "main" });
884
+ await client.command("session.prompt", { text, mode: "auto" }, { sessionId });
587
885
  await settled;
588
- const snapshot = await client.command("session.snapshot", {}, { sessionId: "main" });
886
+ const snapshot = await client.command("session.snapshot", {}, { sessionId });
589
887
  if (!snapshot)
590
888
  throw new CliError("Session disappeared before completion");
591
889
  const finalText = snapshot.messages.at(-1)?.text;
@@ -601,6 +899,61 @@ async function runPrompt(home, text) {
601
899
  await client.close();
602
900
  }
603
901
  }
902
+ async function printSessions(home) {
903
+ const client = await connectFromEndpoint(home, "sessions");
904
+ try {
905
+ const status = await client.command("status.get", {});
906
+ const roots = Object.values(status.sessions)
907
+ .filter((session) => session.rootSession)
908
+ .map((session) => ({
909
+ id: session.sessionId,
910
+ title: session.rootSession?.title,
911
+ status: session.status,
912
+ messages: session.messageCount,
913
+ updatedAt: session.rootSession?.updatedAt,
914
+ }));
915
+ process.stdout.write(`${JSON.stringify(roots, null, 2)}\n`);
916
+ return 0;
917
+ }
918
+ finally {
919
+ await client.close();
920
+ }
921
+ }
922
+ const SESSION_USAGE = "session usage: session new [TITLE] | session delete ID";
923
+ async function runSessionCommand(home, args) {
924
+ if (args[0] === "new")
925
+ return createSession(home, args.slice(1).join(" ").trim());
926
+ if (args[0] === "delete") {
927
+ const sessionId = args[1];
928
+ if (!sessionId || args.length !== 2)
929
+ throw new CliError("session usage: session delete ID");
930
+ return deleteSession(home, sessionId);
931
+ }
932
+ throw new CliError(SESSION_USAGE);
933
+ }
934
+ async function createSession(home, title) {
935
+ const client = await connectFromEndpoint(home, "session-new");
936
+ try {
937
+ const created = await client.command("session.create", title ? { title } : {});
938
+ process.stdout.write(`${created.sessionId}\t${created.rootSession?.title ?? "New session"}\n`);
939
+ return 0;
940
+ }
941
+ finally {
942
+ await client.close();
943
+ }
944
+ }
945
+ /** Deletes a root session together with the transcript file it owns. */
946
+ async function deleteSession(home, sessionId) {
947
+ const client = await connectFromEndpoint(home, "session-delete");
948
+ try {
949
+ const deleted = await client.command("session.delete", {}, { sessionId });
950
+ process.stdout.write(`${deleted.sessionId}\t${deleted.title ?? ""}\t${deleted.transcriptRemoved ? "transcript removed" : "transcript kept"}\n`);
951
+ return 0;
952
+ }
953
+ finally {
954
+ await client.close();
955
+ }
956
+ }
604
957
  async function printProtocolList(home, command) {
605
958
  const client = await connectFromEndpoint(home, command.split(".")[0] ?? "list");
606
959
  try {
@@ -812,7 +1165,7 @@ async function endpointFor(home) {
812
1165
  value = JSON.parse(await readFile(path, "utf8"));
813
1166
  }
814
1167
  catch (error) {
815
- throw new CliError(`No daemon endpoint at ${path}; start it with \`${resolveInvokedName(process.argv[1])} daemon\``);
1168
+ throw new CliError(`No service endpoint at ${path}; start it with \`${resolveInvokedName(process.argv[1])} service start\``);
816
1169
  }
817
1170
  if (!isRecord(value) || value["version"] !== 1 || typeof value["socket"] !== "string" || typeof value["state"] !== "string") {
818
1171
  throw new CliError(`Invalid daemon endpoint file: ${path}`);
@@ -846,7 +1199,7 @@ function requireNoArgs(args, command) {
846
1199
  throw new CliError(`${command} does not accept positional arguments`);
847
1200
  }
848
1201
  function helpText() {
849
- return `pSpore ${PSPORE_VERSION} — Linux local agent core\n\nUsage: ${resolveInvokedName(process.argv[1])} [--home PATH] <command>\n\nCommands:\n setup Guided model setup (provider, model, thinking, API key)\n init Initialize private config/workspace/state directories\n daemon Run the foreground daemon\n tui Open the reconnecting terminal client and start a local daemon when needed\n desktop Open the secure Electron 43 client\n config wizard Same guided setup as the setup command\n config show Show model settings without exposing the API key\n config set ... Configure DeepSeek, OpenAI, or a custom OpenAI endpoint\n config key Read an API key from hidden TTY input or stdin\n config clear-key Remove the current provider's managed API key\n prompt TEXT Send one prompt and wait for completion\n jobs List loaded cron jobs\n runs List durable automation runs\n notifications List durable local notifications\n agents List child agents and their current status\n agent spawn TEXT Spawn a bounded shared-read child agent\n agent send ID TEXT\n Send a queued message to a child agent\n agent join ID... Wait for one or more child agents\n agent cancel ID Cancel a child agent and its descendants\n mcp list List configured MCP health without connecting\n mcp tools List schemas activated in this daemon process\n mcp discover ID Lazily connect and activate one trusted server\n mcp ping ID Check one trusted MCP server connection\n resources List trusted skills and host plugins\n skills List trusted skills and activation state\n plugins List trusted host plugins and activation state\n resource inspect PATH\n Inspect one directory under workspace/staging without executing it\n resource install PATH SHA256\n Trust and install only the exact inspected content digest\n resource disable KIND NAME SHA256\n Revoke trust without deleting installed content\n schedule put FILE\n Atomically create/update a cron job from JSON\n run JOB_ID Trigger one job now and wait for completion\n cancel RUN_ID Cancel a queued or active run\n status Show daemon and session status\n doctor Check Node, config, model, shell, ripgrep, resource trust, MCP, and daemon\n version Print pinned runtime versions\n help Show this help\n`;
1202
+ return `pSpore ${PSPORE_VERSION} — Linux local agent core\n\nUsage: ${resolveInvokedName(process.argv[1])} [--home PATH] <command>\n\nCommands:\n setup Guided model setup (provider, model, thinking, API key, cookie)\n init Initialize private config/workspace/state directories\n service install Install, enable, and start the systemd user service\n service status Show system service, autostart, and linger state\n service logs Show recent system service logs\n service restart Restart the full background service\n service stop Stop it; service uninstall also removes the unit\n daemon Run the service process in the foreground (diagnostics/systemd)\n tui Open the reconnecting terminal shell; service stays running after exit\n desktop Open the secure Electron 43 shell\n config wizard Same guided setup as the setup command\n config show Show model settings without exposing the API key or cookie\n config set ... Configure DeepSeek, OpenAI, or a custom OpenAI endpoint\n config permissions MODE\n Set the approval policy: ask_risky | ask_all | allow_all (bypass) | deny\n config compact on|off [RESERVE] [KEEP] [TRIGGER]\n Configure automatic context compaction thresholds\n config indexing ...\n show | on | off | add DIR | remove DIR | interval MINUTES | ext a,b,c\n config key Read an API key from hidden TTY input or stdin\n config clear-key Remove the current provider's managed API key\n config cookie Read an LLM proxy Cookie header from hidden TTY input or stdin\n config clear-cookie Remove the current provider's managed Cookie header\n config header NAME\n Add or replace one custom request header; the value is read securely\n config clear-header NAME\n Remove one custom request header and its stored value\n sessions List persistent root sessions\n session new [TITLE]\n Create a persistent root session\n session delete ID\n Delete a root session and its stored transcript\n prompt [--session ID] TEXT\n Send normally; if busy, continue queuing and wait\n jobs List loaded cron jobs\n runs List durable automation runs\n notifications List durable local notifications\n agents List child agents and their current status\n agent spawn TEXT Spawn a bounded shared-read child agent\n agent send ID TEXT\n Send a queued message to a child agent\n agent join ID... Wait for one or more child agents\n agent cancel ID Cancel a child agent and its descendants\n mcp list List configured MCP health without connecting\n mcp tools List schemas activated in this service process\n mcp discover ID Lazily connect and activate one trusted server\n mcp ping ID Check one trusted MCP server connection\n resources List trusted skills and host plugins\n skills List installed, bundled, and workspace skills, and why any is not loaded\n plugins List trusted host plugins and activation state\n resource inspect PATH\n Inspect one directory under workspace/staging without executing it\n resource install PATH SHA256\n Trust and install only the exact inspected content digest\n resource disable KIND NAME SHA256\n Revoke trust without deleting installed content\n schedule put FILE\n Atomically create/update a cron job from JSON\n run JOB_ID Trigger one job now and wait for completion\n cancel RUN_ID Cancel a queued or active run\n status Show service and session status\n doctor Check Node, config, model, shell, ripgrep, resource trust, MCP, and service\n version Print pinned runtime versions\n help Show this help\n`;
850
1203
  }
851
1204
  class CliError extends Error {
852
1205
  constructor(message) {