smolcoder-plus 1.0.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.
- package/LICENSE +21 -0
- package/README.md +102 -0
- package/dist/agent.js +748 -0
- package/dist/attachments.js +158 -0
- package/dist/config.js +87 -0
- package/dist/context.js +498 -0
- package/dist/detect.js +474 -0
- package/dist/events.js +24 -0
- package/dist/history.js +9 -0
- package/dist/hosts.js +107 -0
- package/dist/index.js +391 -0
- package/dist/logo.js +48 -0
- package/dist/netscan.js +159 -0
- package/dist/network.js +193 -0
- package/dist/plan.js +102 -0
- package/dist/prompt.js +84 -0
- package/dist/providers/lmstudio.js +347 -0
- package/dist/providers/ollama.js +269 -0
- package/dist/providers/scheduler.js +57 -0
- package/dist/providers/transport.js +86 -0
- package/dist/providers/types.js +62 -0
- package/dist/sandbox.js +207 -0
- package/dist/session.js +639 -0
- package/dist/tools/check.js +193 -0
- package/dist/tools/fs-tools.js +431 -0
- package/dist/tools/index.js +260 -0
- package/dist/tools/search-worker.js +34 -0
- package/dist/tools/shell.js +186 -0
- package/dist/tools/tasks.js +147 -0
- package/dist/tools/web-search.js +155 -0
- package/dist/tui/editor.js +134 -0
- package/dist/tui/keys.js +145 -0
- package/dist/tui/tui.js +723 -0
- package/dist/ui.js +226 -0
- package/dist/util.js +91 -0
- package/dist/verification.js +71 -0
- package/dist/web/channel.js +260 -0
- package/dist/web/client.js +1010 -0
- package/dist/web/hub.js +952 -0
- package/dist/web/page.js +87 -0
- package/dist/web/store.js +199 -0
- package/dist/web/styles.js +333 -0
- package/dist/web/terminal.js +190 -0
- package/package.json +49 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// web_search tool: Brave Search API integration for the agent.
|
|
3
|
+
// Port of scripts/search.js (loadDotEnv + fetchWithTimeout + searchBrave + formatting)
|
|
4
|
+
// into TypeScript so every new project gets internet search without copying files.
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.DEFAULT_MAX_RESULTS = void 0;
|
|
40
|
+
exports.truncate = truncate;
|
|
41
|
+
exports.formatWebSearchResults = formatWebSearchResults;
|
|
42
|
+
exports.searchBrave = searchBrave;
|
|
43
|
+
exports.webSearch = webSearch;
|
|
44
|
+
const MAX_OUTPUT_CHARS = 3000;
|
|
45
|
+
exports.DEFAULT_MAX_RESULTS = 5;
|
|
46
|
+
const FETCH_TIMEOUT_MS = 10000;
|
|
47
|
+
/** Load KEY=VALUE pairs from a .env file in the current working directory or any parent
|
|
48
|
+
* directory into process.env, without any npm dependency. Real, already-exported env vars always
|
|
49
|
+
* win — this only fills in values that aren't set yet (matches scripts/search.js). */
|
|
50
|
+
async function loadDotEnv() {
|
|
51
|
+
try {
|
|
52
|
+
const { readFileSync } = await Promise.resolve().then(() => __importStar(require("node:fs")));
|
|
53
|
+
const { join, dirname } = await Promise.resolve().then(() => __importStar(require("node:path")));
|
|
54
|
+
let currentDir = process.cwd();
|
|
55
|
+
let envLoaded = false;
|
|
56
|
+
while (currentDir !== dirname(currentDir) && !envLoaded) {
|
|
57
|
+
const envPath = join(currentDir, ".env");
|
|
58
|
+
try {
|
|
59
|
+
if (readFileSync(envPath, "utf8")) {
|
|
60
|
+
const content = readFileSync(envPath, "utf8");
|
|
61
|
+
for (const rawLine of content.split("\n")) {
|
|
62
|
+
const line = rawLine.trim();
|
|
63
|
+
if (!line || line.startsWith("#"))
|
|
64
|
+
continue;
|
|
65
|
+
const eq = line.indexOf("=");
|
|
66
|
+
if (eq === -1)
|
|
67
|
+
continue;
|
|
68
|
+
const key = line.slice(0, eq).trim();
|
|
69
|
+
let value = line.slice(eq + 1).trim();
|
|
70
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
71
|
+
value = value.slice(1, -1);
|
|
72
|
+
}
|
|
73
|
+
if (!(key in process.env))
|
|
74
|
+
process.env[key] = value;
|
|
75
|
+
}
|
|
76
|
+
envLoaded = true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// File not found or unreadable, move up
|
|
81
|
+
}
|
|
82
|
+
currentDir = dirname(currentDir);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// General failure — fine, just rely on real env vars.
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** fetch with a hard timeout; returns null on any failure. */
|
|
90
|
+
async function fetchWithTimeout(url, options = {}) {
|
|
91
|
+
const controller = new AbortController();
|
|
92
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
93
|
+
try {
|
|
94
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** Truncate at the end (with a short tail note) — used for search output. */
|
|
101
|
+
function truncate(text, max) {
|
|
102
|
+
if (text.length <= max)
|
|
103
|
+
return text;
|
|
104
|
+
// Calculate how much to cut so that the note is exactly part of the total length
|
|
105
|
+
const note = "\n... (truncated)";
|
|
106
|
+
const cutOff = max - note.length;
|
|
107
|
+
return text.slice(0, cutOff) + note;
|
|
108
|
+
}
|
|
109
|
+
/** Format raw Brave results into numbered titles, snippets and URLs. */
|
|
110
|
+
function formatWebSearchResults(results) {
|
|
111
|
+
if (results.length === 0) {
|
|
112
|
+
return "No results found. (backend: Brave Search)";
|
|
113
|
+
}
|
|
114
|
+
const lines = results.map((r, i) => {
|
|
115
|
+
const title = (r.title || "(no title)").trim();
|
|
116
|
+
const snippet = (r.snippet || "").trim().replace(/\s+/g, " ");
|
|
117
|
+
const url = (r.url || "").trim();
|
|
118
|
+
return `${i + 1}. ${title}\n ${snippet}\n ${url}`;
|
|
119
|
+
});
|
|
120
|
+
return `Search results (backend: Brave Search)\n\n${lines.join("\n\n")}`;
|
|
121
|
+
}
|
|
122
|
+
/** Call the Brave Search API and return up to maxResults items. */
|
|
123
|
+
async function searchBrave(query, maxResults) {
|
|
124
|
+
const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${maxResults}`;
|
|
125
|
+
const res = await fetchWithTimeout(url, {
|
|
126
|
+
headers: {
|
|
127
|
+
Accept: "application/json",
|
|
128
|
+
"X-Subscription-Token": process.env.BRAVE_API_KEY,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
if (!res)
|
|
132
|
+
throw new Error("Network request failed.");
|
|
133
|
+
if (!res.ok)
|
|
134
|
+
throw new Error(`Brave API error: ${res.status} ${res.statusText}`);
|
|
135
|
+
const data = (await res.json());
|
|
136
|
+
const items = (data.web && data.web.results) || [];
|
|
137
|
+
return items.slice(0, maxResults).map((r) => ({
|
|
138
|
+
title: r.title,
|
|
139
|
+
snippet: r.description,
|
|
140
|
+
url: r.url,
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
/** Main entry point used by the tool registry. Returns a formatted string. */
|
|
144
|
+
function webSearch(query, maxResults) {
|
|
145
|
+
const limit = Math.max(1, Math.min(8, maxResults ?? exports.DEFAULT_MAX_RESULTS));
|
|
146
|
+
return loadDotEnv()
|
|
147
|
+
.then(() => {
|
|
148
|
+
if (!process.env.BRAVE_API_KEY) {
|
|
149
|
+
return "Error: Missing BRAVE_API_KEY environment variable.";
|
|
150
|
+
}
|
|
151
|
+
return searchBrave(query, limit)
|
|
152
|
+
.then((results) => truncate(formatWebSearchResults(results), MAX_OUTPUT_CHARS))
|
|
153
|
+
.catch((e) => `Error: ${e.message}`);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Line editor state: a buffer + cursor index, with logical-line navigation
|
|
3
|
+
// for multi-line input, and a wrap-aware layout used by the renderer.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.LineEditor = void 0;
|
|
6
|
+
exports.layoutBuffer = layoutBuffer;
|
|
7
|
+
class LineEditor {
|
|
8
|
+
buffer = "";
|
|
9
|
+
cursor = 0;
|
|
10
|
+
insert(text) {
|
|
11
|
+
this.buffer = this.buffer.slice(0, this.cursor) + text + this.buffer.slice(this.cursor);
|
|
12
|
+
this.cursor += text.length;
|
|
13
|
+
}
|
|
14
|
+
backspace() {
|
|
15
|
+
if (this.cursor === 0)
|
|
16
|
+
return;
|
|
17
|
+
this.buffer = this.buffer.slice(0, this.cursor - 1) + this.buffer.slice(this.cursor);
|
|
18
|
+
this.cursor--;
|
|
19
|
+
}
|
|
20
|
+
del() {
|
|
21
|
+
if (this.cursor >= this.buffer.length)
|
|
22
|
+
return;
|
|
23
|
+
this.buffer = this.buffer.slice(0, this.cursor) + this.buffer.slice(this.cursor + 1);
|
|
24
|
+
}
|
|
25
|
+
left() {
|
|
26
|
+
if (this.cursor > 0)
|
|
27
|
+
this.cursor--;
|
|
28
|
+
}
|
|
29
|
+
right() {
|
|
30
|
+
if (this.cursor < this.buffer.length)
|
|
31
|
+
this.cursor++;
|
|
32
|
+
}
|
|
33
|
+
lineStartIdx() {
|
|
34
|
+
return this.buffer.lastIndexOf("\n", this.cursor - 1) + 1;
|
|
35
|
+
}
|
|
36
|
+
lineEndIdx() {
|
|
37
|
+
const nl = this.buffer.indexOf("\n", this.cursor);
|
|
38
|
+
return nl < 0 ? this.buffer.length : nl;
|
|
39
|
+
}
|
|
40
|
+
home() {
|
|
41
|
+
this.cursor = this.lineStartIdx();
|
|
42
|
+
}
|
|
43
|
+
end() {
|
|
44
|
+
this.cursor = this.lineEndIdx();
|
|
45
|
+
}
|
|
46
|
+
/** Line the cursor is on (0-based) and column within it. */
|
|
47
|
+
lineCol() {
|
|
48
|
+
const before = this.buffer.slice(0, this.cursor);
|
|
49
|
+
const line = (before.match(/\n/g) ?? []).length;
|
|
50
|
+
const col = this.cursor - this.lineStartIdx();
|
|
51
|
+
return { line, col };
|
|
52
|
+
}
|
|
53
|
+
lineCount() {
|
|
54
|
+
return (this.buffer.match(/\n/g) ?? []).length + 1;
|
|
55
|
+
}
|
|
56
|
+
/** Move cursor up one logical line; returns false if already on the first. */
|
|
57
|
+
upLine() {
|
|
58
|
+
const { line, col } = this.lineCol();
|
|
59
|
+
if (line === 0)
|
|
60
|
+
return false;
|
|
61
|
+
const lines = this.buffer.split("\n");
|
|
62
|
+
let idx = 0;
|
|
63
|
+
for (let i = 0; i < line - 1; i++)
|
|
64
|
+
idx += lines[i].length + 1;
|
|
65
|
+
this.cursor = idx + Math.min(col, lines[line - 1].length);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
downLine() {
|
|
69
|
+
const { line, col } = this.lineCol();
|
|
70
|
+
const lines = this.buffer.split("\n");
|
|
71
|
+
if (line >= lines.length - 1)
|
|
72
|
+
return false;
|
|
73
|
+
let idx = 0;
|
|
74
|
+
for (let i = 0; i <= line; i++)
|
|
75
|
+
idx += lines[i].length + 1;
|
|
76
|
+
this.cursor = idx + Math.min(col, lines[line + 1].length);
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
killToLineStart() {
|
|
80
|
+
const start = this.lineStartIdx();
|
|
81
|
+
this.buffer = this.buffer.slice(0, start) + this.buffer.slice(this.cursor);
|
|
82
|
+
this.cursor = start;
|
|
83
|
+
}
|
|
84
|
+
deleteWordBack() {
|
|
85
|
+
if (this.cursor === 0)
|
|
86
|
+
return;
|
|
87
|
+
let i = this.cursor;
|
|
88
|
+
while (i > 0 && /\s/.test(this.buffer[i - 1]))
|
|
89
|
+
i--;
|
|
90
|
+
while (i > 0 && !/\s/.test(this.buffer[i - 1]))
|
|
91
|
+
i--;
|
|
92
|
+
this.buffer = this.buffer.slice(0, i) + this.buffer.slice(this.cursor);
|
|
93
|
+
this.cursor = i;
|
|
94
|
+
}
|
|
95
|
+
clear() {
|
|
96
|
+
this.buffer = "";
|
|
97
|
+
this.cursor = 0;
|
|
98
|
+
}
|
|
99
|
+
set(text) {
|
|
100
|
+
this.buffer = text;
|
|
101
|
+
this.cursor = text.length;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
exports.LineEditor = LineEditor;
|
|
105
|
+
/** Wrap the buffer into display rows of at most `width` chars, tracking where
|
|
106
|
+
* the cursor lands. Explicit newlines always break a row. */
|
|
107
|
+
function layoutBuffer(buffer, cursor, width) {
|
|
108
|
+
const rows = [];
|
|
109
|
+
let row = "";
|
|
110
|
+
let curRow = 0;
|
|
111
|
+
let curCol = 0;
|
|
112
|
+
for (let i = 0; i <= buffer.length; i++) {
|
|
113
|
+
if (i === cursor) {
|
|
114
|
+
curRow = rows.length;
|
|
115
|
+
curCol = row.length;
|
|
116
|
+
}
|
|
117
|
+
if (i === buffer.length)
|
|
118
|
+
break;
|
|
119
|
+
const ch = buffer[i];
|
|
120
|
+
if (ch === "\n") {
|
|
121
|
+
rows.push(row);
|
|
122
|
+
row = "";
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
row += ch;
|
|
126
|
+
if (row.length >= width) {
|
|
127
|
+
rows.push(row);
|
|
128
|
+
row = "";
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
rows.push(row);
|
|
133
|
+
return { rows, curRow, curCol };
|
|
134
|
+
}
|
package/dist/tui/keys.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Hand-rolled terminal key decoder (zero deps). Parses VT escape sequences
|
|
3
|
+
// from raw-mode stdin, including bracketed paste so pasted newlines insert
|
|
4
|
+
// instead of submitting.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.KeyDecoder = void 0;
|
|
7
|
+
const CSI_MAP = {
|
|
8
|
+
A: "up",
|
|
9
|
+
B: "down",
|
|
10
|
+
C: "right",
|
|
11
|
+
D: "left",
|
|
12
|
+
H: "home",
|
|
13
|
+
F: "end",
|
|
14
|
+
Z: "shifttab",
|
|
15
|
+
"1~": "home",
|
|
16
|
+
"3~": "delete",
|
|
17
|
+
"4~": "end",
|
|
18
|
+
"7~": "home",
|
|
19
|
+
"8~": "end",
|
|
20
|
+
};
|
|
21
|
+
function normalizePaste(s) {
|
|
22
|
+
return s
|
|
23
|
+
.replace(/\r\n/g, "\n")
|
|
24
|
+
.replace(/\r/g, "\n")
|
|
25
|
+
// strip control chars except newline and tab
|
|
26
|
+
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
|
|
27
|
+
}
|
|
28
|
+
class KeyDecoder {
|
|
29
|
+
pasteBuf = null;
|
|
30
|
+
decode(s) {
|
|
31
|
+
const out = [];
|
|
32
|
+
let i = 0;
|
|
33
|
+
while (i < s.length) {
|
|
34
|
+
// inside a bracketed paste: consume until the end marker
|
|
35
|
+
if (this.pasteBuf !== null) {
|
|
36
|
+
const end = s.indexOf("\x1b[201~", i);
|
|
37
|
+
if (end < 0) {
|
|
38
|
+
this.pasteBuf += s.slice(i);
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
this.pasteBuf += s.slice(i, end);
|
|
42
|
+
out.push({ type: "text", text: normalizePaste(this.pasteBuf) });
|
|
43
|
+
this.pasteBuf = null;
|
|
44
|
+
i = end + 6;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const ch = s[i];
|
|
48
|
+
if (ch === "\x1b") {
|
|
49
|
+
if (s.startsWith("\x1b[200~", i)) {
|
|
50
|
+
this.pasteBuf = "";
|
|
51
|
+
i += 6;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (s[i + 1] === "[") {
|
|
55
|
+
let j = i + 2;
|
|
56
|
+
while (j < s.length && !(s[j] >= "@" && s[j] <= "~"))
|
|
57
|
+
j++;
|
|
58
|
+
if (j >= s.length)
|
|
59
|
+
break; // incomplete sequence — drop
|
|
60
|
+
const seq = s.slice(i + 2, j + 1);
|
|
61
|
+
const mapped = CSI_MAP[seq];
|
|
62
|
+
if (mapped)
|
|
63
|
+
out.push({ type: mapped });
|
|
64
|
+
i = j + 1;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (s[i + 1] === "O" && i + 2 < s.length) {
|
|
68
|
+
const mapped = CSI_MAP[s[i + 2]];
|
|
69
|
+
if (mapped)
|
|
70
|
+
out.push({ type: mapped });
|
|
71
|
+
i += 3;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
out.push({ type: "esc" });
|
|
75
|
+
i++;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const code = ch.charCodeAt(0);
|
|
79
|
+
if (ch === "\r" || ch === "\n") {
|
|
80
|
+
out.push({ type: "enter" });
|
|
81
|
+
i++;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (ch === "\t") {
|
|
85
|
+
out.push({ type: "tab" });
|
|
86
|
+
i++;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (code === 127 || code === 8) {
|
|
90
|
+
out.push({ type: "backspace" });
|
|
91
|
+
i++;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (code === 3) {
|
|
95
|
+
out.push({ type: "ctrlc" });
|
|
96
|
+
i++;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (code === 4) {
|
|
100
|
+
out.push({ type: "ctrld" });
|
|
101
|
+
i++;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (code === 21) {
|
|
105
|
+
out.push({ type: "ctrlu" });
|
|
106
|
+
i++;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (code === 23) {
|
|
110
|
+
out.push({ type: "ctrlw" });
|
|
111
|
+
i++;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (code === 1) {
|
|
115
|
+
out.push({ type: "ctrla" });
|
|
116
|
+
i++;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (code === 5) {
|
|
120
|
+
out.push({ type: "ctrle" });
|
|
121
|
+
i++;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (code < 32) {
|
|
125
|
+
i++;
|
|
126
|
+
continue;
|
|
127
|
+
} // other control chars: ignore
|
|
128
|
+
// printable run (single keystroke or unbracketed paste)
|
|
129
|
+
let j = i;
|
|
130
|
+
let run = "";
|
|
131
|
+
while (j < s.length) {
|
|
132
|
+
const cj = s[j];
|
|
133
|
+
const cc = cj.charCodeAt(0);
|
|
134
|
+
if (cj === "\x1b" || cj === "\r" || cj === "\n" || cj === "\t" || cc < 32 || cc === 127)
|
|
135
|
+
break;
|
|
136
|
+
run += cj;
|
|
137
|
+
j++;
|
|
138
|
+
}
|
|
139
|
+
out.push(run.length === 1 ? { type: "char", text: run } : { type: "text", text: run });
|
|
140
|
+
i = j;
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
exports.KeyDecoder = KeyDecoder;
|