@knightcodeai/cli 0.17.3 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/CHANGELOG.md +1293 -0
  2. package/README.md +2 -2
  3. package/dist/bundle/chunks/anthropic-messages-A2IH2IY3.js +6 -0
  4. package/dist/bundle/chunks/anthropic.js +101 -0
  5. package/dist/bundle/chunks/azure-openai-responses-XZILQZOG.js +2 -0
  6. package/dist/bundle/chunks/bedrock-converse-stream.js +89 -0
  7. package/dist/bundle/chunks/chunk-4CRI7BMY.js +2 -0
  8. package/dist/bundle/chunks/chunk-57BPR4HF.js +2 -0
  9. package/dist/bundle/chunks/chunk-A5LJINMT.js +2 -0
  10. package/dist/bundle/chunks/chunk-AANBXVWQ.js +2 -0
  11. package/dist/bundle/chunks/chunk-ACHHEROF.js +7 -0
  12. package/dist/bundle/chunks/chunk-AFR2OW6S.js +108 -0
  13. package/dist/bundle/chunks/chunk-AHB7JV5B.js +2 -0
  14. package/dist/bundle/chunks/chunk-B2FT7RH5.js +2 -0
  15. package/dist/bundle/chunks/chunk-BRKUE2MD.js +2 -0
  16. package/dist/bundle/chunks/chunk-DKOURXGU.js +2 -0
  17. package/dist/bundle/chunks/chunk-DVFCXHT4.js +2 -0
  18. package/dist/bundle/chunks/chunk-EPYT326Y.js +30 -0
  19. package/dist/bundle/chunks/chunk-ETOMYYZC.js +2 -0
  20. package/dist/bundle/chunks/chunk-FA32X2UJ.js +7 -0
  21. package/dist/bundle/chunks/chunk-FHM6KUY3.js +44 -0
  22. package/dist/bundle/chunks/chunk-FO7CEQB3.js +11 -0
  23. package/dist/bundle/chunks/chunk-GHVXUCGU.js +130 -0
  24. package/dist/bundle/chunks/chunk-HHWP56PR.js +2 -0
  25. package/dist/bundle/chunks/chunk-HSL72VZI.js +2 -0
  26. package/dist/bundle/chunks/chunk-HUQB436V.js +2 -0
  27. package/dist/bundle/chunks/chunk-HWXGL4YB.js +2 -0
  28. package/dist/bundle/chunks/chunk-KQKUEQJA.js +6 -0
  29. package/dist/bundle/chunks/chunk-M5GCRGV6.js +1333 -0
  30. package/dist/bundle/chunks/chunk-MB4QRS7Y.js +9 -0
  31. package/dist/bundle/chunks/chunk-MXIWLMNT.js +8 -0
  32. package/dist/bundle/chunks/chunk-N6VDWLND.js +2 -0
  33. package/dist/bundle/chunks/chunk-NCIBB66G.js +2 -0
  34. package/dist/bundle/chunks/chunk-NDQ5OW6U.js +2 -0
  35. package/dist/bundle/chunks/chunk-NNCIIYD6.js +2 -0
  36. package/dist/bundle/chunks/chunk-O6JSP6YG.js +6 -0
  37. package/dist/bundle/chunks/chunk-OFST7FDG.js +2 -0
  38. package/dist/bundle/chunks/chunk-SPAUY3RD.js +12 -0
  39. package/dist/bundle/chunks/chunk-U5XE4RWR.js +2 -0
  40. package/dist/bundle/chunks/chunk-UZWP3NHW.js +2 -0
  41. package/dist/bundle/chunks/chunk-V5ANFNKF.js +11 -0
  42. package/dist/bundle/chunks/chunk-VJJ6J35N.js +20 -0
  43. package/dist/bundle/chunks/chunk-XIRAUXOE.js +58 -0
  44. package/dist/bundle/chunks/chunk-ZW7FVRHT.js +31 -0
  45. package/dist/bundle/chunks/cli-6ZTWO6GL.js +51 -0
  46. package/dist/bundle/chunks/cloudflare-workers-ai-system-one-6E2UM7IG.js +2 -0
  47. package/dist/bundle/chunks/codemode-worker.js +478 -0
  48. package/dist/bundle/chunks/dist-DKI7Q6GY.js +13 -0
  49. package/dist/bundle/chunks/easter-egg-3d-FDCGDUF3.js +2 -0
  50. package/dist/bundle/chunks/execute-VSDU5VSN.js +499 -0
  51. package/dist/bundle/chunks/github-copilot.js +2 -0
  52. package/dist/bundle/chunks/google-generative-ai-CSWR3ORZ.js +2 -0
  53. package/dist/bundle/chunks/google-vertex-QIDRFOFB.js +2 -0
  54. package/dist/bundle/chunks/image-resize-worker.js +2 -0
  55. package/dist/bundle/chunks/jiti-loader-BXVX3Y2R.js +21 -0
  56. package/dist/bundle/chunks/jiti-static-loader-NNBCFAO5.js +2 -0
  57. package/dist/bundle/chunks/kimi-coding.js +2 -0
  58. package/dist/bundle/chunks/knightcode-messages-3GNOTMSI.js +8 -0
  59. package/dist/bundle/chunks/lib-IHWMWJDR.js +9 -0
  60. package/dist/bundle/chunks/llama-cpp-classify-HRPRKOZA.js +22 -0
  61. package/dist/bundle/chunks/meta.js +2 -0
  62. package/dist/bundle/chunks/mistral-conversations-46TK5OBA.js +5 -0
  63. package/dist/bundle/chunks/multipart-parser-VTTMF2EO.js +3 -0
  64. package/dist/bundle/chunks/node-7S3GFMOZ.js +16 -0
  65. package/dist/bundle/chunks/node-MAEICQNS.js +2 -0
  66. package/dist/bundle/chunks/openai-chatgpt.js +101 -0
  67. package/dist/bundle/chunks/openai-codex-responses-IT7VEKUU.js +10 -0
  68. package/dist/bundle/chunks/openai-codex.js +101 -0
  69. package/dist/bundle/chunks/openai-completions-LOKIW33V.js +7 -0
  70. package/dist/bundle/chunks/openai-decisions-4F3LWHMG.js +5 -0
  71. package/dist/bundle/chunks/openai-responses-OK36GJXP.js +3 -0
  72. package/dist/bundle/chunks/openrouter-images-ZI7LLZ4Y.js +2 -0
  73. package/dist/bundle/chunks/openrouter.js +101 -0
  74. package/dist/bundle/chunks/radius.js +101 -0
  75. package/dist/bundle/chunks/runtime-RMPIV5AR.js +2 -0
  76. package/dist/bundle/chunks/src-ETRMT73D.js +4 -0
  77. package/dist/bundle/chunks/typesafe-system-one-J2CJBWEH.js +2 -0
  78. package/dist/bundle/chunks/virtual-modules-EGJP3AJA.js +2 -0
  79. package/dist/bundle/chunks/xai.js +2 -0
  80. package/dist/bundle/cli-runtime.js +3 -0
  81. package/dist/bundle/cli.js +5 -0
  82. package/dist/bundle/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
  83. package/dist/bundle/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
  84. package/dist/bundle/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
  85. package/dist/bundle/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
  86. package/dist/bundle/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
  87. package/dist/bundle/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
  88. package/dist/core/export-html/template.css +1015 -0
  89. package/dist/core/export-html/template.html +55 -0
  90. package/dist/core/export-html/template.js +1929 -0
  91. package/dist/core/export-html/vendor/highlight.min.js +1213 -0
  92. package/dist/core/export-html/vendor/marked.min.js +78 -0
  93. package/dist/modes/interactive/theme/dark.json +74 -0
  94. package/dist/modes/interactive/theme/light.json +74 -0
  95. package/dist/modes/interactive/theme/system-theme.ts +579 -0
  96. package/dist/modes/interactive/theme/theme-controller.ts +253 -0
  97. package/dist/modes/interactive/theme/theme-schema.ts +145 -0
  98. package/dist/modes/interactive/theme/theme-tokens.ts +145 -0
  99. package/dist/modes/interactive/theme/theme.ts +1076 -0
  100. package/docs/app-server.md +479 -0
  101. package/docs/app-server.schema.json +8128 -0
  102. package/docs/cli-integration.md +106 -0
  103. package/docs/cli.md +393 -0
  104. package/docs/codemode.md +230 -0
  105. package/docs/compaction.md +463 -0
  106. package/docs/configuration.md +48 -0
  107. package/docs/containerization.md +143 -0
  108. package/docs/custom-provider.md +173 -0
  109. package/docs/desktop.md +73 -0
  110. package/docs/docs.json +236 -0
  111. package/docs/environment-variables.md +138 -0
  112. package/docs/extensions.md +314 -0
  113. package/docs/how-knightcode-works.md +49 -0
  114. package/docs/images/doom-extension.png +0 -0
  115. package/docs/images/interactive-mode.png +0 -0
  116. package/docs/images/tree-view.png +0 -0
  117. package/docs/index.md +39 -0
  118. package/docs/json.md +226 -0
  119. package/docs/keybindings.md +197 -0
  120. package/docs/llama-cpp.md +121 -0
  121. package/docs/mcp.md +272 -0
  122. package/docs/message-types.md +279 -0
  123. package/docs/models-configuration.md +187 -0
  124. package/docs/models.md +221 -0
  125. package/docs/nvidia-rtx-llama-cpp.md +96 -0
  126. package/docs/packages.md +129 -0
  127. package/docs/plan-mode.md +121 -0
  128. package/docs/prompt-templates.md +59 -0
  129. package/docs/providers.md +282 -0
  130. package/docs/quickstart.md +128 -0
  131. package/docs/remote.md +87 -0
  132. package/docs/rpc-commands.md +860 -0
  133. package/docs/rpc-extension-ui.md +214 -0
  134. package/docs/rpc.md +193 -0
  135. package/docs/sdk.md +197 -0
  136. package/docs/security.md +99 -0
  137. package/docs/session-format.md +358 -0
  138. package/docs/sessions.md +135 -0
  139. package/docs/settings.md +220 -0
  140. package/docs/shell-aliases.md +93 -0
  141. package/docs/skills.md +93 -0
  142. package/docs/slash-commands.md +78 -0
  143. package/docs/terminal-setup.md +242 -0
  144. package/docs/termux.md +117 -0
  145. package/docs/themes.md +140 -0
  146. package/docs/tmux.md +55 -0
  147. package/docs/tui.md +125 -0
  148. package/docs/usage.md +167 -0
  149. package/docs/virtual-models.md +114 -0
  150. package/docs/windows.md +67 -0
  151. package/examples/README.md +36 -0
  152. package/examples/extensions/README.md +214 -0
  153. package/examples/extensions/auto-commit-on-exit.ts +55 -0
  154. package/examples/extensions/bash-spawn-hook.ts +30 -0
  155. package/examples/extensions/bookmark.ts +50 -0
  156. package/examples/extensions/border-status-editor.ts +151 -0
  157. package/examples/extensions/built-in-tool-renderer.ts +225 -0
  158. package/examples/extensions/claude-rules.ts +89 -0
  159. package/examples/extensions/commands.ts +72 -0
  160. package/examples/extensions/confirm-destructive.ts +60 -0
  161. package/examples/extensions/custom-compaction.ts +118 -0
  162. package/examples/extensions/custom-footer.ts +66 -0
  163. package/examples/extensions/custom-header.ts +73 -0
  164. package/examples/extensions/custom-provider-anthropic/index.ts +617 -0
  165. package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  166. package/examples/extensions/custom-provider-anthropic/package.json +19 -0
  167. package/examples/extensions/custom-provider-gitlab-duo/index.ts +399 -0
  168. package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  169. package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
  170. package/examples/extensions/debug-provider.ts +90 -0
  171. package/examples/extensions/dirty-repo-guard.ts +56 -0
  172. package/examples/extensions/doom-overlay/README.md +46 -0
  173. package/examples/extensions/doom-overlay/doom/build.sh +152 -0
  174. package/examples/extensions/doom-overlay/doom/doomgeneric_knightcode.c +72 -0
  175. package/examples/extensions/doom-overlay/doom-component.ts +132 -0
  176. package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  177. package/examples/extensions/doom-overlay/doom-keys.ts +104 -0
  178. package/examples/extensions/doom-overlay/index.ts +74 -0
  179. package/examples/extensions/doom-overlay/wad-finder.ts +55 -0
  180. package/examples/extensions/dynamic-resources/SKILL.md +8 -0
  181. package/examples/extensions/dynamic-resources/dynamic.json +75 -0
  182. package/examples/extensions/dynamic-resources/dynamic.md +5 -0
  183. package/examples/extensions/dynamic-resources/index.ts +15 -0
  184. package/examples/extensions/dynamic-tools.ts +74 -0
  185. package/examples/extensions/entry-renderer.ts +41 -0
  186. package/examples/extensions/event-bus.ts +43 -0
  187. package/examples/extensions/file-trigger.ts +41 -0
  188. package/examples/extensions/git-checkpoint.ts +53 -0
  189. package/examples/extensions/git-merge-and-resolve.ts +115 -0
  190. package/examples/extensions/github-issue-autocomplete.ts +185 -0
  191. package/examples/extensions/gondolin/index.ts +531 -0
  192. package/examples/extensions/gondolin/package-lock.json +185 -0
  193. package/examples/extensions/gondolin/package.json +19 -0
  194. package/examples/extensions/handoff.ts +190 -0
  195. package/examples/extensions/hello.ts +26 -0
  196. package/examples/extensions/hidden-thinking-label.ts +53 -0
  197. package/examples/extensions/inline-bash.ts +94 -0
  198. package/examples/extensions/input-transform-streaming.ts +39 -0
  199. package/examples/extensions/input-transform.ts +43 -0
  200. package/examples/extensions/interactive-shell.ts +196 -0
  201. package/examples/extensions/jev-router.ts +113 -0
  202. package/examples/extensions/mac-system-theme.ts +47 -0
  203. package/examples/extensions/message-renderer.ts +59 -0
  204. package/examples/extensions/minimal-mode.ts +353 -0
  205. package/examples/extensions/modal-editor.ts +85 -0
  206. package/examples/extensions/model-status.ts +31 -0
  207. package/examples/extensions/notify.ts +57 -0
  208. package/examples/extensions/overlay-qa-tests.ts +1450 -0
  209. package/examples/extensions/overlay-test.ts +153 -0
  210. package/examples/extensions/permission-gate.ts +34 -0
  211. package/examples/extensions/pirate.ts +47 -0
  212. package/examples/extensions/plan-mode/README.md +66 -0
  213. package/examples/extensions/plan-mode/index.ts +387 -0
  214. package/examples/extensions/plan-mode/utils.ts +168 -0
  215. package/examples/extensions/preset.ts +436 -0
  216. package/examples/extensions/project-trust.ts +64 -0
  217. package/examples/extensions/prompt-customizer.ts +49 -0
  218. package/examples/extensions/protected-paths.ts +30 -0
  219. package/examples/extensions/provider-payload.ts +18 -0
  220. package/examples/extensions/qna.ts +118 -0
  221. package/examples/extensions/question.ts +278 -0
  222. package/examples/extensions/questionnaire.ts +440 -0
  223. package/examples/extensions/rainbow-editor.ts +88 -0
  224. package/examples/extensions/reload-runtime.ts +37 -0
  225. package/examples/extensions/rpc-demo.ts +118 -0
  226. package/examples/extensions/sandbox/index.ts +321 -0
  227. package/examples/extensions/sandbox/package-lock.json +92 -0
  228. package/examples/extensions/sandbox/package.json +19 -0
  229. package/examples/extensions/send-user-message.ts +97 -0
  230. package/examples/extensions/session-name.ts +27 -0
  231. package/examples/extensions/shutdown-command.ts +63 -0
  232. package/examples/extensions/snake.ts +343 -0
  233. package/examples/extensions/space-invaders.ts +560 -0
  234. package/examples/extensions/ssh.ts +220 -0
  235. package/examples/extensions/status-line.ts +32 -0
  236. package/examples/extensions/structured-output.ts +65 -0
  237. package/examples/extensions/subagent/README.md +177 -0
  238. package/examples/extensions/subagent/agents/planner.md +37 -0
  239. package/examples/extensions/subagent/agents/reviewer.md +35 -0
  240. package/examples/extensions/subagent/agents/scout.md +50 -0
  241. package/examples/extensions/subagent/agents/worker.md +24 -0
  242. package/examples/extensions/subagent/agents.ts +157 -0
  243. package/examples/extensions/subagent/index.ts +1010 -0
  244. package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  245. package/examples/extensions/subagent/prompts/implement.md +10 -0
  246. package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  247. package/examples/extensions/summarize.ts +199 -0
  248. package/examples/extensions/system-prompt-header.ts +17 -0
  249. package/examples/extensions/tic-tac-toe.ts +1004 -0
  250. package/examples/extensions/timed-confirm.ts +70 -0
  251. package/examples/extensions/titlebar-spinner.ts +58 -0
  252. package/examples/extensions/todo.ts +294 -0
  253. package/examples/extensions/tool-override.ts +144 -0
  254. package/examples/extensions/tools.ts +146 -0
  255. package/examples/extensions/trigger-compact.ts +50 -0
  256. package/examples/extensions/truncated-tool.ts +195 -0
  257. package/examples/extensions/widget-placement.ts +9 -0
  258. package/examples/extensions/with-deps/index.ts +32 -0
  259. package/examples/extensions/with-deps/package-lock.json +31 -0
  260. package/examples/extensions/with-deps/package.json +22 -0
  261. package/examples/extensions/working-indicator.ts +121 -0
  262. package/examples/extensions/working-message-test.ts +25 -0
  263. package/examples/rpc-client.ts +35 -0
  264. package/examples/rpc-extension-ui.ts +661 -0
  265. package/examples/sdk/01-minimal.ts +26 -0
  266. package/examples/sdk/02-custom-model.ts +49 -0
  267. package/examples/sdk/03-custom-prompt.ts +70 -0
  268. package/examples/sdk/04-skills.ts +55 -0
  269. package/examples/sdk/05-tools.ts +48 -0
  270. package/examples/sdk/06-extensions.ts +94 -0
  271. package/examples/sdk/07-context-files.ts +42 -0
  272. package/examples/sdk/08-prompt-templates.ts +51 -0
  273. package/examples/sdk/09-api-keys-and-oauth.ts +34 -0
  274. package/examples/sdk/10-settings.ts +53 -0
  275. package/examples/sdk/11-sessions.ts +52 -0
  276. package/examples/sdk/12-full-control.ts +74 -0
  277. package/examples/sdk/13-session-runtime.ts +67 -0
  278. package/examples/sdk/14-codemode-mcp.ts +61 -0
  279. package/examples/sdk/README.md +141 -0
  280. package/package.json +15 -15
  281. package/bin/knightcode +0 -62
