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.
- package/TUI-SPEC.md +214 -0
- package/dist/index.js +55 -1
- package/dist/index.js.map +1 -1
- package/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +46 -567
- package/dist/repl.js.map +1 -1
- package/dist/tui/App.d.ts +12 -0
- package/dist/tui/App.d.ts.map +1 -0
- package/dist/tui/App.js +154 -0
- package/dist/tui/App.js.map +1 -0
- package/dist/tui/Footer.d.ts +11 -0
- package/dist/tui/Footer.d.ts.map +1 -0
- package/dist/tui/Footer.js +13 -0
- package/dist/tui/Footer.js.map +1 -0
- package/dist/tui/Header.d.ts +14 -0
- package/dist/tui/Header.d.ts.map +1 -0
- package/dist/tui/Header.js +19 -0
- package/dist/tui/Header.js.map +1 -0
- package/dist/tui/InputLine.d.ts +11 -0
- package/dist/tui/InputLine.d.ts.map +1 -0
- package/dist/tui/InputLine.js +145 -0
- package/dist/tui/InputLine.js.map +1 -0
- package/dist/tui/Viewport.d.ts +14 -0
- package/dist/tui/Viewport.d.ts.map +1 -0
- package/dist/tui/Viewport.js +48 -0
- package/dist/tui/Viewport.js.map +1 -0
- package/dist/tui/index.d.ts +2 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +55 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/useChat.d.ts +11 -0
- package/dist/tui/useChat.d.ts.map +1 -0
- package/dist/tui/useChat.js +91 -0
- package/dist/tui/useChat.js.map +1 -0
- package/dist/tui/useCommand.d.ts +12 -0
- package/dist/tui/useCommand.d.ts.map +1 -0
- package/dist/tui/useCommand.js +137 -0
- package/dist/tui/useCommand.js.map +1 -0
- package/dist/tui/useScroll.d.ts +17 -0
- package/dist/tui/useScroll.d.ts.map +1 -0
- package/dist/tui/useScroll.js +46 -0
- package/dist/tui/useScroll.js.map +1 -0
- package/package.json +5 -1
- package/src/index.ts +21 -1
- package/src/repl.ts +50 -676
- package/src/tui/App.tsx +214 -0
- package/src/tui/Footer.tsx +18 -0
- package/src/tui/Header.tsx +30 -0
- package/src/tui/InputLine.tsx +164 -0
- package/src/tui/Viewport.tsx +70 -0
- package/src/tui/index.tsx +72 -0
- package/src/tui/useChat.ts +103 -0
- package/src/tui/useCommand.ts +148 -0
- package/src/tui/useScroll.ts +65 -0
- 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"]
|