deepclause-sdk 0.0.89 → 0.1.1

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 (329) hide show
  1. package/README.md +481 -941
  2. package/dist/agent.d.ts +19 -2
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +75 -21
  5. package/dist/agent.js.map +1 -1
  6. package/dist/cli/config.d.ts +385 -178
  7. package/dist/cli/config.d.ts.map +1 -1
  8. package/dist/cli/config.js +161 -188
  9. package/dist/cli/config.js.map +1 -1
  10. package/dist/cli/index.d.ts +3 -2
  11. package/dist/cli/index.d.ts.map +1 -1
  12. package/dist/cli/index.js +85 -411
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/cli/login.d.ts +29 -0
  15. package/dist/cli/login.d.ts.map +1 -0
  16. package/dist/cli/login.js +184 -0
  17. package/dist/cli/login.js.map +1 -0
  18. package/dist/cli/readline.d.ts +6 -0
  19. package/dist/cli/readline.d.ts.map +1 -0
  20. package/dist/cli/readline.js +15 -0
  21. package/dist/cli/readline.js.map +1 -0
  22. package/dist/cli/repl-bin.d.ts +9 -0
  23. package/dist/cli/repl-bin.d.ts.map +1 -0
  24. package/dist/cli/repl-bin.js +16 -0
  25. package/dist/cli/repl-bin.js.map +1 -0
  26. package/dist/cli/repl.d.ts +216 -0
  27. package/dist/cli/repl.d.ts.map +1 -0
  28. package/dist/cli/repl.js +1392 -0
  29. package/dist/cli/repl.js.map +1 -0
  30. package/dist/cli/run.d.ts +17 -18
  31. package/dist/cli/run.d.ts.map +1 -1
  32. package/dist/cli/run.js +202 -183
  33. package/dist/cli/run.js.map +1 -1
  34. package/dist/cli/set-backend.d.ts +53 -0
  35. package/dist/cli/set-backend.d.ts.map +1 -0
  36. package/dist/cli/set-backend.js +259 -0
  37. package/dist/cli/set-backend.js.map +1 -0
  38. package/dist/cli/visualize.d.ts +42 -0
  39. package/dist/cli/visualize.d.ts.map +1 -0
  40. package/dist/cli/visualize.js +150 -0
  41. package/dist/cli/visualize.js.map +1 -0
  42. package/dist/compiler.d.ts +5 -52
  43. package/dist/compiler.d.ts.map +1 -1
  44. package/dist/compiler.js +4 -768
  45. package/dist/compiler.js.map +1 -1
  46. package/dist/index.d.ts +27 -2
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +18 -2
  49. package/dist/index.js.map +1 -1
  50. package/dist/prolog/bridge.d.ts +5 -1
  51. package/dist/prolog/bridge.d.ts.map +1 -1
  52. package/dist/prolog/bridge.js +10 -1
  53. package/dist/prolog/bridge.js.map +1 -1
  54. package/dist/prolog-src/deepclause_analysis.pl +10 -2
  55. package/dist/prolog-src/deepclause_mi.pl +2440 -49
  56. package/dist/repl/agent.d.ts +90 -0
  57. package/dist/repl/agent.d.ts.map +1 -0
  58. package/dist/repl/agent.js +410 -0
  59. package/dist/repl/agent.js.map +1 -0
  60. package/dist/repl/auth.d.ts +45 -0
  61. package/dist/repl/auth.d.ts.map +1 -0
  62. package/dist/repl/auth.js +116 -0
  63. package/dist/repl/auth.js.map +1 -0
  64. package/dist/repl/backend.d.ts +321 -0
  65. package/dist/repl/backend.d.ts.map +1 -0
  66. package/dist/repl/backend.js +415 -0
  67. package/dist/repl/backend.js.map +1 -0
  68. package/dist/repl/compaction.d.ts +122 -0
  69. package/dist/repl/compaction.d.ts.map +1 -0
  70. package/dist/repl/compaction.js +318 -0
  71. package/dist/repl/compaction.js.map +1 -0
  72. package/dist/repl/export.d.ts +30 -0
  73. package/dist/repl/export.d.ts.map +1 -0
  74. package/dist/repl/export.js +23 -0
  75. package/dist/repl/export.js.map +1 -0
  76. package/dist/repl/frames.d.ts +21 -0
  77. package/dist/repl/frames.d.ts.map +1 -0
  78. package/dist/repl/frames.js +79 -0
  79. package/dist/repl/frames.js.map +1 -0
  80. package/dist/repl/harness-tools.d.ts +99 -0
  81. package/dist/repl/harness-tools.d.ts.map +1 -0
  82. package/dist/repl/harness-tools.js +192 -0
  83. package/dist/repl/harness-tools.js.map +1 -0
  84. package/dist/repl/highlight.d.ts +30 -0
  85. package/dist/repl/highlight.d.ts.map +1 -0
  86. package/dist/repl/highlight.js +285 -0
  87. package/dist/repl/highlight.js.map +1 -0
  88. package/dist/repl/history.d.ts +234 -0
  89. package/dist/repl/history.d.ts.map +1 -0
  90. package/dist/repl/history.js +661 -0
  91. package/dist/repl/history.js.map +1 -0
  92. package/dist/repl/imagegen.d.ts +29 -0
  93. package/dist/repl/imagegen.d.ts.map +1 -0
  94. package/dist/repl/imagegen.js +84 -0
  95. package/dist/repl/imagegen.js.map +1 -0
  96. package/dist/repl/index.d.ts +130 -0
  97. package/dist/repl/index.d.ts.map +1 -0
  98. package/dist/repl/index.js +112 -0
  99. package/dist/repl/index.js.map +1 -0
  100. package/dist/repl/judge.d.ts +51 -0
  101. package/dist/repl/judge.d.ts.map +1 -0
  102. package/dist/repl/judge.js +58 -0
  103. package/dist/repl/judge.js.map +1 -0
  104. package/dist/repl/markdown.d.ts +25 -0
  105. package/dist/repl/markdown.d.ts.map +1 -0
  106. package/dist/repl/markdown.js +103 -0
  107. package/dist/repl/markdown.js.map +1 -0
  108. package/dist/repl/model-resolver.d.ts +59 -0
  109. package/dist/repl/model-resolver.d.ts.map +1 -0
  110. package/dist/repl/model-resolver.js +156 -0
  111. package/dist/repl/model-resolver.js.map +1 -0
  112. package/dist/repl/picker.d.ts +34 -0
  113. package/dist/repl/picker.d.ts.map +1 -0
  114. package/dist/repl/picker.js +81 -0
  115. package/dist/repl/picker.js.map +1 -0
  116. package/dist/repl/project-context.d.ts +16 -0
  117. package/dist/repl/project-context.d.ts.map +1 -0
  118. package/dist/repl/project-context.js +23 -0
  119. package/dist/repl/project-context.js.map +1 -0
  120. package/dist/repl/render.d.ts +187 -0
  121. package/dist/repl/render.d.ts.map +1 -0
  122. package/dist/repl/render.js +808 -0
  123. package/dist/repl/render.js.map +1 -0
  124. package/dist/repl/session.d.ts +424 -0
  125. package/dist/repl/session.d.ts.map +1 -0
  126. package/dist/repl/session.js +1473 -0
  127. package/dist/repl/session.js.map +1 -0
  128. package/dist/repl/status.d.ts +100 -0
  129. package/dist/repl/status.d.ts.map +1 -0
  130. package/dist/repl/status.js +218 -0
  131. package/dist/repl/status.js.map +1 -0
  132. package/dist/repl/tools.d.ts +53 -0
  133. package/dist/repl/tools.d.ts.map +1 -0
  134. package/dist/repl/tools.js +307 -0
  135. package/dist/repl/tools.js.map +1 -0
  136. package/dist/repl/toolset.d.ts +32 -0
  137. package/dist/repl/toolset.d.ts.map +1 -0
  138. package/dist/repl/toolset.js +82 -0
  139. package/dist/repl/toolset.js.map +1 -0
  140. package/dist/repl/tui/editor.d.ts +17 -0
  141. package/dist/repl/tui/editor.d.ts.map +1 -0
  142. package/dist/repl/tui/editor.js +39 -0
  143. package/dist/repl/tui/editor.js.map +1 -0
  144. package/dist/repl/tui/status-bar.d.ts +26 -0
  145. package/dist/repl/tui/status-bar.d.ts.map +1 -0
  146. package/dist/repl/tui/status-bar.js +47 -0
  147. package/dist/repl/tui/status-bar.js.map +1 -0
  148. package/dist/repl/tui/theme.d.ts +30 -0
  149. package/dist/repl/tui/theme.d.ts.map +1 -0
  150. package/dist/repl/tui/theme.js +81 -0
  151. package/dist/repl/tui/theme.js.map +1 -0
  152. package/dist/repl/tui/transcript.d.ts +41 -0
  153. package/dist/repl/tui/transcript.d.ts.map +1 -0
  154. package/dist/repl/tui/transcript.js +139 -0
  155. package/dist/repl/tui/transcript.js.map +1 -0
  156. package/dist/runner.d.ts +143 -13
  157. package/dist/runner.d.ts.map +1 -1
  158. package/dist/runner.js +532 -290
  159. package/dist/runner.js.map +1 -1
  160. package/dist/sdk.d.ts.map +1 -1
  161. package/dist/sdk.js +0 -32
  162. package/dist/sdk.js.map +1 -1
  163. package/dist/system/assets/docs/AGENT_PROMPT.md +1447 -0
  164. package/dist/system/assets/docs/DML_AUTHORING.md +195 -0
  165. package/dist/system/assets/docs/DML_REFERENCE.md +344 -72
  166. package/dist/system/assets/docs/HARNESS_EXAMPLES.md +172 -0
  167. package/dist/system/assets/docs/HARNESS_PATTERNS.md +562 -0
  168. package/dist/system/assets/docs/META_PROMPT.md +68 -0
  169. package/dist/system/assets/docs/PROLOG_SNIPPETS.md +117 -0
  170. package/dist/system/assets/index.d.ts +9 -17
  171. package/dist/system/assets/index.d.ts.map +1 -1
  172. package/dist/system/assets/index.js +26 -85
  173. package/dist/system/assets/index.js.map +1 -1
  174. package/dist/system/runtime/agentvm-manager.d.ts +9 -2
  175. package/dist/system/runtime/agentvm-manager.d.ts.map +1 -1
  176. package/dist/system/runtime/agentvm-manager.js +70 -44
  177. package/dist/system/runtime/agentvm-manager.js.map +1 -1
  178. package/dist/system/runtime/media.d.ts +23 -0
  179. package/dist/system/runtime/media.d.ts.map +1 -0
  180. package/dist/system/runtime/media.js +55 -0
  181. package/dist/system/runtime/media.js.map +1 -0
  182. package/dist/system/runtime/shell-limits.d.ts +21 -0
  183. package/dist/system/runtime/shell-limits.d.ts.map +1 -0
  184. package/dist/system/runtime/shell-limits.js +39 -0
  185. package/dist/system/runtime/shell-limits.js.map +1 -0
  186. package/dist/system/runtime/shell-manager.d.ts +8 -3
  187. package/dist/system/runtime/shell-manager.d.ts.map +1 -1
  188. package/dist/system/runtime/shell-manager.js +56 -39
  189. package/dist/system/runtime/shell-manager.js.map +1 -1
  190. package/dist/system/runtime/shell-tool-events.js +5 -0
  191. package/dist/system/runtime/shell-tool-events.js.map +1 -1
  192. package/dist/system/runtime/token-usage.d.ts +2 -0
  193. package/dist/system/runtime/token-usage.d.ts.map +1 -1
  194. package/dist/system/runtime/token-usage.js +7 -0
  195. package/dist/system/runtime/token-usage.js.map +1 -1
  196. package/dist/tools.d.ts +3 -21
  197. package/dist/tools.d.ts.map +1 -1
  198. package/dist/tools.js +14 -168
  199. package/dist/tools.js.map +1 -1
  200. package/dist/types.d.ts +136 -119
  201. package/dist/types.d.ts.map +1 -1
  202. package/dist/validate.d.ts +19 -0
  203. package/dist/validate.d.ts.map +1 -0
  204. package/dist/validate.js +338 -0
  205. package/dist/validate.js.map +1 -0
  206. package/dist/visualize/ascii.d.ts +55 -0
  207. package/dist/visualize/ascii.d.ts.map +1 -0
  208. package/dist/visualize/ascii.js +242 -0
  209. package/dist/visualize/ascii.js.map +1 -0
  210. package/dist/visualize/display.d.ts +24 -0
  211. package/dist/visualize/display.d.ts.map +1 -0
  212. package/dist/visualize/display.js +29 -0
  213. package/dist/visualize/display.js.map +1 -0
  214. package/dist/visualize/graphics.d.ts +35 -0
  215. package/dist/visualize/graphics.d.ts.map +1 -0
  216. package/dist/visualize/graphics.js +96 -0
  217. package/dist/visualize/graphics.js.map +1 -0
  218. package/dist/visualize/image.d.ts +38 -0
  219. package/dist/visualize/image.d.ts.map +1 -0
  220. package/dist/visualize/image.js +95 -0
  221. package/dist/visualize/image.js.map +1 -0
  222. package/dist/visualize/index.d.ts +59 -0
  223. package/dist/visualize/index.d.ts.map +1 -0
  224. package/dist/visualize/index.js +198 -0
  225. package/dist/visualize/index.js.map +1 -0
  226. package/dist/visualize/raster.d.ts +23 -0
  227. package/dist/visualize/raster.d.ts.map +1 -0
  228. package/dist/visualize/raster.js +47 -0
  229. package/dist/visualize/raster.js.map +1 -0
  230. package/dist/visualize/svg.d.ts +39 -0
  231. package/dist/visualize/svg.d.ts.map +1 -0
  232. package/dist/visualize/svg.js +148 -0
  233. package/dist/visualize/svg.js.map +1 -0
  234. package/dist/visualize/transcode.d.ts +13 -0
  235. package/dist/visualize/transcode.d.ts.map +1 -0
  236. package/dist/visualize/transcode.js +40 -0
  237. package/dist/visualize/transcode.js.map +1 -0
  238. package/docs/images/repl.png +0 -0
  239. package/package.json +19 -15
  240. package/src/prolog-src/deepclause_analysis.pl +10 -2
  241. package/src/prolog-src/deepclause_mi.pl +2440 -49
  242. package/dist/cli/commands.d.ts +0 -42
  243. package/dist/cli/commands.d.ts.map +0 -1
  244. package/dist/cli/commands.js +0 -246
  245. package/dist/cli/commands.js.map +0 -1
  246. package/dist/cli/compile.d.ts +0 -99
  247. package/dist/cli/compile.d.ts.map +0 -1
  248. package/dist/cli/compile.js +0 -363
  249. package/dist/cli/compile.js.map +0 -1
  250. package/dist/cli/default-skills.d.ts +0 -10
  251. package/dist/cli/default-skills.d.ts.map +0 -1
  252. package/dist/cli/default-skills.js +0 -206
  253. package/dist/cli/default-skills.js.map +0 -1
  254. package/dist/cli/interactive.d.ts +0 -2
  255. package/dist/cli/interactive.d.ts.map +0 -1
  256. package/dist/cli/interactive.js +0 -20
  257. package/dist/cli/interactive.js.map +0 -1
  258. package/dist/cli/mcp.d.ts +0 -56
  259. package/dist/cli/mcp.d.ts.map +0 -1
  260. package/dist/cli/mcp.js +0 -138
  261. package/dist/cli/mcp.js.map +0 -1
  262. package/dist/cli/prompt.d.ts +0 -2
  263. package/dist/cli/prompt.d.ts.map +0 -1
  264. package/dist/cli/prompt.js +0 -2
  265. package/dist/cli/prompt.js.map +0 -1
  266. package/dist/cli/search.d.ts +0 -19
  267. package/dist/cli/search.d.ts.map +0 -1
  268. package/dist/cli/search.js +0 -189
  269. package/dist/cli/search.js.map +0 -1
  270. package/dist/cli/tool-args.d.ts +0 -2
  271. package/dist/cli/tool-args.d.ts.map +0 -1
  272. package/dist/cli/tool-args.js +0 -39
  273. package/dist/cli/tool-args.js.map +0 -1
  274. package/dist/cli/tools.d.ts +0 -36
  275. package/dist/cli/tools.d.ts.map +0 -1
  276. package/dist/cli/tools.js +0 -229
  277. package/dist/cli/tools.js.map +0 -1
  278. package/dist/cli/tui/index.d.ts +0 -22
  279. package/dist/cli/tui/index.d.ts.map +0 -1
  280. package/dist/cli/tui/index.js +0 -29
  281. package/dist/cli/tui/index.js.map +0 -1
  282. package/dist/cli/tui.d.ts +0 -167
  283. package/dist/cli/tui.d.ts.map +0 -1
  284. package/dist/cli/tui.js +0 -5228
  285. package/dist/cli/tui.js.map +0 -1
  286. package/dist/compiler_prompt.d.ts +0 -20
  287. package/dist/compiler_prompt.d.ts.map +0 -1
  288. package/dist/compiler_prompt.js +0 -870
  289. package/dist/compiler_prompt.js.map +0 -1
  290. package/dist/deepclause-cli.skill +0 -0
  291. package/dist/prolog-src/deepclause_mi.pl.bak +0 -570
  292. package/dist/system/assets/docs/CONDUCTOR_PROMPT.md +0 -105
  293. package/dist/system/assets/docs/DML_COMPILER_PROMPT.md +0 -700
  294. package/dist/system/assets/docs/TUI.md +0 -150
  295. package/dist/system/assets/recipes/deepclause-coding-workflow/SKILL.md +0 -100
  296. package/dist/system/assets/skills/conductor.dml +0 -76
  297. package/dist/system/assets/skills/deep-planner.dml +0 -412
  298. package/dist/system/assets/skills/plan.dml +0 -429
  299. package/dist/system/assets/skills/security-planner.dml +0 -337
  300. package/dist/system/assets/skills/skill-creator.dml +0 -187
  301. package/dist/system/runtime/catalog-recipes.d.ts +0 -24
  302. package/dist/system/runtime/catalog-recipes.d.ts.map +0 -1
  303. package/dist/system/runtime/catalog-recipes.js +0 -273
  304. package/dist/system/runtime/catalog-recipes.js.map +0 -1
  305. package/dist/system/runtime/catalog-skills.d.ts +0 -50
  306. package/dist/system/runtime/catalog-skills.d.ts.map +0 -1
  307. package/dist/system/runtime/catalog-skills.js +0 -152
  308. package/dist/system/runtime/catalog-skills.js.map +0 -1
  309. package/dist/system/runtime/conductor.d.ts +0 -105
  310. package/dist/system/runtime/conductor.d.ts.map +0 -1
  311. package/dist/system/runtime/conductor.js +0 -732
  312. package/dist/system/runtime/conductor.js.map +0 -1
  313. package/dist/system/runtime/console-capture.d.ts +0 -9
  314. package/dist/system/runtime/console-capture.d.ts.map +0 -1
  315. package/dist/system/runtime/console-capture.js +0 -67
  316. package/dist/system/runtime/console-capture.js.map +0 -1
  317. package/dist/system/runtime/dml-executor.d.ts +0 -52
  318. package/dist/system/runtime/dml-executor.d.ts.map +0 -1
  319. package/dist/system/runtime/dml-executor.js +0 -203
  320. package/dist/system/runtime/dml-executor.js.map +0 -1
  321. package/dist/system/runtime/runtime-tools.d.ts +0 -31
  322. package/dist/system/runtime/runtime-tools.d.ts.map +0 -1
  323. package/dist/system/runtime/runtime-tools.js +0 -312
  324. package/dist/system/runtime/runtime-tools.js.map +0 -1
  325. package/dist/system/runtime/skill-creator.d.ts +0 -38
  326. package/dist/system/runtime/skill-creator.d.ts.map +0 -1
  327. package/dist/system/runtime/skill-creator.js +0 -763
  328. package/dist/system/runtime/skill-creator.js.map +0 -1
  329. package/src/prolog-src/deepclause_mi.pl.bak +0 -570