@@ -0,0 +1,661 @@
1
+ /**
2
+ * RPC Extension UI Example (TUI)
3
+ *
4
+ * A lightweight TUI chat client that spawns the agent in RPC mode.
5
+ * Demonstrates how to build a custom UI on top of the RPC protocol,
6
+ * including handling extension UI requests (select, confirm, input, editor).
7
+ *
8
+ * Usage: node examples/rpc-extension-ui.ts
9
+ *
10
+ * Slash commands:
11
+ * /select - demo select dialog
12
+ * /confirm - demo confirm dialog
13
+ * /input - demo input dialog
14
+ * /editor - demo editor dialog
15
+ */
16
+
17
+ import { spawn } from "node:child_process";
18
+ import { dirname, join } from "node:path";
19
+ import { StringDecoder } from "node:string_decoder";
20
+ import { fileURLToPath } from "node:url";
21
+ import {
22
+ type Component,
23
+ Container,
24
+ Input,
25
+ matchesKey,
26
+ ProcessTerminal,
27
+ SelectList,
28
+ type TUI,
29
+ TuiMainScreen,
30
+ } from "@knightcode/tui";
31
+
32
+ const __dirname = dirname(fileURLToPath(import.meta.url));
33
+
34
+ // ============================================================================
35
+ // ANSI helpers
36
+ // ============================================================================
37
+
38
+ const GREEN = "\x1b[32m";
39
+ const YELLOW = "\x1b[33m";
40
+ const BLUE = "\x1b[34m";
41
+ const MAGENTA = "\x1b[35m";
42
+ const RED = "\x1b[31m";
43
+ const DIM = "\x1b[2m";
44
+ const BOLD = "\x1b[1m";
45
+ const RESET = "\x1b[0m";
46
+
47
+ // ============================================================================
48
+ // Extension UI request type (subset of rpc-types.ts)
49
+ // ============================================================================
50
+
51
+ interface ExtensionUIRequest {
52
+ type: "extension_ui_request";
53
+ id: string;
54
+ method: string;
55
+ title?: string;
56
+ options?: string[];
57
+ message?: string;
58
+ placeholder?: string;
59
+ prefill?: string;
60
+ notifyType?: "info" | "warning" | "error";
61
+ statusKey?: string;
62
+ statusText?: string;
63
+ widgetKey?: string;
64
+ widgetLines?: string[];
65
+ text?: string;
66
+ }
67
+
68
+ // ============================================================================
69
+ // Output log: accumulates styled lines, renders the tail that fits
70
+ // ============================================================================
71
+
72
+ class OutputLog implements Component {
73
+ private lines: string[] = [];
74
+ private maxLines = 1000;
75
+ private visibleLines = 0;
76
+
77
+ setVisibleLines(n: number): void {
78
+ this.visibleLines = n;
79
+ }
80
+
81
+ append(line: string): void {
82
+ this.lines.push(line);
83
+ if (this.lines.length > this.maxLines) {
84
+ this.lines = this.lines.slice(-this.maxLines);
85
+ }
86
+ }
87
+
88
+ appendRaw(text: string): void {
89
+ if (this.lines.length === 0) {
90
+ this.lines.push(text);
91
+ } else {
92
+ this.lines[this.lines.length - 1] += text;
93
+ }
94
+ }
95
+
96
+ invalidate(): void {}
97
+
98
+ render(width: number): string[] {
99
+ if (this.lines.length === 0) return [""];
100
+ const n = this.visibleLines > 0 ? this.visibleLines : this.lines.length;
101
+ return this.lines.slice(-n).map((l) => l.slice(0, width));
102
+ }
103
+ }
104
+
105
+ // ============================================================================
106
+ // Loading indicator: "Agent: Working." -> ".." -> "..." -> "."
107
+ // ============================================================================
108
+
109
+ class LoadingIndicator implements Component {
110
+ private dots = 1;
111
+ private intervalId: NodeJS.Timeout | null = null;
112
+ private tui: TUI | null = null;
113
+
114
+ start(tui: TUI): void {
115
+ this.tui = tui;
116
+ this.dots = 1;
117
+ this.intervalId = setInterval(() => {
118
+ this.dots = (this.dots % 3) + 1;
119
+ this.tui?.requestRender();
120
+ }, 400);
121
+ }
122
+
123
+ stop(): void {
124
+ if (this.intervalId) {
125
+ clearInterval(this.intervalId);
126
+ this.intervalId = null;
127
+ }
128
+ }
129
+
130
+ invalidate(): void {}
131
+
132
+ render(_width: number): string[] {
133
+ return [`${BLUE}${BOLD}Agent:${RESET} ${DIM}Working${".".repeat(this.dots)}${RESET}`];
134
+ }
135
+ }
136
+
137
+ // ============================================================================
138
+ // Prompt input: label + single-line input
139
+ // ============================================================================
140
+
141
+ class PromptInput implements Component {
142
+ readonly input: Input;
143
+ onCtrlD?: () => void;
144
+
145
+ constructor() {
146
+ this.input = new Input();
147
+ }
148
+
149
+ handleInput(data: string): void {
150
+ if (matchesKey(data, "ctrl+d")) {
151
+ this.onCtrlD?.();
152
+ return;
153
+ }
154
+ this.input.handleInput(data);
155
+ }
156
+
157
+ invalidate(): void {
158
+ this.input.invalidate();
159
+ }
160
+
161
+ render(width: number): string[] {
162
+ return [`${GREEN}${BOLD}You:${RESET}`, ...this.input.render(width)];
163
+ }
164
+ }
165
+
166
+ // ============================================================================
167
+ // Dialog components: replace the prompt input during interactive requests
168
+ // ============================================================================
169
+
170
+ class SelectDialog implements Component {
171
+ private list: SelectList;
172
+ private title: string;
173
+ onSelect?: (value: string) => void;
174
+ onCancel?: () => void;
175
+
176
+ constructor(title: string, options: string[]) {
177
+ this.title = title;
178
+ const items = options.map((o) => ({ value: o, label: o }));
179
+ this.list = new SelectList(items, Math.min(items.length, 8), {
180
+ selectedPrefix: (t) => `${MAGENTA}${t}${RESET}`,
181
+ selectedText: (t) => `${MAGENTA}${t}${RESET}`,
182
+ description: (t) => `${DIM}${t}${RESET}`,
183
+ scrollInfo: (t) => `${DIM}${t}${RESET}`,
184
+ noMatch: (t) => `${YELLOW}${t}${RESET}`,
185
+ });
186
+ this.list.onSelect = (item) => this.onSelect?.(item.value);
187
+ this.list.onCancel = () => this.onCancel?.();
188
+ }
189
+
190
+ handleInput(data: string): void {
191
+ this.list.handleInput(data);
192
+ }
193
+
194
+ invalidate(): void {
195
+ this.list.invalidate();
196
+ }
197
+
198
+ render(width: number): string[] {
199
+ return [
200
+ `${MAGENTA}${BOLD}${this.title}${RESET}`,
201
+ ...this.list.render(width),
202
+ `${DIM}Up/Down, Enter to select, Esc to cancel${RESET}`,
203
+ ];
204
+ }
205
+ }
206
+
207
+ class InputDialog implements Component {
208
+ private dialogInput: Input;
209
+ private title: string;
210
+ onCtrlD?: () => void;
211
+
212
+ constructor(title: string, prefill?: string) {
213
+ this.title = title;
214
+ this.dialogInput = new Input();
215
+ if (prefill) this.dialogInput.setValue(prefill);
216
+ }
217
+
218
+ set onSubmit(fn: ((value: string) => void) | undefined) {
219
+ this.dialogInput.onSubmit = fn;
220
+ }
221
+
222
+ set onEscape(fn: (() => void) | undefined) {
223
+ this.dialogInput.onEscape = fn;
224
+ }
225
+
226
+ get inputComponent(): Input {
227
+ return this.dialogInput;
228
+ }
229
+
230
+ handleInput(data: string): void {
231
+ if (matchesKey(data, "ctrl+d")) {
232
+ this.onCtrlD?.();
233
+ return;
234
+ }
235
+ this.dialogInput.handleInput(data);
236
+ }
237
+
238
+ invalidate(): void {
239
+ this.dialogInput.invalidate();
240
+ }
241
+
242
+ render(width: number): string[] {
243
+ return [
244
+ `${MAGENTA}${BOLD}${this.title}${RESET}`,
245
+ ...this.dialogInput.render(width),
246
+ `${DIM}Enter to submit, Esc to cancel${RESET}`,
247
+ ];
248
+ }
249
+ }
250
+
251
+ // ============================================================================
252
+ // Main
253
+ // ============================================================================
254
+
255
+ async function main() {
256
+ const extensionPath = join(__dirname, "extensions/rpc-demo.ts");
257
+ const cliPath = join(__dirname, "../dist/cli.js");
258
+
259
+ const agent = spawn(
260
+ "node",
261
+ [cliPath, "--mode", "rpc", "--no-session", "--no-extensions", "--extension", extensionPath],
262
+ { stdio: ["pipe", "pipe", "pipe"] },
263
+ );
264
+
265
+ let stderr = "";
266
+ agent.stderr?.on("data", (data: Buffer) => {
267
+ stderr += data.toString();
268
+ });
269
+
270
+ await new Promise((resolve) => setTimeout(resolve, 500));
271
+ if (agent.exitCode !== null) {
272
+ console.error(`Agent exited immediately. Stderr:\n${stderr}`);
273
+ process.exit(1);
274
+ }
275
+
276
+ // -- TUI setup --
277
+
278
+ const terminal = new ProcessTerminal();
279
+ const tui: TUI = new TuiMainScreen(terminal);
280
+
281
+ const outputLog = new OutputLog();
282
+ const loadingIndicator = new LoadingIndicator();
283
+ const promptInput = new PromptInput();
284
+
285
+ const root = new Container();
286
+ root.addChild(outputLog);
287
+ root.addChild(promptInput);
288
+
289
+ tui.addChild(root);
290
+ tui.setFocus(promptInput.input);
291
+
292
+ // -- Agent communication --
293
+
294
+ function send(obj: Record<string, unknown>): void {
295
+ agent.stdin!.write(`${JSON.stringify(obj)}\n`);
296
+ }
297
+
298
+ let isStreaming = false;
299
+ let hasTextOutput = false;
300
+
301
+ function exit(): void {
302
+ tui.stop();
303
+ agent.kill("SIGTERM");
304
+ process.exit(0);
305
+ }
306
+
307
+ // -- Bottom area management --
308
+ // The bottom of the screen is either the prompt input or a dialog.
309
+ // These helpers swap between them.
310
+
311
+ let activeDialog: Component | null = null;
312
+
313
+ function setBottomComponent(component: Component): void {
314
+ root.clear();
315
+ root.addChild(outputLog);
316
+ if (isStreaming) root.addChild(loadingIndicator);
317
+ root.addChild(component);
318
+ tui.setFocus(component);
319
+ tui.requestRender();
320
+ }
321
+
322
+ function showPrompt(): void {
323
+ activeDialog = null;
324
+ setBottomComponent(promptInput);
325
+ tui.setFocus(promptInput.input);
326
+ }
327
+
328
+ function showDialog(dialog: Component): void {
329
+ activeDialog = dialog;
330
+ setBottomComponent(dialog);
331
+ }
332
+
333
+ function showLoading(): void {
334
+ if (!isStreaming) {
335
+ isStreaming = true;
336
+ hasTextOutput = false;
337
+ root.clear();
338
+ root.addChild(outputLog);
339
+ root.addChild(loadingIndicator);
340
+ root.addChild(activeDialog ?? promptInput);
341
+ if (!activeDialog) tui.setFocus(promptInput.input);
342
+ loadingIndicator.start(tui);
343
+ tui.requestRender();
344
+ }
345
+ }
346
+
347
+ function hideLoading(): void {
348
+ loadingIndicator.stop();
349
+ root.clear();
350
+ root.addChild(outputLog);
351
+ root.addChild(activeDialog ?? promptInput);
352
+ if (!activeDialog) tui.setFocus(promptInput.input);
353
+ tui.requestRender();
354
+ }
355
+
356
+ // -- Extension UI dialog handling --
357
+
358
+ function showSelectDialog(title: string, options: string[], onDone: (value: string | undefined) => void): void {
359
+ const dialog = new SelectDialog(title, options);
360
+ dialog.onSelect = (value) => {
361
+ showPrompt();
362
+ onDone(value);
363
+ };
364
+ dialog.onCancel = () => {
365
+ showPrompt();
366
+ onDone(undefined);
367
+ };
368
+ showDialog(dialog);
369
+ }
370
+
371
+ function showInputDialog(title: string, prefill?: string, onDone?: (value: string | undefined) => void): void {
372
+ const dialog = new InputDialog(title, prefill);
373
+ dialog.onSubmit = (value) => {
374
+ showPrompt();
375
+ onDone?.(value.trim() || undefined);
376
+ };
377
+ dialog.onEscape = () => {
378
+ showPrompt();
379
+ onDone?.(undefined);
380
+ };
381
+ dialog.onCtrlD = exit;
382
+ showDialog(dialog);
383
+ tui.setFocus(dialog.inputComponent);
384
+ }
385
+
386
+ function handleExtensionUI(req: ExtensionUIRequest): void {
387
+ const { id, method } = req;
388
+
389
+ switch (method) {
390
+ // Dialog methods: replace prompt with interactive component
391
+ case "select": {
392
+ showSelectDialog(req.title ?? "Select", req.options ?? [], (value) => {
393
+ if (value !== undefined) {
394
+ send({ type: "extension_ui_response", id, value });
395
+ } else {
396
+ send({ type: "extension_ui_response", id, cancelled: true });
397
+ }
398
+ });
399
+ break;
400
+ }
401
+
402
+ case "confirm": {
403
+ const title = req.message ? `${req.title}: ${req.message}` : (req.title ?? "Confirm");
404
+ showSelectDialog(title, ["Yes", "No"], (value) => {
405
+ send({ type: "extension_ui_response", id, confirmed: value === "Yes" });
406
+ });
407
+ break;
408
+ }
409
+
410
+ case "input": {
411
+ const title = req.placeholder ? `${req.title} (${req.placeholder})` : (req.title ?? "Input");
412
+ showInputDialog(title, undefined, (value) => {
413
+ if (value !== undefined) {
414
+ send({ type: "extension_ui_response", id, value });
415
+ } else {
416
+ send({ type: "extension_ui_response", id, cancelled: true });
417
+ }
418
+ });
419
+ break;
420
+ }
421
+
422
+ case "editor": {
423
+ const prefill = req.prefill?.replace(/\n/g, " ");
424
+ showInputDialog(req.title ?? "Editor", prefill, (value) => {
425
+ if (value !== undefined) {
426
+ send({ type: "extension_ui_response", id, value });
427
+ } else {
428
+ send({ type: "extension_ui_response", id, cancelled: true });
429
+ }
430
+ });
431
+ break;
432
+ }
433
+
434
+ // Fire-and-forget methods: display as notification
435
+ case "notify": {
436
+ const notifyType = (req.notifyType as string) ?? "info";
437
+ const color = notifyType === "error" ? RED : notifyType === "warning" ? YELLOW : MAGENTA;
438
+ outputLog.append(`${color}${BOLD}Notification:${RESET} ${req.message}`);
439
+ tui.requestRender();
440
+ break;
441
+ }
442
+
443
+ case "setStatus":
444
+ outputLog.append(
445
+ `${MAGENTA}${BOLD}Notification:${RESET} ${DIM}[status: ${req.statusKey}]${RESET} ${req.statusText ?? "(cleared)"}`,
446
+ );
447
+ tui.requestRender();
448
+ break;
449
+
450
+ case "setWidget": {
451
+ const lines = req.widgetLines;
452
+ if (lines && lines.length > 0) {
453
+ outputLog.append(`${MAGENTA}${BOLD}Notification:${RESET} ${DIM}[widget: ${req.widgetKey}]${RESET}`);
454
+ for (const wl of lines) {
455
+ outputLog.append(` ${DIM}${wl}${RESET}`);
456
+ }
457
+ tui.requestRender();
458
+ }
459
+ break;
460
+ }
461
+
462
+ case "set_editor_text":
463
+ promptInput.input.setValue((req.text as string) ?? "");
464
+ tui.requestRender();
465
+ break;
466
+ }
467
+ }
468
+
469
+ // -- Slash commands (local, not sent to agent) --
470
+
471
+ function handleSlashCommand(cmd: string): boolean {
472
+ switch (cmd) {
473
+ case "/select":
474
+ showSelectDialog("Pick a color", ["Red", "Green", "Blue", "Yellow"], (value) => {
475
+ if (value) {
476
+ outputLog.append(`${MAGENTA}${BOLD}Notification:${RESET} You picked: ${value}`);
477
+ } else {
478
+ outputLog.append(`${MAGENTA}${BOLD}Notification:${RESET} Selection cancelled`);
479
+ }
480
+ tui.requestRender();
481
+ });
482
+ return true;
483
+
484
+ case "/confirm":
485
+ showSelectDialog("Are you sure?", ["Yes", "No"], (value) => {
486
+ const confirmed = value === "Yes";
487
+ outputLog.append(`${MAGENTA}${BOLD}Notification:${RESET} Confirmed: ${confirmed}`);
488
+ tui.requestRender();
489
+ });
490
+ return true;
491
+
492
+ case "/input":
493
+ showInputDialog("Enter your name", undefined, (value) => {
494
+ if (value) {
495
+ outputLog.append(`${MAGENTA}${BOLD}Notification:${RESET} You entered: ${value}`);
496
+ } else {
497
+ outputLog.append(`${MAGENTA}${BOLD}Notification:${RESET} Input cancelled`);
498
+ }
499
+ tui.requestRender();
500
+ });
501
+ return true;
502
+
503
+ case "/editor":
504
+ showInputDialog("Edit text", "Hello, world!", (value) => {
505
+ if (value) {
506
+ outputLog.append(`${MAGENTA}${BOLD}Notification:${RESET} Submitted: ${value}`);
507
+ } else {
508
+ outputLog.append(`${MAGENTA}${BOLD}Notification:${RESET} Editor cancelled`);
509
+ }
510
+ tui.requestRender();
511
+ });
512
+ return true;
513
+
514
+ default:
515
+ return false;
516
+ }
517
+ }
518
+
519
+ // -- Process agent stdout --
520
+
521
+ function handleAgentLine(line: string): void {
522
+ let data: Record<string, unknown>;
523
+ try {
524
+ data = JSON.parse(line);
525
+ } catch {
526
+ return;
527
+ }
528
+
529
+ if (data.type === "response" && !data.success) {
530
+ outputLog.append(`${RED}[error]${RESET} ${data.command}: ${data.error}`);
531
+ tui.requestRender();
532
+ return;
533
+ }
534
+
535
+ if (data.type === "agent_start") {
536
+ showLoading();
537
+ return;
538
+ }
539
+
540
+ if (data.type === "extension_ui_request") {
541
+ handleExtensionUI(data as unknown as ExtensionUIRequest);
542
+ return;
543
+ }
544
+
545
+ if (data.type === "message_update") {
546
+ const evt = data.assistantMessageEvent as Record<string, unknown> | undefined;
547
+ if (evt?.type === "text_delta") {
548
+ if (!hasTextOutput) {
549
+ hasTextOutput = true;
550
+ outputLog.append("");
551
+ outputLog.append(`${BLUE}${BOLD}Agent:${RESET}`);
552
+ }
553
+ const delta = evt.delta as string;
554
+ const parts = delta.split("\n");
555
+ for (let i = 0; i < parts.length; i++) {
556
+ if (i > 0) outputLog.append("");
557
+ if (parts[i]) outputLog.appendRaw(parts[i]);
558
+ }
559
+ tui.requestRender();
560
+ }
561
+ return;
562
+ }
563
+
564
+ if (data.type === "tool_execution_start") {
565
+ outputLog.append(`${DIM}[tool: ${data.toolName}]${RESET}`);
566
+ tui.requestRender();
567
+ return;
568
+ }
569
+
570
+ if (data.type === "tool_execution_end") {
571
+ const result = JSON.stringify(data.result).slice(0, 120);
572
+ outputLog.append(`${DIM}[result: ${result}...]${RESET}`);
573
+ tui.requestRender();
574
+ return;
575
+ }
576
+
577
+ if (data.type === "agent_settled") {
578
+ isStreaming = false;
579
+ hideLoading();
580
+ outputLog.append("");
581
+ tui.requestRender();
582
+ return;
583
+ }
584
+ }
585
+
586
+ const stdoutDecoder = new StringDecoder("utf8");
587
+ let stdoutBuffer = "";
588
+
589
+ agent.stdout!.on("data", (chunk: Buffer) => {
590
+ stdoutBuffer += stdoutDecoder.write(chunk);
591
+ while (true) {
592
+ const newlineIndex = stdoutBuffer.indexOf("\n");
593
+ if (newlineIndex === -1) break;
594
+
595
+ const line = stdoutBuffer.slice(0, newlineIndex);
596
+ stdoutBuffer = stdoutBuffer.slice(newlineIndex + 1);
597
+ handleAgentLine(line.endsWith("\r") ? line.slice(0, -1) : line);
598
+ }
599
+ });
600
+
601
+ agent.stdout!.on("end", () => {
602
+ stdoutBuffer += stdoutDecoder.end();
603
+ if (stdoutBuffer.length > 0) {
604
+ handleAgentLine(stdoutBuffer.endsWith("\r") ? stdoutBuffer.slice(0, -1) : stdoutBuffer);
605
+ }
606
+ });
607
+
608
+ // -- User input --
609
+
610
+ promptInput.input.onSubmit = (value) => {
611
+ const trimmed = value.trim();
612
+ if (!trimmed) return;
613
+
614
+ promptInput.input.setValue("");
615
+
616
+ if (handleSlashCommand(trimmed)) {
617
+ outputLog.append(`${GREEN}${BOLD}You:${RESET} ${trimmed}`);
618
+ tui.requestRender();
619
+ return;
620
+ }
621
+
622
+ outputLog.append(`${GREEN}${BOLD}You:${RESET} ${trimmed}`);
623
+ send({ type: "prompt", message: trimmed });
624
+ tui.requestRender();
625
+ };
626
+
627
+ promptInput.onCtrlD = exit;
628
+
629
+ promptInput.input.onEscape = () => {
630
+ if (isStreaming) {
631
+ send({ type: "abort" });
632
+ outputLog.append(`${YELLOW}[aborted]${RESET}`);
633
+ tui.requestRender();
634
+ } else {
635
+ exit();
636
+ }
637
+ };
638
+
639
+ // -- Agent exit --
640
+
641
+ agent.on("exit", (code) => {
642
+ tui.stop();
643
+ if (stderr) console.error(stderr);
644
+ console.log(`Agent exited with code ${code}`);
645
+ process.exit(code ?? 0);
646
+ });
647
+
648
+ // -- Start --
649
+
650
+ outputLog.append(`${BOLD}RPC Chat${RESET}`);
651
+ outputLog.append(`${DIM}Type a message and press Enter. Esc to abort or exit. Ctrl+D to quit.${RESET}`);
652
+ outputLog.append(`${DIM}Slash commands: /select /confirm /input /editor${RESET}`);
653
+ outputLog.append("");
654
+
655
+ tui.start();
656
+ }
657
+
658
+ main().catch((err) => {
659
+ console.error(err);
660
+ process.exit(1);
661
+ });
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Minimal SDK Usage
3
+ *
4
+ * Uses all defaults: discovers skills, extensions, tools, context files
5
+ * from cwd and ~/.knightcode/agent. Model chosen from settings or first available.
6
+ */
7
+
8
+ import { createAgentSession } from "@knightcodeai/cli";
9
+
10
+ const { session } = await createAgentSession();
11
+
12
+ try {
13
+ session.subscribe((event) => {
14
+ if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
15
+ process.stdout.write(event.assistantMessageEvent.delta);
16
+ }
17
+ });
18
+
19
+ await session.prompt("What files are in the current directory?");
20
+ session.state.messages.forEach((msg) => {
21
+ console.log(msg);
22
+ });
23
+ console.log();
24
+ } finally {
25
+ session.dispose();
26
+ }