@agentuity/cli 0.0.51 → 0.0.52

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 (299) hide show
  1. package/dist/api.js +68 -0
  2. package/dist/api.js.map +1 -0
  3. package/dist/auth.js +225 -0
  4. package/dist/auth.js.map +1 -0
  5. package/dist/banner.js +35 -0
  6. package/dist/banner.js.map +1 -0
  7. package/dist/cli-logger.js +72 -0
  8. package/dist/cli-logger.js.map +1 -0
  9. package/dist/cli.js +822 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/cmd/ai/capabilities/index.js +10 -0
  12. package/dist/cmd/ai/capabilities/index.js.map +1 -0
  13. package/dist/cmd/ai/capabilities/show.js +221 -0
  14. package/dist/cmd/ai/capabilities/show.js.map +1 -0
  15. package/dist/cmd/ai/index.js +11 -0
  16. package/dist/cmd/ai/index.js.map +1 -0
  17. package/dist/cmd/ai/prompt/index.js +10 -0
  18. package/dist/cmd/ai/prompt/index.js.map +1 -0
  19. package/dist/cmd/ai/prompt/llm.js +365 -0
  20. package/dist/cmd/ai/prompt/llm.js.map +1 -0
  21. package/dist/cmd/ai/schema/index.js +10 -0
  22. package/dist/cmd/ai/schema/index.js.map +1 -0
  23. package/dist/cmd/ai/schema/show.js +23 -0
  24. package/dist/cmd/ai/schema/show.js.map +1 -0
  25. package/dist/cmd/auth/api.js +85 -0
  26. package/dist/cmd/auth/api.js.map +1 -0
  27. package/dist/cmd/auth/index.js +13 -0
  28. package/dist/cmd/auth/index.js.map +1 -0
  29. package/dist/cmd/auth/login.js +84 -0
  30. package/dist/cmd/auth/login.js.map +1 -0
  31. package/dist/cmd/auth/logout.js +17 -0
  32. package/dist/cmd/auth/logout.js.map +1 -0
  33. package/dist/cmd/auth/signup.js +55 -0
  34. package/dist/cmd/auth/signup.js.map +1 -0
  35. package/dist/cmd/auth/ssh/add.js +239 -0
  36. package/dist/cmd/auth/ssh/add.js.map +1 -0
  37. package/dist/cmd/auth/ssh/api.js +53 -0
  38. package/dist/cmd/auth/ssh/api.js.map +1 -0
  39. package/dist/cmd/auth/ssh/delete.js +126 -0
  40. package/dist/cmd/auth/ssh/delete.js.map +1 -0
  41. package/dist/cmd/auth/ssh/index.js +11 -0
  42. package/dist/cmd/auth/ssh/index.js.map +1 -0
  43. package/dist/cmd/auth/ssh/list.js +70 -0
  44. package/dist/cmd/auth/ssh/list.js.map +1 -0
  45. package/dist/cmd/auth/whoami.js +68 -0
  46. package/dist/cmd/auth/whoami.js.map +1 -0
  47. package/dist/cmd/build/ast.js +608 -0
  48. package/dist/cmd/build/ast.js.map +1 -0
  49. package/dist/cmd/build/ast.test.js +389 -0
  50. package/dist/cmd/build/ast.test.js.map +1 -0
  51. package/dist/cmd/build/bundler.js +304 -0
  52. package/dist/cmd/build/bundler.js.map +1 -0
  53. package/dist/cmd/build/file.js +10 -0
  54. package/dist/cmd/build/file.js.map +1 -0
  55. package/dist/cmd/build/fix-duplicate-exports.js +167 -0
  56. package/dist/cmd/build/fix-duplicate-exports.js.map +1 -0
  57. package/dist/cmd/build/fix-duplicate-exports.test.js +300 -0
  58. package/dist/cmd/build/fix-duplicate-exports.test.js.map +1 -0
  59. package/dist/cmd/build/index.d.ts.map +1 -1
  60. package/dist/cmd/build/index.js +79 -0
  61. package/dist/cmd/build/index.js.map +1 -0
  62. package/dist/cmd/build/patch/_util.js +42 -0
  63. package/dist/cmd/build/patch/_util.js.map +1 -0
  64. package/dist/cmd/build/patch/aisdk.js +65 -0
  65. package/dist/cmd/build/patch/aisdk.js.map +1 -0
  66. package/dist/cmd/build/patch/index.js +97 -0
  67. package/dist/cmd/build/patch/index.js.map +1 -0
  68. package/dist/cmd/build/patch/llm.js +18 -0
  69. package/dist/cmd/build/patch/llm.js.map +1 -0
  70. package/dist/cmd/build/plugin.d.ts.map +1 -1
  71. package/dist/cmd/build/plugin.js +556 -0
  72. package/dist/cmd/build/plugin.js.map +1 -0
  73. package/dist/cmd/cloud/agents/index.js +133 -0
  74. package/dist/cmd/cloud/agents/index.js.map +1 -0
  75. package/dist/cmd/cloud/deploy.js +341 -0
  76. package/dist/cmd/cloud/deploy.js.map +1 -0
  77. package/dist/cmd/cloud/deployment/index.js +20 -0
  78. package/dist/cmd/cloud/deployment/index.js.map +1 -0
  79. package/dist/cmd/cloud/deployment/list.js +89 -0
  80. package/dist/cmd/cloud/deployment/list.js.map +1 -0
  81. package/dist/cmd/cloud/deployment/remove.js +60 -0
  82. package/dist/cmd/cloud/deployment/remove.js.map +1 -0
  83. package/dist/cmd/cloud/deployment/rollback.js +80 -0
  84. package/dist/cmd/cloud/deployment/rollback.js.map +1 -0
  85. package/dist/cmd/cloud/deployment/show.js +106 -0
  86. package/dist/cmd/cloud/deployment/show.js.map +1 -0
  87. package/dist/cmd/cloud/deployment/undeploy.js +45 -0
  88. package/dist/cmd/cloud/deployment/undeploy.js.map +1 -0
  89. package/dist/cmd/cloud/deployment/utils.js +10 -0
  90. package/dist/cmd/cloud/deployment/utils.js.map +1 -0
  91. package/dist/cmd/cloud/domain.js +77 -0
  92. package/dist/cmd/cloud/domain.js.map +1 -0
  93. package/dist/cmd/cloud/env/delete.js +50 -0
  94. package/dist/cmd/cloud/env/delete.js.map +1 -0
  95. package/dist/cmd/cloud/env/get.js +65 -0
  96. package/dist/cmd/cloud/env/get.js.map +1 -0
  97. package/dist/cmd/cloud/env/import.js +113 -0
  98. package/dist/cmd/cloud/env/import.js.map +1 -0
  99. package/dist/cmd/cloud/env/index.js +24 -0
  100. package/dist/cmd/cloud/env/index.js.map +1 -0
  101. package/dist/cmd/cloud/env/list.js +58 -0
  102. package/dist/cmd/cloud/env/list.js.map +1 -0
  103. package/dist/cmd/cloud/env/pull.js +81 -0
  104. package/dist/cmd/cloud/env/pull.js.map +1 -0
  105. package/dist/cmd/cloud/env/push.js +61 -0
  106. package/dist/cmd/cloud/env/push.js.map +1 -0
  107. package/dist/cmd/cloud/env/set.js +73 -0
  108. package/dist/cmd/cloud/env/set.js.map +1 -0
  109. package/dist/cmd/cloud/index.js +31 -0
  110. package/dist/cmd/cloud/index.js.map +1 -0
  111. package/dist/cmd/cloud/keyvalue/create-namespace.js +41 -0
  112. package/dist/cmd/cloud/keyvalue/create-namespace.js.map +1 -0
  113. package/dist/cmd/cloud/keyvalue/delete-namespace.js +64 -0
  114. package/dist/cmd/cloud/keyvalue/delete-namespace.js.map +1 -0
  115. package/dist/cmd/cloud/keyvalue/delete.js +47 -0
  116. package/dist/cmd/cloud/keyvalue/delete.js.map +1 -0
  117. package/dist/cmd/cloud/keyvalue/get.js +65 -0
  118. package/dist/cmd/cloud/keyvalue/get.js.map +1 -0
  119. package/dist/cmd/cloud/keyvalue/index.js +32 -0
  120. package/dist/cmd/cloud/keyvalue/index.js.map +1 -0
  121. package/dist/cmd/cloud/keyvalue/keys.js +50 -0
  122. package/dist/cmd/cloud/keyvalue/keys.js.map +1 -0
  123. package/dist/cmd/cloud/keyvalue/list-namespaces.js +37 -0
  124. package/dist/cmd/cloud/keyvalue/list-namespaces.js.map +1 -0
  125. package/dist/cmd/cloud/keyvalue/repl.js +277 -0
  126. package/dist/cmd/cloud/keyvalue/repl.js.map +1 -0
  127. package/dist/cmd/cloud/keyvalue/search.js +72 -0
  128. package/dist/cmd/cloud/keyvalue/search.js.map +1 -0
  129. package/dist/cmd/cloud/keyvalue/set.js +59 -0
  130. package/dist/cmd/cloud/keyvalue/set.js.map +1 -0
  131. package/dist/cmd/cloud/keyvalue/stats.js +82 -0
  132. package/dist/cmd/cloud/keyvalue/stats.js.map +1 -0
  133. package/dist/cmd/cloud/keyvalue/util.js +19 -0
  134. package/dist/cmd/cloud/keyvalue/util.js.map +1 -0
  135. package/dist/cmd/cloud/objectstore/delete-bucket.js +66 -0
  136. package/dist/cmd/cloud/objectstore/delete-bucket.js.map +1 -0
  137. package/dist/cmd/cloud/objectstore/delete.js +56 -0
  138. package/dist/cmd/cloud/objectstore/delete.js.map +1 -0
  139. package/dist/cmd/cloud/objectstore/get.js +64 -0
  140. package/dist/cmd/cloud/objectstore/get.js.map +1 -0
  141. package/dist/cmd/cloud/objectstore/index.js +28 -0
  142. package/dist/cmd/cloud/objectstore/index.js.map +1 -0
  143. package/dist/cmd/cloud/objectstore/list-buckets.js +37 -0
  144. package/dist/cmd/cloud/objectstore/list-buckets.js.map +1 -0
  145. package/dist/cmd/cloud/objectstore/list-keys.js +52 -0
  146. package/dist/cmd/cloud/objectstore/list-keys.js.map +1 -0
  147. package/dist/cmd/cloud/objectstore/put.js +57 -0
  148. package/dist/cmd/cloud/objectstore/put.js.map +1 -0
  149. package/dist/cmd/cloud/objectstore/repl.js +219 -0
  150. package/dist/cmd/cloud/objectstore/repl.js.map +1 -0
  151. package/dist/cmd/cloud/objectstore/url.js +55 -0
  152. package/dist/cmd/cloud/objectstore/url.js.map +1 -0
  153. package/dist/cmd/cloud/objectstore/util.js +18 -0
  154. package/dist/cmd/cloud/objectstore/util.js.map +1 -0
  155. package/dist/cmd/cloud/resource/add.js +70 -0
  156. package/dist/cmd/cloud/resource/add.js.map +1 -0
  157. package/dist/cmd/cloud/resource/delete.js +126 -0
  158. package/dist/cmd/cloud/resource/delete.js.map +1 -0
  159. package/dist/cmd/cloud/resource/index.js +12 -0
  160. package/dist/cmd/cloud/resource/index.js.map +1 -0
  161. package/dist/cmd/cloud/resource/list.js +89 -0
  162. package/dist/cmd/cloud/resource/list.js.map +1 -0
  163. package/dist/cmd/cloud/scp/download.js +72 -0
  164. package/dist/cmd/cloud/scp/download.js.map +1 -0
  165. package/dist/cmd/cloud/scp/index.js +10 -0
  166. package/dist/cmd/cloud/scp/index.js.map +1 -0
  167. package/dist/cmd/cloud/scp/upload.js +75 -0
  168. package/dist/cmd/cloud/scp/upload.js.map +1 -0
  169. package/dist/cmd/cloud/secret/delete.js +50 -0
  170. package/dist/cmd/cloud/secret/delete.js.map +1 -0
  171. package/dist/cmd/cloud/secret/get.js +69 -0
  172. package/dist/cmd/cloud/secret/get.js.map +1 -0
  173. package/dist/cmd/cloud/secret/import.js +88 -0
  174. package/dist/cmd/cloud/secret/import.js.map +1 -0
  175. package/dist/cmd/cloud/secret/index.js +24 -0
  176. package/dist/cmd/cloud/secret/index.js.map +1 -0
  177. package/dist/cmd/cloud/secret/list.js +58 -0
  178. package/dist/cmd/cloud/secret/list.js.map +1 -0
  179. package/dist/cmd/cloud/secret/pull.js +81 -0
  180. package/dist/cmd/cloud/secret/pull.js.map +1 -0
  181. package/dist/cmd/cloud/secret/push.js +61 -0
  182. package/dist/cmd/cloud/secret/push.js.map +1 -0
  183. package/dist/cmd/cloud/secret/set.js +57 -0
  184. package/dist/cmd/cloud/secret/set.js.map +1 -0
  185. package/dist/cmd/cloud/session/get.d.ts.map +1 -1
  186. package/dist/cmd/cloud/session/get.js +155 -0
  187. package/dist/cmd/cloud/session/get.js.map +1 -0
  188. package/dist/cmd/cloud/session/index.js +11 -0
  189. package/dist/cmd/cloud/session/index.js.map +1 -0
  190. package/dist/cmd/cloud/session/list.js +132 -0
  191. package/dist/cmd/cloud/session/list.js.map +1 -0
  192. package/dist/cmd/cloud/session/logs.js +56 -0
  193. package/dist/cmd/cloud/session/logs.js.map +1 -0
  194. package/dist/cmd/cloud/ssh.js +67 -0
  195. package/dist/cmd/cloud/ssh.js.map +1 -0
  196. package/dist/cmd/dev/agents.js +103 -0
  197. package/dist/cmd/dev/agents.js.map +1 -0
  198. package/dist/cmd/dev/api.js +26 -0
  199. package/dist/cmd/dev/api.js.map +1 -0
  200. package/dist/cmd/dev/download.js +77 -0
  201. package/dist/cmd/dev/download.js.map +1 -0
  202. package/dist/cmd/dev/index.js +745 -0
  203. package/dist/cmd/dev/index.js.map +1 -0
  204. package/dist/cmd/dev/sync.js +229 -0
  205. package/dist/cmd/dev/sync.js.map +1 -0
  206. package/dist/cmd/dev/templates.js +75 -0
  207. package/dist/cmd/dev/templates.js.map +1 -0
  208. package/dist/cmd/index.js +49 -0
  209. package/dist/cmd/index.js.map +1 -0
  210. package/dist/cmd/profile/create.js +89 -0
  211. package/dist/cmd/profile/create.js.map +1 -0
  212. package/dist/cmd/profile/delete.js +63 -0
  213. package/dist/cmd/profile/delete.js.map +1 -0
  214. package/dist/cmd/profile/index.js +14 -0
  215. package/dist/cmd/profile/index.js.map +1 -0
  216. package/dist/cmd/profile/list.js +28 -0
  217. package/dist/cmd/profile/list.js.map +1 -0
  218. package/dist/cmd/profile/show.js +68 -0
  219. package/dist/cmd/profile/show.js.map +1 -0
  220. package/dist/cmd/profile/use.js +37 -0
  221. package/dist/cmd/profile/use.js.map +1 -0
  222. package/dist/cmd/project/create.js +92 -0
  223. package/dist/cmd/project/create.js.map +1 -0
  224. package/dist/cmd/project/delete.js +117 -0
  225. package/dist/cmd/project/delete.js.map +1 -0
  226. package/dist/cmd/project/download.js +217 -0
  227. package/dist/cmd/project/download.js.map +1 -0
  228. package/dist/cmd/project/index.js +12 -0
  229. package/dist/cmd/project/index.js.map +1 -0
  230. package/dist/cmd/project/list.js +51 -0
  231. package/dist/cmd/project/list.js.map +1 -0
  232. package/dist/cmd/project/show.js +54 -0
  233. package/dist/cmd/project/show.js.map +1 -0
  234. package/dist/cmd/project/template-flow.js +315 -0
  235. package/dist/cmd/project/template-flow.js.map +1 -0
  236. package/dist/cmd/project/templates.js +31 -0
  237. package/dist/cmd/project/templates.js.map +1 -0
  238. package/dist/cmd/repl/index.js +444 -0
  239. package/dist/cmd/repl/index.js.map +1 -0
  240. package/dist/cmd/version/index.js +29 -0
  241. package/dist/cmd/version/index.js.map +1 -0
  242. package/dist/command-prefix.js +37 -0
  243. package/dist/command-prefix.js.map +1 -0
  244. package/dist/config.js +536 -0
  245. package/dist/config.js.map +1 -0
  246. package/dist/crypto/box.js +382 -0
  247. package/dist/crypto/box.js.map +1 -0
  248. package/dist/crypto/box.test.js +317 -0
  249. package/dist/crypto/box.test.js.map +1 -0
  250. package/dist/download.js +64 -0
  251. package/dist/download.js.map +1 -0
  252. package/dist/env-util.js +219 -0
  253. package/dist/env-util.js.map +1 -0
  254. package/dist/env-util.test.js +146 -0
  255. package/dist/env-util.test.js.map +1 -0
  256. package/dist/errors.js +177 -0
  257. package/dist/errors.js.map +1 -0
  258. package/dist/explain.js +90 -0
  259. package/dist/explain.js.map +1 -0
  260. package/dist/index.js +23 -0
  261. package/dist/index.js.map +1 -0
  262. package/dist/json.js +29 -0
  263. package/dist/json.js.map +1 -0
  264. package/dist/legacy-check.js +104 -0
  265. package/dist/legacy-check.js.map +1 -0
  266. package/dist/output.js +207 -0
  267. package/dist/output.js.map +1 -0
  268. package/dist/repl.js +1176 -0
  269. package/dist/repl.js.map +1 -0
  270. package/dist/runtime.js +19 -0
  271. package/dist/runtime.js.map +1 -0
  272. package/dist/schema-generator.js +289 -0
  273. package/dist/schema-generator.js.map +1 -0
  274. package/dist/schema-parser.js +145 -0
  275. package/dist/schema-parser.js.map +1 -0
  276. package/dist/sound.js +44 -0
  277. package/dist/sound.js.map +1 -0
  278. package/dist/steps.js +293 -0
  279. package/dist/steps.js.map +1 -0
  280. package/dist/terminal.js +130 -0
  281. package/dist/terminal.js.map +1 -0
  282. package/dist/tui.js +1124 -0
  283. package/dist/tui.js.map +1 -0
  284. package/dist/types.js +163 -0
  285. package/dist/types.js.map +1 -0
  286. package/dist/utils/detectSubagent.js +25 -0
  287. package/dist/utils/detectSubagent.js.map +1 -0
  288. package/dist/utils/format.js +21 -0
  289. package/dist/utils/format.js.map +1 -0
  290. package/dist/utils/zip.js +33 -0
  291. package/dist/utils/zip.js.map +1 -0
  292. package/dist/version.js +24 -0
  293. package/dist/version.js.map +1 -0
  294. package/package.json +6 -6
  295. package/src/banner.ts +1 -1
  296. package/src/cmd/build/index.ts +15 -21
  297. package/src/cmd/build/plugin.ts +2 -1
  298. package/src/cmd/cloud/session/get.ts +20 -14
  299. package/src/cmd/cloud/session/list.ts +1 -1
