@wenathlan/devthink 2.0.12 → 2.0.14

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 (444) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/accounts.ts +2087 -0
  3. package/agent.ts +2331 -0
  4. package/antigravity.ts +2139 -0
  5. package/apifreeze.ts +4146 -0
  6. package/auth.ts +642 -0
  7. package/background.ts +16837 -0
  8. package/bridge.ts +2080 -0
  9. package/build.ts +54 -0
  10. package/bun.ts +26 -0
  11. package/capture.ts +1614 -0
  12. package/checksecrets.ts +72 -0
  13. package/cjs.ts +9 -0
  14. package/cli.ts +3500 -0
  15. package/commands.ts +1101 -0
  16. package/companion.ts +450 -0
  17. package/compatibility.ts +37 -0
  18. package/config.ts +3137 -0
  19. package/constants.ts +943 -0
  20. package/core.ts +1473 -0
  21. package/crossbrowser.ts +1359 -0
  22. package/dashdone.ts +605 -0
  23. package/data.ts +943 -0
  24. package/database.ts +133 -0
  25. package/debug.ts +850 -0
  26. package/deno.ts +27 -0
  27. package/devthink.ts +707 -0
  28. package/dist/accounts.d.ts +409 -0
  29. package/dist/agent.d.ts +837 -0
  30. package/dist/antigravity.d.ts +380 -0
  31. package/dist/apifreeze.d.ts +113 -0
  32. package/dist/auth.d.ts +224 -0
  33. package/{bridge.d.ts → dist/bridge.d.ts} +1 -1
  34. package/dist/capture.d.ts +595 -0
  35. package/dist/cjs.d.ts +6 -0
  36. package/{cli.js → dist/cli.js} +2 -2
  37. package/dist/commands.d.ts +310 -0
  38. package/dist/compatibility.d.ts +17 -0
  39. package/dist/config.d.ts +1853 -0
  40. package/dist/constants.d.ts +637 -0
  41. package/dist/core.d.ts +361 -0
  42. package/dist/data.d.ts +330 -0
  43. package/dist/database.d.ts +24 -0
  44. package/dist/debug.d.ts +270 -0
  45. package/dist/engine.d.ts +74 -0
  46. package/dist/environments.d.ts +244 -0
  47. package/dist/evidence.d.ts +277 -0
  48. package/dist/export.d.ts +311 -0
  49. package/dist/fingerprint.d.ts +594 -0
  50. package/dist/flow.d.ts +300 -0
  51. package/dist/gates.d.ts +103 -0
  52. package/dist/identity.d.ts +51 -0
  53. package/dist/ids.d.ts +7 -0
  54. package/dist/llm.d.ts +652 -0
  55. package/dist/models.d.ts +1807 -0
  56. package/dist/net.d.ts +325 -0
  57. package/dist/neutral.d.ts +41 -0
  58. package/dist/oauth.d.ts +703 -0
  59. package/dist/page.d.ts +1166 -0
  60. package/dist/perf.d.ts +481 -0
  61. package/dist/plan.d.ts +134 -0
  62. package/dist/project.d.ts +395 -0
  63. package/dist/providers.d.ts +37 -0
  64. package/dist/quota.d.ts +453 -0
  65. package/dist/recovery.d.ts +621 -0
  66. package/dist/request.d.ts +1492 -0
  67. package/dist/run.d.ts +779 -0
  68. package/dist/runtime.d.ts +212 -0
  69. package/dist/search.d.ts +375 -0
  70. package/dist/security.d.ts +534 -0
  71. package/dist/serve.d.ts +136 -0
  72. package/dist/session.d.ts +296 -0
  73. package/dist/storage.d.ts +53 -0
  74. package/dist/streaming.d.ts +1119 -0
  75. package/dist/swarm.d.ts +328 -0
  76. package/dist/tools.d.ts +170 -0
  77. package/dist/types.d.ts +8357 -0
  78. package/dist/utils.d.ts +82 -0
  79. package/dist/version.d.ts +7 -0
  80. package/dist/versionregistry.d.ts +406 -0
  81. package/dist/views.d.ts +579 -0
  82. package/dist/workbenchsession.d.ts +69 -0
  83. package/dist/workflow.d.ts +649 -0
  84. package/docs/readme.md +92 -0
  85. package/docs/schemas/antigravity.json +192 -0
  86. package/engine.ts +1524 -0
  87. package/environments.ts +1005 -0
  88. package/evidence.ts +643 -0
  89. package/export.ts +889 -0
  90. package/fingerprint.ts +1458 -0
  91. package/flow.ts +1190 -0
  92. package/gates.ts +307 -0
  93. package/gateway.ts +34 -0
  94. package/hardening.ts +191 -0
  95. package/headless.ts +369 -0
  96. package/http.ts +2110 -0
  97. package/identity.ts +246 -0
  98. package/ids.ts +26 -0
  99. package/index.ts +369 -0
  100. package/llm.ts +2682 -0
  101. package/mcp.ts +1835 -0
  102. package/{memory.js → memory.ts} +3834 -2310
  103. package/models.ts +3805 -0
  104. package/modes.ts +43 -0
  105. package/net.ts +1214 -0
  106. package/neutral.ts +362 -0
  107. package/node.ts +26 -0
  108. package/oauth.ts +2216 -0
  109. package/offscreen.ts +170 -0
  110. package/pack.ts +956 -0
  111. package/package.json +175 -82
  112. package/page.ts +7824 -0
  113. package/pagebridge.ts +1116 -0
  114. package/perf.ts +957 -0
  115. package/plan.ts +672 -0
  116. package/plugin.ts +76 -0
  117. package/{policy.js → policy.ts} +5073 -4679
  118. package/prisma.config.ts +27 -0
  119. package/progress.ts +1308 -0
  120. package/project.ts +1291 -0
  121. package/protocol.ts +4187 -0
  122. package/providers.ts +246 -0
  123. package/quota.ts +1887 -0
  124. package/recovery.ts +2433 -0
  125. package/request.ts +4482 -0
  126. package/run.ts +2135 -0
  127. package/runtime.ts +526 -0
  128. package/search.ts +1760 -0
  129. package/security.ts +1673 -0
  130. package/serve.ts +419 -0
  131. package/server.ts +5538 -0
  132. package/session.ts +1162 -0
  133. package/storage.ts +182 -0
  134. package/streaming.ts +4101 -0
  135. package/swarm.ts +884 -0
  136. package/sync.ts +56 -0
  137. package/tests/agentcert.test.ts +128 -0
  138. package/tests/agentfleet.test.ts +214 -0
  139. package/tests/agentreview.test.ts +241 -0
  140. package/tests/agentstream.test.ts +335 -0
  141. package/tests/agentwork.test.ts +356 -0
  142. package/tests/apifreeze.test.ts +290 -0
  143. package/tests/apimap.test.ts +112 -0
  144. package/tests/approvalgate.test.ts +175 -0
  145. package/tests/artifactmanifest.test.ts +204 -0
  146. package/tests/attentionfeed.test.ts +163 -0
  147. package/tests/backgroundruns.test.ts +100 -0
  148. package/tests/batchscheduling.test.ts +131 -0
  149. package/tests/blackboard.test.ts +238 -0
  150. package/tests/bridge.test.ts +1088 -0
  151. package/tests/browsercoverage.test.ts +134 -0
  152. package/tests/browserpolyfills.test.ts +124 -0
  153. package/tests/build.mjs +1260 -0
  154. package/tests/clientauth.test.ts +223 -0
  155. package/tests/clitools.test.ts +578 -0
  156. package/tests/confirmgates.test.ts +190 -0
  157. package/tests/containerpack.test.ts +143 -0
  158. package/tests/controlflow.test.ts +2120 -0
  159. package/tests/coordination.test.ts +345 -0
  160. package/tests/copyscreen.test.ts +139 -0
  161. package/tests/core.test.ts +417 -0
  162. package/tests/costcert.test.ts +95 -0
  163. package/tests/crossbrowser.test.ts +115 -0
  164. package/tests/cspaudit.test.ts +79 -0
  165. package/tests/dashdone.test.ts +615 -0
  166. package/tests/datagrid.test.ts +137 -0
  167. package/tests/doccheck.test.ts +107 -0
  168. package/tests/dombatch.test.ts +109 -0
  169. package/tests/emulation.test.ts +282 -0
  170. package/tests/environments.test.ts +259 -0
  171. package/tests/evidenceviews.test.ts +88 -0
  172. package/tests/exporttools.test.ts +185 -0
  173. package/tests/firefoxprep.test.ts +134 -0
  174. package/tests/flowlibrary.test.ts +242 -0
  175. package/tests/flowrun.test.ts +303 -0
  176. package/tests/forensics.test.ts +439 -0
  177. package/tests/gateway.test.ts +939 -0
  178. package/tests/headlesslib.test.ts +171 -0
  179. package/tests/httpstream.test.ts +277 -0
  180. package/tests/immutablelog.test.ts +157 -0
  181. package/tests/inboundguard.test.ts +177 -0
  182. package/tests/lazyload.test.ts +72 -0
  183. package/tests/librarymodes.test.ts +623 -0
  184. package/tests/llm.test.ts +696 -0
  185. package/tests/manifest.test.ts +226 -0
  186. package/tests/maskinputs.test.ts +150 -0
  187. package/tests/matrixverify.test.ts +111 -0
  188. package/tests/mavenpack.test.ts +75 -0
  189. package/tests/mcpserver.test.ts +704 -0
  190. package/tests/memory.test.ts +4155 -0
  191. package/tests/memorycare.test.ts +230 -0
  192. package/tests/migrateplan.test.ts +349 -0
  193. package/tests/minimization.test.ts +414 -0
  194. package/tests/modelroute.test.ts +112 -0
  195. package/tests/multiagent.test.ts +440 -0
  196. package/tests/nativebridge.test.ts +807 -0
  197. package/tests/navigation.test.ts +284 -0
  198. package/tests/nugetpack.test.ts +83 -0
  199. package/tests/orchestration.test.ts +449 -0
  200. package/tests/originpolicy.test.ts +405 -0
  201. package/tests/outputcompare.test.ts +97 -0
  202. package/tests/package.test.ts +91 -0
  203. package/tests/pagebridge.test.ts +5909 -0
  204. package/tests/pentest.test.ts +75 -0
  205. package/tests/perfrecords.test.ts +257 -0
  206. package/tests/permdiff.test.ts +85 -0
  207. package/tests/phishguard.test.ts +128 -0
  208. package/tests/pickerviews.test.ts +155 -0
  209. package/tests/pipelines.test.ts +381 -0
  210. package/tests/planlint.test.ts +240 -0
  211. package/tests/planreview.test.ts +383 -0
  212. package/tests/platformtargets.test.ts +56 -0
  213. package/tests/policy.test.ts +8284 -0
  214. package/tests/portability.test.ts +96 -0
  215. package/tests/progress.test.ts +1036 -0
  216. package/tests/promptlibrary.test.ts +116 -0
  217. package/tests/protocol.test.ts +4793 -0
  218. package/tests/publishingpipeline.test.ts +314 -0
  219. package/tests/quickactions.test.ts +164 -0
  220. package/tests/readiness.test.ts +78 -0
  221. package/tests/recipes.test.ts +163 -0
  222. package/tests/redactshots.test.ts +216 -0
  223. package/tests/registernvidiakeys.ts +51 -0
  224. package/tests/relayserve.test.ts +304 -0
  225. package/tests/releasenotes.test.ts +276 -0
  226. package/tests/resilience.test.ts +467 -0
  227. package/tests/resourceaware.test.ts +187 -0
  228. package/tests/runbudget.test.ts +125 -0
  229. package/tests/runhistory.test.ts +299 -0
  230. package/tests/runreplay.test.ts +113 -0
  231. package/tests/runresume.test.ts +184 -0
  232. package/tests/runstate.test.ts +288 -0
  233. package/tests/runtimeadapters.test.ts +251 -0
  234. package/tests/safariskeleton.test.ts +115 -0
  235. package/tests/sandboxframe.test.ts +196 -0
  236. package/tests/sbom.test.ts +105 -0
  237. package/tests/secretvault.test.ts +188 -0
  238. package/tests/selcache.test.ts +111 -0
  239. package/tests/sessioninterface.test.ts +791 -0
  240. package/tests/sessions.test.ts +591 -0
  241. package/tests/siteprefs.test.ts +228 -0
  242. package/tests/snapshotdelta.test.ts +165 -0
  243. package/tests/statusviews.test.ts +237 -0
  244. package/tests/stepmeter.test.ts +200 -0
  245. package/tests/streamparse.test.ts +163 -0
  246. package/tests/stylepruning.test.ts +90 -0
  247. package/tests/surfaces.test.ts +282 -0
  248. package/tests/sweep.test.ts +78 -0
  249. package/tests/syncbridge.test.ts +164 -0
  250. package/tests/taskqueue.test.ts +206 -0
  251. package/tests/toolcalls.test.ts +336 -0
  252. package/tests/toolcatalog.test.ts +201 -0
  253. package/tests/torture-agent.test.ts +706 -0
  254. package/tests/torture-auth.test.ts +461 -0
  255. package/tests/torture-commands.test.ts +895 -0
  256. package/tests/torture-data.test.ts +923 -0
  257. package/tests/torture-environments.test.ts +285 -0
  258. package/tests/torture-export.test.ts +263 -0
  259. package/tests/torture-http.test.ts +1379 -0
  260. package/tests/torture-llm.test.ts +808 -0
  261. package/tests/torture-memory.test.ts +1084 -0
  262. package/tests/torture-plan.test.ts +829 -0
  263. package/tests/torture-run.test.ts +626 -0
  264. package/tests/torture-session.test.ts +928 -0
  265. package/tests/torture-swarm.test.ts +1245 -0
  266. package/tests/torture-tools.test.ts +964 -0
  267. package/tests/torture-workflow.test.ts +2354 -0
  268. package/tests/tourviews.test.ts +52 -0
  269. package/tests/transparency.test.ts +147 -0
  270. package/tests/trigger.test.ts +941 -0
  271. package/tests/v1sunset.test.ts +428 -0
  272. package/tests/virtlist.test.ts +94 -0
  273. package/tests/vision.test.ts +540 -0
  274. package/tests/vsixpack.test.ts +145 -0
  275. package/tests/webapi.test.ts +523 -0
  276. package/tests/workflow.test.ts +1785 -0
  277. package/tests/workfloweditor.test.ts +759 -0
  278. package/tests/xpipack.test.ts +128 -0
  279. package/tools.ts +734 -0
  280. package/types.ts +9908 -0
  281. package/umd.ts +9 -0
  282. package/utils.ts +249 -0
  283. package/version.ts +8 -0
  284. package/versionregistry.ts +1310 -0
  285. package/views.ts +2153 -0
  286. package/vitest.config.ts +12 -0
  287. package/web/App.tsx +78 -0
  288. package/web/README.md +47 -0
  289. package/web/capacitor.config.ts +46 -0
  290. package/web/const.ts +19 -0
  291. package/web/control.shell.tsx +66 -0
  292. package/web/dashboardpage.ts +554 -0
  293. package/web/db.ts +493 -0
  294. package/web/design.html +541 -0
  295. package/web/gatewayview/Gateway.tsx +345 -0
  296. package/web/gatewayview/config.ts +1042 -0
  297. package/web/gatewayview/definition.ts +174 -0
  298. package/web/icons.ts +15 -0
  299. package/{manifest.json → web/manifest.json} +1 -1
  300. package/web/optionspage.ts +691 -0
  301. package/web/popup.ts +1651 -0
  302. package/web/schema.prisma +176 -0
  303. package/web/sidepanel.ts +7941 -0
  304. package/web/sitemanifest.json +13 -0
  305. package/web/terminal-ui.tsx +622 -0
  306. package/web/transparencypage.ts +541 -0
  307. package/web/vite.config.ts +39 -0
  308. package/web/workspace.ts +23 -0
  309. package/workbenchmemory.ts +104 -0
  310. package/workbenchsession.ts +304 -0
  311. package/workflow.ts +4495 -0
  312. package/bridge.d.ts.map +0 -1
  313. package/bridge.js +0 -1877
  314. package/bun.d.ts.map +0 -1
  315. package/bun.js +0 -52329
  316. package/caps/background.json +0 -712
  317. package/caps/cli.json +0 -360
  318. package/caps/library.json +0 -3611
  319. package/caps/mcp.json +0 -126
  320. package/caps/pagebridge.json +0 -377
  321. package/caps/popup.json +0 -51
  322. package/caps/sidepanel.json +0 -522
  323. package/checksums.txt +0 -142
  324. package/cli.d.ts.map +0 -1
  325. package/companion.d.ts.map +0 -1
  326. package/companion.js +0 -389
  327. package/crossbrowser.d.ts.map +0 -1
  328. package/crossbrowser.js +0 -1191
  329. package/dashdone.d.ts.map +0 -1
  330. package/dashdone.js +0 -368
  331. package/deno.d.ts.map +0 -1
  332. package/deno.js +0 -52325
  333. package/devthink.umd.js +0 -52346
  334. package/fixtures/bun.ts +0 -7
  335. package/fixtures/cjs.js +0 -6
  336. package/fixtures/deno.ts +0 -6
  337. package/fixtures/example-org-pagestate.json +0 -22
  338. package/fixtures/importers/automa-workflow.json +0 -18
  339. package/fixtures/importers/selenium-side.json +0 -20
  340. package/fixtures/importers/tabular-plan.csv +0 -7
  341. package/fixtures/importers/uivision-macro.json +0 -10
  342. package/fixtures/importers/v1-plan.json +0 -13
  343. package/fixtures/mcp-client.mjs +0 -14
  344. package/fixtures/pages/checkout.html +0 -192
  345. package/fixtures/pages/dashboard.html +0 -110
  346. package/fixtures/pages/feed.html +0 -113
  347. package/fixtures/pages/product-grid.html +0 -150
  348. package/fixtures/plans/form-inventory-plan.json +0 -11
  349. package/fixtures/plans/release-notes-plan.json +0 -11
  350. package/fixtures/recipes/agentscompete.json +0 -11
  351. package/fixtures/recipes/agentsforms.json +0 -11
  352. package/fixtures/recipes/agentsmonitor.json +0 -11
  353. package/fixtures/recipes/agentsreview.json +0 -29
  354. package/fixtures/recipes/agentswarm.json +0 -11
  355. package/fixtures/recipes/fillcalendar.json +0 -12
  356. package/fixtures/recipes/fillcheckout.json +0 -13
  357. package/fixtures/recipes/filldependentdropdowns.json +0 -12
  358. package/fixtures/recipes/fillfromcsv.json +0 -13
  359. package/fixtures/recipes/fillloginform.json +0 -12
  360. package/fixtures/recipes/fillmultistepwizard.json +0 -13
  361. package/fixtures/recipes/fillregistration.json +0 -12
  362. package/fixtures/recipes/fillsearchfilters.json +0 -12
  363. package/fixtures/recipes/filltypeahead.json +0 -11
  364. package/fixtures/recipes/monitoralerts.json +0 -12
  365. package/fixtures/recipes/monitoravailability.json +0 -11
  366. package/fixtures/recipes/monitornetworkquiet.json +0 -12
  367. package/fixtures/recipes/monitorpagechanges.json +0 -11
  368. package/fixtures/recipes/monitorpricechange.json +0 -11
  369. package/fixtures/recipes/monitorschedule.json +0 -12
  370. package/fixtures/recipes/scrapedynamicprices.json +0 -13
  371. package/fixtures/recipes/scrapeimagegallery.json +0 -12
  372. package/fixtures/recipes/scrapeinfinitefeed.json +0 -13
  373. package/fixtures/recipes/scrapenestedlists.json +0 -12
  374. package/fixtures/recipes/scrapeproductgrid.json +0 -12
  375. package/fixtures/recipes/scrapereviews.json +0 -12
  376. package/fixtures/recipes/scrapesearchresults.json +0 -12
  377. package/fixtures/recipes/scrapetablepagination.json +0 -12
  378. package/fixtures/recipes/testaccessibility.json +0 -34
  379. package/fixtures/recipes/testconsoleerrors.json +0 -12
  380. package/fixtures/recipes/testdeeplinks.json +0 -12
  381. package/fixtures/recipes/testformvalidation.json +0 -13
  382. package/fixtures/recipes/testlayoutshift.json +0 -12
  383. package/fixtures/recipes/testlinkcheck.json +0 -12
  384. package/fixtures/recipes/testloadtiming.json +0 -12
  385. package/fixtures/recipes/testvisualdiff.json +0 -12
  386. package/fixtures/workflows/changelog-digest-workflow.json +0 -13
  387. package/fixtures/workflows/form-survey-workflow.json +0 -13
  388. package/gallery.json +0 -706
  389. package/hardening.d.ts.map +0 -1
  390. package/hardening.js +0 -188
  391. package/headless.d.ts.map +0 -1
  392. package/headless.js +0 -11522
  393. package/http.d.ts.map +0 -1
  394. package/http.js +0 -2165
  395. package/index.cjs +0 -54682
  396. package/index.d.ts.map +0 -1
  397. package/index.js +0 -52310
  398. package/index.neutral.js +0 -46343
  399. package/mcp.d.ts.map +0 -1
  400. package/mcp.js +0 -20515
  401. package/memory.d.ts.map +0 -1
  402. package/nativehost.template.json +0 -10
  403. package/node.cjs +0 -54686
  404. package/node.d.ts.map +0 -1
  405. package/pack.d.ts.map +0 -1
  406. package/pack.js +0 -805
  407. package/policy.d.ts.map +0 -1
  408. package/progress.d.ts.map +0 -1
  409. package/progress.js +0 -645
  410. package/protocol.d.ts.map +0 -1
  411. package/protocol.js +0 -8334
  412. package/schemas/audit.schema.json +0 -226
  413. package/schemas/capability.schema.json +0 -113
  414. package/schemas/envelope.schema.json +0 -162
  415. package/schemas/memory.schema.json +0 -51
  416. package/schemas/observation.schema.json +0 -113
  417. package/schemas/plan.schema.json +0 -208
  418. package/schemas/progress.schema.json +0 -126
  419. package/schemas/proposal.schema.json +0 -68
  420. package/schemas/session.schema.json +0 -82
  421. package/schemas/tool.schema.json +0 -94
  422. package/server.d.ts.map +0 -1
  423. package/server.js +0 -28877
  424. package/umd-example.html +0 -18
  425. package/umd.d.ts.map +0 -1
  426. /package/{bun.d.ts → dist/bun.d.ts} +0 -0
  427. /package/{cli.d.ts → dist/cli.d.ts} +0 -0
  428. /package/{companion.d.ts → dist/companion.d.ts} +0 -0
  429. /package/{crossbrowser.d.ts → dist/crossbrowser.d.ts} +0 -0
  430. /package/{dashdone.d.ts → dist/dashdone.d.ts} +0 -0
  431. /package/{deno.d.ts → dist/deno.d.ts} +0 -0
  432. /package/{hardening.d.ts → dist/hardening.d.ts} +0 -0
  433. /package/{headless.d.ts → dist/headless.d.ts} +0 -0
  434. /package/{http.d.ts → dist/http.d.ts} +0 -0
  435. /package/{index.d.ts → dist/index.d.ts} +0 -0
  436. /package/{mcp.d.ts → dist/mcp.d.ts} +0 -0
  437. /package/{memory.d.ts → dist/memory.d.ts} +0 -0
  438. /package/{node.d.ts → dist/node.d.ts} +0 -0
  439. /package/{pack.d.ts → dist/pack.d.ts} +0 -0
  440. /package/{policy.d.ts → dist/policy.d.ts} +0 -0
  441. /package/{progress.d.ts → dist/progress.d.ts} +0 -0
  442. /package/{protocol.d.ts → dist/protocol.d.ts} +0 -0
  443. /package/{server.d.ts → dist/server.d.ts} +0 -0
  444. /package/{umd.d.ts → dist/umd.d.ts} +0 -0
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "devthink site",
3
+ "shortname": "devthink site",
4
+ "servercontract": 1,
5
+ "capabilities": {
6
+ "version": 1,
7
+ "operations": ["sessioncreate", "sessionjoin", "eventpost", "eventstream"],
8
+ "events": ["chat", "planproposal", "planreview", "progress"],
9
+ "heartbeat": true,
10
+ "multiplex": true
11
+ },
12
+ "staticonly": true
13
+ }
@@ -0,0 +1,622 @@
1
+ /** Design: DevThink v1.1.15 — Ink shares the compact local workspace grammar, identity, destinations, settings and command language with the paired React workbench. The plain ANSI renderer family of the former root ui.ts lives here too: the web design room owns the whole terminal design (CLI, pages, TV, Android), so the pure string renderers coexist with the Ink components. */
2
+ import { Box, Text, render, useApp, useInput, useWindowSize } from "ink";
3
+ import { useMemo, useState } from "react";
4
+ import { listProviders } from "../providers.js";
5
+ import { listSessions, type Session } from "../workbenchsession.js";
6
+ import { readPreferences, savePreference } from "../storage.js";
7
+ import { getIdentity, pairingStatus } from "../identity.js";
8
+ import type { DevThinkConfig, DevThinkPaths } from "../config.js";
9
+ import type { ChatEvent } from "../streaming.js";
10
+ import {
11
+ isWorkspaceDestination,
12
+ workspaceDestination,
13
+ workspaceDestinations,
14
+ type WorkspaceDestination,
15
+ } from "./workspace.js";
16
+
17
+ const categories = ["features", "bugs", "refactor", "snippets", "tasks", "notes", "all"] as const;
18
+ const categoryGlyphs: Record<(typeof categories)[number], string> = {
19
+ features: "ϟ",
20
+ bugs: "⊗",
21
+ refactor: "◌",
22
+ snippets: "◫",
23
+ tasks: "☑",
24
+ notes: "□",
25
+ all: "◉",
26
+ };
27
+ const entryArt = [
28
+ " +++++",
29
+ " +++++++++++++++",
30
+ " ++++++ ++++++",
31
+ " +++++ +++++",
32
+ " +++++ +++++",
33
+ " +++++ +++++",
34
+ " ++++++ ++++++",
35
+ " +++++++++++++",
36
+ " +++++",
37
+ ];
38
+
39
+ type Runtime = { config: DevThinkConfig; paths: DevThinkPaths };
40
+ type Execute = (
41
+ prompt: string,
42
+ current: Session | undefined,
43
+ onEvent: (event: ChatEvent) => void,
44
+ ) => Promise<Session | undefined>;
45
+ type Entry = { role: "user" | "devthink" | "error"; title: string; body: string };
46
+
47
+ function clip(value: string, width: number): string {
48
+ return value.length > width ? `${value.slice(0, Math.max(0, width - 1))}…` : value;
49
+ }
50
+
51
+ function TerminalEntry({
52
+ draft,
53
+ provider,
54
+ userId,
55
+ width,
56
+ }: {
57
+ draft: string;
58
+ provider: string;
59
+ userId: string;
60
+ width: number;
61
+ }) {
62
+ return (
63
+ <Box
64
+ flexDirection="column"
65
+ width="100%"
66
+ height="100%"
67
+ minHeight={20}
68
+ paddingX={2}
69
+ paddingY={1}
70
+ justifyContent="space-between"
71
+ >
72
+ <Box justifyContent="space-between">
73
+ <Text color="#807970">✦ DEVTHINK</Text>
74
+ <Text color="#8ab4f8">{userId}</Text>
75
+ </Box>
76
+ <Box flexDirection="column" alignItems="center">
77
+ <Box flexDirection="column" alignItems="center">
78
+ {entryArt.map((line, index) => (
79
+ <Text key={`${line}-${index}`} color="#382c23">
80
+ {line}
81
+ </Text>
82
+ ))}
83
+ </Box>
84
+ <Box marginTop={-1}>
85
+ <Text color="#ff5f00" bold>
86
+ DEVTHINK
87
+ </Text>
88
+ </Box>
89
+ <Box marginTop={1}>
90
+ <Text color="#746f66">begin with one intention</Text>
91
+ </Box>
92
+ <Box marginTop={2}>
93
+ <Text color="#282724">┌{"─".repeat(Math.max(16, width - 2))}┐</Text>
94
+ </Box>
95
+ <Text color="#e8e1d7">
96
+ │ <Text color="#ff5f00">›_ </Text>
97
+ <Text color={draft ? "#e8e1d7" : "#746f66"}>{clip(draft || "Ask anything…", Math.max(12, width - 22))}</Text>
98
+ <Text color="#8ab4f8"> {provider}</Text>
99
+ <Text color="#ff5f00"> [open]</Text> │
100
+ </Text>
101
+ <Text color="#282724">└{"─".repeat(Math.max(16, width - 2))}┘</Text>
102
+ </Box>
103
+ <Text color="#746f66">enter opens a local workspace · esc exits</Text>
104
+ </Box>
105
+ );
106
+ }
107
+
108
+ function projectRows(runtime: Runtime): string[] {
109
+ const projects = new Map<string, { title: string; sessions: number }>();
110
+ for (const session of listSessions(runtime.paths))
111
+ projects.set(session.workspaceId, {
112
+ title: session.title,
113
+ sessions: (projects.get(session.workspaceId)?.sessions || 0) + 1,
114
+ });
115
+ return [...projects.entries()].map(
116
+ ([id, project]) =>
117
+ `${id} ${project.sessions} ${project.sessions === 1 ? "session" : "sessions"} ${project.title}`,
118
+ );
119
+ }
120
+
121
+ function viewRows(view: WorkspaceDestination, runtime: Runtime, session: Session | undefined): string[] {
122
+ if (view === "history")
123
+ return listSessions(runtime.paths).map((item) => `${item.id} ${item.title}`) || ["No saved sessions."];
124
+ if (view === "projects") return projectRows(runtime) || ["No local projects."];
125
+ if (view === "providers")
126
+ return listProviders().map((provider) => `${provider.id} ${provider.protocol} ${provider.env}`);
127
+ if (view === "usage") {
128
+ const sessions = listSessions(runtime.paths);
129
+ return [
130
+ `workspaces ${new Set(sessions.map((item) => item.workspaceId)).size}`,
131
+ `sessions ${sessions.length}`,
132
+ `tabs ${sessions.reduce((total, item) => total + item.tabs.length, 0)}`,
133
+ `messages ${sessions.reduce((total, item) => total + item.messages.length, 0)}`,
134
+ ];
135
+ }
136
+ if (view === "routes")
137
+ return [
138
+ "GET /health",
139
+ "GET /providers",
140
+ "GET /workspaces",
141
+ "GET /usage",
142
+ "GET /preferences",
143
+ "PATCH /preferences",
144
+ "POST /sessions",
145
+ "POST /chat",
146
+ ];
147
+ if (view === "settings") {
148
+ const preferences = Object.fromEntries(
149
+ Object.entries(readPreferences(runtime.paths)).map(([key, preference]) => [key, preference.value]),
150
+ );
151
+ const identity = getIdentity(runtime.paths);
152
+ const pairing = pairingStatus(runtime.paths);
153
+ const sessions = listSessions(runtime.paths);
154
+ return [
155
+ `userId ${identity.userId}`,
156
+ `deviceId ${identity.deviceId}`,
157
+ `browser sessions ${pairing.activeSessions}`,
158
+ `database cli-owned-sqlite · ${new Set(sessions.map((session) => session.workspaceId)).size} workspaces · ${sessions.length} sessions`,
159
+ `provider ${runtime.config.activeProvider || runtime.config.provider || "not configured"}`,
160
+ `theme ${preferences.theme || "dark"}`,
161
+ `railMode ${preferences.railMode || "auto"}`,
162
+ `interfaceZoom ${preferences.interfaceZoom || "100"}`,
163
+ "inspect all: devthink config settings",
164
+ "edit identity: devthink identity --id <10-15 lowercase chars>",
165
+ "edit: /settings set theme dark|light",
166
+ "edit: /settings set railMode always|auto|off",
167
+ "edit: /settings set interfaceZoom 80-150",
168
+ "credentials remain in ~/.config/devthink/auth.json",
169
+ ];
170
+ }
171
+ if (session) return [`workspace ${session.workspaceId}`, `session ${session.id}`, `tab ${session.activeTabId}`];
172
+ return ["Describe the next feature, bug or piece of work below."];
173
+ }
174
+
175
+ function TerminalWorkspace({ runtime, execute, version }: { runtime: Runtime; execute: Execute; version: string }) {
176
+ const { exit } = useApp();
177
+ const { columns } = useWindowSize();
178
+ const [entered, setEntered] = useState(false);
179
+ const [active, setActive] = useState<(typeof categories)[number]>("all");
180
+ const [view, setView] = useState<WorkspaceDestination>("chat");
181
+ const [entries, setEntries] = useState<Entry[]>([]);
182
+ const [session, setSession] = useState<Session>();
183
+ const [draft, setDraft] = useState("");
184
+ const [pending, setPending] = useState(false);
185
+ const [preferenceRevision, setPreferenceRevision] = useState(0);
186
+ const provider = runtime.config.activeProvider || runtime.config.provider || "local";
187
+ const identity = getIdentity(runtime.paths);
188
+ const model = runtime.config.activeModel || runtime.config.model || "not configured";
189
+ const compact = columns < 100;
190
+ const terminalWidth = Math.max(38, columns - (compact ? 2 : 6));
191
+ const tabLine = compact
192
+ ? workspaceDestinations.map((destination) => destination.glyph).join(" ")
193
+ : workspaceDestinations
194
+ .map(
195
+ (destination) =>
196
+ `${destination.id === view ? "[" : ""}${destination.glyph} ${destination.label}${destination.id === view ? "]" : ""}`,
197
+ )
198
+ .join(" ");
199
+ const rows = useMemo(() => viewRows(view, runtime, session), [preferenceRevision, runtime, session, view]);
200
+
201
+ function openView(next: WorkspaceDestination) {
202
+ setView(next);
203
+ setEntries((current) => [
204
+ ...current,
205
+ { role: "devthink", title: workspaceDestination(next).label, body: rows.join(" · ") },
206
+ ]);
207
+ }
208
+
209
+ async function submit() {
210
+ const prompt = draft.trim();
211
+ if (!prompt || pending) return;
212
+ if (prompt === "/exit" || prompt === "/quit") return exit();
213
+ if (!entered) {
214
+ setEntries([
215
+ { role: "user", title: "first intention", body: prompt },
216
+ {
217
+ role: "devthink",
218
+ title: "local workspace ready",
219
+ body: "The first command opened a local DevThink session. Use arrows or /view to inspect local history, projects, providers, usage, routes and settings.",
220
+ },
221
+ ]);
222
+ setDraft("");
223
+ setEntered(true);
224
+ return;
225
+ }
226
+ if (prompt === "/clear") {
227
+ setEntries([]);
228
+ setDraft("");
229
+ return;
230
+ }
231
+ if (prompt === "/new") {
232
+ setSession(undefined);
233
+ setEntries([]);
234
+ setView("chat");
235
+ setDraft("");
236
+ return;
237
+ }
238
+ const viewMatch = prompt.match(/^\/(?:view\s+)?(chat|history|projects|providers|usage|routes|settings)$/);
239
+ if (viewMatch && isWorkspaceDestination(viewMatch[1])) {
240
+ openView(viewMatch[1]);
241
+ setDraft("");
242
+ return;
243
+ }
244
+ if (prompt === "/sessions") {
245
+ openView("history");
246
+ setDraft("");
247
+ return;
248
+ }
249
+ const preferenceMatch = prompt.match(/^\/(?:settings\s+)?set\s+(theme|railMode|interfaceZoom)\s+(\S+)$/);
250
+ if (preferenceMatch) {
251
+ const [, key, value] = preferenceMatch;
252
+ const valid =
253
+ (key === "theme" && ["dark", "light"].includes(value)) ||
254
+ (key === "railMode" && ["always", "auto", "off"].includes(value)) ||
255
+ (key === "interfaceZoom" && /^(?:[8-9][0-9]|1[0-4][0-9]|150)$/.test(value));
256
+ if (!valid)
257
+ setEntries((current) => [
258
+ ...current,
259
+ {
260
+ role: "error",
261
+ title: "settings",
262
+ body: "Use theme dark|light, railMode always|auto|off, or interfaceZoom 80-150.",
263
+ },
264
+ ]);
265
+ else {
266
+ savePreference(runtime.paths, key, value);
267
+ setPreferenceRevision((current) => current + 1);
268
+ setView("settings");
269
+ setEntries((current) => [
270
+ ...current,
271
+ { role: "devthink", title: "settings", body: `${key} updated to ${value}.` },
272
+ ]);
273
+ }
274
+ setDraft("");
275
+ return;
276
+ }
277
+ if (prompt === "/help") {
278
+ setEntries((current) => [
279
+ ...current,
280
+ {
281
+ role: "devthink",
282
+ title: "command reference",
283
+ body: "/view chat|history|projects|providers|usage|routes|settings /settings set <key> <value> /new /clear /models /modes /sessions /exit",
284
+ },
285
+ ]);
286
+ setDraft("");
287
+ return;
288
+ }
289
+ setView("chat");
290
+ setEntries((current) => [
291
+ ...current,
292
+ { role: "user", title: active, body: prompt },
293
+ { role: "devthink", title: "connecting", body: "waiting for local gateway events…" },
294
+ ]);
295
+ setDraft("");
296
+ setPending(true);
297
+ let partial = "";
298
+ try {
299
+ const next = await execute(prompt, session, (event) => {
300
+ if (event.type === "text" || event.type === "reasoning") partial += event.text;
301
+ setEntries((current) =>
302
+ current.map((entry, index) =>
303
+ index === current.length - 1
304
+ ? {
305
+ ...entry,
306
+ title: event.type === "start" ? "streaming" : "devthink",
307
+ body: partial || "connecting to local provider…",
308
+ }
309
+ : entry,
310
+ ),
311
+ );
312
+ });
313
+ setSession(next);
314
+ setEntries((current) =>
315
+ current.map((entry, index) =>
316
+ index === current.length - 1
317
+ ? {
318
+ ...entry,
319
+ title: "devthink",
320
+ body: partial || next?.messages.at(-1)?.content || "the provider returned no text.",
321
+ }
322
+ : entry,
323
+ ),
324
+ );
325
+ } catch (error) {
326
+ setEntries((current) =>
327
+ current.map((entry, index) =>
328
+ index === current.length - 1
329
+ ? {
330
+ role: "error",
331
+ title: "gateway error",
332
+ body: error instanceof Error ? error.message : "request could not be completed",
333
+ }
334
+ : entry,
335
+ ),
336
+ );
337
+ } finally {
338
+ setPending(false);
339
+ }
340
+ }
341
+
342
+ useInput((input, key) => {
343
+ if (key.escape || (key.ctrl && input === "c")) return exit();
344
+ if (key.return) return void submit();
345
+ if (key.backspace || key.delete) return setDraft((current) => current.slice(0, -1));
346
+ if (entered && key.upArrow)
347
+ return setView(
348
+ (current) =>
349
+ workspaceDestinations[
350
+ (workspaceDestinations.findIndex((item) => item.id === current) + workspaceDestinations.length - 1) %
351
+ workspaceDestinations.length
352
+ ].id,
353
+ );
354
+ if (entered && key.downArrow)
355
+ return setView(
356
+ (current) =>
357
+ workspaceDestinations[
358
+ (workspaceDestinations.findIndex((item) => item.id === current) + 1) % workspaceDestinations.length
359
+ ].id,
360
+ );
361
+ if (entered && view === "chat" && (key.leftArrow || key.tab))
362
+ return setActive(
363
+ (current) => categories[(categories.indexOf(current) + categories.length - 1) % categories.length],
364
+ );
365
+ if (entered && view === "chat" && key.rightArrow)
366
+ return setActive((current) => categories[(categories.indexOf(current) + 1) % categories.length]);
367
+ if (!key.ctrl && !key.meta && input) setDraft((current) => current + input);
368
+ });
369
+
370
+ if (!entered)
371
+ return <TerminalEntry draft={draft} provider={provider} userId={identity.userId} width={terminalWidth} />;
372
+
373
+ return (
374
+ <Box flexDirection="column" width="100%" height="100%" minHeight={20} paddingX={compact ? 1 : 3} paddingY={1}>
375
+ <Box justifyContent="space-between">
376
+ <Text color="#a19a91">
377
+ ✦ DEVTHINK <Text color="#746f66">/ {workspaceDestination(view).label}</Text>
378
+ </Text>
379
+ <Text color="#8ab4f8">◉ {identity.userId}</Text>
380
+ </Box>
381
+ <Text color="#282724">{"─".repeat(terminalWidth)}</Text>
382
+ <Text color="#746f66">{clip(tabLine, terminalWidth)}</Text>
383
+ <Box marginTop={1}>
384
+ <Text color="#e8e1d7">
385
+ {workspaceDestination(view).glyph} {workspaceDestination(view).label.toUpperCase()}{" "}
386
+ <Text color="#746f66">· {clip(model, 22)}</Text>
387
+ </Text>
388
+ </Box>
389
+ <Box
390
+ flexDirection="column"
391
+ flexGrow={1}
392
+ justifyContent={entries.length && view === "chat" ? "flex-start" : "center"}
393
+ alignItems={entries.length && view === "chat" ? "flex-start" : "center"}
394
+ >
395
+ {view !== "chat" ? (
396
+ rows.map((row, index) => (
397
+ <Text key={`${view}-${index}`} color={index === 0 ? "#e8e1d7" : "#746f66"}>
398
+ ▎ {clip(row, terminalWidth - 4)}
399
+ </Text>
400
+ ))
401
+ ) : entries.length ? (
402
+ entries.map((entry, index) => (
403
+ <Box key={`${entry.title}-${index}`} flexDirection="column" marginBottom={1} paddingLeft={1}>
404
+ <Box>
405
+ <Text color={entry.role === "user" ? "#ff5f00" : entry.role === "error" ? "#ff6b6b" : "#8ab4f8"}>
406
+ ▎ {entry.title.toUpperCase()}
407
+ </Text>
408
+ </Box>
409
+ <Box>
410
+ <Text color="#e8e1d7">{clip(entry.body, terminalWidth - 6)}</Text>
411
+ </Box>
412
+ </Box>
413
+ ))
414
+ ) : (
415
+ <Box flexDirection="column" alignItems="center">
416
+ <Text color="#4b4842">Nothing is active yet.</Text>
417
+ <Text color="#625c54">Describe the next feature, bug or piece of work below.</Text>
418
+ <Box marginTop={2}>
419
+ <Text color="#ff5f00" bold>
420
+ DEVTHINK
421
+ </Text>
422
+ </Box>
423
+ </Box>
424
+ )}
425
+ </Box>
426
+ <Text color="#282724">┌{"─".repeat(Math.max(16, terminalWidth - 2))}┐</Text>
427
+ <Text color="#e8e1d7">
428
+ │ <Text color="#ff5f00">›_ </Text>
429
+ <Text color={draft ? "#e8e1d7" : "#746f66"}>
430
+ {clip(
431
+ draft ||
432
+ (view === "chat"
433
+ ? `Ask DevThink about ${active === "all" ? "the work…" : `${active}…`}`
434
+ : `Type /view chat to return…`),
435
+ Math.max(12, terminalWidth - 20),
436
+ )}
437
+ </Text>
438
+ <Text color="#8ab4f8"> {provider}</Text>
439
+ <Text color="#ff5f00"> [run]</Text> │
440
+ </Text>
441
+ <Text color="#282724">└{"─".repeat(Math.max(16, terminalWidth - 2))}┘</Text>
442
+ <Text color="#746f66">↑↓ destinations · ←→ categories · /help commands · v{version} · esc exit</Text>
443
+ </Box>
444
+ );
445
+ }
446
+
447
+ export async function startTerminalWorkspace(runtime: Runtime, version: string, execute: Execute): Promise<void> {
448
+ const app = render(<TerminalWorkspace runtime={runtime} version={version} execute={execute} />, {
449
+ exitOnCtrlC: false,
450
+ });
451
+ await app.waitUntilExit();
452
+ }
453
+
454
+ /* ── The plain ANSI renderer family (the ui.ts merge): pure string renderers of
455
+ the DevThink Orbital Signal Room — canonical ANSI art, orange intent and
456
+ orb-blue connection states. The CLI imports these for the non-Ink fallback
457
+ path and every plain-text surface; they are pure functions returning
458
+ strings, so they coexist with the Ink components above. ── */
459
+
460
+ /** The ANSI palette: background, panel, text and muted chrome with orange intent, orb-blue connections and signal accents. */
461
+ export const colors = {
462
+ background: "\u001b[38;5;234m",
463
+ panel: "\u001b[38;5;240m",
464
+ text: "\u001b[38;5;255m",
465
+ muted: "\u001b[38;5;246m",
466
+ cyan: "\u001b[38;5;117m",
467
+ blue: "\u001b[38;5;111m",
468
+ orange: "\u001b[38;5;208m",
469
+ orangeSoft: "\u001b[38;5;215m",
470
+ green: "\u001b[38;5;151m",
471
+ red: "\u001b[38;5;167m",
472
+ yellow: "\u001b[38;5;222m",
473
+ reset: "\u001b[0m",
474
+ bold: "\u001b[1m",
475
+ };
476
+
477
+ /** The spinner frames of the streaming indicator. */
478
+ const frames = ["◐", "◓", "◑", "◒"];
479
+
480
+ /** Canonical DevThink ANSI mark transcribed from docs/logo.md. */
481
+ const canonicalMark = String.raw`
482
+ +
483
+ +
484
+ -+
485
+ ++
486
+ ++
487
+ +++
488
+ -++
489
+ ++++-
490
+ +++++++++
491
+ +-+++++++++++++--
492
+ --++++++-++++++++++++
493
+ +++++++-+ +++ +--++++++-
494
+ -+++++++-+ +++ -+-+++++++
495
+ ++++++++++ -+++ --++++++++
496
+ ++++++++-+ ++++ --+++++++-
497
+ ++++++++++ ++++ --+++++++-
498
+ ---++++++- -++++ -++++++--+
499
+ ++++++++-- +++++ ++++++++--
500
+ +--+++++-- +++++ +++++++---
501
+ ++++++++- +++++ +-+++++++
502
+ -+++++-- -++++- --++++++
503
+ -+++++- +++++- +++++++
504
+ -++++++ -+++++- +++++++
505
+ -++++++ ++++++- +++++++
506
+ -++++++ +++++++- +++++++
507
+ -++++++ ++++++++- +++++++
508
+ -++++++ -++++++++++-+ +++++++
509
+ -++++++ -+++++++++++++- +++++++
510
+ -++++++ -+++++++++++++++++ +++++++
511
+ -++++++ -++++++++++++++++++++ +++++++
512
+ -++++++ -++++++++++++++++++++++ +++++++
513
+ -++++++ -+++++++++++++++++++++++++- +++++++
514
+ -++++++ +++++++++++++++++-+--++++++++-- +++++++
515
+ -++++++ +++++++++++++++ -+++++++++ +++++++
516
+ -++++++ +++++++++++-++ --++++++-+ +-++++
517
+ -++++++ ++++++++++-+- +--++++-- --++
518
+ -++++++ +--++++++++++ ---++++-+ +-
519
+ -++++++ ++-++++++++ ++++++++
520
+ -++++++ -+++++-+-- +-++-+
521
+ -+++++++++++++ -++++++
522
+ -++++++++ +-+++-+++-
523
+ -+++++++++++ ++++++++- +---+
524
+ --++-+ -++++++-++ ---++++++++ +-++
525
+ ++++ +-+++++++ -++++++-+ ++
526
+ -++ ++++++++-- ++++++++-+- +
527
+ +-++++++-+ --++++++++
528
+ ++-+++++++ ++++++++-+
529
+ +-++++++++ --++++++++
530
+ ++++++++++ +--++++++-
531
+ --+++++++- ++++++++++
532
+ -+++++++-++++--++++++++
533
+ +-++++++++++++++-
534
+ --++++++++-
535
+ +-++-`;
536
+
537
+ /** Wraps a value in an ANSI color code and resets after it. */
538
+ function color(code: string, value: string): string {
539
+ return `${code}${value}${colors.reset}`;
540
+ }
541
+
542
+ /** Strips ANSI escape sequences so visible width can be measured. */
543
+ function stripAnsi(value: string): string {
544
+ return value.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "");
545
+ }
546
+
547
+ /** Pads a possibly-colored value to a visible width. */
548
+ function pad(value: string, width: number): string {
549
+ const visible = stripAnsi(value);
550
+ return visible.length >= width
551
+ ? value.slice(0, Math.max(0, width - Math.max(0, visible.length - width)))
552
+ : value + " ".repeat(width - visible.length);
553
+ }
554
+
555
+ /** Renders the banner: the canonical ANSI mark, the version and the local workspace signal line. */
556
+ export function banner(version: string): string {
557
+ const mark = canonicalMark
558
+ .split("\n")
559
+ .map((line) => color(colors.orange, line))
560
+ .join("\n");
561
+ const title = color(colors.orange + colors.bold, "DEVTHINK");
562
+ const signal = color(colors.blue, "◆ local workspace");
563
+ return [
564
+ color(colors.muted, "╭────────────────────────────── signal workspace ──────────────────────────────╮"),
565
+ mark,
566
+ ` ${title} ${color(colors.muted, `v${version}`)} ${signal}`,
567
+ ` ${color(colors.muted, "route the work · keep provider credentials local · ctrl+p commands")}`,
568
+ color(colors.muted, "╰────────────────────────────────────────────────────────────────────────────────╯"),
569
+ ].join("\n");
570
+ }
571
+
572
+ /** Renders a titled content box with the workspace panel chrome. */
573
+ export function box(title: string, content: string, width = 76): string {
574
+ const innerWidth = Math.max(20, width - 4);
575
+ const top = `╭─ ${color(colors.orange, title)} ${color(colors.muted, "─".repeat(Math.max(0, innerWidth - title.length - 3)))}╮`;
576
+ const body = content.split("\n").map((line) => `│ ${color(colors.blue, "▎")} ${pad(line, innerWidth - 2)} │`);
577
+ return [top, ...body, `╰${"─".repeat(width - 2)}╯`].join("\n");
578
+ }
579
+
580
+ /** Renders the one-line provider/model/mode status bar. */
581
+ export function statusBar(provider: string, model: string, mode: string): string {
582
+ const providerLabel = provider || "not configured";
583
+ const modelLabel = model || "not configured";
584
+ return [
585
+ color(colors.orange, "◆"),
586
+ color(colors.text, providerLabel),
587
+ color(colors.muted, "/"),
588
+ color(colors.blue, modelLabel),
589
+ color(colors.muted, "·"),
590
+ color(colors.cyan, mode),
591
+ ].join(" ");
592
+ }
593
+
594
+ /** Renders one streaming spinner frame with its message. */
595
+ export function spinnerFrame(index: number, message: string): string {
596
+ return `${color(colors.blue, frames[index % frames.length])} ${color(colors.muted, "stream")} ${message}`;
597
+ }
598
+
599
+ /** Renders one chat event as plain ANSI text. */
600
+ export function formatEvent(event: ChatEvent): string {
601
+ if (event.type === "text") return event.text;
602
+ if (event.type === "reasoning") return `${color(colors.blue, "·")} ${color(colors.muted, event.text)}`;
603
+ if (event.type === "error") return color(colors.red, event.message);
604
+ if (event.type === "finish") return "";
605
+ if (event.type === "start")
606
+ return `${color(colors.blue, "◆")} ${color(colors.text, "connected")} ${color(colors.muted, `${event.provider} / ${event.model}`)}`;
607
+ return `${color(colors.orange, "→")} ${color(colors.blue, event.name)} ${color(colors.muted, JSON.stringify(event.input))}`;
608
+ }
609
+
610
+ /** Filters a suggestion list down to the values matching the input, capped at a limit. */
611
+ export function suggestions(input: string, values: string[], limit = 8): string[] {
612
+ const query = input.trim().toLowerCase();
613
+ if (!query) return values.slice(0, limit);
614
+ return values.filter((value) => value.toLowerCase().includes(query)).slice(0, limit);
615
+ }
616
+
617
+ /** Renders a config record as aligned key/value lines. */
618
+ export function formatConfig(config: Record<string, unknown>): string {
619
+ return Object.entries(config)
620
+ .map(([key, value]) => `${color(colors.muted, pad(key, 20))} ${String(value)}`)
621
+ .join("\n");
622
+ }