arc402-cli 0.7.2 → 0.7.4

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 (55) hide show
  1. package/TUI-SPEC.md +214 -0
  2. package/dist/index.js +55 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/repl.d.ts.map +1 -1
  5. package/dist/repl.js +46 -567
  6. package/dist/repl.js.map +1 -1
  7. package/dist/tui/App.d.ts +12 -0
  8. package/dist/tui/App.d.ts.map +1 -0
  9. package/dist/tui/App.js +154 -0
  10. package/dist/tui/App.js.map +1 -0
  11. package/dist/tui/Footer.d.ts +11 -0
  12. package/dist/tui/Footer.d.ts.map +1 -0
  13. package/dist/tui/Footer.js +13 -0
  14. package/dist/tui/Footer.js.map +1 -0
  15. package/dist/tui/Header.d.ts +14 -0
  16. package/dist/tui/Header.d.ts.map +1 -0
  17. package/dist/tui/Header.js +19 -0
  18. package/dist/tui/Header.js.map +1 -0
  19. package/dist/tui/InputLine.d.ts +11 -0
  20. package/dist/tui/InputLine.d.ts.map +1 -0
  21. package/dist/tui/InputLine.js +145 -0
  22. package/dist/tui/InputLine.js.map +1 -0
  23. package/dist/tui/Viewport.d.ts +14 -0
  24. package/dist/tui/Viewport.d.ts.map +1 -0
  25. package/dist/tui/Viewport.js +48 -0
  26. package/dist/tui/Viewport.js.map +1 -0
  27. package/dist/tui/index.d.ts +2 -0
  28. package/dist/tui/index.d.ts.map +1 -0
  29. package/dist/tui/index.js +55 -0
  30. package/dist/tui/index.js.map +1 -0
  31. package/dist/tui/useChat.d.ts +11 -0
  32. package/dist/tui/useChat.d.ts.map +1 -0
  33. package/dist/tui/useChat.js +91 -0
  34. package/dist/tui/useChat.js.map +1 -0
  35. package/dist/tui/useCommand.d.ts +12 -0
  36. package/dist/tui/useCommand.d.ts.map +1 -0
  37. package/dist/tui/useCommand.js +137 -0
  38. package/dist/tui/useCommand.js.map +1 -0
  39. package/dist/tui/useScroll.d.ts +17 -0
  40. package/dist/tui/useScroll.d.ts.map +1 -0
  41. package/dist/tui/useScroll.js +46 -0
  42. package/dist/tui/useScroll.js.map +1 -0
  43. package/package.json +5 -1
  44. package/src/index.ts +21 -1
  45. package/src/repl.ts +50 -676
  46. package/src/tui/App.tsx +214 -0
  47. package/src/tui/Footer.tsx +18 -0
  48. package/src/tui/Header.tsx +30 -0
  49. package/src/tui/InputLine.tsx +164 -0
  50. package/src/tui/Viewport.tsx +70 -0
  51. package/src/tui/index.tsx +72 -0
  52. package/src/tui/useChat.ts +103 -0
  53. package/src/tui/useCommand.ts +148 -0
  54. package/src/tui/useScroll.ts +65 -0
  55. package/tsconfig.json +6 -1