package/dist/repl.js ADDED
@@ -0,0 +1,1176 @@
1
+ /**
2
+ * Reusable REPL (Read-Eval-Print Loop) component for building interactive CLI tools
3
+ */
4
+ import * as tui from './tui';
5
+ import { getDefaultConfigDir } from './config';
6
+ import { join } from 'node:path';
7
+ import { mkdir } from 'node:fs/promises';
8
+ import { z } from 'zod';
9
+ import { colorize } from 'json-colorizer';
10
+ /**
11
+ * Parse a command line into command, args, and options
12
+ */
13
+ function parseCommandLine(line) {
14
+ const tokens = [];
15
+ let current = '';
16
+ let inQuotes = false;
17
+ let quoteChar = '';
18
+ let braceDepth = 0;
19
+ let bracketDepth = 0;
20
+ // Tokenize the input, respecting quotes and JSON objects/arrays
21
+ for (let i = 0; i < line.length; i++) {
22
+ const char = line[i];
23
+ if ((char === '"' || char === "'") && !inQuotes) {
24
+ inQuotes = true;
25
+ quoteChar = char;
26
+ current += char;
27
+ }
28
+ else if (char === quoteChar && inQuotes) {
29
+ inQuotes = false;
30
+ quoteChar = '';
31
+ current += char;
32
+ }
33
+ else if (char === '{' && !inQuotes) {
34
+ braceDepth++;
35
+ current += char;
36
+ }
37
+ else if (char === '}' && !inQuotes) {
38
+ braceDepth--;
39
+ current += char;
40
+ }
41
+ else if (char === '[' && !inQuotes) {
42
+ bracketDepth++;
43
+ current += char;
44
+ }
45
+ else if (char === ']' && !inQuotes) {
46
+ bracketDepth--;
47
+ current += char;
48
+ }
49
+ else if (char === ' ' && !inQuotes && braceDepth === 0 && bracketDepth === 0) {
50
+ if (current) {
51
+ tokens.push(current);
52
+ current = '';
53
+ }
54
+ }
55
+ else {
56
+ current += char;
57
+ }
58
+ }
59
+ if (current) {
60
+ tokens.push(current);
61
+ }
62
+ const command = tokens[0] || '';
63
+ const args = [];
64
+ const options = {};
65
+ // Parse remaining tokens into args and options
66
+ for (let i = 1; i < tokens.length; i++) {
67
+ const token = tokens[i];
68
+ if (token.startsWith('--')) {
69
+ // Long option: --name=value or --flag
70
+ const name = token.slice(2);
71
+ const eqIndex = name.indexOf('=');
72
+ if (eqIndex > 0) {
73
+ options[name.slice(0, eqIndex)] = name.slice(eqIndex + 1);
74
+ }
75
+ else {
76
+ // Check if next token is a value
77
+ if (i + 1 < tokens.length && !tokens[i + 1].startsWith('-')) {
78
+ options[name] = tokens[i + 1];
79
+ i++;
80
+ }
81
+ else {
82
+ options[name] = true;
83
+ }
84
+ }
85
+ }
86
+ else if (token.startsWith('-') && token.length > 1) {
87
+ // Short option: -f or -n value
88
+ const name = token.slice(1);
89
+ // Check if next token is a value
90
+ if (i + 1 < tokens.length && !tokens[i + 1].startsWith('-')) {
91
+ options[name] = tokens[i + 1];
92
+ i++;
93
+ }
94
+ else {
95
+ options[name] = true;
96
+ }
97
+ }
98
+ else {
99
+ args.push(token);
100
+ }
101
+ }
102
+ return { command, args, options };
103
+ }
104
+ /**
105
+ * Load history from file
106
+ */
107
+ async function loadHistory(name) {
108
+ if (!name)
109
+ return [];
110
+ try {
111
+ const historyDir = join(getDefaultConfigDir(), 'history');
112
+ const historyFile = join(historyDir, `${name}.txt`);
113
+ if (!(await Bun.file(historyFile).exists())) {
114
+ return [];
115
+ }
116
+ const content = await Bun.file(historyFile).text();
117
+ return content.split('\n').filter((line) => line.trim());
118
+ }
119
+ catch (_err) {
120
+ return [];
121
+ }
122
+ }
123
+ /**
124
+ * Save history to file
125
+ */
126
+ async function saveHistory(name, history) {
127
+ if (!name)
128
+ return;
129
+ try {
130
+ const historyDir = join(getDefaultConfigDir(), 'history');
131
+ await mkdir(historyDir, { recursive: true });
132
+ const historyFile = join(historyDir, `${name}.txt`);
133
+ await Bun.write(historyFile, history.join('\n'));
134
+ }
135
+ catch (_err) {
136
+ // Silently fail - history is not critical
137
+ }
138
+ }
139
+ /**
140
+ * Create and run a REPL
141
+ */
142
+ export async function createRepl(config) {
143
+ const prompt = config.prompt || '> ';
144
+ const showHelp = config.showHelp !== false;
145
+ const historyName = config.name || '';
146
+ // Load command history
147
+ const history = await loadHistory(historyName);
148
+ let historyIndex = history.length;
149
+ // Build command map with aliases
150
+ const commandMap = new Map();
151
+ for (const cmd of config.commands) {
152
+ commandMap.set(cmd.name.toLowerCase(), cmd);
153
+ if (cmd.aliases) {
154
+ for (const alias of cmd.aliases) {
155
+ commandMap.set(alias.toLowerCase(), cmd);
156
+ }
157
+ }
158
+ }
159
+ // Add built-in help command
160
+ if (showHelp) {
161
+ const helpCommand = {
162
+ name: 'help',
163
+ description: 'Show available commands',
164
+ aliases: ['?'],
165
+ handler: (ctx) => {
166
+ ctx.info('Available commands:');
167
+ tui.newline();
168
+ for (const cmd of config.commands) {
169
+ const aliases = cmd.aliases ? ` (${cmd.aliases.join(', ')})` : '';
170
+ const desc = cmd.description || 'No description';
171
+ console.log(` ${tui.bold(cmd.name)}${tui.muted(aliases)}`);
172
+ console.log(` ${desc}`);
173
+ }
174
+ tui.newline();
175
+ console.log(` ${tui.bold('exit')} ${tui.muted('(quit, q)')}`);
176
+ console.log(` Exit the REPL`);
177
+ },
178
+ };
179
+ commandMap.set('help', helpCommand);
180
+ commandMap.set('?', helpCommand);
181
+ }
182
+ // Show welcome message
183
+ if (config.welcome) {
184
+ console.log(tui.bold(config.welcome));
185
+ tui.newline();
186
+ }
187
+ // REPL state
188
+ let running = true;
189
+ const ctrlCState = { lastTime: 0 };
190
+ let commandAbortController = null;
191
+ const exitRepl = () => {
192
+ running = false;
193
+ };
194
+ // Build list of all command names for autocomplete
195
+ const commandNames = Array.from(commandMap.keys());
196
+ // Remove any existing SIGINT handlers to prevent default exit
197
+ process.removeAllListeners('SIGINT');
198
+ // Setup global SIGINT handler to prevent default exit
199
+ const globalSigintHandler = () => {
200
+ // If command is running, abort it
201
+ if (commandAbortController && !commandAbortController.signal.aborted) {
202
+ commandAbortController.abort();
203
+ // Don't exit - just abort the command
204
+ return;
205
+ }
206
+ // If not running command, ignore - let raw mode handler deal with it
207
+ };
208
+ process.on('SIGINT', globalSigintHandler);
209
+ // Main REPL loop
210
+ while (running) {
211
+ // Reset Ctrl+C timer when starting new command
212
+ ctrlCState.lastTime = 0;
213
+ // Read input with history support
214
+ process.stdout.write(prompt);
215
+ const input = await readLine(history, historyIndex, prompt, commandNames, commandMap, ctrlCState);
216
+ if (input === null) {
217
+ // EOF (Ctrl+D)
218
+ break;
219
+ }
220
+ const { line: rawInput, newHistoryIndex } = input;
221
+ historyIndex = newHistoryIndex;
222
+ const line = rawInput.trim();
223
+ if (!line) {
224
+ continue;
225
+ }
226
+ // Parse command
227
+ const parsed = parseCommandLine(line);
228
+ // Check for exit commands
229
+ if (['exit', 'quit', 'q'].includes(parsed.command.toLowerCase())) {
230
+ break;
231
+ }
232
+ // Find and execute command
233
+ const cmd = commandMap.get(parsed.command.toLowerCase());
234
+ if (!cmd) {
235
+ tui.error(`Unknown command: ${parsed.command}`);
236
+ console.log(`Type ${tui.bold('help')} for available commands`);
237
+ continue;
238
+ }
239
+ // Check if command has subcommands
240
+ let actualHandler = cmd.handler;
241
+ let actualSchema = cmd.schema;
242
+ if (cmd.subcommands && cmd.subcommands.length > 0) {
243
+ // Parse subcommand from first arg
244
+ const subcommandName = parsed.args[0]?.toLowerCase();
245
+ if (!subcommandName) {
246
+ tui.error(`Missing subcommand for ${parsed.command}`);
247
+ console.log('Available subcommands:');
248
+ for (const sub of cmd.subcommands) {
249
+ const argHint = sub.schema?.argNames?.map((n) => `<${n}>`).join(' ') || '';
250
+ console.log(` ${tui.bold(sub.name)} ${argHint} ${tui.muted(sub.description || '')}`);
251
+ }
252
+ continue;
253
+ }
254
+ const subcommand = cmd.subcommands.find((sub) => sub.name.toLowerCase() === subcommandName || sub.aliases?.includes(subcommandName));
255
+ if (!subcommand) {
256
+ tui.error(`Unknown subcommand: ${parsed.command} ${subcommandName}`);
257
+ console.log('Available subcommands:');
258
+ for (const sub of cmd.subcommands) {
259
+ const argHint = sub.schema?.argNames?.map((n) => `<${n}>`).join(' ') || '';
260
+ console.log(` ${tui.bold(sub.name)} ${argHint} ${tui.muted(sub.description || '')}`);
261
+ }
262
+ continue;
263
+ }
264
+ // Use subcommand handler and schema, remove subcommand from args
265
+ actualHandler = subcommand.handler;
266
+ actualSchema = subcommand.schema;
267
+ parsed.args = parsed.args.slice(1);
268
+ }
269
+ if (!actualHandler) {
270
+ tui.error(`Command ${parsed.command} requires a subcommand`);
271
+ continue;
272
+ }
273
+ // Validate against schema if provided
274
+ if (actualSchema) {
275
+ try {
276
+ if (actualSchema.args) {
277
+ parsed.args = actualSchema.args.parse(parsed.args);
278
+ }
279
+ if (actualSchema.options) {
280
+ parsed.options = actualSchema.options.parse(parsed.options);
281
+ }
282
+ }
283
+ catch (err) {
284
+ if (err instanceof z.ZodError) {
285
+ tui.error('Invalid arguments:');
286
+ for (const issue of err.issues) {
287
+ const path = issue.path.join('.');
288
+ console.log(` ${tui.colorError('•')} ${path ? `${path}: ` : ''}${issue.message}`);
289
+ }
290
+ continue;
291
+ }
292
+ throw err;
293
+ }
294
+ }
295
+ // Create context with output buffering for paging
296
+ const outputBuffer = [];
297
+ const bufferWrite = (msg) => {
298
+ outputBuffer.push(...msg.split('\n'));
299
+ };
300
+ // Helper to format messages with icons and colors (without printing)
301
+ const formatMessage = (type, msg) => {
302
+ const icons = {
303
+ success: '✓',
304
+ error: '✗',
305
+ warning: '⚠',
306
+ info: 'ℹ',
307
+ debug: '',
308
+ };
309
+ const colorFormatters = {
310
+ success: tui.colorSuccess,
311
+ error: tui.colorError,
312
+ warning: tui.colorWarning,
313
+ info: tui.colorInfo,
314
+ debug: tui.colorMuted,
315
+ };
316
+ const icon = icons[type];
317
+ const formatter = colorFormatters[type];
318
+ if (!icon) {
319
+ return formatter(msg); // debug messages have no icon
320
+ }
321
+ return `${formatter(icon + ' ' + msg)}`;
322
+ };
323
+ // Create abort controller for this command
324
+ const abortController = new AbortController();
325
+ commandAbortController = abortController;
326
+ const ctx = {
327
+ parsed,
328
+ raw: line,
329
+ write: bufferWrite,
330
+ error: (msg) => outputBuffer.push(formatMessage('error', msg)),
331
+ success: (msg) => outputBuffer.push(formatMessage('success', msg)),
332
+ info: (msg) => outputBuffer.push(formatMessage('info', msg)),
333
+ warning: (msg) => outputBuffer.push(formatMessage('warning', msg)),
334
+ debug: (msg) => outputBuffer.push(formatMessage('debug', msg)),
335
+ setProgress: (msg) => spinner.updateMessage(msg),
336
+ signal: abortController.signal,
337
+ exit: exitRepl,
338
+ table: (columns, data) => {
339
+ // Capture table output to buffer instead of direct stdout
340
+ const tableOutput = tui.table(data, columns, { render: true }) || '';
341
+ outputBuffer.push(...tableOutput.split('\n'));
342
+ },
343
+ json: (value) => {
344
+ // Use util.inspect for colorized output if colors are enabled
345
+ const stringValue = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
346
+ const output = colorize(stringValue);
347
+ outputBuffer.push(output);
348
+ },
349
+ };
350
+ // Execute command handler with activity indicator
351
+ const spinner = new ActivityIndicator(parsed.command);
352
+ spinner.start();
353
+ try {
354
+ const result = actualHandler(ctx);
355
+ // Handle async generator (streaming)
356
+ if (result && typeof result === 'object' && Symbol.asyncIterator in result) {
357
+ for await (const chunk of result) {
358
+ if (abortController.signal.aborted) {
359
+ break;
360
+ }
361
+ outputBuffer.push(...chunk.split('\n'));
362
+ }
363
+ }
364
+ else if (result && typeof result === 'object' && 'then' in result) {
365
+ // Handle promise
366
+ await result;
367
+ }
368
+ spinner.stop();
369
+ commandAbortController = null;
370
+ // Check if aborted
371
+ if (abortController.signal.aborted) {
372
+ tui.warning('Command aborted');
373
+ continue;
374
+ }
375
+ // Display output with paging
376
+ if (outputBuffer.length > 0) {
377
+ await displayWithPaging(outputBuffer);
378
+ }
379
+ // Add successful command to history
380
+ if (history[history.length - 1] !== line) {
381
+ history.push(line);
382
+ historyIndex = history.length;
383
+ // Save history asynchronously (don't await to avoid blocking)
384
+ saveHistory(historyName, history);
385
+ }
386
+ }
387
+ catch (err) {
388
+ spinner.stop();
389
+ commandAbortController = null;
390
+ // Check if it was an abort
391
+ if (err instanceof Error && err.name === 'AbortError') {
392
+ tui.warning('Command aborted');
393
+ }
394
+ else {
395
+ tui.error(`Command failed: ${err instanceof Error ? err.message : String(err)}`);
396
+ }
397
+ }
398
+ }
399
+ // Cleanup global handler
400
+ process.off('SIGINT', globalSigintHandler);
401
+ // Show exit message
402
+ if (config.exitMessage) {
403
+ tui.info(config.exitMessage);
404
+ }
405
+ }
406
+ /**
407
+ * Get all autocomplete matches based on current input
408
+ */
409
+ function getAutocompleteMatches(buffer, commands, commandMap) {
410
+ if (!buffer.trim())
411
+ return [];
412
+ const tokens = buffer.trim().split(/\s+/);
413
+ const firstToken = tokens[0].toLowerCase();
414
+ // If we're typing the first word (no trailing space), suggest commands
415
+ if (tokens.length === 1 && buffer === buffer.trimEnd()) {
416
+ return commands.filter((cmd) => cmd.startsWith(firstToken) && cmd !== firstToken);
417
+ }
418
+ // If we have a command + space, suggest subcommands
419
+ if (tokens.length === 1 && buffer !== buffer.trimEnd() && commandMap) {
420
+ const cmd = commandMap.get(firstToken);
421
+ if (cmd?.subcommands) {
422
+ return cmd.subcommands.map((sub) => sub.name);
423
+ }
424
+ return [];
425
+ }
426
+ // If we're typing a subcommand, filter matches
427
+ if (tokens.length === 2 && buffer === buffer.trimEnd() && commandMap) {
428
+ const cmd = commandMap.get(firstToken);
429
+ if (cmd?.subcommands) {
430
+ const subToken = tokens[1].toLowerCase();
431
+ return cmd.subcommands
432
+ .filter((sub) => sub.name.startsWith(subToken) && sub.name !== subToken)
433
+ .map((sub) => sub.name);
434
+ }
435
+ return [];
436
+ }
437
+ return [];
438
+ }
439
+ /**
440
+ * Get autocomplete suggestion based on current input and cycle index
441
+ */
442
+ function getAutocompleteSuggestion(buffer, commands, commandMap, cycleIndex = 0) {
443
+ const matches = getAutocompleteMatches(buffer, commands, commandMap);
444
+ if (matches.length === 0)
445
+ return '';
446
+ const selectedMatch = matches[cycleIndex % matches.length];
447
+ const tokens = buffer.trim().split(/\s+/);
448
+ const firstToken = tokens[0].toLowerCase();
449
+ // Typing first word (command name)
450
+ if (tokens.length === 1 && buffer === buffer.trimEnd()) {
451
+ const cmd = commandMap.get(selectedMatch.toLowerCase());
452
+ let suggestion = selectedMatch.slice(firstToken.length);
453
+ // Add argument placeholders if schema exists
454
+ if (cmd?.schema?.argNames) {
455
+ suggestion += ' ' + cmd.schema.argNames.map((name) => `<${name}>`).join(' ');
456
+ }
457
+ // Add subcommand hint if this command has subcommands
458
+ else if (cmd?.subcommands && cmd.subcommands.length > 0) {
459
+ suggestion += ' <subcommand>';
460
+ }
461
+ return suggestion;
462
+ }
463
+ // After command + space, suggesting subcommands
464
+ if (tokens.length === 1 && buffer !== buffer.trimEnd()) {
465
+ return selectedMatch;
466
+ }
467
+ // Typing subcommand name
468
+ if (tokens.length === 2 && buffer === buffer.trimEnd()) {
469
+ const cmd = commandMap.get(firstToken);
470
+ const subToken = tokens[1];
471
+ const subcommand = cmd?.subcommands?.find((sub) => sub.name === selectedMatch);
472
+ let suggestion = selectedMatch.slice(subToken.length);
473
+ // Add argument placeholders for subcommand
474
+ if (subcommand?.schema?.argNames) {
475
+ suggestion += ' ' + subcommand.schema.argNames.map((name) => `<${name}>`).join(' ');
476
+ }
477
+ return suggestion;
478
+ }
479
+ return '';
480
+ }
481
+ /**
482
+ * Display output with paging if it's too long
483
+ */
484
+ async function displayWithPaging(lines) {
485
+ const terminalHeight = process.stdout.rows || 24;
486
+ const pageSize = terminalHeight - 2; // Leave room for prompt
487
+ if (lines.length <= pageSize) {
488
+ // Short output, just display it
489
+ for (const line of lines) {
490
+ console.log(`\x1b[2m│\x1b[0m ${line}`);
491
+ }
492
+ return;
493
+ }
494
+ // Long output, page it
495
+ let currentLine = 0;
496
+ while (currentLine < lines.length) {
497
+ // Clear screen and show current page
498
+ const endLine = Math.min(currentLine + pageSize, lines.length);
499
+ for (let i = currentLine; i < endLine; i++) {
500
+ console.log(`${tui.colorMuted('│')} ${lines[i]}`);
501
+ }
502
+ // Check if there's more
503
+ if (endLine < lines.length) {
504
+ const remaining = lines.length - endLine;
505
+ process.stdout.write(tui.bold(`-- More (${remaining} lines) -- [Space=next, q=quit]`));
506
+ // Wait for keypress
507
+ const key = await waitForKey();
508
+ process.stdout.write('\r\x1b[K'); // Clear the "More" line
509
+ if (key === 'q' || key === '\x03') {
510
+ // Quit
511
+ console.log(`${tui.colorMuted('│')} (output truncated)`);
512
+ break;
513
+ }
514
+ else if (key === ' ' || key === '\r' || key === '\n') {
515
+ // Continue to next page
516
+ currentLine = endLine;
517
+ }
518
+ else {
519
+ // Any other key, continue
520
+ currentLine = endLine;
521
+ }
522
+ }
523
+ else {
524
+ break;
525
+ }
526
+ }
527
+ }
528
+ /**
529
+ * Wait for a single keypress
530
+ */
531
+ async function waitForKey() {
532
+ return new Promise((resolve) => {
533
+ process.stdin.setRawMode(true);
534
+ process.stdin.resume();
535
+ const onData = (chunk) => {
536
+ const key = chunk.toString();
537
+ process.stdin.setRawMode(false);
538
+ process.stdin.removeListener('data', onData);
539
+ process.stdin.pause();
540
+ resolve(key);
541
+ };
542
+ process.stdin.on('data', onData);
543
+ });
544
+ }
545
+ /**
546
+ * Activity indicator that shows a spinner while command is executing
547
+ */
548
+ class ActivityIndicator {
549
+ frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
550
+ currentFrame = 0;
551
+ intervalId = null;
552
+ message;
553
+ constructor(message = 'Running') {
554
+ this.message = message;
555
+ }
556
+ start() {
557
+ // Hide cursor
558
+ process.stdout.write('\x1b[?25l');
559
+ // Show initial spinner on current line
560
+ this.draw();
561
+ // Update spinner every 80ms
562
+ this.intervalId = setInterval(() => {
563
+ this.currentFrame = (this.currentFrame + 1) % this.frames.length;
564
+ this.draw();
565
+ }, 80);
566
+ }
567
+ draw() {
568
+ const frame = this.frames[this.currentFrame];
569
+ // Clear line, draw spinner, stay on same line
570
+ process.stdout.write('\r\x1b[K'); // Clear line from cursor
571
+ process.stdout.write(`${tui.muted(frame)} ${tui.muted(this.message)}...`);
572
+ }
573
+ stop() {
574
+ if (this.intervalId) {
575
+ clearInterval(this.intervalId);
576
+ this.intervalId = null;
577
+ }
578
+ // Clear the spinner line - cursor stays at start of line
579
+ process.stdout.write('\r\x1b[K'); // Clear current line, cursor at start
580
+ // Show cursor again
581
+ process.stdout.write('\x1b[?25h');
582
+ }
583
+ updateMessage(message) {
584
+ this.message = message;
585
+ }
586
+ }
587
+ /**
588
+ * Show command picker popup and return selected command
589
+ */
590
+ async function showCommandPicker(commandMap, prompt) {
591
+ // Build list of commands
592
+ const commands = Array.from(commandMap.entries())
593
+ .filter(([name, cmd]) => name === cmd.name.toLowerCase())
594
+ .sort(([a], [b]) => a.localeCompare(b))
595
+ .map(([name, cmd]) => ({
596
+ name,
597
+ description: cmd.description || 'No description',
598
+ argHint: cmd.schema?.argNames?.map((n) => `<${n}>`).join(' ') || '',
599
+ }));
600
+ let selectedIndex = 0;
601
+ const menuHeight = commands.length + 2; // +2 for header and blank line
602
+ // Calculate max command text length for padding
603
+ const maxCmdLength = Math.max(...commands.map((cmd) => {
604
+ const cmdText = `${cmd.name}${cmd.argHint ? ' ' + cmd.argHint : ''}`;
605
+ return cmdText.length;
606
+ }));
607
+ const drawPicker = () => {
608
+ // Save cursor position, move down to draw menu
609
+ process.stdout.write('\n'); // Move to next line
610
+ // Draw header
611
+ console.log(tui.bold('Command Picker') + ' ' + tui.muted('(↑/↓ navigate, Enter select, Esc cancel)'));
612
+ // Draw commands
613
+ for (let i = 0; i < commands.length; i++) {
614
+ const cmd = commands[i];
615
+ const isSelected = i === selectedIndex;
616
+ const prefix = isSelected ? '▶ ' : ' ';
617
+ const style = isSelected ? '\x1b[7m' : ''; // Reverse video for selected
618
+ const reset = isSelected ? '\x1b[0m' : '';
619
+ const cmdText = `${cmd.name}${cmd.argHint ? ' ' + cmd.argHint : ''}`;
620
+ const paddedCmdText = cmdText.padEnd(maxCmdLength);
621
+ const description = tui.muted(cmd.description);
622
+ console.log(`${prefix}${style}${tui.bold(paddedCmdText)}${reset} ${description}`);
623
+ }
624
+ // Move cursor back to prompt line
625
+ process.stdout.write(`\x1b[${menuHeight}A`); // Move up N lines
626
+ process.stdout.write(`\r${prompt}/`); // Redraw prompt with /
627
+ };
628
+ const clearPicker = () => {
629
+ // Move down to menu area
630
+ process.stdout.write(`\x1b[${menuHeight}B`); // Move down to after menu
631
+ // Clear all menu lines by moving up and clearing
632
+ for (let i = 0; i < menuHeight; i++) {
633
+ process.stdout.write('\x1b[A'); // Move up
634
+ process.stdout.write('\r\x1b[K'); // Clear line
635
+ }
636
+ // Back to prompt - clear the line completely
637
+ process.stdout.write('\r\x1b[K');
638
+ };
639
+ drawPicker();
640
+ return new Promise((resolve) => {
641
+ process.stdin.setRawMode(true);
642
+ process.stdin.resume();
643
+ const onData = (chunk) => {
644
+ const bytes = Array.from(chunk);
645
+ // Escape or Ctrl+C - cancel
646
+ if (bytes[0] === 0x1b && bytes.length === 1) {
647
+ cleanup();
648
+ clearPicker();
649
+ resolve(null);
650
+ return;
651
+ }
652
+ if (bytes[0] === 0x03) {
653
+ cleanup();
654
+ clearPicker();
655
+ resolve(null);
656
+ return;
657
+ }
658
+ // Enter - select
659
+ if (bytes[0] === 0x0d || bytes[0] === 0x0a) {
660
+ const selected = commands[selectedIndex];
661
+ cleanup();
662
+ clearPicker();
663
+ resolve(selected.name + (selected.argHint ? ' ' : ''));
664
+ return;
665
+ }
666
+ // Arrow keys
667
+ if (bytes[0] === 0x1b && bytes[1] === 0x5b) {
668
+ // Up arrow
669
+ if (bytes[2] === 0x41) {
670
+ selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : commands.length - 1;
671
+ drawPicker();
672
+ return;
673
+ }
674
+ // Down arrow
675
+ if (bytes[2] === 0x42) {
676
+ selectedIndex = (selectedIndex + 1) % commands.length;
677
+ drawPicker();
678
+ return;
679
+ }
680
+ }
681
+ };
682
+ const cleanup = () => {
683
+ process.stdin.setRawMode(false);
684
+ process.stdin.removeListener('data', onData);
685
+ process.stdin.pause();
686
+ };
687
+ process.stdin.on('data', onData);
688
+ });
689
+ }
690
+ /**
691
+ * Apply syntax highlighting to buffer
692
+ */
693
+ function applySyntaxHighlighting(buffer, commands) {
694
+ if (!buffer.trim())
695
+ return buffer;
696
+ const tokens = buffer.split(/(\s+)/); // Split but keep whitespace
697
+ let result = '';
698
+ let isFirstToken = true;
699
+ for (let i = 0; i < tokens.length; i++) {
700
+ const token = tokens[i];
701
+ // Skip whitespace
702
+ if (/^\s+$/.test(token)) {
703
+ result += token;
704
+ continue;
705
+ }
706
+ // First token is the command
707
+ if (isFirstToken) {
708
+ const isValid = commands.includes(token.toLowerCase());
709
+ if (isValid) {
710
+ result += tui.colorSuccess(token); // Green for valid command
711
+ }
712
+ else {
713
+ result += tui.colorError(token); // Red for invalid command
714
+ }
715
+ isFirstToken = false;
716
+ }
717
+ // Options (start with - or --)
718
+ else if (token.startsWith('-')) {
719
+ result += tui.colorInfo(token); // Cyan for options
720
+ }
721
+ // Regular arguments
722
+ else {
723
+ result += tui.colorMuted(token); // Gray for arguments
724
+ }
725
+ }
726
+ return result;
727
+ }
728
+ /**
729
+ * Read a line from stdin with arrow key history support and autocomplete
730
+ */
731
+ async function readLine(history, startIndex, prompt = '', commands = [], commandMap, ctrlCState) {
732
+ return new Promise((resolve) => {
733
+ process.stdin.setRawMode(true);
734
+ process.stdin.resume();
735
+ // Enable vertical bar cursor
736
+ process.stdout.write('\x1b[6 q');
737
+ let buffer = '';
738
+ let cursorPos = 0;
739
+ let historyIndex = startIndex;
740
+ let searchMode = false;
741
+ let searchQuery = '';
742
+ let searchResultIndex = -1;
743
+ let autocompleteCycleIndex = 0;
744
+ let lastAutocompleteBuffer = '';
745
+ const lines = [''];
746
+ let currentLineIndex = 0;
747
+ const searchHistory = (query, startFrom) => {
748
+ for (let i = startFrom - 1; i >= 0; i--) {
749
+ if (history[i].toLowerCase().includes(query.toLowerCase())) {
750
+ return i;
751
+ }
752
+ }
753
+ return -1;
754
+ };
755
+ const redraw = (cmdMap) => {
756
+ if (searchMode) {
757
+ // Search mode display
758
+ process.stdout.write('\r\x1b[K');
759
+ const foundEntry = searchResultIndex >= 0 ? history[searchResultIndex] : '';
760
+ const searchPrompt = `(reverse-i-search)\`${searchQuery}': `;
761
+ process.stdout.write(searchPrompt + foundEntry);
762
+ }
763
+ else if (lines.length > 1) {
764
+ // Multi-line mode - redraw all lines
765
+ // Move to start of first line
766
+ for (let i = 0; i < currentLineIndex; i++) {
767
+ process.stdout.write('\x1b[A'); // Move up
768
+ }
769
+ process.stdout.write('\r');
770
+ // Redraw all lines
771
+ for (let i = 0; i < lines.length; i++) {
772
+ process.stdout.write('\x1b[K'); // Clear line
773
+ const linePrompt = i === 0 ? prompt : '... ';
774
+ process.stdout.write(linePrompt + lines[i]);
775
+ if (i < lines.length - 1) {
776
+ process.stdout.write('\n');
777
+ }
778
+ }
779
+ // Position cursor on current line at cursor position
780
+ const linesToMove = lines.length - 1 - currentLineIndex;
781
+ for (let i = 0; i < linesToMove; i++) {
782
+ process.stdout.write('\x1b[A'); // Move up to current line
783
+ }
784
+ const linePrompt = currentLineIndex === 0 ? prompt : '... ';
785
+ process.stdout.write('\r');
786
+ process.stdout.write(linePrompt + lines[currentLineIndex].slice(0, cursorPos));
787
+ }
788
+ else {
789
+ // Single-line mode (original behavior)
790
+ process.stdout.write('\r\x1b[K');
791
+ const suggestion = cmdMap
792
+ ? getAutocompleteSuggestion(buffer, commands, cmdMap, autocompleteCycleIndex)
793
+ : '';
794
+ const matches = cmdMap ? getAutocompleteMatches(buffer, commands, cmdMap) : [];
795
+ // Apply syntax highlighting to buffer
796
+ const highlightedBuffer = applySyntaxHighlighting(buffer, commands);
797
+ process.stdout.write(prompt + highlightedBuffer);
798
+ // Show suggestion in dark gray (only when cursor is at end)
799
+ const showSuggestion = suggestion && cursorPos === buffer.length;
800
+ if (showSuggestion) {
801
+ process.stdout.write(`\x1b[90m${suggestion}\x1b[0m`);
802
+ // Show match count if multiple matches
803
+ if (matches.length > 1) {
804
+ process.stdout.write(` \x1b[90m[${autocompleteCycleIndex + 1}/${matches.length}]\x1b[0m`);
805
+ }
806
+ }
807
+ // Move cursor to correct position (accounting for prompt length and suggestion)
808
+ const suggestionLength = showSuggestion ? suggestion.length : 0;
809
+ const counterLength = showSuggestion && matches.length > 1
810
+ ? ` [${autocompleteCycleIndex + 1}/${matches.length}]`.length
811
+ : 0;
812
+ const totalLength = buffer.length + suggestionLength + counterLength;
813
+ const diff = totalLength - cursorPos;
814
+ if (diff > 0) {
815
+ process.stdout.write(`\x1b[${diff}D`);
816
+ }
817
+ }
818
+ };
819
+ const onData = async (chunk) => {
820
+ const bytes = Array.from(chunk);
821
+ // Check for / key - show command picker
822
+ if (bytes[0] === 0x2f && buffer.length === 0 && commandMap) {
823
+ // '/' key at start of line
824
+ // Temporarily remove our listener to avoid conflicts
825
+ process.stdin.removeListener('data', onData);
826
+ const selected = await showCommandPicker(commandMap, prompt);
827
+ // Re-attach our listener and restore state
828
+ process.stdin.setRawMode(true);
829
+ process.stdin.resume();
830
+ process.stdin.on('data', onData);
831
+ if (selected) {
832
+ buffer = selected;
833
+ cursorPos = buffer.length;
834
+ lines[currentLineIndex] = buffer;
835
+ // Reset autocomplete state
836
+ autocompleteCycleIndex = 0;
837
+ lastAutocompleteBuffer = '';
838
+ // Force full redraw after command picker
839
+ redraw(commandMap);
840
+ }
841
+ return;
842
+ }
843
+ // Check for Ctrl+C - double press to exit
844
+ if (bytes[0] === 0x03 && ctrlCState) {
845
+ const now = Date.now();
846
+ const timeSinceLastCtrlC = now - ctrlCState.lastTime;
847
+ // If pressed within 2 seconds, exit
848
+ if (timeSinceLastCtrlC < 2000 && ctrlCState.lastTime > 0) {
849
+ cleanup();
850
+ console.log(''); // Newline before exit
851
+ process.exit(0);
852
+ }
853
+ // First Ctrl+C - show message below, keep prompt in place
854
+ ctrlCState.lastTime = now;
855
+ // Save cursor position
856
+ const promptWithBuffer = prompt + buffer;
857
+ // Move to new line and show message
858
+ process.stdout.write('\n');
859
+ process.stdout.write(tui.muted('Press Ctrl+C again to exit'));
860
+ // Move back up to prompt line
861
+ process.stdout.write('\x1b[A'); // Move up one line
862
+ process.stdout.write(`\r`); // Go to start of line
863
+ process.stdout.write(promptWithBuffer); // Redraw prompt
864
+ // Position cursor at correct location
865
+ if (cursorPos < buffer.length) {
866
+ const diff = buffer.length - cursorPos;
867
+ process.stdout.write(`\x1b[${diff}D`);
868
+ }
869
+ return;
870
+ }
871
+ // Check for Ctrl+D (EOF)
872
+ if (bytes[0] === 0x04 && buffer.length === 0) {
873
+ cleanup();
874
+ resolve(null);
875
+ return;
876
+ }
877
+ // Check for Ctrl+A (jump to start)
878
+ if (bytes[0] === 0x01) {
879
+ cursorPos = 0;
880
+ redraw(commandMap);
881
+ return;
882
+ }
883
+ // Check for Ctrl+E (jump to end)
884
+ if (bytes[0] === 0x05) {
885
+ cursorPos = buffer.length;
886
+ redraw(commandMap);
887
+ return;
888
+ }
889
+ // Check for Ctrl+K (delete to end of line)
890
+ if (bytes[0] === 0x0b) {
891
+ buffer = buffer.slice(0, cursorPos);
892
+ redraw(commandMap);
893
+ return;
894
+ }
895
+ // Check for Ctrl+U (delete entire line)
896
+ if (bytes[0] === 0x15) {
897
+ buffer = '';
898
+ cursorPos = 0;
899
+ redraw(commandMap);
900
+ return;
901
+ }
902
+ // Check for Ctrl+W (delete word backward)
903
+ if (bytes[0] === 0x17) {
904
+ const beforeCursor = buffer.slice(0, cursorPos);
905
+ const match = beforeCursor.match(/\s*\S+\s*$/);
906
+ if (match) {
907
+ const deleteCount = match[0].length;
908
+ buffer = buffer.slice(0, cursorPos - deleteCount) + buffer.slice(cursorPos);
909
+ cursorPos -= deleteCount;
910
+ redraw(commandMap);
911
+ }
912
+ return;
913
+ }
914
+ // Check for Ctrl+L (clear screen)
915
+ if (bytes[0] === 0x0c) {
916
+ process.stdout.write('\x1b[2J\x1b[H');
917
+ redraw(commandMap);
918
+ return;
919
+ }
920
+ // Check for Ctrl+R (reverse search)
921
+ if (bytes[0] === 0x12) {
922
+ if (!searchMode) {
923
+ // Enter search mode
924
+ searchMode = true;
925
+ searchQuery = '';
926
+ searchResultIndex = searchHistory('', history.length);
927
+ redraw(commandMap);
928
+ }
929
+ else {
930
+ // Find next match
931
+ if (searchResultIndex > 0) {
932
+ searchResultIndex = searchHistory(searchQuery, searchResultIndex);
933
+ redraw(commandMap);
934
+ }
935
+ }
936
+ return;
937
+ }
938
+ // Check for Tab (autocomplete - cycle only)
939
+ if (bytes[0] === 0x09 && commandMap) {
940
+ const matches = getAutocompleteMatches(buffer, commands, commandMap);
941
+ if (matches.length === 0) {
942
+ // No matches, do nothing
943
+ return;
944
+ }
945
+ // Check if we're cycling through the same buffer
946
+ if (buffer === lastAutocompleteBuffer && matches.length > 0) {
947
+ // Continue cycling
948
+ autocompleteCycleIndex = (autocompleteCycleIndex + 1) % matches.length;
949
+ redraw(commandMap);
950
+ }
951
+ else {
952
+ // Start new cycle - show first suggestion
953
+ autocompleteCycleIndex = 0;
954
+ lastAutocompleteBuffer = buffer;
955
+ redraw(commandMap);
956
+ }
957
+ return;
958
+ }
959
+ // Check for Shift+Enter (newline without submit)
960
+ // Different terminals send different sequences:
961
+ // - iTerm2/Terminal.app: ESC + Enter (0x1b, 0x0d or 0x1b, 0x0a)
962
+ // - Some terminals: ESC[27;2;13~
963
+ // - VSCode: ESC[13;2u
964
+ if ((bytes[0] === 0x1b && bytes.length === 2 && (bytes[1] === 0x0d || bytes[1] === 0x0a)) || // ESC + Enter
965
+ (bytes[0] === 0x1b &&
966
+ bytes[1] === 0x5b &&
967
+ bytes.includes(0x3b) &&
968
+ bytes.includes(0x32)) || // ESC[...;2;...
969
+ (bytes[0] === 0x1b &&
970
+ bytes[1] === 0x5b &&
971
+ bytes[2] === 0x31 &&
972
+ bytes[3] === 0x33 &&
973
+ bytes[4] === 0x3b &&
974
+ bytes[5] === 0x32) // ESC[13;2u
975
+ ) {
976
+ // Shift+Enter detected - add newline
977
+ lines.push('');
978
+ currentLineIndex++;
979
+ cursorPos = 0;
980
+ buffer = lines[currentLineIndex];
981
+ process.stdout.write('\n');
982
+ process.stdout.write('... ');
983
+ return;
984
+ }
985
+ // Check for Enter
986
+ if (bytes[0] === 0x0d || bytes[0] === 0x0a) {
987
+ if (searchMode) {
988
+ // Accept search result
989
+ if (searchResultIndex >= 0) {
990
+ buffer = history[searchResultIndex];
991
+ }
992
+ searchMode = false;
993
+ process.stdout.write('\n');
994
+ cleanup();
995
+ resolve({ line: buffer, newHistoryIndex: history.length });
996
+ return;
997
+ }
998
+ // Check if line ends with backslash (continuation)
999
+ if (buffer.endsWith('\\')) {
1000
+ // Remove backslash and continue to next line
1001
+ lines[currentLineIndex] = buffer.slice(0, -1);
1002
+ lines.push('');
1003
+ currentLineIndex++;
1004
+ cursorPos = 0;
1005
+ buffer = '';
1006
+ process.stdout.write('\n');
1007
+ process.stdout.write('... ');
1008
+ return;
1009
+ }
1010
+ // Check for unclosed quotes or brackets
1011
+ const hasUnclosedQuote = (buffer.match(/"/g) || []).length % 2 !== 0 ||
1012
+ (buffer.match(/'/g) || []).length % 2 !== 0;
1013
+ const openBrackets = (buffer.match(/[{[(]/g) || []).length;
1014
+ const closeBrackets = (buffer.match(/[}\])]/g) || []).length;
1015
+ if (hasUnclosedQuote || openBrackets > closeBrackets) {
1016
+ // Auto-continue to next line
1017
+ lines[currentLineIndex] = buffer;
1018
+ lines.push('');
1019
+ currentLineIndex++;
1020
+ cursorPos = 0;
1021
+ buffer = '';
1022
+ process.stdout.write('\n');
1023
+ process.stdout.write('... ');
1024
+ return;
1025
+ }
1026
+ // Submit the command
1027
+ process.stdout.write('\n');
1028
+ const finalBuffer = lines.length > 1 ? lines.join('\n') : buffer;
1029
+ cleanup();
1030
+ resolve({ line: finalBuffer, newHistoryIndex: history.length });
1031
+ return;
1032
+ }
1033
+ // Check for Esc key (cancel search mode)
1034
+ if (bytes[0] === 0x1b && bytes.length === 1) {
1035
+ if (searchMode) {
1036
+ searchMode = false;
1037
+ searchQuery = '';
1038
+ searchResultIndex = -1;
1039
+ redraw(commandMap);
1040
+ }
1041
+ return;
1042
+ }
1043
+ // Check for escape sequences (arrow keys, delete, home, end)
1044
+ if (bytes[0] === 0x1b && bytes[1] === 0x5b) {
1045
+ // Up arrow
1046
+ if (bytes[2] === 0x41) {
1047
+ if (historyIndex > 0) {
1048
+ historyIndex--;
1049
+ buffer = history[historyIndex] || '';
1050
+ cursorPos = buffer.length;
1051
+ redraw(commandMap);
1052
+ }
1053
+ return;
1054
+ }
1055
+ // Down arrow
1056
+ if (bytes[2] === 0x42) {
1057
+ if (historyIndex < history.length) {
1058
+ historyIndex++;
1059
+ buffer = history[historyIndex] || '';
1060
+ cursorPos = buffer.length;
1061
+ redraw(commandMap);
1062
+ }
1063
+ return;
1064
+ }
1065
+ // Right arrow
1066
+ if (bytes[2] === 0x43) {
1067
+ // Check if we're at the end and have a suggestion to accept
1068
+ if (cursorPos === buffer.length && commandMap) {
1069
+ const suggestion = getAutocompleteSuggestion(buffer, commands, commandMap, autocompleteCycleIndex);
1070
+ if (suggestion) {
1071
+ // Accept the autocomplete suggestion (without argument placeholders)
1072
+ // Remove argument placeholders like <key> <value> from suggestion
1073
+ let completionOnly = suggestion.replace(/<[^>]+>/g, '').trimEnd();
1074
+ // Add trailing space if there were argument placeholders
1075
+ if (suggestion.includes('<')) {
1076
+ completionOnly += ' ';
1077
+ }
1078
+ buffer += completionOnly;
1079
+ cursorPos = buffer.length;
1080
+ lines[currentLineIndex] = buffer;
1081
+ autocompleteCycleIndex = 0;
1082
+ lastAutocompleteBuffer = '';
1083
+ redraw(commandMap);
1084
+ return;
1085
+ }
1086
+ }
1087
+ // Normal cursor movement
1088
+ if (cursorPos < buffer.length) {
1089
+ cursorPos++;
1090
+ process.stdout.write('\x1b[C');
1091
+ }
1092
+ return;
1093
+ }
1094
+ // Left arrow
1095
+ if (bytes[2] === 0x44) {
1096
+ if (cursorPos > 0) {
1097
+ cursorPos--;
1098
+ process.stdout.write('\x1b[D');
1099
+ }
1100
+ return;
1101
+ }
1102
+ // Delete key (ESC[3~)
1103
+ if (bytes[2] === 0x33 && bytes.length > 3 && bytes[3] === 0x7e) {
1104
+ if (cursorPos < buffer.length) {
1105
+ buffer = buffer.slice(0, cursorPos) + buffer.slice(cursorPos + 1);
1106
+ redraw(commandMap);
1107
+ }
1108
+ return;
1109
+ }
1110
+ // Home key (ESC[H or ESC[1~)
1111
+ if (bytes[2] === 0x48 || (bytes[2] === 0x31 && bytes[3] === 0x7e)) {
1112
+ cursorPos = 0;
1113
+ redraw(commandMap);
1114
+ return;
1115
+ }
1116
+ // End key (ESC[F or ESC[4~)
1117
+ if (bytes[2] === 0x46 || (bytes[2] === 0x34 && bytes[3] === 0x7e)) {
1118
+ cursorPos = buffer.length;
1119
+ redraw(commandMap);
1120
+ return;
1121
+ }
1122
+ }
1123
+ // Backspace
1124
+ if (bytes[0] === 0x7f || bytes[0] === 0x08) {
1125
+ if (searchMode) {
1126
+ // In search mode, delete from search query
1127
+ if (searchQuery.length > 0) {
1128
+ searchQuery = searchQuery.slice(0, -1);
1129
+ searchResultIndex = searchHistory(searchQuery, history.length);
1130
+ redraw(commandMap);
1131
+ }
1132
+ }
1133
+ else {
1134
+ if (cursorPos > 0) {
1135
+ buffer = buffer.slice(0, cursorPos - 1) + buffer.slice(cursorPos);
1136
+ cursorPos--;
1137
+ redraw(commandMap);
1138
+ }
1139
+ }
1140
+ return;
1141
+ }
1142
+ // Regular character input
1143
+ if (searchMode) {
1144
+ // In search mode, add to search query
1145
+ const char = chunk.toString();
1146
+ if (char.match(/^[\x20-\x7E]$/)) {
1147
+ // Printable ASCII
1148
+ searchQuery += char;
1149
+ searchResultIndex = searchHistory(searchQuery, history.length);
1150
+ redraw(commandMap);
1151
+ }
1152
+ }
1153
+ else {
1154
+ const char = chunk.toString();
1155
+ buffer = buffer.slice(0, cursorPos) + char + buffer.slice(cursorPos);
1156
+ cursorPos += char.length;
1157
+ // Update current line
1158
+ lines[currentLineIndex] = buffer;
1159
+ // Reset autocomplete cycle on new input
1160
+ autocompleteCycleIndex = 0;
1161
+ lastAutocompleteBuffer = '';
1162
+ // Always redraw to show autocomplete suggestion
1163
+ redraw(commandMap);
1164
+ }
1165
+ };
1166
+ const cleanup = () => {
1167
+ // Restore default block cursor
1168
+ process.stdout.write('\x1b[0 q');
1169
+ process.stdin.setRawMode(false);
1170
+ process.stdin.removeListener('data', onData);
1171
+ process.stdin.pause();
1172
+ };
1173
+ process.stdin.on('data', onData);
1174
+ });
1175
+ }
1176
+ //# sourceMappingURL=repl.js.map