@@ -0,0 +1,1392 @@
1
+ /**
2
+ * `deepclause repl` — the DML REPL.
3
+ *
4
+ * M1 implements goal mode: `goal: <dml>.` runs a DML goal, anything else is
5
+ * classified as an English prompt (prompt mode lands in M3). A small set of
6
+ * out-of-band commands (`halt.`, `help.`, `listing.`, `reset.`, …) is matched
7
+ * as whole-line keywords before the classifier.
8
+ */
9
+ import { readFileSync } from 'fs';
10
+ import * as path from 'path';
11
+ import * as readline from 'readline';
12
+ import { fileURLToPath } from 'url';
13
+ import { createHarnessRepl } from '../repl/index.js';
14
+ import { HistoryStore } from '../repl/history.js';
15
+ import { resolveReplJudgeSelection } from '../repl/judge.js';
16
+ import { ReplRenderer } from '../repl/render.js';
17
+ import { ReplStatusTracker } from '../repl/status.js';
18
+ import { LineSink, TranscriptComponent } from '../repl/tui/transcript.js';
19
+ import { StatusBarComponent } from '../repl/tui/status-bar.js';
20
+ import { Container, ProcessTerminal, Spacer, TuiMainScreen, matchesKey } from '@earendil-works/pi-tui';
21
+ import { editorTheme, formatInputAnswer } from '../repl/tui/theme.js';
22
+ import { PromptEditor } from '../repl/tui/editor.js';
23
+ import { loadProjectContext } from '../repl/project-context.js';
24
+ import { createPiBackend } from '../repl/backend.js';
25
+ import { createReplToolset } from '../repl/toolset.js';
26
+ import { createPiImageBackend } from '../repl/imagegen.js';
27
+ import { EnvModelResolver, PiModelResolver } from '../repl/model-resolver.js';
28
+ import { parseModelId, resolveModelSlotConfig } from '../system/config/model-slots.js';
29
+ import { loadReplConfig, resolveReplCompactionConfig, updateConfig } from './config.js';
30
+ import { renderDiagramImage, resolveMedia, } from '../visualize/index.js';
31
+ const OUT_OF_BAND = new Set(['halt', 'help', 'reset', 'rollback', 'freeze', 'unfreeze', 'trace', 'notrace', 'params']);
32
+ const GRAPHICS_MODES = ['auto', 'kitty', 'iterm2', 'ascii', 'off'];
33
+ /** Parse the `/viz` argument list: `[entry] [--spec] [--graphics=<mode>]`. */
34
+ export function parseVizArgs(arg) {
35
+ const tokens = arg.split(/\s+/).filter(Boolean);
36
+ let entry;
37
+ let mode = 'presentation';
38
+ let graphics = 'auto';
39
+ for (let i = 0; i < tokens.length; i += 1) {
40
+ const token = tokens[i];
41
+ if (token === '--spec') {
42
+ mode = 'spec';
43
+ }
44
+ else if (token === '--presentation') {
45
+ mode = 'presentation';
46
+ }
47
+ else if (token === '--ascii') {
48
+ graphics = 'ascii';
49
+ }
50
+ else if (token === '--graphics') {
51
+ const value = tokens[i + 1];
52
+ if (value && GRAPHICS_MODES.includes(value)) {
53
+ graphics = value;
54
+ i += 1;
55
+ }
56
+ }
57
+ else if (token.startsWith('--graphics=')) {
58
+ const value = token.slice('--graphics='.length);
59
+ if (GRAPHICS_MODES.includes(value)) {
60
+ graphics = value;
61
+ }
62
+ }
63
+ else if (!token.startsWith('-') && !entry) {
64
+ entry = token;
65
+ }
66
+ }
67
+ return { entry, mode, graphics };
68
+ }
69
+ /** Render a recorded turn as the line the user would retype (goals get `/goal`). */
70
+ export function formatTurnInput(turn) {
71
+ return turn.mode === 'goal' ? `/goal ${turn.request}` : turn.request;
72
+ }
73
+ /**
74
+ * Classify one REPL input line.
75
+ *
76
+ * Rules (proposal §9.1): out-of-band commands are matched first as whole-line
77
+ * keywords; `goal:` selects goal mode; everything else is an English prompt.
78
+ * A `goal:` payload is never reinterpreted as a prompt.
79
+ */
80
+ export function classifyReplInput(line) {
81
+ const trimmed = line.trim();
82
+ if (!trimmed) {
83
+ return { kind: 'empty' };
84
+ }
85
+ if (trimmed.startsWith('%')) {
86
+ return { kind: 'comment' };
87
+ }
88
+ // Slash commands (the TUI-first surface): `/goal <dml>`, `/listing`, `/halt`, …
89
+ if (trimmed.startsWith('/')) {
90
+ const withoutSlash = trimmed.slice(1);
91
+ const spaceIndex = withoutSlash.search(/\s/);
92
+ const name = (spaceIndex === -1 ? withoutSlash : withoutSlash.slice(0, spaceIndex)).toLowerCase();
93
+ const arg = (spaceIndex === -1 ? '' : withoutSlash.slice(spaceIndex)).trim();
94
+ switch (name) {
95
+ case 'goal':
96
+ return arg ? { kind: 'goal', goal: arg } : { kind: 'unknown-command', name: 'goal (missing goal)' };
97
+ case 'listing':
98
+ return { kind: 'listing', filter: arg || undefined };
99
+ case 'halt':
100
+ case 'exit':
101
+ case 'quit':
102
+ return { kind: 'halt' };
103
+ case 'help':
104
+ return { kind: 'help' };
105
+ case 'reset':
106
+ return { kind: 'reset' };
107
+ case 'rollback':
108
+ return { kind: 'rollback' };
109
+ case 'compact':
110
+ return { kind: 'compact', instructions: arg || undefined };
111
+ case 'export':
112
+ return { kind: 'export', path: arg || undefined };
113
+ case 'model':
114
+ return { kind: 'model', model: arg || undefined };
115
+ case 'freeze':
116
+ return { kind: 'freeze' };
117
+ case 'unfreeze':
118
+ return { kind: 'unfreeze' };
119
+ case 'params':
120
+ return { kind: 'params' };
121
+ case 'trace':
122
+ return { kind: 'trace' };
123
+ case 'notrace':
124
+ return { kind: 'notrace' };
125
+ case 'viz':
126
+ case 'diagram':
127
+ return { kind: 'viz', ...parseVizArgs(arg) };
128
+ case 'show':
129
+ case 'image':
130
+ return arg ? { kind: 'show', path: arg } : { kind: 'unknown-command', name: 'show (missing path)' };
131
+ case 'gen':
132
+ case 'imagine':
133
+ return arg ? { kind: 'gen', prompt: arg } : { kind: 'unknown-command', name: 'gen (missing prompt)' };
134
+ default:
135
+ return { kind: 'unknown-command', name };
136
+ }
137
+ }
138
+ // Out-of-band commands: whole line, optional trailing full stop.
139
+ const commandLine = trimmed.replace(/\.\s*$/, '').trim();
140
+ if (OUT_OF_BAND.has(commandLine)) {
141
+ return { kind: commandLine };
142
+ }
143
+ if (commandLine === 'listing') {
144
+ return { kind: 'listing' };
145
+ }
146
+ if (commandLine.startsWith('listing(') && commandLine.endsWith(')')) {
147
+ const filter = commandLine.slice('listing('.length, -1).trim();
148
+ return { kind: 'listing', filter: filter || undefined };
149
+ }
150
+ if (commandLine === 'model') {
151
+ return { kind: 'model' };
152
+ }
153
+ if (commandLine.startsWith('model ')) {
154
+ return { kind: 'model', model: commandLine.slice('model '.length).trim() || undefined };
155
+ }
156
+ if (commandLine === 'compact') {
157
+ return { kind: 'compact' };
158
+ }
159
+ if (commandLine.startsWith('compact ')) {
160
+ return { kind: 'compact', instructions: commandLine.slice('compact '.length).trim() || undefined };
161
+ }
162
+ if (commandLine === 'export') {
163
+ return { kind: 'export' };
164
+ }
165
+ if (commandLine.startsWith('export ')) {
166
+ return { kind: 'export', path: commandLine.slice('export '.length).trim() || undefined };
167
+ }
168
+ if (trimmed.startsWith('goal:')) {
169
+ return { kind: 'goal', goal: trimmed.slice('goal:'.length).trim() };
170
+ }
171
+ return { kind: 'prompt', prompt: trimmed };
172
+ }
173
+ /** Run one goal and render its solutions. Returns a process exit code. */
174
+ export async function runGoalWithRenderer(repl, goal, options) {
175
+ let hadError = false;
176
+ let printedAny = false;
177
+ for await (const event of repl.query(goal, { signal: options.signal })) {
178
+ if (event.type === 'solution') {
179
+ options.renderer.solution(event.solution?.bindings ?? []);
180
+ printedAny = true;
181
+ if (options.first)
182
+ break;
183
+ }
184
+ else if (event.type === 'error') {
185
+ hadError = true;
186
+ options.renderer.render(event);
187
+ }
188
+ else if (event.type === 'finished') {
189
+ // Nothing to print for finished.
190
+ }
191
+ else {
192
+ options.renderer.render(event);
193
+ }
194
+ }
195
+ if (!printedAny && !hadError) {
196
+ options.renderer.writeLine('false.');
197
+ }
198
+ return hadError ? 1 : 0;
199
+ }
200
+ function selectRenderMode(options) {
201
+ if (options.json)
202
+ return 'json';
203
+ if (options.plain)
204
+ return 'plain';
205
+ return process.stdout.isTTY ? 'rich' : 'plain';
206
+ }
207
+ /**
208
+ * Build the unified input channel from CLI flags (proposal §13.6):
209
+ * - `--answer` supplies a canned reply; `--no-ask` fails immediately;
210
+ * - a TTY (or `--interactive-input`) prompts;
211
+ * - everything else returns undefined so autonomous runs never block.
212
+ */
213
+ export function buildCliOnInput(options) {
214
+ if (options.answer !== undefined) {
215
+ return async () => options.answer;
216
+ }
217
+ if (options.noAsk) {
218
+ return async () => undefined;
219
+ }
220
+ const interactive = Boolean(process.stdin.isTTY) || Boolean(options.interactiveInput);
221
+ if (!interactive) {
222
+ return async () => undefined;
223
+ }
224
+ return async (prompt, choices) => {
225
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
226
+ try {
227
+ const suffix = choices && choices.length > 0 ? ` (${choices.join('/')})` : '';
228
+ return await new Promise((resolve) => rl.question(`${prompt}${suffix}\n> `, resolve));
229
+ }
230
+ finally {
231
+ rl.close();
232
+ }
233
+ };
234
+ }
235
+ function resolveModel(modelId) {
236
+ try {
237
+ const parsed = parseModelId(modelId);
238
+ return { provider: parsed.provider, model: parsed.model };
239
+ }
240
+ catch {
241
+ return { provider: 'openai', model: modelId };
242
+ }
243
+ }
244
+ const PROVIDER_ENV_KEYS = {
245
+ openai: 'OPENAI_API_KEY',
246
+ anthropic: 'ANTHROPIC_API_KEY',
247
+ google: 'GOOGLE_GENERATIVE_AI_API_KEY',
248
+ openrouter: 'OPENROUTER_API_KEY',
249
+ };
250
+ /** Build the harness + renderer from CLI options. */
251
+ export async function createReplRuntime(options) {
252
+ const workspace = path.resolve(options.workspace ?? './');
253
+ const config = await loadReplConfig(workspace);
254
+ const replCompaction = resolveReplCompactionConfig(config);
255
+ // One harness per name within a project: `--harness`, else the `-l` file's
256
+ // name, else the configured default (`main`).
257
+ const harnessName = normalizeHarnessName(options.harness)
258
+ ?? (options.load?.length ? harnessNameFromFile(options.load[0]) : undefined)
259
+ ?? normalizeHarnessName(config.repl.harness)
260
+ ?? 'main';
261
+ const modelId = options.model ?? config.models.run;
262
+ const resolved = resolveModel(modelId);
263
+ const provider = options.provider ?? resolved.provider;
264
+ const renderer = new ReplRenderer({
265
+ mode: selectRenderMode(options),
266
+ color: options.color !== false && config.repl.color !== false,
267
+ verbose: options.verbose ?? false,
268
+ stream: options.stream !== false,
269
+ events: options.events !== false,
270
+ reasoning: options.showReasoning ?? config.repl.showReasoning,
271
+ onEvent: (event) => statusHolder.tracker?.observe(event),
272
+ workspace,
273
+ openImages: options.open !== false && config.repl.media.open !== false,
274
+ });
275
+ const statusHolder = {};
276
+ // `null` means no capability tools; `undefined` means use the default
277
+ // (bash). An explicit `tools: []` in config also disables capability tools.
278
+ const toolNames = options.noTools
279
+ ? null
280
+ : options.tools
281
+ ? options.tools.split(',').map((name) => name.trim()).filter(Boolean)
282
+ : (config.repl.tools ?? undefined);
283
+ const toolset = await createReplToolset({
284
+ workspacePath: workspace,
285
+ sandbox: options.sandbox,
286
+ network: config.agentvm?.network ?? false,
287
+ hostConfig: config.shell,
288
+ toolNames,
289
+ // The runner already emits the tool_call start/completion events, so the
290
+ // shell bridge here only forwards stream/log lines to avoid duplicates.
291
+ onEvent: (event) => {
292
+ if (event.type !== 'tool_call') {
293
+ renderer.render(event);
294
+ }
295
+ },
296
+ });
297
+ const backendKind = options.backend ?? config.backend?.kind ?? 'pi';
298
+ let llmBackend;
299
+ let harnessModel = resolved.model;
300
+ let harnessProvider = provider;
301
+ let contextWindow;
302
+ let reasoning = false;
303
+ let thinkingLevel;
304
+ let modelResolver;
305
+ if (backendKind === 'pi') {
306
+ try {
307
+ const pi = await createPiBackend({
308
+ agentDir: config.backend?.agentDir,
309
+ modelsPath: config.backend?.modelsPath ?? undefined,
310
+ authPath: config.backend?.authPath ?? undefined,
311
+ model: options.model ?? config.backend?.model,
312
+ thinkingLevel: options.thinking ?? config.backend?.thinkingLevel,
313
+ });
314
+ llmBackend = pi.backend;
315
+ harnessModel = pi.modelId.split('/').slice(1).join('/') || pi.modelId;
316
+ harnessProvider = pi.provider ?? harnessProvider;
317
+ contextWindow = pi.contextWindow;
318
+ reasoning = pi.reasoning;
319
+ thinkingLevel = pi.thinkingLevel;
320
+ modelResolver = new PiModelResolver(pi.registry);
321
+ if (pi.warning)
322
+ process.stderr.write(`[deepclause] ${pi.warning}\n`);
323
+ }
324
+ catch (error) {
325
+ const message = error instanceof Error ? error.message : String(error);
326
+ throw new Error(`pi backend unavailable: ${message}\n`
327
+ + "Run 'deepclause login' to sign in, or 'deepclause set-backend env' to use provider API keys.");
328
+ }
329
+ }
330
+ else {
331
+ // env backend: take the context window from the bundled model database so
332
+ // compaction thresholds still work without pi.
333
+ contextWindow = resolveModelSlotConfig(config, 'run', {
334
+ modelId: options.model ?? config.models.run,
335
+ temperature: options.temperature ?? config.temperatures.run,
336
+ }).contextWindow;
337
+ modelResolver = new EnvModelResolver(String(harnessProvider));
338
+ }
339
+ // Judgment backends. `llm` reuses the active model/backend; `jev` is opt-in
340
+ // and only registered when enabled and its API key is present. Building the
341
+ // registry here (rather than in createHarnessRepl) lets the CLI report a
342
+ // skipped `jev` backend and honor --judge.
343
+ const { selection: judgeSelection, warnings: judgeWarnings } = resolveReplJudgeSelection({
344
+ llmBackend,
345
+ model: harnessModel,
346
+ provider: harnessProvider,
347
+ temperature: options.temperature ?? config.temperatures.run,
348
+ defaultJudge: options.judge ?? config.judgment.default,
349
+ jev: {
350
+ enabled: config.judgment.jev.enabled,
351
+ model: config.judgment.jev.model,
352
+ apiKeyEnv: config.judgment.jev.apiKeyEnv,
353
+ },
354
+ });
355
+ for (const warning of judgeWarnings) {
356
+ process.stderr.write(`[deepclause] ${warning}\n`);
357
+ }
358
+ // History is needed before the session opens: `--session` resolves the
359
+ // target id here and the resumed session reads its harness back on open.
360
+ const historyOptions = config.repl.history.enabled
361
+ ? {
362
+ dir: config.repl.history.dir,
363
+ trace: config.repl.history.trace,
364
+ maxEventBytes: config.repl.history.maxEventBytes,
365
+ maxSessions: config.repl.history.maxSessions,
366
+ maxVersions: config.repl.history.maxVersions,
367
+ includeInExport: config.repl.history.includeInExport,
368
+ redact: config.repl.history.redact,
369
+ }
370
+ : undefined;
371
+ let sessionId = options.session;
372
+ let resume = false;
373
+ if (sessionId) {
374
+ if (!historyOptions) {
375
+ throw new Error('--session requires repl.history to be enabled');
376
+ }
377
+ const store = new HistoryStore(workspace, '__session_lookup__', historyOptions);
378
+ if (sessionId === 'last') {
379
+ const latest = await store.latestSessionId();
380
+ if (!latest) {
381
+ throw new Error('no previous sessions found to resume');
382
+ }
383
+ sessionId = latest;
384
+ }
385
+ else if (!(await store.sessionExists(sessionId))) {
386
+ throw new Error(`unknown session: ${sessionId}`);
387
+ }
388
+ resume = true;
389
+ }
390
+ // Seed the editor's up/down history from the target session's recorded turns.
391
+ let inputHistory = [];
392
+ if (historyOptions) {
393
+ const store = new HistoryStore(workspace, '__session_lookup__', historyOptions);
394
+ const target = sessionId ?? await store.latestSessionId();
395
+ if (target) {
396
+ inputHistory = (await store.readTurns(target))
397
+ .map((turn) => formatTurnInput(turn))
398
+ .filter((request) => request.length > 0);
399
+ }
400
+ }
401
+ // Image generation for `task(…, media(X))`; best effort, pi backend only.
402
+ let imageBackend;
403
+ if (backendKind === 'pi') {
404
+ try {
405
+ imageBackend = await createPiImageBackend({
406
+ workspacePath: workspace,
407
+ agentDir: config.backend?.agentDir ?? undefined,
408
+ modelsPath: config.backend?.modelsPath ?? undefined,
409
+ authPath: config.backend?.authPath ?? undefined,
410
+ model: options.imageModel ?? config.repl.media.imageModel,
411
+ dir: config.repl.media.dir,
412
+ size: config.repl.media.size,
413
+ });
414
+ }
415
+ catch {
416
+ imageBackend = undefined;
417
+ }
418
+ }
419
+ const repl = await createHarnessRepl({
420
+ sessionId,
421
+ resume,
422
+ workspacePath: workspace,
423
+ model: harnessModel,
424
+ provider: harnessProvider,
425
+ temperature: options.temperature ?? config.temperatures.run,
426
+ params: options.param ?? {},
427
+ gasLimit: options.gasLimit,
428
+ maxDepth: options.depth ?? config.repl.maxDepth,
429
+ maxModelCalls: options.maxModelCalls ?? config.repl.maxModelCalls,
430
+ tools: toolset.tools,
431
+ hostToolsCatalog: toolset.catalog,
432
+ llmBackend,
433
+ judgeSelection,
434
+ imageBackend,
435
+ modelResolver,
436
+ history: historyOptions,
437
+ harness: harnessName,
438
+ autoLoadHarness: !(options.load?.length),
439
+ compaction: { settings: replCompaction, auto: replCompaction.auto },
440
+ contextWindow,
441
+ onInput: buildCliOnInput(options),
442
+ });
443
+ // Terminal images (agent `visualize` and `/viz`) are painted through the
444
+ // renderer; the TUI installs an image sink later, which this follows.
445
+ repl.session.setImageHandler((lines) => renderer.image(lines));
446
+ let missingCredential;
447
+ const statusTracker = new ReplStatusTracker({
448
+ model: {
449
+ id: harnessModel,
450
+ provider: String(harnessProvider),
451
+ reasoning,
452
+ thinkingLevel,
453
+ },
454
+ contextWindow,
455
+ });
456
+ statusHolder.tracker = statusTracker;
457
+ if (backendKind === 'env') {
458
+ const envKey = PROVIDER_ENV_KEYS[harnessProvider];
459
+ const configured = config.providers?.[harnessProvider]?.apiKey;
460
+ if (envKey && !configured && !process.env[envKey]) {
461
+ missingCredential = `no credentials for provider '${harnessProvider}'. `
462
+ + `Set ${envKey}, configure providers.${harnessProvider}.apiKey, `
463
+ + "or run 'deepclause set-backend pi'.";
464
+ }
465
+ }
466
+ return {
467
+ repl,
468
+ renderer,
469
+ workspace,
470
+ promptPrefix: config.repl.promptPrefix ?? 'dml> ',
471
+ toolset,
472
+ version: PACKAGE_VERSION,
473
+ imageBackend,
474
+ model: backendKind === 'pi' ? harnessModel : `${harnessProvider}:${harnessModel}`,
475
+ backend: backendKind,
476
+ contextWindow,
477
+ reasoning,
478
+ thinkingLevel,
479
+ sessionId: repl.session.getSessionId(),
480
+ harnessName,
481
+ resumedSession: resume ? sessionId : undefined,
482
+ historyEnabled: Boolean(historyOptions),
483
+ inputHistory,
484
+ statusSnapshot: () => statusTracker.snapshot(),
485
+ tuiMode: options.tui ?? config.repl.tui ?? 'auto',
486
+ setOnInput: (handler) => repl.setOnInput(handler),
487
+ missingCredential,
488
+ };
489
+ }
490
+ /** Package version, read once from package.json for the banner/`--version`. */
491
+ export const PACKAGE_VERSION = (() => {
492
+ try {
493
+ const here = path.dirname(fileURLToPath(import.meta.url));
494
+ const pkg = JSON.parse(readFileSync(path.join(here, '..', '..', 'package.json'), 'utf8'));
495
+ return pkg.version ?? 'unknown';
496
+ }
497
+ catch {
498
+ return 'unknown';
499
+ }
500
+ })();
501
+ /** Startup banner for the interactive REPL (proposal §2.1). */
502
+ async function printBanner(runtime, loaded) {
503
+ const rich = runtime.renderer.mode === 'rich';
504
+ const paint = (text, code) => (rich ? `\u001b[${code}m${text}\u001b[0m` : text);
505
+ const dim = (text) => paint(text, '2');
506
+ const accent = (text) => paint(text, '36');
507
+ const bold = (text) => paint(text, '1');
508
+ const harnessFresh = runtime.repl.session.isEmpty();
509
+ const predicateCount = runtime.repl.session.predicateCount();
510
+ const harnessInfo = harnessFresh
511
+ ? 'new — no predicates yet'
512
+ : `${predicateCount} predicate${predicateCount === 1 ? '' : 's'}`;
513
+ const loadedState = loaded.length > 0
514
+ ? `${loaded.length} file${loaded.length === 1 ? '' : 's'} loaded`
515
+ : undefined;
516
+ const frozen = runtime.repl.isFrozen() ? ` ${paint(' FROZEN ', '41;97')}` : '';
517
+ const summary = [
518
+ loadedState ? dim(loadedState) : undefined,
519
+ paint(runtime.model, '35'),
520
+ dim(`backend ${runtime.backend}`),
521
+ `${dim('harness')} ${paint(runtime.harnessName, '35')} ${dim(`(${harnessInfo})`)}`,
522
+ ].filter((part) => Boolean(part)).join(` ${dim('·')} `);
523
+ const lines = [
524
+ '',
525
+ ` ${paint('DeepClause', '1;36')} ${paint('DML REPL', '1')} ${dim(`v${runtime.version}`)}${frozen}`,
526
+ ` ${summary}`,
527
+ ` ${dim(runtime.workspace)}`,
528
+ ];
529
+ lines.push(runtime.resumedSession
530
+ ? ` ${dim('session')} ${runtime.sessionId} ${accent('(resumed)')}`
531
+ : ` ${dim('session')} ${runtime.sessionId}`);
532
+ const project = await loadProjectContext(runtime.workspace);
533
+ if (project) {
534
+ lines.push(` ${dim('project context')} AGENTS.md ${dim(`(${project.bytes} bytes)`)}`);
535
+ }
536
+ // A short getting-started guide, shown on a fresh (blank) harness only.
537
+ if (harnessFresh) {
538
+ lines.push('', ` ${bold('Two modes.')} Plain English authors the harness; ${accent('/goal')} runs DML directly.`, '', ` ${accent('<plain English>')} default. Ask for anything: add a new skill, add a tool,`, ` change existing logic, run or test code in the living`, ` module, or just ask a question about the harness. An authoring`, ` agent writes the DML, tests it with ${dim('execute')}, and commits it.`, ` ${accent('/goal <dml>')} run DML/Prolog directly against the current harness.`, '', ` ${bold('Example')}`, ` add a research(Question, Answer) skill that answers with task/2`, ` run research on "why is the sky blue?"`, ` A = "…"`, '', ` ...or run DML directly (${accent('/goal')}):`, ` ${accent('/goal')} research("why is the sky blue?", A).`, ` A = "…"`, '', ` When you're ready, export the harness and re-use it whenever you need:`, ` /export research.dml save a portable harness`, ` ${dim('$')} deepclause repl -l research.dml load it back any time`, ` ${dim('$')} deepclause repl --session last resume the last session`);
539
+ }
540
+ lines.push('', ` ${accent('/goal')} ${dim('<dml>')} run a DML goal, e.g. ${dim('/goal member(X, [1,2,3])')}`, ` ${accent('<plain English>')} author the harness`, '', ` ${accent('/help')} ${accent('/listing')} ${accent('/viz')} ${accent('/export')} ${accent('/freeze')} ${accent('/model')} ${accent('/halt')}`);
541
+ if (runtime.historyEnabled) {
542
+ lines.push(` ${dim('resume later:')} deepclause repl --session ${runtime.sessionId}`);
543
+ }
544
+ lines.push('');
545
+ runtime.renderer.writeLine(lines.join('\n'));
546
+ }
547
+ /** Dispatch one classified input. Returns false when the REPL should exit. */
548
+ export async function dispatchReplInput(runtime, input, options = {}) {
549
+ const { repl, renderer } = runtime;
550
+ const print = options.print ?? ((text) => renderer.writeLine(text));
551
+ switch (input.kind) {
552
+ case 'empty':
553
+ case 'comment':
554
+ return true;
555
+ case 'unknown-command':
556
+ print(`unknown command: /${input.name} — try /help.`);
557
+ return true;
558
+ case 'halt':
559
+ return false;
560
+ case 'help':
561
+ print(helpText());
562
+ return true;
563
+ case 'listing': {
564
+ const text = await repl.list(input.filter);
565
+ print(runtime.renderer.listing(text.trimEnd() || '% (empty harness)'));
566
+ return true;
567
+ }
568
+ case 'reset':
569
+ if (repl.isFrozen()) {
570
+ print('ERROR: harness is frozen; reset is disabled. Use \'unfreeze.\' first.');
571
+ return true;
572
+ }
573
+ await repl.reset();
574
+ print('harness reset');
575
+ return true;
576
+ case 'rollback': {
577
+ const result = await repl.rollback();
578
+ if (result.ok) {
579
+ print(`rolled back harness: version ${result.from} → ${result.to}`);
580
+ }
581
+ else {
582
+ print(`ERROR: ${result.error ?? 'rollback failed'}`);
583
+ }
584
+ return true;
585
+ }
586
+ case 'compact': {
587
+ const result = await repl.compact(input.instructions);
588
+ if (result.applied) {
589
+ print(`compacted meta memory: ${result.beforeTokens} → ${result.afterTokens} tokens`);
590
+ }
591
+ else if (result.error) {
592
+ print(`compact skipped: ${result.error}`);
593
+ }
594
+ else {
595
+ print(`nothing to compact (${result.beforeTokens} tokens)`);
596
+ }
597
+ return true;
598
+ }
599
+ case 'model': {
600
+ if (input.model) {
601
+ await updateConfig(runtime.workspace, { backend: { kind: 'pi', model: input.model } });
602
+ print(`default model set to ${input.model} (takes effect on the next start)`);
603
+ }
604
+ else {
605
+ print(`model: ${runtime.model}`);
606
+ print(`backend: ${runtime.backend}`);
607
+ print("set with 'model <provider/id>.' or 'deepclause login'");
608
+ }
609
+ return true;
610
+ }
611
+ case 'export': {
612
+ const target = input.path ?? options.exportPath ?? 'harness.dml';
613
+ try {
614
+ const result = await repl.export(target);
615
+ print(`exported ${result.clauses} clauses, ${result.tools} tools, ${result.scripts} scripts → ${result.path}`);
616
+ }
617
+ catch (error) {
618
+ print(`ERROR: ${error.message}`);
619
+ }
620
+ return true;
621
+ }
622
+ case 'freeze':
623
+ await repl.freeze();
624
+ print(`harness frozen: assert/retract/load disabled. Use 'unfreeze.' to re-enable.`);
625
+ return true;
626
+ case 'unfreeze':
627
+ try {
628
+ await repl.unfreeze();
629
+ print('harness unfrozen');
630
+ }
631
+ catch (error) {
632
+ print(`ERROR: ${error.message}`);
633
+ }
634
+ return true;
635
+ case 'trace':
636
+ print('trace enabled');
637
+ return true;
638
+ case 'notrace':
639
+ print('trace disabled');
640
+ return true;
641
+ case 'params':
642
+ print('params: (see session configuration)');
643
+ return true;
644
+ case 'viz': {
645
+ if (runtime.missingCredential) {
646
+ print(`ERROR: ${runtime.missingCredential}`);
647
+ return true;
648
+ }
649
+ let result;
650
+ try {
651
+ result = await repl.diagram(input.entry, input.mode, { signal: options.signal });
652
+ }
653
+ catch (error) {
654
+ print(`ERROR: ${error.message}`);
655
+ return true;
656
+ }
657
+ print(`📊 ${input.mode} diagram of ${result.entry}`);
658
+ for (const warning of result.warnings) {
659
+ print(` ⚠ ${warning}`);
660
+ }
661
+ const lines = await renderDiagramImage(result, {
662
+ graphics: input.graphics,
663
+ maxWidthCells: Math.max(20, renderer.getWidth() - 2),
664
+ maxHeightCells: Math.max(8, (process.stdout.rows ?? 40) - 8),
665
+ });
666
+ if (lines) {
667
+ renderer.image(lines);
668
+ return true;
669
+ }
670
+ print(result.ascii);
671
+ if (input.graphics !== 'ascii' && input.graphics !== 'off') {
672
+ print('(no terminal image support here — showing text; /viz --ascii to force text)');
673
+ }
674
+ return true;
675
+ }
676
+ case 'show': {
677
+ const resolved = await resolveMedia(runtime.workspace, { path: input.path });
678
+ if (!resolved) {
679
+ print(`ERROR: cannot read image ${input.path}`);
680
+ return true;
681
+ }
682
+ renderer.render({
683
+ type: 'media',
684
+ media: { path: input.path, mimeType: resolved.mimeType, data: resolved.pngBase64 },
685
+ });
686
+ return true;
687
+ }
688
+ case 'gen': {
689
+ if (!runtime.imageBackend) {
690
+ print('ERROR: image generation is unavailable (no image backend; sign in to a provider with image models, or pass --image-model)');
691
+ return true;
692
+ }
693
+ try {
694
+ const generated = await runtime.imageBackend.generate({ prompt: input.prompt, signal: options.signal });
695
+ const resolved = await resolveMedia(runtime.workspace, { path: generated.path, mimeType: generated.mimeType });
696
+ renderer.render({
697
+ type: 'media',
698
+ media: { path: generated.path, mimeType: generated.mimeType, data: resolved?.pngBase64 },
699
+ });
700
+ }
701
+ catch (error) {
702
+ print(`ERROR: ${error.message}`);
703
+ }
704
+ return true;
705
+ }
706
+ case 'goal':
707
+ await runGoalWithRenderer(repl, input.goal, { first: options.first, renderer, signal: options.signal });
708
+ return true;
709
+ case 'prompt': {
710
+ if (runtime.missingCredential) {
711
+ print(`ERROR: ${runtime.missingCredential}`);
712
+ return true;
713
+ }
714
+ for await (const event of repl.prompt(input.prompt, { signal: options.signal })) {
715
+ renderer.render(event);
716
+ }
717
+ return true;
718
+ }
719
+ default:
720
+ return true;
721
+ }
722
+ }
723
+ function helpText() {
724
+ return [
725
+ 'DeepClause REPL — a living DML harness you author by talking to it.',
726
+ '',
727
+ ' Modes',
728
+ ' /goal <dml> run DML/Prolog directly against the harness.',
729
+ ' <plain English> author: ask for a skill, change existing logic, or ask a',
730
+ ' question. An agent writes, tests, and commits the DML.',
731
+ '',
732
+ ' Commands',
733
+ ' /listing [filter] inspect the harness (all | Name/Arity | tools | scripts)',
734
+ ' /viz [entry] [--spec] diagram of a skill, drawn in the terminal',
735
+ ' (--graphics=auto|kitty|iterm2|ascii|off)',
736
+ ' /show <path> display a workspace image inline',
737
+ ' /gen <prompt> generate an image and display it',
738
+ ' /help this help',
739
+ ' /reset empty the harness',
740
+ ' /rollback undo the last change',
741
+ ' /compact [notes] compact the authoring memory',
742
+ ' /export [file] save the harness (use .zip to bundle linked files)',
743
+ ' /model [provider/id] show / set the model',
744
+ ' /freeze /unfreeze read-only lock',
745
+ ' /params show session parameters',
746
+ ' /trace /notrace toggle verbose logging',
747
+ ' /halt exit',
748
+ '',
749
+ ' Examples',
750
+ ' /goal member(X, [a,b,c]).',
751
+ ' /listing all',
752
+ ' add a tally(List, Counts) skill, plus a demo',
753
+ ' what skills does this harness have?',
754
+ '',
755
+ ' Skills compose: task/N (LLM calls), exec/2 (host tools), judgments, CLP(FD)/',
756
+ ' CLP(Q), and script_file/2 (bundled on export).',
757
+ ].join('\n');
758
+ }
759
+ async function executeLoad(runtime, files) {
760
+ for (const file of files) {
761
+ const target = path.isAbsolute(file) ? file : path.resolve(runtime.workspace, file);
762
+ const result = await runtime.repl.load(target);
763
+ if (result.error) {
764
+ runtime.renderer.writeErrorLine(`ERROR: ${result.error}`);
765
+ return 1;
766
+ }
767
+ runtime.renderer.writeLine(`loaded ${file}`);
768
+ }
769
+ return 0;
770
+ }
771
+ /** Non-interactive mode: read classified inputs line by line until EOF. */
772
+ /**
773
+ * Route Ctrl-C to the active turn. While a turn is running the first Ctrl-C
774
+ * aborts it (the session then saves the harness to a temp file); a Ctrl-C while
775
+ * idle exits.
776
+ */
777
+ function installSigint(runtime, holder, rl, onExit) {
778
+ const handler = () => {
779
+ if (holder.turn) {
780
+ holder.turn.abort(new Error('cancelled by user'));
781
+ runtime.renderer.writeErrorLine('(cancelling current turn… press Ctrl-C again to exit)');
782
+ }
783
+ else if (onExit) {
784
+ // Save the harness before exiting (Ctrl-C while idle).
785
+ void onExit().finally(() => process.exit(130));
786
+ }
787
+ else {
788
+ process.exit(130);
789
+ }
790
+ };
791
+ process.on('SIGINT', handler);
792
+ rl?.on('SIGINT', handler);
793
+ return () => {
794
+ process.off('SIGINT', handler);
795
+ rl?.off('SIGINT', handler);
796
+ };
797
+ }
798
+ async function runNonInteractive(runtime, options, onExit) {
799
+ let exitCode = 0;
800
+ const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
801
+ const holder = { turn: null };
802
+ const removeSigint = installSigint(runtime, holder, rl, onExit);
803
+ try {
804
+ for await (const line of rl) {
805
+ const input = classifyReplInput(line);
806
+ if (input.kind === 'halt')
807
+ break;
808
+ const controller = new AbortController();
809
+ holder.turn = controller;
810
+ try {
811
+ const keepGoing = await dispatchReplInput(runtime, input, {
812
+ first: options.first,
813
+ exportPath: options.exportPath,
814
+ signal: controller.signal,
815
+ });
816
+ if (!keepGoing)
817
+ break;
818
+ }
819
+ finally {
820
+ holder.turn = null;
821
+ }
822
+ }
823
+ }
824
+ finally {
825
+ removeSigint();
826
+ rl.close();
827
+ }
828
+ return exitCode;
829
+ }
830
+ /** Interactive mode with the `dml>` prompt and a `;`/`.` pager for goals. */
831
+ async function runInteractive(runtime, options, onExit) {
832
+ const { promptPrefix } = runtime;
833
+ const rl = readline.createInterface({
834
+ input: process.stdin,
835
+ output: process.stdout,
836
+ terminal: true,
837
+ });
838
+ // A single readline interface serves the REPL prompt, the input handler, and
839
+ // the pager. `rl.question` can be called whenever no other question is
840
+ // pending (the loop is blocked inside a turn), so ask_user / input/2 do not
841
+ // need a second interface (which double-echoed and closed stdin).
842
+ let rlClosed = false;
843
+ rl.on('close', () => {
844
+ rlClosed = true;
845
+ });
846
+ const ask = (query) => new Promise((resolve) => {
847
+ // Calling question() on a closed interface throws; treat it as EOF.
848
+ if (rlClosed) {
849
+ resolve(null);
850
+ return;
851
+ }
852
+ let settled = false;
853
+ const finish = (value) => {
854
+ if (settled)
855
+ return;
856
+ settled = true;
857
+ rl.off('close', onClose);
858
+ resolve(value);
859
+ };
860
+ const onClose = () => finish(null);
861
+ rl.once('close', onClose);
862
+ rl.question(query, (answer) => finish(answer));
863
+ });
864
+ runtime.setOnInput(async (prompt, choices) => {
865
+ const options = choices && choices.length > 0 ? ` (${choices.join('/')})` : '';
866
+ runtime.renderer.writeLine('');
867
+ runtime.renderer.writeLine(`? ${prompt}${options}`);
868
+ const answer = await ask('> ');
869
+ return answer === null ? undefined : answer;
870
+ });
871
+ const holder = { turn: null };
872
+ const removeSigint = installSigint(runtime, holder, rl, onExit);
873
+ try {
874
+ while (true) {
875
+ const line = await ask(promptPrefix);
876
+ if (line === null)
877
+ break;
878
+ const input = classifyReplInput(line);
879
+ if (input.kind === 'halt')
880
+ break;
881
+ const controller = new AbortController();
882
+ holder.turn = controller;
883
+ try {
884
+ if (input.kind === 'goal') {
885
+ await runInteractiveGoal(runtime.repl, runtime.renderer, input.goal, options, ask, controller.signal);
886
+ }
887
+ else {
888
+ const keepGoing = await dispatchReplInput(runtime, input, {
889
+ first: options.first,
890
+ exportPath: options.exportPath,
891
+ signal: controller.signal,
892
+ });
893
+ if (!keepGoing)
894
+ break;
895
+ }
896
+ }
897
+ finally {
898
+ holder.turn = null;
899
+ }
900
+ }
901
+ }
902
+ finally {
903
+ removeSigint();
904
+ rl.close();
905
+ }
906
+ return 0;
907
+ }
908
+ export async function runInteractiveGoal(repl, renderer, goal, options, ask, signal) {
909
+ const iterator = repl.query(goal, { signal })[Symbol.asyncIterator]();
910
+ let answered = false;
911
+ let pageAll = false;
912
+ try {
913
+ while (true) {
914
+ const { value, done } = await iterator.next();
915
+ if (done)
916
+ break;
917
+ if (value.type === 'solution') {
918
+ renderer.solution(value.solution?.bindings ?? [], '');
919
+ answered = true;
920
+ if (options.first) {
921
+ renderer.writeLine('.');
922
+ await iterator.return?.(undefined);
923
+ return;
924
+ }
925
+ if (!pageAll) {
926
+ // Pager: Enter or `;` pages the next solution, `a` prints all,
927
+ // `.`/`q` stops. (Enter is the common "next" gesture in the TUI.)
928
+ const key = await ask(' ;');
929
+ const trimmed = (key ?? '').trim().toLowerCase();
930
+ if (trimmed === 'a' || trimmed === 'w' || trimmed === 'p') {
931
+ pageAll = true;
932
+ }
933
+ else if (trimmed === '.' || trimmed === 'q' || trimmed === 'd' || trimmed === 'stop') {
934
+ renderer.writeLine('.');
935
+ await iterator.return?.(undefined);
936
+ return;
937
+ }
938
+ // Empty/Enter/`;`/anything else: fall through to the next solution.
939
+ }
940
+ }
941
+ else if (value.type === 'error') {
942
+ renderer.render(value);
943
+ }
944
+ else if (value.type !== 'finished') {
945
+ // Render output/stream/tool_call/task_activity/usage/reasoning, exactly
946
+ // like the `-g` path (runGoalWithRenderer); otherwise an interactive
947
+ // goal is silent until its first solution.
948
+ renderer.render(value);
949
+ }
950
+ }
951
+ }
952
+ finally {
953
+ await iterator.return?.(undefined);
954
+ }
955
+ renderer.writeLine(answered ? '.' : 'false.');
956
+ }
957
+ /**
958
+ * Interactive TUI for the REPL: a scrolling transcript, a fixed editor, and a
959
+ * fixed status bar below the input. It reuses `ReplRenderer` (rich mode) for all
960
+ * transcript formatting by redirecting its output through a `LineSink`.
961
+ *
962
+ * `ask_user` / `input/2` route through the same editor: a question is written
963
+ * to the transcript and the next submitted line is returned to the runtime, so
964
+ * input prompts never fight the main prompt for stdin.
965
+ */
966
+ export async function runTui(runtime, options, onExit, loaded = []) {
967
+ const terminal = new ProcessTerminal();
968
+ const tui = new TuiMainScreen(terminal);
969
+ const transcript = new TranscriptComponent();
970
+ const sink = new LineSink(transcript);
971
+ const runState = { running: false, steerable: false };
972
+ /** Lines typed during a goal query, consumed by the solution pager. */
973
+ const bufferedInput = [];
974
+ const statusBar = new StatusBarComponent({
975
+ getSnapshot: () => runtime.statusSnapshot(),
976
+ workspace: runtime.workspace,
977
+ sessionId: runtime.sessionId,
978
+ harness: runtime.harnessName,
979
+ backend: runtime.backend,
980
+ isFrozen: () => runtime.repl.isFrozen(),
981
+ isRunning: () => runState.running,
982
+ });
983
+ transcript.onChange = () => tui.requestRender();
984
+ // Redirect the renderer into the transcript (including error lines).
985
+ runtime.renderer.setSink((text) => sink.write(text), (text) => sink.write(text));
986
+ runtime.renderer.setImageSink((lines) => transcript.appendImageLines(lines));
987
+ runtime.renderer.setWidth(terminal.columns);
988
+ const onResize = () => {
989
+ runtime.renderer.setWidth(terminal.columns);
990
+ tui.requestRender();
991
+ };
992
+ process.stdout.on('resize', onResize);
993
+ const editorContainer = new Container();
994
+ let editor;
995
+ let activePrompt = runtime.promptPrefix;
996
+ let pendingIsInput = false;
997
+ let pendingResolve = null;
998
+ const holder = { turn: null };
999
+ // The editor shows ask_user/input/2 questions in its border, so suppress the
1000
+ // duplicate `input_required` transcript line.
1001
+ runtime.renderer.setSuppressInputPrompt(true);
1002
+ // A single editor is reused for every prompt so up/down history persists for
1003
+ // the whole session (recreating it reset the history).
1004
+ const handleSubmit = (text) => {
1005
+ const resolve = pendingResolve;
1006
+ const wasInput = pendingIsInput;
1007
+ pendingResolve = null;
1008
+ pendingIsInput = false;
1009
+ if (resolve) {
1010
+ // A pending ask (the main prompt, or ask_user / input/2): resolve it.
1011
+ if (text) {
1012
+ if (wasInput) {
1013
+ // A `ask_user` / `input/2` answer: keep a short record.
1014
+ transcript.append(formatInputAnswer(text));
1015
+ }
1016
+ else if (activePrompt === runtime.promptPrefix) {
1017
+ // A user turn: separate it from the previous output and from the
1018
+ // response that follows; render it as a pi-style blue block.
1019
+ if (transcript.getLines().length > 0)
1020
+ transcript.append('');
1021
+ transcript.append(text, 'user');
1022
+ transcript.append('');
1023
+ }
1024
+ }
1025
+ }
1026
+ else if (text.trim()) {
1027
+ // No pending ask means a turn is running.
1028
+ if (runState.steerable) {
1029
+ transcript.append(text, 'steer');
1030
+ runtime.repl.steer(text);
1031
+ }
1032
+ else {
1033
+ // A goal query is running: queue the line for the solution pager.
1034
+ bufferedInput.push(text);
1035
+ transcript.append(text, 'steer');
1036
+ }
1037
+ }
1038
+ if (editor) {
1039
+ editor.setPrompt(null);
1040
+ editor.setText('');
1041
+ if (text)
1042
+ editor.addToHistory(text);
1043
+ tui.setFocus(editor);
1044
+ }
1045
+ tui.requestRender();
1046
+ resolve?.(text);
1047
+ };
1048
+ const createEditor = () => {
1049
+ editor = new PromptEditor(tui, editorTheme());
1050
+ editor.onSubmit = handleSubmit;
1051
+ editorContainer.clear();
1052
+ editorContainer.addChild(editor);
1053
+ tui.setFocus(editor);
1054
+ };
1055
+ /**
1056
+ * Wait for one submitted line. When `isInput` is set the question is shown in
1057
+ * the editor's top border (orange) to signal that the runtime needs input.
1058
+ */
1059
+ const ask = (label, isInput = false) => {
1060
+ activePrompt = label;
1061
+ pendingIsInput = isInput;
1062
+ // The solution pager consumes a line typed while the goal was running.
1063
+ if (label === ' ;') {
1064
+ const buffered = bufferedInput.shift();
1065
+ if (buffered !== undefined)
1066
+ return Promise.resolve(buffered);
1067
+ }
1068
+ return new Promise((resolve) => {
1069
+ if (editor) {
1070
+ const hint = isInput
1071
+ ? label
1072
+ : label === ' ;'
1073
+ ? 'Enter/; more · a all · . stop'
1074
+ : null;
1075
+ editor.setPrompt(hint);
1076
+ tui.setFocus(editor);
1077
+ }
1078
+ tui.requestRender();
1079
+ pendingResolve = resolve;
1080
+ });
1081
+ };
1082
+ // Route ask_user / input/2 through the editor prompt instead of the transcript.
1083
+ runtime.setOnInput(async (prompt, choices) => {
1084
+ const suffix = choices && choices.length > 0 ? ` (${choices.join('/')})` : '';
1085
+ const answer = await ask(`${prompt}${suffix}`, true);
1086
+ return answer === null ? undefined : answer;
1087
+ });
1088
+ const cancelTurn = () => {
1089
+ if (holder.turn) {
1090
+ holder.turn.abort(new Error('cancelled by user'));
1091
+ transcript.append('(cancelling current turn… press Ctrl-C again to exit)');
1092
+ tui.requestRender();
1093
+ return;
1094
+ }
1095
+ void (onExit ? onExit() : Promise.resolve()).finally(() => {
1096
+ tui.stop();
1097
+ process.exit(130);
1098
+ });
1099
+ };
1100
+ // Ctrl-C aborts the current turn; Ctrl-D (on an empty editor) exits. Use pi's
1101
+ // key matcher so kitty/meta key encodings are recognized, not just raw bytes.
1102
+ tui.addInputListener((data) => {
1103
+ if (matchesKey(data, 'ctrl+c')) {
1104
+ cancelTurn();
1105
+ return { consume: true };
1106
+ }
1107
+ if (matchesKey(data, 'ctrl+d') && !holder.turn && (editor?.getText().length ?? 0) === 0) {
1108
+ cancelTurn();
1109
+ return { consume: true };
1110
+ }
1111
+ return undefined;
1112
+ });
1113
+ // Fallback for terminals/raw-mode setups that deliver SIGINT instead of \x03.
1114
+ process.on('SIGINT', cancelTurn);
1115
+ createEditor();
1116
+ for (const line of runtime.inputHistory)
1117
+ editor?.addToHistory(line);
1118
+ tui.addChild(transcript);
1119
+ tui.addChild(new Spacer(1));
1120
+ tui.addChild(editorContainer);
1121
+ tui.addChild(statusBar);
1122
+ tui.start();
1123
+ await printBanner(runtime, loaded);
1124
+ tui.requestRender();
1125
+ try {
1126
+ while (true) {
1127
+ const line = await ask(runtime.promptPrefix);
1128
+ if (line === null)
1129
+ break;
1130
+ const input = classifyReplInput(line);
1131
+ if (input.kind === 'halt')
1132
+ break;
1133
+ const controller = new AbortController();
1134
+ holder.turn = controller;
1135
+ runState.running = true;
1136
+ runState.steerable = input.kind === 'prompt';
1137
+ bufferedInput.length = 0;
1138
+ tui.requestRender();
1139
+ try {
1140
+ if (input.kind === 'goal') {
1141
+ await runInteractiveGoal(runtime.repl, runtime.renderer, input.goal, options, ask, controller.signal);
1142
+ }
1143
+ else {
1144
+ const keepGoing = await dispatchReplInput(runtime, input, {
1145
+ first: options.first,
1146
+ exportPath: options.exportPath,
1147
+ signal: controller.signal,
1148
+ });
1149
+ if (!keepGoing)
1150
+ break;
1151
+ }
1152
+ }
1153
+ finally {
1154
+ holder.turn = null;
1155
+ runState.running = false;
1156
+ tui.requestRender();
1157
+ }
1158
+ }
1159
+ }
1160
+ finally {
1161
+ tui.stop();
1162
+ process.off('SIGINT', cancelTurn);
1163
+ process.stdout.off('resize', onResize);
1164
+ // Restore stdout so the exit footer and any later output are visible.
1165
+ runtime.renderer.setSink((text) => process.stdout.write(text), (text) => process.stderr.write(text));
1166
+ }
1167
+ return 0;
1168
+ }
1169
+ /** Entry point for `deepclause repl`. */
1170
+ export async function startRepl(options) {
1171
+ let runtime;
1172
+ try {
1173
+ runtime = await createReplRuntime(options);
1174
+ }
1175
+ catch (error) {
1176
+ process.stderr.write(`ERROR: ${error.message}\n`);
1177
+ return 1;
1178
+ }
1179
+ let bannerShown = false;
1180
+ // Save the harness to a temp bundle on exit (halt./Ctrl-D/Ctrl-C). Skipped
1181
+ // when an explicit --export path was given, when disabled, when already
1182
+ // saved, or when the harness has no predicates.
1183
+ let exitSaved = false;
1184
+ const saveHarnessOnExit = async () => {
1185
+ if (exitSaved || options.exportPath || options.saveOnExit === false)
1186
+ return;
1187
+ exitSaved = true;
1188
+ try {
1189
+ if (runtime.repl.session.isEmpty())
1190
+ return;
1191
+ const saved = await runtime.repl.session.saveCurrentHarness('exit', 'repl exit');
1192
+ if (saved)
1193
+ runtime.renderer.writeLine(`harness saved to ${saved}`);
1194
+ }
1195
+ catch {
1196
+ // best effort
1197
+ }
1198
+ };
1199
+ try {
1200
+ const loadExit = await executeLoad(runtime, options.load ?? []);
1201
+ if (loadExit !== 0) {
1202
+ return loadExit;
1203
+ }
1204
+ // A harness file loaded into a fresh harness becomes its baseline.
1205
+ if (options.load?.length) {
1206
+ await runtime.repl.session.finalizeHarnessBaseline();
1207
+ }
1208
+ // Freeze/lock only after all `-l` loads succeed (load before lock).
1209
+ if (options.locked) {
1210
+ await runtime.repl.lock();
1211
+ }
1212
+ else if (options.freeze) {
1213
+ await runtime.repl.freeze();
1214
+ }
1215
+ const goals = options.goal ?? [];
1216
+ if (goals.length > 0) {
1217
+ let exitCode = 0;
1218
+ const controller = new AbortController();
1219
+ const removeSigint = installSigint(runtime, { turn: controller });
1220
+ try {
1221
+ for (const goal of goals) {
1222
+ if (controller.signal.aborted)
1223
+ break;
1224
+ const code = await runGoalWithRenderer(runtime.repl, goal, {
1225
+ first: options.first,
1226
+ renderer: runtime.renderer,
1227
+ signal: controller.signal,
1228
+ });
1229
+ if (code !== 0)
1230
+ exitCode = code;
1231
+ }
1232
+ }
1233
+ finally {
1234
+ removeSigint();
1235
+ }
1236
+ return exitCode;
1237
+ }
1238
+ if (!process.stdin.isTTY) {
1239
+ return await runNonInteractive(runtime, options, saveHarnessOnExit);
1240
+ }
1241
+ bannerShown = true;
1242
+ const tuiMode = runtime.tuiMode;
1243
+ const wantTui = tuiMode !== 'off'
1244
+ && !options.plain
1245
+ && !options.json
1246
+ && (tuiMode === 'on' || Boolean(process.stdout.isTTY));
1247
+ if (wantTui) {
1248
+ return await runTui(runtime, options, saveHarnessOnExit, options.load ?? []);
1249
+ }
1250
+ await printBanner(runtime, options.load ?? []);
1251
+ return await runInteractive(runtime, options, saveHarnessOnExit);
1252
+ }
1253
+ finally {
1254
+ await saveHarnessOnExit();
1255
+ if (bannerShown) {
1256
+ const footer = runtime.historyEnabled
1257
+ ? `session ${runtime.sessionId} · resume: deepclause repl --session ${runtime.sessionId}`
1258
+ : `session ${runtime.sessionId} (history disabled; this session is not resumable)`;
1259
+ runtime.renderer.writeLine(`\n${footer}`);
1260
+ }
1261
+ if (options.exportPath) {
1262
+ try {
1263
+ const result = await runtime.repl.export(options.exportPath);
1264
+ runtime.renderer.writeLine(`exported ${result.clauses} clauses, ${result.tools} tools, ${result.scripts} scripts → ${result.path}`);
1265
+ }
1266
+ catch (error) {
1267
+ runtime.renderer.writeErrorLine(`ERROR: ${error.message}`);
1268
+ }
1269
+ }
1270
+ runtime.renderer.dispose();
1271
+ await runtime.repl.dispose();
1272
+ await runtime.toolset.dispose();
1273
+ }
1274
+ }
1275
+ /** Register the `repl` command on the CLI program. */
1276
+ export function registerReplCommand(program) {
1277
+ program
1278
+ .command('repl')
1279
+ .description('Interactive DML REPL (goal mode; English prompts build the harness)')
1280
+ .option('-g, --goal <goal>', 'run one goal and exit (repeatable)', collect)
1281
+ .option('-l, --load <file>', 'load a DML file before starting (repeatable)', collect)
1282
+ .option('--session <id>', 'resume a previous session by id (or "last")')
1283
+ .option('--harness <name>', 'harness to edit (a project can hold several; default main)')
1284
+ .option('--first', 'with a goal, print only the first solution')
1285
+ .option('--plain', 'no TUI formatting (auto when stdout is not a TTY)')
1286
+ .option('--json', 'emit results as JSON lines')
1287
+ .option('--workspace <path>', 'workspace root for file operations', './')
1288
+ .option('--model <model>', 'override the configured run model')
1289
+ .option('--provider <provider>', 'override the configured provider')
1290
+ .option('--thinking <level>', 'thinking level for reasoning models (off|minimal|low|medium|high|xhigh|max)')
1291
+ .option('--temperature <number>', 'override temperature (0.0-2.0)', parseFloat)
1292
+ .option('--gas-limit <number>', 'per-frame step limit', parseInt)
1293
+ .option('--depth <number>', 'maximum nested frame depth (default 3)', parseInt)
1294
+ .option('--max-model-calls <number>', 'maximum model calls per turn (default 100)', parseInt)
1295
+ .option('--backend <kind>', 'override the configured backend (env|pi)')
1296
+ .option('--no-stream', 'hide streamed model text/reasoning (show only outputs and solutions)')
1297
+ .option('--show-reasoning <mode>', 'how much model reasoning to show: off|summary|full', (value) => {
1298
+ if (value !== 'off' && value !== 'summary' && value !== 'full') {
1299
+ throw new Error(`invalid --show-reasoning: ${value} (use off, summary, or full)`);
1300
+ }
1301
+ return value;
1302
+ })
1303
+ .option('--no-events', 'hide task activity, tool calls, usage, and logs')
1304
+ .option('--no-save-on-exit', 'do not save the harness to a temp bundle on exit')
1305
+ .option('--judge <name>', 'default judgment backend (llm|jev)')
1306
+ .option('--tools <list>', 'comma-separated capability host tools (default: bash)')
1307
+ .option('--no-tools', 'register no capability host tools')
1308
+ .option('--sandbox', 'run shell tools in AgentVM')
1309
+ .option('--no-ask', 'make ask_user / input/2 fail instead of prompting')
1310
+ .option('--image-model <provider:id>', 'image model for generation (default: first available)')
1311
+ .option('--no-open', 'do not hand images to the OS viewer on terminals without inline images')
1312
+ .option('--answer <text>', 'canned answer for any input request')
1313
+ .option('--interactive-input', 'allow input requests to read stdin in non-TTY/--json mode')
1314
+ .option('--tui <mode>', 'interactive UI: auto (TUI on a TTY), on, or off', (value) => {
1315
+ if (value !== 'auto' && value !== 'on' && value !== 'off') {
1316
+ throw new Error(`invalid --tui mode: ${value} (use auto, on, or off)`);
1317
+ }
1318
+ return value;
1319
+ })
1320
+ .option('-P, --param <key=value>', 'session parameter (repeatable)', collectParams, {})
1321
+ .option('-f, --freeze', 'start with the harness frozen (read-only lock)')
1322
+ .option('--locked', 'like --freeze, but forbid unfreeze for this process')
1323
+ .option('--export <file>', 'export the harness on exit')
1324
+ .option('--verbose', 'show debug output')
1325
+ .option('--debug', 'show debug output')
1326
+ .option('--no-color', 'disable ANSI color')
1327
+ .action(async (options) => {
1328
+ const exitCode = await startRepl({
1329
+ goal: options.goal,
1330
+ load: options.load,
1331
+ session: options.session,
1332
+ first: options.first,
1333
+ plain: options.plain,
1334
+ json: options.json,
1335
+ workspace: options.workspace,
1336
+ model: options.model,
1337
+ provider: options.provider,
1338
+ thinking: options.thinking,
1339
+ temperature: options.temperature,
1340
+ gasLimit: options.gasLimit,
1341
+ depth: options.depth,
1342
+ maxModelCalls: options.maxModelCalls,
1343
+ backend: options.backend,
1344
+ stream: options.stream,
1345
+ showReasoning: options.showReasoning,
1346
+ events: options.events,
1347
+ saveOnExit: options.saveOnExit,
1348
+ judge: options.judge,
1349
+ tools: typeof options.tools === 'string' ? options.tools : undefined,
1350
+ noTools: options.tools === false,
1351
+ sandbox: options.sandbox,
1352
+ noAsk: options.ask === false,
1353
+ imageModel: options.imageModel,
1354
+ open: options.open,
1355
+ answer: options.answer,
1356
+ interactiveInput: options.interactiveInput,
1357
+ tui: options.tui,
1358
+ param: options.param,
1359
+ verbose: Boolean(options.verbose || options.debug),
1360
+ color: options.color,
1361
+ freeze: options.freeze,
1362
+ locked: options.locked,
1363
+ exportPath: options.export,
1364
+ });
1365
+ if (exitCode !== 0) {
1366
+ process.exit(exitCode);
1367
+ }
1368
+ });
1369
+ }
1370
+ function collect(value, previous) {
1371
+ return [...(previous ?? []), value];
1372
+ }
1373
+ /** Slug a harness name (letters, digits, `.`, `_`, `-`); undefined when empty. */
1374
+ export function normalizeHarnessName(name) {
1375
+ const trimmed = name?.trim();
1376
+ if (!trimmed)
1377
+ return undefined;
1378
+ const slug = trimmed.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');
1379
+ return slug || undefined;
1380
+ }
1381
+ /** Harness name implied by a `-l` file: its basename without the `.dml`/`.zip`. */
1382
+ function harnessNameFromFile(file) {
1383
+ return normalizeHarnessName(path.basename(file).replace(/\.(dml|zip)$/i, ''));
1384
+ }
1385
+ function collectParams(value, previous) {
1386
+ const [key, val] = value.split('=');
1387
+ if (!key || val === undefined) {
1388
+ throw new Error(`Invalid param format: ${value}. Use key=value`);
1389
+ }
1390
+ return { ...previous, [key]: val };
1391
+ }
1392
+ //# sourceMappingURL=repl.js.map