@@ -0,0 +1,148 @@
1
+ import { useState, useCallback } from "react";
2
+ import { createProgram } from "../program";
3
+ import chalk from "chalk";
4
+ import { c } from "../ui/colors";
5
+
6
+ interface UseCommandResult {
7
+ execute: (input: string, onLine: (line: string) => void) => Promise<void>;
8
+ isRunning: boolean;
9
+ }
10
+
11
+ /**
12
+ * Dispatches parsed commands to the commander program.
13
+ * Captures stdout/stderr by monkey-patching process.stdout.write
14
+ * and routes all output to the viewport buffer via onLine callback.
15
+ */
16
+ export function useCommand(): UseCommandResult {
17
+ const [isRunning, setIsRunning] = useState(false);
18
+
19
+ const execute = useCallback(
20
+ async (input: string, onLine: (line: string) => void): Promise<void> => {
21
+ setIsRunning(true);
22
+
23
+ // Capture stdout/stderr
24
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
25
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
26
+
27
+ let captureBuffer = "";
28
+
29
+ const flushBuffer = (): void => {
30
+ if (!captureBuffer) return;
31
+ const lines = captureBuffer.split("\n");
32
+ captureBuffer = lines.pop() ?? "";
33
+ for (const line of lines) {
34
+ onLine(line);
35
+ }
36
+ };
37
+
38
+ const capturedWrite = (
39
+ chunk: string | Uint8Array,
40
+ encodingOrCb?: BufferEncoding | ((err?: Error | null) => void),
41
+ cb?: (err?: Error | null) => void
42
+ ): boolean => {
43
+ const str =
44
+ typeof chunk === "string"
45
+ ? chunk
46
+ : Buffer.from(chunk).toString("utf8");
47
+ captureBuffer += str;
48
+ flushBuffer();
49
+ // call callback if provided
50
+ const callback =
51
+ typeof encodingOrCb === "function" ? encodingOrCb : cb;
52
+ if (callback) callback();
53
+ return true;
54
+ };
55
+
56
+ // Monkey-patch (cast through unknown to bypass strict overload checking)
57
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
+ (process.stdout as any).write = capturedWrite;
59
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
+ (process.stderr as any).write = capturedWrite;
61
+
62
+ try {
63
+ const tokens = parseTokens(input);
64
+ const prog = createProgram();
65
+ prog.exitOverride();
66
+ prog.configureOutput({
67
+ writeOut: (str) => process.stdout.write(str),
68
+ writeErr: (str) => process.stderr.write(str),
69
+ });
70
+
71
+ await prog.parseAsync(["node", "arc402", ...tokens]);
72
+ } catch (err) {
73
+ const e = err as { code?: string; message?: string };
74
+ if (
75
+ e.code === "commander.helpDisplayed" ||
76
+ e.code === "commander.version" ||
77
+ e.code === "commander.executeSubCommandAsync"
78
+ ) {
79
+ // already written or normal exit — no-op
80
+ } else if (e.code === "commander.unknownCommand") {
81
+ const tokens = parseTokens(input);
82
+ onLine(
83
+ ` ${c.failure} ${chalk.red(`Unknown command: ${chalk.white(tokens[0])}`)} `
84
+ );
85
+ onLine(chalk.dim(" Type 'help' for available commands"));
86
+ } else if (e.code?.startsWith("commander.")) {
87
+ onLine(` ${c.failure} ${chalk.red(e.message ?? String(err))}`);
88
+ } else {
89
+ onLine(` ${c.failure} ${chalk.red(e.message ?? String(err))}`);
90
+ }
91
+ } finally {
92
+ // Flush remaining buffer
93
+ if (captureBuffer.trim()) {
94
+ onLine(captureBuffer);
95
+ captureBuffer = "";
96
+ }
97
+
98
+ // Restore original write functions
99
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
+ (process.stdout as any).write = originalStdoutWrite;
101
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
102
+ (process.stderr as any).write = originalStderrWrite;
103
+
104
+ setIsRunning(false);
105
+ }
106
+ },
107
+ []
108
+ );
109
+
110
+ return { execute, isRunning };
111
+ }
112
+
113
+ // Shell-style tokenizer
114
+ function parseTokens(input: string): string[] {
115
+ const tokens: string[] = [];
116
+ let current = "";
117
+ let inQuote = false;
118
+ let quoteChar = "";
119
+ let escape = false;
120
+ for (const ch of input) {
121
+ if (escape) {
122
+ current += ch;
123
+ escape = false;
124
+ continue;
125
+ }
126
+ if (inQuote) {
127
+ if (quoteChar === '"' && ch === "\\") {
128
+ escape = true;
129
+ } else if (ch === quoteChar) {
130
+ inQuote = false;
131
+ } else {
132
+ current += ch;
133
+ }
134
+ } else if (ch === '"' || ch === "'") {
135
+ inQuote = true;
136
+ quoteChar = ch;
137
+ } else if (ch === " ") {
138
+ if (current) {
139
+ tokens.push(current);
140
+ current = "";
141
+ }
142
+ } else {
143
+ current += ch;
144
+ }
145
+ }
146
+ if (current) tokens.push(current);
147
+ return tokens;
148
+ }
@@ -0,0 +1,65 @@
1
+ import { useState, useCallback } from "react";
2
+ import { useInput } from "ink";
3
+
4
+ interface UseScrollResult {
5
+ scrollOffset: number;
6
+ setScrollOffset: (offset: number) => void;
7
+ viewportHeight: number;
8
+ isAutoScroll: boolean;
9
+ scrollUp: (lines?: number) => void;
10
+ scrollDown: (lines?: number) => void;
11
+ snapToBottom: () => void;
12
+ }
13
+
14
+ /**
15
+ * Manages scroll state for the Viewport.
16
+ * scrollOffset=0 means auto-scroll (pinned to bottom).
17
+ * Positive scrollOffset means scrolled up by that many lines.
18
+ */
19
+ export function useScroll(viewportHeight: number): UseScrollResult {
20
+ const [scrollOffset, setScrollOffsetState] = useState(0);
21
+
22
+ const isAutoScroll = scrollOffset === 0;
23
+
24
+ const setScrollOffset = useCallback((offset: number) => {
25
+ setScrollOffsetState(Math.max(0, offset));
26
+ }, []);
27
+
28
+ const scrollUp = useCallback(
29
+ (lines?: number) => {
30
+ const step = lines ?? viewportHeight;
31
+ setScrollOffsetState((prev) => prev + step);
32
+ },
33
+ [viewportHeight]
34
+ );
35
+
36
+ const scrollDown = useCallback(
37
+ (lines?: number) => {
38
+ const step = lines ?? viewportHeight;
39
+ setScrollOffsetState((prev) => Math.max(0, prev - step));
40
+ },
41
+ [viewportHeight]
42
+ );
43
+
44
+ const snapToBottom = useCallback(() => {
45
+ setScrollOffsetState(0);
46
+ }, []);
47
+
48
+ useInput((_input, key) => {
49
+ if (key.pageUp) {
50
+ scrollUp(viewportHeight);
51
+ } else if (key.pageDown) {
52
+ scrollDown(viewportHeight);
53
+ }
54
+ });
55
+
56
+ return {
57
+ scrollOffset,
58
+ setScrollOffset,
59
+ viewportHeight,
60
+ isAutoScroll,
61
+ scrollUp,
62
+ scrollDown,
63
+ snapToBottom,
64
+ };
65
+ }
package/tsconfig.json CHANGED
@@ -3,6 +3,7 @@
3
3
  "target": "ES2020",
4
4
  "module": "commonjs",
5
5
  "lib": ["ES2020"],
6
+ "jsx": "react-jsx",
6
7
  "outDir": "./dist",
7
8
  "rootDir": "./src",
8
9
  "strict": true,
@@ -12,7 +13,11 @@
12
13
  "resolveJsonModule": true,
13
14
  "declaration": true,
14
15
  "declarationMap": true,
15
- "sourceMap": true
16
+ "sourceMap": true,
17
+ "paths": {
18
+ "ink": ["./node_modules/ink/build/index.d.ts"],
19
+ "ink-text-input": ["./node_modules/ink-text-input/build/index.d.ts"]
20
+ }
16
21
  },
17
22
  "include": ["src/**/*"],
18
23
  "exclude": ["node_modules", "dist"]