@rind-ai/cli 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/rind.js +5 -0
- package/lib/assistant-renderer.js +265 -0
- package/lib/assistant-stream-buffer.js +25 -0
- package/lib/background-controller.js +307 -0
- package/lib/choice-menu-state.js +46 -0
- package/lib/command-controller.js +126 -0
- package/lib/compact-context-state.js +22 -0
- package/lib/composer-terminal.js +203 -0
- package/lib/event-controller.js +242 -0
- package/lib/frontend-cli-implementation.js +1111 -0
- package/lib/frontend-cli.js +5 -0
- package/lib/input-controller.js +94 -0
- package/lib/input-errors.js +3 -0
- package/lib/interrupt-state.js +9 -0
- package/lib/line-editor.js +541 -0
- package/lib/model-menu-state.js +50 -0
- package/lib/rendering.js +1060 -0
- package/lib/runtime-client.js +201 -0
- package/lib/runtime-env.js +21 -0
- package/lib/runtime-protocol.js +15 -0
- package/lib/slash-command-mode.js +27 -0
- package/lib/slash-menu-state.js +59 -0
- package/lib/terminal-key.js +97 -0
- package/lib/terminal-ui.js +581 -0
- package/lib/text-width.js +151 -0
- package/lib/turn-controller.js +78 -0
- package/package.json +28 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
const ANSI_RE = /\x1b\[[0-?]*[ -/]*[@-~]/g;
|
|
2
|
+
const segmenter = typeof Intl?.Segmenter === "function"
|
|
3
|
+
? new Intl.Segmenter(undefined, { granularity: "grapheme" })
|
|
4
|
+
: null;
|
|
5
|
+
|
|
6
|
+
export function stripAnsi(value) {
|
|
7
|
+
return String(value || "").replace(ANSI_RE, "");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function graphemes(value) {
|
|
11
|
+
const text = String(value || "");
|
|
12
|
+
return segmenter
|
|
13
|
+
? Array.from(segmenter.segment(text), (segment) => segment.segment)
|
|
14
|
+
: Array.from(text);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function textWidth(value) {
|
|
18
|
+
return graphemes(stripAnsi(value)).reduce((width, segment) => width + segmentWidth(segment), 0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function wrapTextCells(value, firstWidth, continuationWidth = firstWidth) {
|
|
22
|
+
const chars = graphemes(value);
|
|
23
|
+
if (!chars.length) {
|
|
24
|
+
return [{
|
|
25
|
+
text: "",
|
|
26
|
+
startColumn: 0,
|
|
27
|
+
length: 0,
|
|
28
|
+
allowsEnd: true,
|
|
29
|
+
}];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const chunks = [];
|
|
33
|
+
let startColumn = 0;
|
|
34
|
+
let availableWidth = positiveWidth(firstWidth);
|
|
35
|
+
const nextLineWidth = positiveWidth(continuationWidth);
|
|
36
|
+
while (startColumn < chars.length) {
|
|
37
|
+
let endColumn = startColumn;
|
|
38
|
+
let width = 0;
|
|
39
|
+
while (endColumn < chars.length) {
|
|
40
|
+
const nextWidth = segmentWidth(chars[endColumn]);
|
|
41
|
+
if (width > 0 && width + nextWidth > availableWidth) {
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
width += nextWidth;
|
|
45
|
+
endColumn += 1;
|
|
46
|
+
}
|
|
47
|
+
chunks.push({
|
|
48
|
+
text: chars.slice(startColumn, endColumn).join(""),
|
|
49
|
+
startColumn,
|
|
50
|
+
length: endColumn - startColumn,
|
|
51
|
+
allowsEnd: width < availableWidth,
|
|
52
|
+
});
|
|
53
|
+
startColumn = endColumn;
|
|
54
|
+
availableWidth = nextLineWidth;
|
|
55
|
+
}
|
|
56
|
+
return chunks;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function clipCells(value, maxWidth) {
|
|
60
|
+
const text = String(value || "");
|
|
61
|
+
if (textWidth(text) <= maxWidth) {
|
|
62
|
+
return text;
|
|
63
|
+
}
|
|
64
|
+
const suffix = "...";
|
|
65
|
+
const suffixWidth = textWidth(suffix);
|
|
66
|
+
if (maxWidth <= suffixWidth) {
|
|
67
|
+
return suffix.slice(0, Math.max(0, maxWidth));
|
|
68
|
+
}
|
|
69
|
+
return `${takeStartCells(text, maxWidth - suffixWidth)}${suffix}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function middleClipCells(value, maxWidth) {
|
|
73
|
+
const text = String(value || "");
|
|
74
|
+
if (textWidth(text) <= maxWidth) {
|
|
75
|
+
return text;
|
|
76
|
+
}
|
|
77
|
+
const suffix = "...";
|
|
78
|
+
const available = Math.max(0, maxWidth - textWidth(suffix));
|
|
79
|
+
const headWidth = Math.ceil(available / 2);
|
|
80
|
+
const tailWidth = Math.floor(available / 2);
|
|
81
|
+
return `${takeStartCells(text, headWidth)}${suffix}${takeEndCells(text, tailWidth)}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function takeStartCells(value, maxWidth) {
|
|
85
|
+
let output = "";
|
|
86
|
+
let width = 0;
|
|
87
|
+
for (const segment of graphemes(value)) {
|
|
88
|
+
const nextWidth = segmentWidth(segment);
|
|
89
|
+
if (width + nextWidth > maxWidth) {
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
output += segment;
|
|
93
|
+
width += nextWidth;
|
|
94
|
+
}
|
|
95
|
+
return output;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function takeEndCells(value, maxWidth) {
|
|
99
|
+
let output = "";
|
|
100
|
+
let width = 0;
|
|
101
|
+
for (const segment of graphemes(value).reverse()) {
|
|
102
|
+
const nextWidth = segmentWidth(segment);
|
|
103
|
+
if (width + nextWidth > maxWidth) {
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
output = `${segment}${output}`;
|
|
107
|
+
width += nextWidth;
|
|
108
|
+
}
|
|
109
|
+
return output;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function segmentWidth(segment) {
|
|
113
|
+
const text = stripAnsi(segment);
|
|
114
|
+
if (!text || isZeroWidth(text)) {
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
if (isEmoji(text)) {
|
|
118
|
+
return 2;
|
|
119
|
+
}
|
|
120
|
+
return Array.from(text).some((char) => isWideCodePoint(char.codePointAt(0))) ? 2 : 1;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function positiveWidth(value) {
|
|
124
|
+
const width = Number(value);
|
|
125
|
+
return Number.isFinite(width) && width > 0 ? Math.floor(width) : 1;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isZeroWidth(text) {
|
|
129
|
+
return /^[\u0300-\u036f\u0483-\u0489\u200b-\u200f\u20d0-\u20ff\ufe00-\ufe0f]+$/u.test(text);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function isEmoji(text) {
|
|
133
|
+
return /^[0-9#*]\ufe0f?\u20e3$/u.test(text)
|
|
134
|
+
|| /[\u{1f1e6}-\u{1f1ff}]/u.test(text)
|
|
135
|
+
|| /\p{Extended_Pictographic}/u.test(text)
|
|
136
|
+
|| text.includes("\u200d")
|
|
137
|
+
|| text.includes("\ufe0f");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function isWideCodePoint(codePoint) {
|
|
141
|
+
return (codePoint >= 0x1100 && codePoint <= 0x115f)
|
|
142
|
+
|| codePoint === 0x2329
|
|
143
|
+
|| codePoint === 0x232a
|
|
144
|
+
|| (codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f)
|
|
145
|
+
|| (codePoint >= 0xac00 && codePoint <= 0xd7a3)
|
|
146
|
+
|| (codePoint >= 0xf900 && codePoint <= 0xfaff)
|
|
147
|
+
|| (codePoint >= 0xfe10 && codePoint <= 0xfe19)
|
|
148
|
+
|| (codePoint >= 0xfe30 && codePoint <= 0xfe6f)
|
|
149
|
+
|| (codePoint >= 0xff00 && codePoint <= 0xff60)
|
|
150
|
+
|| (codePoint >= 0xffe0 && codePoint <= 0xffe6);
|
|
151
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { turnInputMethod } from "./runtime-protocol.js";
|
|
2
|
+
|
|
3
|
+
export function createTurnController({ request, state, output, refreshGoalState = () => {}, onTurnStart = () => {} }) {
|
|
4
|
+
function submit(text, extra = {}) {
|
|
5
|
+
const method = turnInputMethod(state.activeTurn);
|
|
6
|
+
if (method === "turn.follow_up") {
|
|
7
|
+
output.logQueuedInput(text);
|
|
8
|
+
void submitQueuedInput(method, text, text);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
start(text, extra);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function submitSteering(text, originalInput) {
|
|
15
|
+
void submitQueuedInput("turn.steer", text, originalInput);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function submitQueuedInput(method, text, originalInput) {
|
|
19
|
+
try {
|
|
20
|
+
await request(method, { input: text });
|
|
21
|
+
} catch (error) {
|
|
22
|
+
handleSubmissionError(error, originalInput);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function handleSubmissionError(error, text) {
|
|
27
|
+
output.restoreInputText(text);
|
|
28
|
+
if (!state.runtimeClosing) {
|
|
29
|
+
output.writeError(error instanceof Error ? error.message : String(error));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function start(text, extra = {}) {
|
|
34
|
+
if (state.runtimeClosing) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
state.activeTurn = true;
|
|
38
|
+
state.interruptRequested = false;
|
|
39
|
+
state.turnTools = { completed: 0, failed: 0 };
|
|
40
|
+
onTurnStart();
|
|
41
|
+
output.refreshInputState();
|
|
42
|
+
void run(text, extra).catch((error) => handleSubmissionError(error, text));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function run(text, extra = {}) {
|
|
46
|
+
try {
|
|
47
|
+
await request("turn.start", { input: text, ...extra });
|
|
48
|
+
} finally {
|
|
49
|
+
void refreshGoalState();
|
|
50
|
+
state.activeTurn = false;
|
|
51
|
+
state.interruptRequested = false;
|
|
52
|
+
output.closeAssistant();
|
|
53
|
+
output.refreshInputState();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function interrupt() {
|
|
58
|
+
state.interruptRequested = true;
|
|
59
|
+
output.cancelInput();
|
|
60
|
+
output.closeAssistant();
|
|
61
|
+
output.logInterrupt();
|
|
62
|
+
void request("turn.interrupt").catch(() => {});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function reset() {
|
|
66
|
+
state.activeTurn = false;
|
|
67
|
+
state.interruptRequested = false;
|
|
68
|
+
output.resetTurnTools();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
submit,
|
|
73
|
+
submitSteering,
|
|
74
|
+
interrupt,
|
|
75
|
+
isActive: () => state.activeTurn,
|
|
76
|
+
reset,
|
|
77
|
+
};
|
|
78
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rind-ai/cli",
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "Rind command-line client",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": "https://github.com/Azzurroooo/rind.git",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"rind": "bin/rind.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin",
|
|
13
|
+
"lib"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"optionalDependencies": {
|
|
19
|
+
"@rind-ai/runtime-win32-x64": "0.4.1",
|
|
20
|
+
"@rind-ai/runtime-linux-x64": "0.4.1",
|
|
21
|
+
"@rind-ai/runtime-darwin-x64": "0.4.1",
|
|
22
|
+
"@rind-ai/runtime-darwin-arm64": "0.4.1"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public",
|
|
26
|
+
"registry": "https://registry.npmjs.org"
|
|
27
|
+
}
|
|
28
|
+
}
|