terminal-pilot 0.0.5 → 0.0.7
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/README.md +40 -0
- package/dist/ansi.js +68 -63
- package/dist/ansi.js.map +7 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +6254 -0
- package/dist/cli.js.map +7 -0
- package/dist/commands/close-session.d.ts +12 -0
- package/dist/commands/close-session.js +1509 -0
- package/dist/commands/close-session.js.map +7 -0
- package/dist/commands/create-session.d.ts +26 -0
- package/dist/commands/create-session.js +1516 -0
- package/dist/commands/create-session.js.map +7 -0
- package/dist/commands/fill.d.ts +10 -0
- package/dist/commands/fill.js +1512 -0
- package/dist/commands/fill.js.map +7 -0
- package/dist/commands/get-session.d.ts +18 -0
- package/dist/commands/get-session.js +1514 -0
- package/dist/commands/get-session.js.map +7 -0
- package/dist/commands/index.d.ts +242 -0
- package/dist/commands/index.js +3973 -0
- package/dist/commands/index.js.map +7 -0
- package/dist/commands/install.d.ts +20 -0
- package/dist/commands/install.js +1480 -0
- package/dist/commands/install.js.map +7 -0
- package/dist/commands/installer.d.ts +43 -0
- package/dist/commands/installer.js +289 -0
- package/dist/commands/installer.js.map +7 -0
- package/dist/commands/list-sessions.d.ts +16 -0
- package/dist/commands/list-sessions.js +1513 -0
- package/dist/commands/list-sessions.js.map +7 -0
- package/dist/commands/press-key.d.ts +10 -0
- package/dist/commands/press-key.js +1512 -0
- package/dist/commands/press-key.js.map +7 -0
- package/dist/commands/read-history.d.ts +16 -0
- package/dist/commands/read-history.js +1511 -0
- package/dist/commands/read-history.js.map +7 -0
- package/dist/commands/read-screen.d.ts +21 -0
- package/dist/commands/read-screen.js +1515 -0
- package/dist/commands/read-screen.js.map +7 -0
- package/dist/commands/resize.d.ts +12 -0
- package/dist/commands/resize.js +1512 -0
- package/dist/commands/resize.js.map +7 -0
- package/dist/commands/runtime.d.ts +36 -0
- package/dist/commands/runtime.js +1262 -0
- package/dist/commands/runtime.js.map +7 -0
- package/dist/commands/screenshot.d.ts +14 -0
- package/dist/commands/screenshot.js +2280 -0
- package/dist/commands/screenshot.js.map +7 -0
- package/dist/commands/send-signal.d.ts +10 -0
- package/dist/commands/send-signal.js +1512 -0
- package/dist/commands/send-signal.js.map +7 -0
- package/dist/commands/type.d.ts +10 -0
- package/dist/commands/type.js +1512 -0
- package/dist/commands/type.js.map +7 -0
- package/dist/commands/uninstall.d.ts +14 -0
- package/dist/commands/uninstall.js +529 -0
- package/dist/commands/uninstall.js.map +7 -0
- package/dist/commands/wait-for-exit.d.ts +14 -0
- package/dist/commands/wait-for-exit.js +1513 -0
- package/dist/commands/wait-for-exit.js.map +7 -0
- package/dist/commands/wait-for.d.ts +20 -0
- package/dist/commands/wait-for.js +1520 -0
- package/dist/commands/wait-for.js.map +7 -0
- package/dist/exports.compile-check.js +1 -1
- package/dist/exports.compile-check.js.map +7 -0
- package/dist/index.js +1118 -5
- package/dist/index.js.map +7 -0
- package/dist/keys.js +58 -49
- package/dist/keys.js.map +7 -0
- package/dist/templates/terminal-pilot.md +45 -0
- package/dist/terminal-buffer.d.ts +9 -1
- package/dist/terminal-buffer.js +525 -425
- package/dist/terminal-buffer.js.map +7 -0
- package/dist/terminal-pilot.js +1105 -35
- package/dist/terminal-pilot.js.map +7 -0
- package/dist/terminal-screen.js +108 -26
- package/dist/terminal-screen.js.map +7 -0
- package/dist/terminal-session.js +1020 -292
- package/dist/terminal-session.js.map +7 -0
- package/dist/testing/cli-repl.d.ts +15 -0
- package/dist/testing/cli-repl.js +6359 -0
- package/dist/testing/cli-repl.js.map +7 -0
- package/dist/testing/qa-cli.d.ts +1 -0
- package/dist/testing/qa-cli.js +7276 -0
- package/dist/testing/qa-cli.js.map +7 -0
- package/package.json +37 -7
|
@@ -0,0 +1,3973 @@
|
|
|
1
|
+
// ../cmdkit/src/index.ts
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
4
|
+
// ../cmdkit-schema/src/index.ts
|
|
5
|
+
function assertValidEnumValues(values) {
|
|
6
|
+
if (values.length === 0) {
|
|
7
|
+
throw new Error("Enum schema requires at least one value");
|
|
8
|
+
}
|
|
9
|
+
const uniqueValues = new Set(values);
|
|
10
|
+
if (uniqueValues.size !== values.length) {
|
|
11
|
+
throw new Error("Enum schema values must be unique");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
var S = {
|
|
15
|
+
String(options = {}) {
|
|
16
|
+
return {
|
|
17
|
+
kind: "string",
|
|
18
|
+
...options
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
Number(options = {}) {
|
|
22
|
+
return {
|
|
23
|
+
kind: "number",
|
|
24
|
+
...options
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
Boolean(options = {}) {
|
|
28
|
+
return {
|
|
29
|
+
kind: "boolean",
|
|
30
|
+
...options
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
Enum(values, options = {}) {
|
|
34
|
+
assertValidEnumValues(values);
|
|
35
|
+
return {
|
|
36
|
+
kind: "enum",
|
|
37
|
+
values,
|
|
38
|
+
...options
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
Array(item, options = {}) {
|
|
42
|
+
return {
|
|
43
|
+
kind: "array",
|
|
44
|
+
item,
|
|
45
|
+
...options
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
Object(shape) {
|
|
49
|
+
return {
|
|
50
|
+
kind: "object",
|
|
51
|
+
shape
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
Optional(inner) {
|
|
55
|
+
return {
|
|
56
|
+
kind: "optional",
|
|
57
|
+
inner
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// ../cmdkit/src/index.ts
|
|
63
|
+
var commandConfigSymbol = /* @__PURE__ */ Symbol("cmdkit.command.config");
|
|
64
|
+
var groupConfigSymbol = /* @__PURE__ */ Symbol("cmdkit.group.config");
|
|
65
|
+
var commandSourcePathSymbol = /* @__PURE__ */ Symbol("cmdkit.command.sourcePath");
|
|
66
|
+
var UserError = class extends Error {
|
|
67
|
+
constructor(message) {
|
|
68
|
+
super(message);
|
|
69
|
+
this.name = "UserError";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
function cloneScope(scope) {
|
|
73
|
+
return scope === void 0 ? void 0 : [...scope];
|
|
74
|
+
}
|
|
75
|
+
function cloneSecretDefinition(secret) {
|
|
76
|
+
return {
|
|
77
|
+
env: secret.env,
|
|
78
|
+
description: secret.description,
|
|
79
|
+
optional: secret.optional
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function cloneSecrets(secrets) {
|
|
83
|
+
if (secrets === void 0) {
|
|
84
|
+
return {};
|
|
85
|
+
}
|
|
86
|
+
return Object.fromEntries(
|
|
87
|
+
Object.entries(secrets).map(([key, secret]) => [key, cloneSecretDefinition(secret)])
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
function cloneRequires(requires) {
|
|
91
|
+
if (requires === void 0) {
|
|
92
|
+
return void 0;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
auth: requires.auth,
|
|
96
|
+
apiVersion: requires.apiVersion,
|
|
97
|
+
check: requires.check
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function parseStackPath(candidate) {
|
|
101
|
+
if (candidate.startsWith("file://")) {
|
|
102
|
+
try {
|
|
103
|
+
return fileURLToPath(candidate);
|
|
104
|
+
} catch {
|
|
105
|
+
return void 0;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (candidate.startsWith("/")) {
|
|
109
|
+
return candidate;
|
|
110
|
+
}
|
|
111
|
+
return void 0;
|
|
112
|
+
}
|
|
113
|
+
function extractStackPath(line) {
|
|
114
|
+
const trimmed = line.trim();
|
|
115
|
+
const fileIndex = trimmed.indexOf("file://");
|
|
116
|
+
if (fileIndex >= 0) {
|
|
117
|
+
const location2 = trimmed.slice(fileIndex);
|
|
118
|
+
const separatorIndex2 = location2.lastIndexOf(":");
|
|
119
|
+
const previousSeparatorIndex2 = separatorIndex2 >= 0 ? location2.lastIndexOf(":", separatorIndex2 - 1) : -1;
|
|
120
|
+
const candidate2 = separatorIndex2 >= 0 && previousSeparatorIndex2 >= 0 ? location2.slice(0, previousSeparatorIndex2) : location2;
|
|
121
|
+
return parseStackPath(candidate2);
|
|
122
|
+
}
|
|
123
|
+
const slashIndex = trimmed.indexOf("/");
|
|
124
|
+
if (slashIndex < 0) {
|
|
125
|
+
return void 0;
|
|
126
|
+
}
|
|
127
|
+
const location = trimmed.slice(slashIndex);
|
|
128
|
+
const separatorIndex = location.lastIndexOf(":");
|
|
129
|
+
const previousSeparatorIndex = separatorIndex >= 0 ? location.lastIndexOf(":", separatorIndex - 1) : -1;
|
|
130
|
+
const candidate = separatorIndex >= 0 && previousSeparatorIndex >= 0 ? location.slice(0, previousSeparatorIndex) : location;
|
|
131
|
+
return parseStackPath(candidate);
|
|
132
|
+
}
|
|
133
|
+
function inferCommandSourcePath() {
|
|
134
|
+
const stack = new Error().stack;
|
|
135
|
+
if (typeof stack !== "string") {
|
|
136
|
+
return void 0;
|
|
137
|
+
}
|
|
138
|
+
for (const line of stack.split("\n").slice(1)) {
|
|
139
|
+
const candidate = extractStackPath(line);
|
|
140
|
+
if (candidate === void 0) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (candidate.includes("/packages/cmdkit/src/index.ts") || candidate.includes("/packages/cmdkit/dist/index.js")) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
return candidate;
|
|
147
|
+
}
|
|
148
|
+
return void 0;
|
|
149
|
+
}
|
|
150
|
+
function composeChecks(parentCheck, childCheck) {
|
|
151
|
+
if (parentCheck === void 0) {
|
|
152
|
+
return childCheck;
|
|
153
|
+
}
|
|
154
|
+
if (childCheck === void 0) {
|
|
155
|
+
return parentCheck;
|
|
156
|
+
}
|
|
157
|
+
return async (ctx) => {
|
|
158
|
+
const parentResult = await parentCheck(ctx);
|
|
159
|
+
if (!parentResult.ok) {
|
|
160
|
+
return parentResult;
|
|
161
|
+
}
|
|
162
|
+
return childCheck(ctx);
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function mergeRequires(parent, child) {
|
|
166
|
+
if (parent === void 0 && child === void 0) {
|
|
167
|
+
return void 0;
|
|
168
|
+
}
|
|
169
|
+
const merged = {
|
|
170
|
+
auth: child?.auth ?? parent?.auth,
|
|
171
|
+
apiVersion: child?.apiVersion ?? parent?.apiVersion,
|
|
172
|
+
check: composeChecks(parent?.check, child?.check)
|
|
173
|
+
};
|
|
174
|
+
if (merged.auth === void 0 && merged.apiVersion === void 0 && merged.check === void 0) {
|
|
175
|
+
return void 0;
|
|
176
|
+
}
|
|
177
|
+
return merged;
|
|
178
|
+
}
|
|
179
|
+
function mergeSecrets(parent, child) {
|
|
180
|
+
return cloneSecrets({
|
|
181
|
+
...parent,
|
|
182
|
+
...child
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
function resolveCommandScope(ownScope, inheritedScope) {
|
|
186
|
+
return cloneScope(ownScope ?? inheritedScope) ?? ["cli", "sdk"];
|
|
187
|
+
}
|
|
188
|
+
function resolveGroupScope(ownScope, inheritedScope) {
|
|
189
|
+
return cloneScope(ownScope ?? inheritedScope);
|
|
190
|
+
}
|
|
191
|
+
function createBaseCommand(config) {
|
|
192
|
+
const command = {
|
|
193
|
+
kind: "command",
|
|
194
|
+
name: config.name,
|
|
195
|
+
description: config.description,
|
|
196
|
+
aliases: [...config.aliases ?? []],
|
|
197
|
+
positional: [...config.positional ?? []],
|
|
198
|
+
params: config.params,
|
|
199
|
+
secrets: cloneSecrets(config.secrets),
|
|
200
|
+
scope: resolveCommandScope(config.scope, void 0),
|
|
201
|
+
confirm: config.confirm ?? false,
|
|
202
|
+
requires: cloneRequires(config.requires),
|
|
203
|
+
handler: config.handler,
|
|
204
|
+
render: config.render
|
|
205
|
+
};
|
|
206
|
+
Object.defineProperty(command, commandConfigSymbol, {
|
|
207
|
+
value: {
|
|
208
|
+
scope: cloneScope(config.scope),
|
|
209
|
+
secrets: cloneSecrets(config.secrets),
|
|
210
|
+
requires: cloneRequires(config.requires),
|
|
211
|
+
sourcePath: inferCommandSourcePath()
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
return command;
|
|
215
|
+
}
|
|
216
|
+
function createBaseGroup(config) {
|
|
217
|
+
const group = {
|
|
218
|
+
kind: "group",
|
|
219
|
+
name: config.name,
|
|
220
|
+
description: config.description,
|
|
221
|
+
aliases: [...config.aliases ?? []],
|
|
222
|
+
scope: resolveGroupScope(config.scope, void 0),
|
|
223
|
+
secrets: cloneSecrets(config.secrets),
|
|
224
|
+
requires: cloneRequires(config.requires),
|
|
225
|
+
children: [],
|
|
226
|
+
default: void 0
|
|
227
|
+
};
|
|
228
|
+
Object.defineProperty(group, groupConfigSymbol, {
|
|
229
|
+
value: {
|
|
230
|
+
scope: cloneScope(config.scope),
|
|
231
|
+
secrets: cloneSecrets(config.secrets),
|
|
232
|
+
requires: cloneRequires(config.requires),
|
|
233
|
+
children: [...config.children],
|
|
234
|
+
default: config.default
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
return group;
|
|
238
|
+
}
|
|
239
|
+
function getInternalCommandConfig(command) {
|
|
240
|
+
return command[commandConfigSymbol];
|
|
241
|
+
}
|
|
242
|
+
function getInternalGroupConfig(group) {
|
|
243
|
+
return group[groupConfigSymbol];
|
|
244
|
+
}
|
|
245
|
+
function materializeCommand(command, inherited) {
|
|
246
|
+
const internal = getInternalCommandConfig(command);
|
|
247
|
+
const materialized = {
|
|
248
|
+
kind: "command",
|
|
249
|
+
name: command.name,
|
|
250
|
+
description: command.description,
|
|
251
|
+
aliases: [...command.aliases],
|
|
252
|
+
positional: [...command.positional],
|
|
253
|
+
params: command.params,
|
|
254
|
+
secrets: mergeSecrets(inherited.secrets, internal.secrets),
|
|
255
|
+
scope: resolveCommandScope(internal.scope, inherited.scope),
|
|
256
|
+
confirm: command.confirm,
|
|
257
|
+
requires: mergeRequires(inherited.requires, internal.requires),
|
|
258
|
+
handler: command.handler,
|
|
259
|
+
render: command.render
|
|
260
|
+
};
|
|
261
|
+
Object.defineProperty(materialized, commandConfigSymbol, {
|
|
262
|
+
value: {
|
|
263
|
+
scope: cloneScope(internal.scope),
|
|
264
|
+
secrets: cloneSecrets(internal.secrets),
|
|
265
|
+
requires: cloneRequires(internal.requires),
|
|
266
|
+
sourcePath: internal.sourcePath
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
Object.defineProperty(materialized, commandSourcePathSymbol, {
|
|
270
|
+
value: internal.sourcePath
|
|
271
|
+
});
|
|
272
|
+
return materialized;
|
|
273
|
+
}
|
|
274
|
+
function materializeGroup(group, inherited) {
|
|
275
|
+
const internal = getInternalGroupConfig(group);
|
|
276
|
+
const scope = resolveGroupScope(internal.scope, inherited.scope);
|
|
277
|
+
const secrets = mergeSecrets(inherited.secrets, internal.secrets);
|
|
278
|
+
const requires = mergeRequires(inherited.requires, internal.requires);
|
|
279
|
+
const materializedChildren = internal.children.map(
|
|
280
|
+
(child) => materializeNode(child, {
|
|
281
|
+
scope,
|
|
282
|
+
secrets,
|
|
283
|
+
requires
|
|
284
|
+
})
|
|
285
|
+
);
|
|
286
|
+
let defaultChild;
|
|
287
|
+
if (internal.default !== void 0) {
|
|
288
|
+
const defaultIndex = internal.children.indexOf(internal.default);
|
|
289
|
+
if (defaultIndex === -1) {
|
|
290
|
+
throw new UserError(`Default command "${internal.default.name}" must be listed in children.`);
|
|
291
|
+
}
|
|
292
|
+
const resolvedDefault = materializedChildren[defaultIndex];
|
|
293
|
+
if (resolvedDefault?.kind !== "command") {
|
|
294
|
+
throw new UserError(`Default child "${internal.default.name}" must be a command.`);
|
|
295
|
+
}
|
|
296
|
+
defaultChild = resolvedDefault;
|
|
297
|
+
}
|
|
298
|
+
const materialized = {
|
|
299
|
+
kind: "group",
|
|
300
|
+
name: group.name,
|
|
301
|
+
description: group.description,
|
|
302
|
+
aliases: [...group.aliases],
|
|
303
|
+
scope,
|
|
304
|
+
secrets,
|
|
305
|
+
requires,
|
|
306
|
+
children: materializedChildren,
|
|
307
|
+
default: defaultChild
|
|
308
|
+
};
|
|
309
|
+
Object.defineProperty(materialized, groupConfigSymbol, {
|
|
310
|
+
value: {
|
|
311
|
+
scope: cloneScope(internal.scope),
|
|
312
|
+
secrets: cloneSecrets(internal.secrets),
|
|
313
|
+
requires: cloneRequires(internal.requires),
|
|
314
|
+
children: [...internal.children],
|
|
315
|
+
default: internal.default
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
return materialized;
|
|
319
|
+
}
|
|
320
|
+
function materializeNode(node, inherited) {
|
|
321
|
+
if (node.kind === "command") {
|
|
322
|
+
return materializeCommand(node, inherited);
|
|
323
|
+
}
|
|
324
|
+
return materializeGroup(node, inherited);
|
|
325
|
+
}
|
|
326
|
+
function defineCommand(config) {
|
|
327
|
+
return materializeCommand(createBaseCommand(config), {
|
|
328
|
+
scope: void 0,
|
|
329
|
+
secrets: {},
|
|
330
|
+
requires: void 0
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
function defineGroup(config) {
|
|
334
|
+
return materializeGroup(createBaseGroup(config), {
|
|
335
|
+
scope: void 0,
|
|
336
|
+
secrets: {},
|
|
337
|
+
requires: void 0
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/terminal-pilot.ts
|
|
342
|
+
import { randomUUID } from "node:crypto";
|
|
343
|
+
|
|
344
|
+
// src/terminal-session.ts
|
|
345
|
+
import { spawn as spawnChildProcess } from "node:child_process";
|
|
346
|
+
import { EventEmitter } from "node:events";
|
|
347
|
+
import * as nodePty from "node-pty";
|
|
348
|
+
|
|
349
|
+
// src/ansi.ts
|
|
350
|
+
var ESC = 27;
|
|
351
|
+
var BEL = 7;
|
|
352
|
+
var ST = 156;
|
|
353
|
+
var CSI = 155;
|
|
354
|
+
var OSC = 157;
|
|
355
|
+
var DCS = 144;
|
|
356
|
+
var SOS = 152;
|
|
357
|
+
var PM = 158;
|
|
358
|
+
var APC = 159;
|
|
359
|
+
function stripAnsi(input) {
|
|
360
|
+
let output = "";
|
|
361
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
362
|
+
const code = input.charCodeAt(index);
|
|
363
|
+
if (code === ESC) {
|
|
364
|
+
const nextCode = input.charCodeAt(index + 1);
|
|
365
|
+
if (nextCode === 91) {
|
|
366
|
+
index = consumeCsi(input, index + 2);
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (nextCode === 93) {
|
|
370
|
+
index = consumeTerminatedString(input, index + 2, true);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (nextCode === 80 || nextCode === 88 || nextCode === 94 || nextCode === 95) {
|
|
374
|
+
index = consumeTerminatedString(input, index + 2, false);
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (!Number.isNaN(nextCode)) {
|
|
378
|
+
index += 1;
|
|
379
|
+
}
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
if (code === CSI) {
|
|
383
|
+
index = consumeCsi(input, index + 1);
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (code === OSC) {
|
|
387
|
+
index = consumeTerminatedString(input, index + 1, true);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (code === DCS || code === SOS || code === PM || code === APC) {
|
|
391
|
+
index = consumeTerminatedString(input, index + 1, false);
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
output += input[index];
|
|
395
|
+
}
|
|
396
|
+
return output;
|
|
397
|
+
}
|
|
398
|
+
function consumeCsi(input, index) {
|
|
399
|
+
while (index < input.length) {
|
|
400
|
+
const code = input.charCodeAt(index);
|
|
401
|
+
if (code >= 64 && code <= 126) {
|
|
402
|
+
return index;
|
|
403
|
+
}
|
|
404
|
+
index += 1;
|
|
405
|
+
}
|
|
406
|
+
return input.length;
|
|
407
|
+
}
|
|
408
|
+
function consumeTerminatedString(input, index, allowBellTerminator) {
|
|
409
|
+
while (index < input.length) {
|
|
410
|
+
const code = input.charCodeAt(index);
|
|
411
|
+
if (code === ST || allowBellTerminator && code === BEL) {
|
|
412
|
+
return index;
|
|
413
|
+
}
|
|
414
|
+
if (code === ESC && input.charCodeAt(index + 1) === 92) {
|
|
415
|
+
return index + 1;
|
|
416
|
+
}
|
|
417
|
+
index += 1;
|
|
418
|
+
}
|
|
419
|
+
return input.length;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/terminal-buffer.ts
|
|
423
|
+
var RESET_SGR = "\x1B[0m";
|
|
424
|
+
var TerminalBuffer = class {
|
|
425
|
+
_cols;
|
|
426
|
+
_rows;
|
|
427
|
+
_screen;
|
|
428
|
+
_cursorX = 0;
|
|
429
|
+
_cursorY = 0;
|
|
430
|
+
_savedCursor = { x: 0, y: 0 };
|
|
431
|
+
_scrollTop = 0;
|
|
432
|
+
_scrollBottom;
|
|
433
|
+
_state = 0 /* Normal */;
|
|
434
|
+
_csiParams = "";
|
|
435
|
+
_csiPrivate = "";
|
|
436
|
+
_style = createDefaultStyleState();
|
|
437
|
+
_styleSequence = "";
|
|
438
|
+
displayBuffer;
|
|
439
|
+
constructor(cols, rows) {
|
|
440
|
+
this._cols = cols;
|
|
441
|
+
this._rows = rows;
|
|
442
|
+
this._scrollBottom = rows - 1;
|
|
443
|
+
this._screen = this._makeScreen(cols, rows);
|
|
444
|
+
this.displayBuffer = Object.defineProperties(
|
|
445
|
+
{},
|
|
446
|
+
{
|
|
447
|
+
cursorX: { get: () => this._cursorX, enumerable: true },
|
|
448
|
+
cursorY: { get: () => this._cursorY, enumerable: true },
|
|
449
|
+
data: { get: () => this._screen, enumerable: true }
|
|
450
|
+
}
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
write(data) {
|
|
454
|
+
for (const ch of data) {
|
|
455
|
+
this._feed(ch);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
renderLine(row) {
|
|
459
|
+
const cells = this._screen[row] ?? [];
|
|
460
|
+
let lastVisibleCell = -1;
|
|
461
|
+
for (let index = cells.length - 1; index >= 0; index -= 1) {
|
|
462
|
+
if (cells[index] !== null) {
|
|
463
|
+
lastVisibleCell = index;
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (lastVisibleCell === -1) {
|
|
468
|
+
return "";
|
|
469
|
+
}
|
|
470
|
+
let line = "";
|
|
471
|
+
let activeStyle = "";
|
|
472
|
+
for (let index = 0; index <= lastVisibleCell; index += 1) {
|
|
473
|
+
const cell = cells[index];
|
|
474
|
+
const cellStyle = cell?.style ?? "";
|
|
475
|
+
if (cellStyle !== activeStyle) {
|
|
476
|
+
line += cellStyle.length > 0 ? cellStyle : RESET_SGR;
|
|
477
|
+
activeStyle = cellStyle;
|
|
478
|
+
}
|
|
479
|
+
line += cell?.[1] ?? " ";
|
|
480
|
+
}
|
|
481
|
+
if (activeStyle.length > 0) {
|
|
482
|
+
line += RESET_SGR;
|
|
483
|
+
}
|
|
484
|
+
return line;
|
|
485
|
+
}
|
|
486
|
+
resize(cols, rows) {
|
|
487
|
+
while (this._screen.length < rows) {
|
|
488
|
+
this._screen.push(this._makeRow(cols));
|
|
489
|
+
}
|
|
490
|
+
this._screen.length = rows;
|
|
491
|
+
for (let y = 0; y < rows; y++) {
|
|
492
|
+
const row = this._screen[y] ?? this._makeRow(cols);
|
|
493
|
+
while (row.length < cols) row.push(null);
|
|
494
|
+
row.length = cols;
|
|
495
|
+
this._screen[y] = row;
|
|
496
|
+
}
|
|
497
|
+
this._cols = cols;
|
|
498
|
+
this._rows = rows;
|
|
499
|
+
this._scrollTop = 0;
|
|
500
|
+
this._scrollBottom = rows - 1;
|
|
501
|
+
this._cursorX = this._clamp(this._cursorX, 0, cols - 1);
|
|
502
|
+
this._cursorY = this._clamp(this._cursorY, 0, rows - 1);
|
|
503
|
+
}
|
|
504
|
+
_makeScreen(cols, rows) {
|
|
505
|
+
return Array.from({ length: rows }, () => this._makeRow(cols));
|
|
506
|
+
}
|
|
507
|
+
_makeRow(cols) {
|
|
508
|
+
return Array(cols).fill(null);
|
|
509
|
+
}
|
|
510
|
+
_clamp(value, min, max) {
|
|
511
|
+
return Math.max(min, Math.min(max, value));
|
|
512
|
+
}
|
|
513
|
+
_setChar(y, x, ch) {
|
|
514
|
+
const row = this._screen[y];
|
|
515
|
+
if (row && x >= 0 && x < this._cols) {
|
|
516
|
+
const cell = [ch.charCodeAt(0), ch];
|
|
517
|
+
if (this._styleSequence.length > 0) {
|
|
518
|
+
Object.defineProperty(cell, "style", {
|
|
519
|
+
value: this._styleSequence,
|
|
520
|
+
writable: true,
|
|
521
|
+
configurable: true
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
row[x] = cell;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
_eraseLine(y, fromX, toX) {
|
|
528
|
+
const row = this._screen[y];
|
|
529
|
+
if (!row) return;
|
|
530
|
+
for (let x = fromX; x <= toX && x < this._cols; x++) {
|
|
531
|
+
row[x] = null;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
_scrollUp(count) {
|
|
535
|
+
for (let i = 0; i < count; i++) {
|
|
536
|
+
this._screen.splice(this._scrollTop, 1);
|
|
537
|
+
this._screen.splice(this._scrollBottom, 0, this._makeRow(this._cols));
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
_scrollDown(count) {
|
|
541
|
+
for (let i = 0; i < count; i++) {
|
|
542
|
+
this._screen.splice(this._scrollBottom, 1);
|
|
543
|
+
this._screen.splice(this._scrollTop, 0, this._makeRow(this._cols));
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
_newline() {
|
|
547
|
+
if (this._cursorY === this._scrollBottom) {
|
|
548
|
+
this._scrollUp(1);
|
|
549
|
+
} else {
|
|
550
|
+
this._cursorY = Math.min(this._cursorY + 1, this._rows - 1);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
_parseCsiParams() {
|
|
554
|
+
if (!this._csiParams) return [];
|
|
555
|
+
return this._csiParams.split(";").map((s) => s === "" ? 0 : parseInt(s, 10));
|
|
556
|
+
}
|
|
557
|
+
_execCsi(final) {
|
|
558
|
+
const params17 = this._parseCsiParams();
|
|
559
|
+
const p0 = params17[0] ?? 0;
|
|
560
|
+
const p1 = params17[1] ?? 0;
|
|
561
|
+
if (this._csiPrivate === "?") {
|
|
562
|
+
if (final === "h" || final === "l") {
|
|
563
|
+
if (params17.includes(1049)) {
|
|
564
|
+
if (final === "h") {
|
|
565
|
+
this._screen = this._makeScreen(this._cols, this._rows);
|
|
566
|
+
this._cursorX = 0;
|
|
567
|
+
this._cursorY = 0;
|
|
568
|
+
} else {
|
|
569
|
+
this._screen = this._makeScreen(this._cols, this._rows);
|
|
570
|
+
this._cursorX = 0;
|
|
571
|
+
this._cursorY = 0;
|
|
572
|
+
}
|
|
573
|
+
this._resetStyle();
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
switch (final) {
|
|
579
|
+
case "A":
|
|
580
|
+
this._cursorY = this._clamp(this._cursorY - Math.max(1, p0), 0, this._rows - 1);
|
|
581
|
+
break;
|
|
582
|
+
case "B":
|
|
583
|
+
this._cursorY = this._clamp(this._cursorY + Math.max(1, p0), 0, this._rows - 1);
|
|
584
|
+
break;
|
|
585
|
+
case "C":
|
|
586
|
+
// cursor forward
|
|
587
|
+
case "a":
|
|
588
|
+
this._cursorX = this._clamp(this._cursorX + Math.max(1, p0), 0, this._cols - 1);
|
|
589
|
+
break;
|
|
590
|
+
case "D":
|
|
591
|
+
this._cursorX = this._clamp(this._cursorX - Math.max(1, p0), 0, this._cols - 1);
|
|
592
|
+
break;
|
|
593
|
+
case "E":
|
|
594
|
+
this._cursorY = this._clamp(this._cursorY + Math.max(1, p0), 0, this._rows - 1);
|
|
595
|
+
this._cursorX = 0;
|
|
596
|
+
break;
|
|
597
|
+
case "F":
|
|
598
|
+
this._cursorY = this._clamp(this._cursorY - Math.max(1, p0), 0, this._rows - 1);
|
|
599
|
+
this._cursorX = 0;
|
|
600
|
+
break;
|
|
601
|
+
case "G":
|
|
602
|
+
// cursor horizontal absolute
|
|
603
|
+
case "`":
|
|
604
|
+
this._cursorX = this._clamp(Math.max(1, p0) - 1, 0, this._cols - 1);
|
|
605
|
+
break;
|
|
606
|
+
case "H":
|
|
607
|
+
// cursor position
|
|
608
|
+
case "f":
|
|
609
|
+
this._cursorY = this._clamp(Math.max(1, p0) - 1, 0, this._rows - 1);
|
|
610
|
+
this._cursorX = this._clamp(Math.max(1, p1) - 1, 0, this._cols - 1);
|
|
611
|
+
break;
|
|
612
|
+
case "I":
|
|
613
|
+
for (let i = 0; i < Math.max(1, p0); i++) {
|
|
614
|
+
this._cursorX = Math.min(this._cols - 1, (Math.floor(this._cursorX / 8) + 1) * 8);
|
|
615
|
+
}
|
|
616
|
+
break;
|
|
617
|
+
case "J":
|
|
618
|
+
if (p0 === 0) {
|
|
619
|
+
this._eraseLine(this._cursorY, this._cursorX, this._cols - 1);
|
|
620
|
+
for (let y = this._cursorY + 1; y < this._rows; y++) this._eraseLine(y, 0, this._cols - 1);
|
|
621
|
+
} else if (p0 === 1) {
|
|
622
|
+
for (let y = 0; y < this._cursorY; y++) this._eraseLine(y, 0, this._cols - 1);
|
|
623
|
+
this._eraseLine(this._cursorY, 0, this._cursorX);
|
|
624
|
+
} else if (p0 === 2 || p0 === 3) {
|
|
625
|
+
for (let y = 0; y < this._rows; y++) this._eraseLine(y, 0, this._cols - 1);
|
|
626
|
+
}
|
|
627
|
+
break;
|
|
628
|
+
case "K":
|
|
629
|
+
if (p0 === 0) this._eraseLine(this._cursorY, this._cursorX, this._cols - 1);
|
|
630
|
+
else if (p0 === 1) this._eraseLine(this._cursorY, 0, this._cursorX);
|
|
631
|
+
else if (p0 === 2) this._eraseLine(this._cursorY, 0, this._cols - 1);
|
|
632
|
+
break;
|
|
633
|
+
case "X":
|
|
634
|
+
this._eraseLine(this._cursorY, this._cursorX, this._cursorX + Math.max(1, p0) - 1);
|
|
635
|
+
break;
|
|
636
|
+
case "L": {
|
|
637
|
+
const n = Math.max(1, p0);
|
|
638
|
+
for (let i = 0; i < n; i++) {
|
|
639
|
+
this._screen.splice(this._scrollBottom, 1);
|
|
640
|
+
this._screen.splice(this._cursorY, 0, this._makeRow(this._cols));
|
|
641
|
+
}
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
case "M": {
|
|
645
|
+
const n = Math.max(1, p0);
|
|
646
|
+
for (let i = 0; i < n; i++) {
|
|
647
|
+
this._screen.splice(this._cursorY, 1);
|
|
648
|
+
this._screen.splice(this._scrollBottom, 0, this._makeRow(this._cols));
|
|
649
|
+
}
|
|
650
|
+
break;
|
|
651
|
+
}
|
|
652
|
+
case "P": {
|
|
653
|
+
const row = this._screen[this._cursorY];
|
|
654
|
+
if (row) {
|
|
655
|
+
const n = Math.max(1, p0);
|
|
656
|
+
row.splice(this._cursorX, n);
|
|
657
|
+
while (row.length < this._cols) row.push(null);
|
|
658
|
+
}
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
case "@": {
|
|
662
|
+
const row = this._screen[this._cursorY];
|
|
663
|
+
if (row) {
|
|
664
|
+
const n = Math.max(1, p0);
|
|
665
|
+
for (let i = 0; i < n; i++) row.splice(this._cursorX, 0, null);
|
|
666
|
+
row.splice(this._cols);
|
|
667
|
+
}
|
|
668
|
+
break;
|
|
669
|
+
}
|
|
670
|
+
case "S":
|
|
671
|
+
this._scrollUp(Math.max(1, p0));
|
|
672
|
+
break;
|
|
673
|
+
case "T":
|
|
674
|
+
if (params17.length <= 1) this._scrollDown(Math.max(1, p0));
|
|
675
|
+
break;
|
|
676
|
+
case "Z": {
|
|
677
|
+
const n = Math.max(1, p0);
|
|
678
|
+
for (let i = 0; i < n; i++) {
|
|
679
|
+
this._cursorX = Math.max(0, (Math.ceil(this._cursorX / 8) - 1) * 8);
|
|
680
|
+
}
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
case "d":
|
|
684
|
+
this._cursorY = this._clamp(Math.max(1, p0) - 1, 0, this._rows - 1);
|
|
685
|
+
break;
|
|
686
|
+
case "e":
|
|
687
|
+
this._cursorY = this._clamp(this._cursorY + Math.max(1, p0), 0, this._rows - 1);
|
|
688
|
+
break;
|
|
689
|
+
case "r": {
|
|
690
|
+
const top = this._clamp(Math.max(1, p0) - 1, 0, this._rows - 1);
|
|
691
|
+
const bottom = this._clamp((p1 === 0 ? this._rows : p1) - 1, 0, this._rows - 1);
|
|
692
|
+
if (top < bottom) {
|
|
693
|
+
this._scrollTop = top;
|
|
694
|
+
this._scrollBottom = bottom;
|
|
695
|
+
}
|
|
696
|
+
this._cursorX = 0;
|
|
697
|
+
this._cursorY = 0;
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
case "s":
|
|
701
|
+
this._savedCursor = { x: this._cursorX, y: this._cursorY };
|
|
702
|
+
break;
|
|
703
|
+
case "u":
|
|
704
|
+
this._cursorX = this._clamp(this._savedCursor.x, 0, this._cols - 1);
|
|
705
|
+
this._cursorY = this._clamp(this._savedCursor.y, 0, this._rows - 1);
|
|
706
|
+
break;
|
|
707
|
+
case "m":
|
|
708
|
+
this._applySgr(params17);
|
|
709
|
+
break;
|
|
710
|
+
default:
|
|
711
|
+
break;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
_feed(ch) {
|
|
715
|
+
const code = ch.charCodeAt(0);
|
|
716
|
+
switch (this._state) {
|
|
717
|
+
case 0 /* Normal */:
|
|
718
|
+
this._feedNormal(ch, code);
|
|
719
|
+
break;
|
|
720
|
+
case 1 /* Escape */:
|
|
721
|
+
this._feedEscape(ch, code);
|
|
722
|
+
break;
|
|
723
|
+
case 2 /* Csi */:
|
|
724
|
+
this._feedCsi(ch, code);
|
|
725
|
+
break;
|
|
726
|
+
case 3 /* Osc */:
|
|
727
|
+
if (code === 7 || code === 156) {
|
|
728
|
+
this._state = 0 /* Normal */;
|
|
729
|
+
} else if (code === 27) {
|
|
730
|
+
this._state = 0 /* Normal */;
|
|
731
|
+
}
|
|
732
|
+
break;
|
|
733
|
+
case 4 /* Str */:
|
|
734
|
+
if (code === 156 || code === 7) {
|
|
735
|
+
this._state = 0 /* Normal */;
|
|
736
|
+
} else if (code === 27) {
|
|
737
|
+
this._state = 0 /* Normal */;
|
|
738
|
+
}
|
|
739
|
+
break;
|
|
740
|
+
case 5 /* EscCharset */:
|
|
741
|
+
this._state = 0 /* Normal */;
|
|
742
|
+
break;
|
|
743
|
+
case 6 /* EscHash */:
|
|
744
|
+
this._state = 0 /* Normal */;
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
_feedNormal(ch, code) {
|
|
749
|
+
if (code === 27) {
|
|
750
|
+
this._state = 1 /* Escape */;
|
|
751
|
+
} else if (code === 155) {
|
|
752
|
+
this._csiParams = "";
|
|
753
|
+
this._csiPrivate = "";
|
|
754
|
+
this._state = 2 /* Csi */;
|
|
755
|
+
} else if (code === 157) {
|
|
756
|
+
this._state = 3 /* Osc */;
|
|
757
|
+
} else if (code === 144 || code === 152 || code === 158 || code === 159) {
|
|
758
|
+
this._state = 4 /* Str */;
|
|
759
|
+
} else if (code === 7 || code === 5 || code === 6) {
|
|
760
|
+
} else if (code === 8) {
|
|
761
|
+
if (this._cursorX > 0) this._cursorX--;
|
|
762
|
+
} else if (code === 127) {
|
|
763
|
+
if (this._cursorX > 0) {
|
|
764
|
+
this._cursorX--;
|
|
765
|
+
this._setChar(this._cursorY, this._cursorX, " ");
|
|
766
|
+
}
|
|
767
|
+
} else if (code === 9) {
|
|
768
|
+
this._cursorX = Math.min(this._cols - 1, (Math.floor(this._cursorX / 8) + 1) * 8);
|
|
769
|
+
} else if (code === 10 || code === 11 || code === 12) {
|
|
770
|
+
this._newline();
|
|
771
|
+
} else if (code === 13) {
|
|
772
|
+
this._cursorX = 0;
|
|
773
|
+
} else if (code === 14 || code === 15) {
|
|
774
|
+
} else if (code >= 32 && code !== 127) {
|
|
775
|
+
this._setChar(this._cursorY, this._cursorX, ch);
|
|
776
|
+
this._cursorX++;
|
|
777
|
+
if (this._cursorX >= this._cols) {
|
|
778
|
+
this._cursorX = 0;
|
|
779
|
+
this._newline();
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
_feedEscape(ch, code) {
|
|
784
|
+
this._state = 0 /* Normal */;
|
|
785
|
+
if (code === 91) {
|
|
786
|
+
this._csiParams = "";
|
|
787
|
+
this._csiPrivate = "";
|
|
788
|
+
this._state = 2 /* Csi */;
|
|
789
|
+
} else if (code === 93) {
|
|
790
|
+
this._state = 3 /* Osc */;
|
|
791
|
+
} else if (code === 80 || code === 88 || code === 94 || code === 95) {
|
|
792
|
+
this._state = 4 /* Str */;
|
|
793
|
+
} else if (code === 40 || code === 41 || code === 42 || code === 43 || code === 45 || code === 46) {
|
|
794
|
+
this._state = 5 /* EscCharset */;
|
|
795
|
+
} else if (code === 35) {
|
|
796
|
+
this._state = 6 /* EscHash */;
|
|
797
|
+
} else if (code === 55) {
|
|
798
|
+
this._savedCursor = { x: this._cursorX, y: this._cursorY };
|
|
799
|
+
} else if (code === 56) {
|
|
800
|
+
this._cursorX = this._clamp(this._savedCursor.x, 0, this._cols - 1);
|
|
801
|
+
this._cursorY = this._clamp(this._savedCursor.y, 0, this._rows - 1);
|
|
802
|
+
} else if (code === 68) {
|
|
803
|
+
this._newline();
|
|
804
|
+
} else if (code === 69) {
|
|
805
|
+
this._cursorX = 0;
|
|
806
|
+
this._newline();
|
|
807
|
+
} else if (code === 77) {
|
|
808
|
+
if (this._cursorY === this._scrollTop) {
|
|
809
|
+
this._scrollDown(1);
|
|
810
|
+
} else {
|
|
811
|
+
this._cursorY = Math.max(0, this._cursorY - 1);
|
|
812
|
+
}
|
|
813
|
+
} else if (code === 72) {
|
|
814
|
+
} else if (code === 99) {
|
|
815
|
+
this._screen = this._makeScreen(this._cols, this._rows);
|
|
816
|
+
this._cursorX = 0;
|
|
817
|
+
this._cursorY = 0;
|
|
818
|
+
this._savedCursor = { x: 0, y: 0 };
|
|
819
|
+
this._scrollTop = 0;
|
|
820
|
+
this._scrollBottom = this._rows - 1;
|
|
821
|
+
this._resetStyle();
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
_feedCsi(ch, code) {
|
|
825
|
+
if (code >= 64 && code <= 126) {
|
|
826
|
+
this._execCsi(ch);
|
|
827
|
+
this._state = 0 /* Normal */;
|
|
828
|
+
} else if (code === 63 || code === 33 || code === 62 || code === 32) {
|
|
829
|
+
this._csiPrivate = ch;
|
|
830
|
+
} else if (code >= 48 && code <= 57 || code === 59) {
|
|
831
|
+
this._csiParams += ch;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
_resetStyle() {
|
|
835
|
+
this._style = createDefaultStyleState();
|
|
836
|
+
this._styleSequence = "";
|
|
837
|
+
}
|
|
838
|
+
_applySgr(params17) {
|
|
839
|
+
const normalizedParams = params17.length === 0 ? [0] : params17;
|
|
840
|
+
for (let index = 0; index < normalizedParams.length; index += 1) {
|
|
841
|
+
const value = normalizedParams[index] ?? 0;
|
|
842
|
+
switch (value) {
|
|
843
|
+
case 0:
|
|
844
|
+
this._resetStyle();
|
|
845
|
+
break;
|
|
846
|
+
case 1:
|
|
847
|
+
this._style.bold = true;
|
|
848
|
+
break;
|
|
849
|
+
case 2:
|
|
850
|
+
this._style.dim = true;
|
|
851
|
+
break;
|
|
852
|
+
case 3:
|
|
853
|
+
this._style.italic = true;
|
|
854
|
+
break;
|
|
855
|
+
case 4:
|
|
856
|
+
this._style.underline = true;
|
|
857
|
+
break;
|
|
858
|
+
case 7:
|
|
859
|
+
this._style.inverse = true;
|
|
860
|
+
break;
|
|
861
|
+
case 9:
|
|
862
|
+
this._style.strikethrough = true;
|
|
863
|
+
break;
|
|
864
|
+
case 21:
|
|
865
|
+
case 22:
|
|
866
|
+
this._style.bold = false;
|
|
867
|
+
this._style.dim = false;
|
|
868
|
+
break;
|
|
869
|
+
case 23:
|
|
870
|
+
this._style.italic = false;
|
|
871
|
+
break;
|
|
872
|
+
case 24:
|
|
873
|
+
this._style.underline = false;
|
|
874
|
+
break;
|
|
875
|
+
case 27:
|
|
876
|
+
this._style.inverse = false;
|
|
877
|
+
break;
|
|
878
|
+
case 29:
|
|
879
|
+
this._style.strikethrough = false;
|
|
880
|
+
break;
|
|
881
|
+
case 39:
|
|
882
|
+
this._style.fg = void 0;
|
|
883
|
+
break;
|
|
884
|
+
case 49:
|
|
885
|
+
this._style.bg = void 0;
|
|
886
|
+
break;
|
|
887
|
+
case 38:
|
|
888
|
+
case 48:
|
|
889
|
+
index = this._applyExtendedColor(value, normalizedParams, index);
|
|
890
|
+
break;
|
|
891
|
+
default:
|
|
892
|
+
if (value >= 30 && value <= 37 || value >= 90 && value <= 97) {
|
|
893
|
+
this._style.fg = [value];
|
|
894
|
+
} else if (value >= 40 && value <= 47 || value >= 100 && value <= 107) {
|
|
895
|
+
this._style.bg = [value];
|
|
896
|
+
}
|
|
897
|
+
break;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
this._styleSequence = serializeStyleState(this._style);
|
|
901
|
+
}
|
|
902
|
+
_applyExtendedColor(control, params17, index) {
|
|
903
|
+
const mode = params17[index + 1];
|
|
904
|
+
const target = control === 38 ? "fg" : "bg";
|
|
905
|
+
if (mode === 5) {
|
|
906
|
+
const paletteIndex = params17[index + 2];
|
|
907
|
+
if (paletteIndex !== void 0) {
|
|
908
|
+
this._style[target] = [control, 5, paletteIndex];
|
|
909
|
+
return index + 2;
|
|
910
|
+
}
|
|
911
|
+
return index;
|
|
912
|
+
}
|
|
913
|
+
if (mode === 2) {
|
|
914
|
+
const red = params17[index + 2];
|
|
915
|
+
const green = params17[index + 3];
|
|
916
|
+
const blue = params17[index + 4];
|
|
917
|
+
if (red !== void 0 && green !== void 0 && blue !== void 0) {
|
|
918
|
+
this._style[target] = [control, 2, red, green, blue];
|
|
919
|
+
return index + 4;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
return index;
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
function createDefaultStyleState() {
|
|
926
|
+
return {
|
|
927
|
+
bold: false,
|
|
928
|
+
dim: false,
|
|
929
|
+
italic: false,
|
|
930
|
+
underline: false,
|
|
931
|
+
inverse: false,
|
|
932
|
+
strikethrough: false
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
function serializeStyleState(state) {
|
|
936
|
+
const codes = [];
|
|
937
|
+
if (state.bold) {
|
|
938
|
+
codes.push(1);
|
|
939
|
+
}
|
|
940
|
+
if (state.dim) {
|
|
941
|
+
codes.push(2);
|
|
942
|
+
}
|
|
943
|
+
if (state.italic) {
|
|
944
|
+
codes.push(3);
|
|
945
|
+
}
|
|
946
|
+
if (state.underline) {
|
|
947
|
+
codes.push(4);
|
|
948
|
+
}
|
|
949
|
+
if (state.inverse) {
|
|
950
|
+
codes.push(7);
|
|
951
|
+
}
|
|
952
|
+
if (state.strikethrough) {
|
|
953
|
+
codes.push(9);
|
|
954
|
+
}
|
|
955
|
+
if (state.fg !== void 0) {
|
|
956
|
+
codes.push(...state.fg);
|
|
957
|
+
}
|
|
958
|
+
if (state.bg !== void 0) {
|
|
959
|
+
codes.push(...state.bg);
|
|
960
|
+
}
|
|
961
|
+
return codes.length === 0 ? "" : `\x1B[${codes.join(";")}m`;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// src/keys.ts
|
|
965
|
+
var NAMED_KEY_SEQUENCES = {
|
|
966
|
+
Enter: "\r",
|
|
967
|
+
Tab: " ",
|
|
968
|
+
Escape: "\x1B",
|
|
969
|
+
Backspace: "\x7F",
|
|
970
|
+
Delete: "\x1B[3~",
|
|
971
|
+
ArrowUp: "\x1B[A",
|
|
972
|
+
ArrowDown: "\x1B[B",
|
|
973
|
+
ArrowRight: "\x1B[C",
|
|
974
|
+
ArrowLeft: "\x1B[D",
|
|
975
|
+
Home: "\x1B[H",
|
|
976
|
+
End: "\x1B[F",
|
|
977
|
+
PageUp: "\x1B[5~",
|
|
978
|
+
PageDown: "\x1B[6~",
|
|
979
|
+
Space: " "
|
|
980
|
+
};
|
|
981
|
+
var NAMED_KEY_LOWER = new Map(
|
|
982
|
+
Object.entries(NAMED_KEY_SEQUENCES).map(([k, v]) => [k.toLowerCase(), v])
|
|
983
|
+
);
|
|
984
|
+
var VALID_KEYS_HINT = `Valid keys: ${Object.keys(NAMED_KEY_SEQUENCES).join(", ")}, Control+<letter>, Alt+<key>`;
|
|
985
|
+
function unknownKeyError(key) {
|
|
986
|
+
return new Error(`Unknown terminal key: ${key}. ${VALID_KEYS_HINT}`);
|
|
987
|
+
}
|
|
988
|
+
function keyToSequence(key) {
|
|
989
|
+
const lowerKey = key.toLowerCase();
|
|
990
|
+
const namedSequence = NAMED_KEY_LOWER.get(lowerKey);
|
|
991
|
+
if (namedSequence !== void 0) {
|
|
992
|
+
return namedSequence;
|
|
993
|
+
}
|
|
994
|
+
if (lowerKey.startsWith("control+")) {
|
|
995
|
+
return controlKeyToSequence(key.slice("control+".length));
|
|
996
|
+
}
|
|
997
|
+
if (lowerKey.startsWith("alt+")) {
|
|
998
|
+
const nestedKey = key.slice("alt+".length);
|
|
999
|
+
if (nestedKey.length === 0) {
|
|
1000
|
+
throw unknownKeyError(key);
|
|
1001
|
+
}
|
|
1002
|
+
if (nestedKey.length === 1) {
|
|
1003
|
+
return "\x1B" + nestedKey;
|
|
1004
|
+
}
|
|
1005
|
+
try {
|
|
1006
|
+
return "\x1B" + keyToSequence(nestedKey);
|
|
1007
|
+
} catch {
|
|
1008
|
+
throw unknownKeyError(key);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (key.length === 1) {
|
|
1012
|
+
return key;
|
|
1013
|
+
}
|
|
1014
|
+
throw unknownKeyError(key);
|
|
1015
|
+
}
|
|
1016
|
+
function controlKeyToSequence(controlKey) {
|
|
1017
|
+
if (controlKey.length !== 1) {
|
|
1018
|
+
throw unknownKeyError(`Control+${controlKey}`);
|
|
1019
|
+
}
|
|
1020
|
+
const uppercaseLetter = controlKey.toUpperCase();
|
|
1021
|
+
const charCode = uppercaseLetter.charCodeAt(0);
|
|
1022
|
+
if (charCode < 65 || charCode > 90) {
|
|
1023
|
+
throw unknownKeyError(`Control+${controlKey}`);
|
|
1024
|
+
}
|
|
1025
|
+
return String.fromCharCode(charCode - 64);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// src/terminal-screen.ts
|
|
1029
|
+
var TerminalScreen = class {
|
|
1030
|
+
lines;
|
|
1031
|
+
rawLines;
|
|
1032
|
+
cursor;
|
|
1033
|
+
size;
|
|
1034
|
+
constructor({
|
|
1035
|
+
lines,
|
|
1036
|
+
rawLines,
|
|
1037
|
+
cursor,
|
|
1038
|
+
size
|
|
1039
|
+
}) {
|
|
1040
|
+
this.lines = Object.freeze(lines.map((line) => stripAnsi(line)));
|
|
1041
|
+
this.rawLines = Object.freeze([...rawLines]);
|
|
1042
|
+
this.cursor = Object.freeze({ ...cursor });
|
|
1043
|
+
this.size = Object.freeze({ ...size });
|
|
1044
|
+
Object.freeze(this);
|
|
1045
|
+
}
|
|
1046
|
+
get text() {
|
|
1047
|
+
return this.lines.join("\n");
|
|
1048
|
+
}
|
|
1049
|
+
contains(substring) {
|
|
1050
|
+
return this.text.includes(substring);
|
|
1051
|
+
}
|
|
1052
|
+
line(index) {
|
|
1053
|
+
const normalizedIndex = index < 0 ? this.lines.length + index : index;
|
|
1054
|
+
const line = this.lines[normalizedIndex];
|
|
1055
|
+
if (line === void 0) {
|
|
1056
|
+
throw new RangeError(`Line index out of bounds: ${index}`);
|
|
1057
|
+
}
|
|
1058
|
+
return line;
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
// src/terminal-session.ts
|
|
1063
|
+
var DEFAULT_COLS = 120;
|
|
1064
|
+
var DEFAULT_ROWS = 40;
|
|
1065
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
1066
|
+
var WAIT_FOR_POLL_MS = 10;
|
|
1067
|
+
var TYPE_DELAY_MS = 15;
|
|
1068
|
+
var CLOSE_AFTER_SIGNAL_GRACE_MS = 250;
|
|
1069
|
+
var CLOSE_AFTER_SIGTERM_MS = 1e3;
|
|
1070
|
+
var TerminalSession = class {
|
|
1071
|
+
id;
|
|
1072
|
+
command;
|
|
1073
|
+
pid;
|
|
1074
|
+
exitCode = null;
|
|
1075
|
+
pty;
|
|
1076
|
+
terminal;
|
|
1077
|
+
emitter = new EventEmitter();
|
|
1078
|
+
exitPromise;
|
|
1079
|
+
rawBuffer = "";
|
|
1080
|
+
lastDataAt = Date.now();
|
|
1081
|
+
currentCols;
|
|
1082
|
+
currentRows;
|
|
1083
|
+
closeRequested = false;
|
|
1084
|
+
signalRequested = false;
|
|
1085
|
+
constructor({
|
|
1086
|
+
id,
|
|
1087
|
+
command,
|
|
1088
|
+
args = [],
|
|
1089
|
+
cwd = process.cwd(),
|
|
1090
|
+
env = process.env,
|
|
1091
|
+
cols = DEFAULT_COLS,
|
|
1092
|
+
rows = DEFAULT_ROWS,
|
|
1093
|
+
observe = false
|
|
1094
|
+
}) {
|
|
1095
|
+
this.id = id;
|
|
1096
|
+
this.command = command;
|
|
1097
|
+
this.currentCols = cols;
|
|
1098
|
+
this.currentRows = rows;
|
|
1099
|
+
this.terminal = new TerminalBuffer(cols, rows);
|
|
1100
|
+
this.pty = createPtyProcess({ command, args, cwd, env, cols, rows });
|
|
1101
|
+
this.pid = this.pty.pid;
|
|
1102
|
+
const dataSubscription = this.pty.onData((chunk) => {
|
|
1103
|
+
this.rawBuffer += chunk;
|
|
1104
|
+
this.lastDataAt = Date.now();
|
|
1105
|
+
this.terminal.write(chunk);
|
|
1106
|
+
if (observe) {
|
|
1107
|
+
process.stderr.write(chunk);
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
1110
|
+
let exitSubscription;
|
|
1111
|
+
this.exitPromise = new Promise((resolve) => {
|
|
1112
|
+
exitSubscription = this.pty.onExit(({ exitCode }) => {
|
|
1113
|
+
if (this.exitCode !== null) {
|
|
1114
|
+
resolve(this.exitCode);
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
this.exitCode = exitCode;
|
|
1118
|
+
dataSubscription.dispose();
|
|
1119
|
+
exitSubscription?.dispose();
|
|
1120
|
+
this.emitter.emit("exit", exitCode);
|
|
1121
|
+
resolve(exitCode);
|
|
1122
|
+
});
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
async type(text) {
|
|
1126
|
+
for (const character of text) {
|
|
1127
|
+
await this.send(character);
|
|
1128
|
+
await sleep(TYPE_DELAY_MS);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
async fill(text) {
|
|
1132
|
+
await this.send(text.replace(/\r?\n/g, "\r"));
|
|
1133
|
+
}
|
|
1134
|
+
async press(key) {
|
|
1135
|
+
await this.send(keyToSequence(key));
|
|
1136
|
+
}
|
|
1137
|
+
async send(raw) {
|
|
1138
|
+
if (this.exitCode !== null) {
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
this.pty.write(raw);
|
|
1142
|
+
}
|
|
1143
|
+
async signal(sig) {
|
|
1144
|
+
if (this.exitCode !== null) {
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
this.signalRequested = true;
|
|
1148
|
+
this.pty.kill(sig);
|
|
1149
|
+
}
|
|
1150
|
+
async waitFor(pattern, opts) {
|
|
1151
|
+
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
1152
|
+
const startedAt = Date.now();
|
|
1153
|
+
while (Date.now() - startedAt <= timeout) {
|
|
1154
|
+
const matched = matchPattern(this.rawBuffer, pattern);
|
|
1155
|
+
if (matched !== null) {
|
|
1156
|
+
return matched;
|
|
1157
|
+
}
|
|
1158
|
+
await sleep(WAIT_FOR_POLL_MS);
|
|
1159
|
+
}
|
|
1160
|
+
throw new Error(`Timed out waiting for pattern after ${timeout}ms: ${String(pattern)}`);
|
|
1161
|
+
}
|
|
1162
|
+
async waitForQuiet(ms) {
|
|
1163
|
+
while (true) {
|
|
1164
|
+
const remaining = ms - (Date.now() - this.lastDataAt);
|
|
1165
|
+
if (remaining <= 0) {
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
await sleep(remaining);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
async screen() {
|
|
1172
|
+
const rawLines = [];
|
|
1173
|
+
for (let row = 0; row < this.currentRows; row += 1) {
|
|
1174
|
+
rawLines.push(this.terminal.renderLine(row));
|
|
1175
|
+
}
|
|
1176
|
+
return new TerminalScreen({
|
|
1177
|
+
lines: rawLines,
|
|
1178
|
+
rawLines,
|
|
1179
|
+
cursor: {
|
|
1180
|
+
row: this.terminal.displayBuffer.cursorY,
|
|
1181
|
+
col: this.terminal.displayBuffer.cursorX
|
|
1182
|
+
},
|
|
1183
|
+
size: {
|
|
1184
|
+
rows: this.currentRows,
|
|
1185
|
+
cols: this.currentCols
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
async history(opts) {
|
|
1190
|
+
const normalized = normalizeHistoryBuffer(stripAnsi(this.rawBuffer));
|
|
1191
|
+
const lines = splitHistoryLines(normalized);
|
|
1192
|
+
if (opts?.last === void 0) {
|
|
1193
|
+
return lines;
|
|
1194
|
+
}
|
|
1195
|
+
const start = Math.max(0, lines.length - opts.last);
|
|
1196
|
+
return lines.slice(start);
|
|
1197
|
+
}
|
|
1198
|
+
async resize(cols, rows) {
|
|
1199
|
+
this.currentCols = cols;
|
|
1200
|
+
this.currentRows = rows;
|
|
1201
|
+
if (this.exitCode === null) {
|
|
1202
|
+
this.pty.resize(cols, rows);
|
|
1203
|
+
}
|
|
1204
|
+
this.terminal.resize(cols, rows);
|
|
1205
|
+
}
|
|
1206
|
+
async waitForExit(opts) {
|
|
1207
|
+
if (this.exitCode !== null) {
|
|
1208
|
+
return this.exitCode;
|
|
1209
|
+
}
|
|
1210
|
+
if (opts?.timeout !== void 0) {
|
|
1211
|
+
const result = await waitForExit(this.exitPromise, opts.timeout);
|
|
1212
|
+
if (result === null) {
|
|
1213
|
+
throw new Error(`Timed out waiting for process to exit after ${opts.timeout}ms`);
|
|
1214
|
+
}
|
|
1215
|
+
return result;
|
|
1216
|
+
}
|
|
1217
|
+
return this.exitPromise;
|
|
1218
|
+
}
|
|
1219
|
+
async close() {
|
|
1220
|
+
if (this.exitCode !== null) {
|
|
1221
|
+
return this.exitCode;
|
|
1222
|
+
}
|
|
1223
|
+
if (!this.closeRequested) {
|
|
1224
|
+
this.closeRequested = true;
|
|
1225
|
+
const gracefulExitCode = await waitForExit(this.exitPromise, CLOSE_AFTER_SIGNAL_GRACE_MS);
|
|
1226
|
+
if (gracefulExitCode !== null) {
|
|
1227
|
+
return gracefulExitCode;
|
|
1228
|
+
}
|
|
1229
|
+
if (this.signalRequested) {
|
|
1230
|
+
return this.exitPromise;
|
|
1231
|
+
}
|
|
1232
|
+
if (this.exitCode === null) {
|
|
1233
|
+
this.pty.kill("SIGTERM");
|
|
1234
|
+
const afterSigterm = await waitForExit(this.exitPromise, CLOSE_AFTER_SIGTERM_MS);
|
|
1235
|
+
if (afterSigterm !== null) {
|
|
1236
|
+
return afterSigterm;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
if (this.exitCode === null) {
|
|
1240
|
+
this.pty.kill("SIGKILL");
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
return this.exitPromise;
|
|
1244
|
+
}
|
|
1245
|
+
on(event, cb) {
|
|
1246
|
+
this.emitter.on(event, cb);
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
function createPtyProcess({
|
|
1250
|
+
command,
|
|
1251
|
+
args,
|
|
1252
|
+
cwd,
|
|
1253
|
+
env,
|
|
1254
|
+
cols,
|
|
1255
|
+
rows
|
|
1256
|
+
}) {
|
|
1257
|
+
try {
|
|
1258
|
+
return nodePty.spawn(command, args, {
|
|
1259
|
+
cwd,
|
|
1260
|
+
env,
|
|
1261
|
+
cols,
|
|
1262
|
+
rows,
|
|
1263
|
+
encoding: "utf8"
|
|
1264
|
+
});
|
|
1265
|
+
} catch {
|
|
1266
|
+
return createChildProcessFallback({ command, args, cwd, env });
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
function createChildProcessFallback({
|
|
1270
|
+
command,
|
|
1271
|
+
args,
|
|
1272
|
+
cwd,
|
|
1273
|
+
env
|
|
1274
|
+
}) {
|
|
1275
|
+
const child = spawnChildProcess(command, args, {
|
|
1276
|
+
cwd,
|
|
1277
|
+
env,
|
|
1278
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1279
|
+
});
|
|
1280
|
+
return new ChildProcessFallback(child);
|
|
1281
|
+
}
|
|
1282
|
+
var ChildProcessFallback = class {
|
|
1283
|
+
pid;
|
|
1284
|
+
child;
|
|
1285
|
+
dataEmitter = new EventEmitter();
|
|
1286
|
+
exitEmitter = new EventEmitter();
|
|
1287
|
+
constructor(child) {
|
|
1288
|
+
this.child = child;
|
|
1289
|
+
this.pid = child.pid ?? -1;
|
|
1290
|
+
child.stdout.setEncoding("utf8");
|
|
1291
|
+
child.stderr.setEncoding("utf8");
|
|
1292
|
+
child.stdout.on("data", this.handleData);
|
|
1293
|
+
child.stderr.on("data", this.handleData);
|
|
1294
|
+
child.on("exit", (exitCode, signal) => {
|
|
1295
|
+
this.exitEmitter.emit("exit", {
|
|
1296
|
+
exitCode: exitCode ?? signalToExitCode(signal),
|
|
1297
|
+
signal: void 0
|
|
1298
|
+
});
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
write(data) {
|
|
1302
|
+
this.child.stdin.write(data);
|
|
1303
|
+
}
|
|
1304
|
+
resize() {
|
|
1305
|
+
}
|
|
1306
|
+
kill(signal) {
|
|
1307
|
+
this.child.kill(signal);
|
|
1308
|
+
}
|
|
1309
|
+
onData(listener) {
|
|
1310
|
+
this.dataEmitter.on("data", listener);
|
|
1311
|
+
return {
|
|
1312
|
+
dispose: () => {
|
|
1313
|
+
this.dataEmitter.off("data", listener);
|
|
1314
|
+
}
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
onExit(listener) {
|
|
1318
|
+
this.exitEmitter.on("exit", listener);
|
|
1319
|
+
return {
|
|
1320
|
+
dispose: () => {
|
|
1321
|
+
this.exitEmitter.off("exit", listener);
|
|
1322
|
+
}
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
handleData = (chunk) => {
|
|
1326
|
+
this.dataEmitter.emit("data", String(chunk));
|
|
1327
|
+
};
|
|
1328
|
+
};
|
|
1329
|
+
function signalToExitCode(signal) {
|
|
1330
|
+
if (signal === null) {
|
|
1331
|
+
return 0;
|
|
1332
|
+
}
|
|
1333
|
+
const signalNumbers = {
|
|
1334
|
+
SIGTERM: 15,
|
|
1335
|
+
SIGINT: 2,
|
|
1336
|
+
SIGHUP: 1,
|
|
1337
|
+
SIGKILL: 9
|
|
1338
|
+
};
|
|
1339
|
+
const signalNumber = signalNumbers[signal];
|
|
1340
|
+
if (signalNumber === void 0) {
|
|
1341
|
+
return 1;
|
|
1342
|
+
}
|
|
1343
|
+
return 128 + signalNumber;
|
|
1344
|
+
}
|
|
1345
|
+
function matchPattern(buffer, pattern) {
|
|
1346
|
+
const clean = normalizeHistoryBuffer(stripAnsi(buffer));
|
|
1347
|
+
for (const line of clean.split("\n")) {
|
|
1348
|
+
if (typeof pattern === "string") {
|
|
1349
|
+
if (line.includes(pattern)) return line;
|
|
1350
|
+
} else {
|
|
1351
|
+
const flags = removeCharacter(pattern.flags, "g");
|
|
1352
|
+
if (new RegExp(pattern.source, flags).test(line)) return line;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
return null;
|
|
1356
|
+
}
|
|
1357
|
+
function removeCharacter(input, charToRemove) {
|
|
1358
|
+
let output = "";
|
|
1359
|
+
for (const character of input) {
|
|
1360
|
+
if (character !== charToRemove) {
|
|
1361
|
+
output += character;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
return output;
|
|
1365
|
+
}
|
|
1366
|
+
function splitHistoryLines(input) {
|
|
1367
|
+
const lines = input.split("\n");
|
|
1368
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
1369
|
+
lines.pop();
|
|
1370
|
+
}
|
|
1371
|
+
return lines;
|
|
1372
|
+
}
|
|
1373
|
+
function normalizeHistoryBuffer(input) {
|
|
1374
|
+
let output = "";
|
|
1375
|
+
for (const character of input) {
|
|
1376
|
+
if (character === "\r" || character === "\b") {
|
|
1377
|
+
continue;
|
|
1378
|
+
}
|
|
1379
|
+
output += character;
|
|
1380
|
+
}
|
|
1381
|
+
return output;
|
|
1382
|
+
}
|
|
1383
|
+
function sleep(ms) {
|
|
1384
|
+
return new Promise((resolve) => {
|
|
1385
|
+
setTimeout(resolve, ms);
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
async function waitForExit(exitPromise, timeout) {
|
|
1389
|
+
return new Promise((resolve) => {
|
|
1390
|
+
let settled = false;
|
|
1391
|
+
const timer = setTimeout(() => {
|
|
1392
|
+
if (settled) {
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
settled = true;
|
|
1396
|
+
resolve(null);
|
|
1397
|
+
}, timeout);
|
|
1398
|
+
void exitPromise.then((code) => {
|
|
1399
|
+
if (settled) {
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
settled = true;
|
|
1403
|
+
clearTimeout(timer);
|
|
1404
|
+
resolve(code);
|
|
1405
|
+
});
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
// src/terminal-pilot.ts
|
|
1410
|
+
var DEFAULT_COLS2 = 120;
|
|
1411
|
+
var DEFAULT_ROWS2 = 40;
|
|
1412
|
+
var TerminalPilot = class _TerminalPilot {
|
|
1413
|
+
sessionMap = /* @__PURE__ */ new Map();
|
|
1414
|
+
static async launch() {
|
|
1415
|
+
return new _TerminalPilot();
|
|
1416
|
+
}
|
|
1417
|
+
async newSession(opts) {
|
|
1418
|
+
const session = new TerminalSession({
|
|
1419
|
+
id: randomUUID(),
|
|
1420
|
+
command: opts.command,
|
|
1421
|
+
args: opts.args,
|
|
1422
|
+
cwd: opts.cwd,
|
|
1423
|
+
env: opts.env,
|
|
1424
|
+
cols: opts.cols ?? DEFAULT_COLS2,
|
|
1425
|
+
rows: opts.rows ?? DEFAULT_ROWS2,
|
|
1426
|
+
observe: opts.observe ?? false
|
|
1427
|
+
});
|
|
1428
|
+
this.sessionMap.set(session.id, session);
|
|
1429
|
+
return session;
|
|
1430
|
+
}
|
|
1431
|
+
getSession(id) {
|
|
1432
|
+
const session = this.sessionMap.get(id);
|
|
1433
|
+
if (session === void 0) {
|
|
1434
|
+
throw new Error(`Session not found: ${id}`);
|
|
1435
|
+
}
|
|
1436
|
+
return session;
|
|
1437
|
+
}
|
|
1438
|
+
deleteSession(id) {
|
|
1439
|
+
this.sessionMap.delete(id);
|
|
1440
|
+
}
|
|
1441
|
+
sessions() {
|
|
1442
|
+
return [...this.sessionMap.values()].filter((s) => s.exitCode === null);
|
|
1443
|
+
}
|
|
1444
|
+
async close() {
|
|
1445
|
+
const sessions = [...this.sessionMap.values()];
|
|
1446
|
+
try {
|
|
1447
|
+
await Promise.all(sessions.map((session) => session.close()));
|
|
1448
|
+
} finally {
|
|
1449
|
+
this.sessionMap.clear();
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
|
|
1454
|
+
// src/commands/runtime.ts
|
|
1455
|
+
var SESSION_ENV_VAR = "TERMINAL_PILOT_SESSION";
|
|
1456
|
+
var sharedRuntime;
|
|
1457
|
+
function getTerminalPilotRuntime(runtime) {
|
|
1458
|
+
if (runtime !== void 0) {
|
|
1459
|
+
return runtime;
|
|
1460
|
+
}
|
|
1461
|
+
sharedRuntime ??= createTerminalPilotRuntime();
|
|
1462
|
+
return sharedRuntime;
|
|
1463
|
+
}
|
|
1464
|
+
function createTerminalPilotRuntime(options = {}) {
|
|
1465
|
+
const launchPilot = options.launchPilot ?? TerminalPilot.launch;
|
|
1466
|
+
const nameToId = /* @__PURE__ */ new Map();
|
|
1467
|
+
const idToName = /* @__PURE__ */ new Map();
|
|
1468
|
+
let pilotPromise;
|
|
1469
|
+
function getRequestedName(name, env) {
|
|
1470
|
+
return name ?? env?.get(SESSION_ENV_VAR);
|
|
1471
|
+
}
|
|
1472
|
+
async function getPilot() {
|
|
1473
|
+
pilotPromise ??= launchPilot();
|
|
1474
|
+
return pilotPromise;
|
|
1475
|
+
}
|
|
1476
|
+
function nextSessionName() {
|
|
1477
|
+
let index = 1;
|
|
1478
|
+
while (nameToId.has(`s${index}`)) {
|
|
1479
|
+
index += 1;
|
|
1480
|
+
}
|
|
1481
|
+
return `s${index}`;
|
|
1482
|
+
}
|
|
1483
|
+
function rememberSession(name, session) {
|
|
1484
|
+
nameToId.set(name, session.id);
|
|
1485
|
+
idToName.set(session.id, name);
|
|
1486
|
+
return { name, session };
|
|
1487
|
+
}
|
|
1488
|
+
function forgetSession(name, sessionId) {
|
|
1489
|
+
nameToId.delete(name);
|
|
1490
|
+
idToName.delete(sessionId);
|
|
1491
|
+
}
|
|
1492
|
+
function formatAvailableSessions(names) {
|
|
1493
|
+
if (names.length === 0) {
|
|
1494
|
+
return "No active sessions are available.";
|
|
1495
|
+
}
|
|
1496
|
+
return `Available sessions: ${names.join(", ")}.`;
|
|
1497
|
+
}
|
|
1498
|
+
async function lookupNamedSession(name) {
|
|
1499
|
+
const sessionId = nameToId.get(name);
|
|
1500
|
+
if (sessionId === void 0) {
|
|
1501
|
+
const active = await listSessions2();
|
|
1502
|
+
throw new UserError(`Session "${name}" was not found. ${formatAvailableSessions(active.map((entry) => entry.name))}`);
|
|
1503
|
+
}
|
|
1504
|
+
const pilot = await getPilot();
|
|
1505
|
+
try {
|
|
1506
|
+
return { name, session: pilot.getSession(sessionId) };
|
|
1507
|
+
} catch {
|
|
1508
|
+
forgetSession(name, sessionId);
|
|
1509
|
+
const active = await listSessions2();
|
|
1510
|
+
throw new UserError(`Session "${name}" was not found. ${formatAvailableSessions(active.map((entry) => entry.name))}`);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
async function listSessions2() {
|
|
1514
|
+
const pilot = await getPilot();
|
|
1515
|
+
return pilot.sessions().flatMap((session) => {
|
|
1516
|
+
const name = idToName.get(session.id);
|
|
1517
|
+
if (name === void 0) {
|
|
1518
|
+
return [];
|
|
1519
|
+
}
|
|
1520
|
+
return [{ name, session }];
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
return {
|
|
1524
|
+
async createSession(params17, env) {
|
|
1525
|
+
const requestedName = getRequestedName(params17.session, env) ?? nextSessionName();
|
|
1526
|
+
if (nameToId.has(requestedName)) {
|
|
1527
|
+
throw new UserError(`Session "${requestedName}" already exists.`);
|
|
1528
|
+
}
|
|
1529
|
+
const pilot = await getPilot();
|
|
1530
|
+
const session = await pilot.newSession({
|
|
1531
|
+
command: params17.command,
|
|
1532
|
+
args: params17.args,
|
|
1533
|
+
cwd: params17.cwd,
|
|
1534
|
+
cols: params17.cols,
|
|
1535
|
+
rows: params17.rows,
|
|
1536
|
+
observe: params17.observe
|
|
1537
|
+
});
|
|
1538
|
+
return rememberSession(requestedName, session);
|
|
1539
|
+
},
|
|
1540
|
+
async resolveSession(name, env) {
|
|
1541
|
+
const requestedName = getRequestedName(name, env);
|
|
1542
|
+
if (requestedName !== void 0) {
|
|
1543
|
+
return lookupNamedSession(requestedName);
|
|
1544
|
+
}
|
|
1545
|
+
const active = await listSessions2();
|
|
1546
|
+
if (active.length === 1) {
|
|
1547
|
+
return active[0];
|
|
1548
|
+
}
|
|
1549
|
+
if (active.length === 0) {
|
|
1550
|
+
throw new UserError("No active sessions. Create one with create-session.");
|
|
1551
|
+
}
|
|
1552
|
+
throw new UserError(
|
|
1553
|
+
`Multiple active sessions require an explicit session name. Pass --session or set ${SESSION_ENV_VAR}. ${formatAvailableSessions(active.map((entry) => entry.name))}`
|
|
1554
|
+
);
|
|
1555
|
+
},
|
|
1556
|
+
async closeSession(name, env) {
|
|
1557
|
+
const namedSession = await this.resolveSession(name, env);
|
|
1558
|
+
const exitCode = await namedSession.session.close();
|
|
1559
|
+
const pilot = await getPilot();
|
|
1560
|
+
pilot.deleteSession(namedSession.session.id);
|
|
1561
|
+
forgetSession(namedSession.name, namedSession.session.id);
|
|
1562
|
+
return {
|
|
1563
|
+
exitCode,
|
|
1564
|
+
name: namedSession.name
|
|
1565
|
+
};
|
|
1566
|
+
},
|
|
1567
|
+
listSessions: listSessions2,
|
|
1568
|
+
async close() {
|
|
1569
|
+
if (pilotPromise === void 0) {
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
const pilot = await pilotPromise;
|
|
1573
|
+
await pilot.close();
|
|
1574
|
+
pilotPromise = void 0;
|
|
1575
|
+
nameToId.clear();
|
|
1576
|
+
idToName.clear();
|
|
1577
|
+
}
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
// src/commands/close-session.ts
|
|
1582
|
+
var params = S.Object({
|
|
1583
|
+
session: S.Optional(S.String({ short: "s", description: "Session name" }))
|
|
1584
|
+
});
|
|
1585
|
+
var closeSession = defineCommand({
|
|
1586
|
+
name: "close-session",
|
|
1587
|
+
description: "Close an active terminal session",
|
|
1588
|
+
scope: ["cli", "mcp", "sdk"],
|
|
1589
|
+
params,
|
|
1590
|
+
handler: async ({ params: params17, env, terminalPilotRuntime }) => {
|
|
1591
|
+
const { exitCode } = await getTerminalPilotRuntime(terminalPilotRuntime).closeSession(params17.session, env);
|
|
1592
|
+
return { exitCode };
|
|
1593
|
+
}
|
|
1594
|
+
});
|
|
1595
|
+
|
|
1596
|
+
// src/commands/create-session.ts
|
|
1597
|
+
var params2 = S.Object({
|
|
1598
|
+
command: S.String({ description: "Command to execute" }),
|
|
1599
|
+
args: S.Optional(S.Array(S.String(), { description: "Command arguments" })),
|
|
1600
|
+
session: S.Optional(S.String({ short: "s", description: "Session name" })),
|
|
1601
|
+
cwd: S.Optional(S.String({ description: "Working directory" })),
|
|
1602
|
+
cols: S.Optional(S.Number({ description: "Terminal width in columns" })),
|
|
1603
|
+
rows: S.Optional(S.Number({ description: "Terminal height in rows" })),
|
|
1604
|
+
observe: S.Optional(S.Boolean({ description: "Mirror PTY output to stderr" }))
|
|
1605
|
+
});
|
|
1606
|
+
var createSession = defineCommand({
|
|
1607
|
+
name: "create-session",
|
|
1608
|
+
description: "Spawn an interactive CLI in a PTY",
|
|
1609
|
+
scope: ["cli", "mcp", "sdk"],
|
|
1610
|
+
positional: ["command", "args"],
|
|
1611
|
+
params: params2,
|
|
1612
|
+
handler: async ({ params: params17, env, terminalPilotRuntime }) => {
|
|
1613
|
+
const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).createSession(params17, env);
|
|
1614
|
+
return { session: namedSession.name, pid: namedSession.session.pid };
|
|
1615
|
+
}
|
|
1616
|
+
});
|
|
1617
|
+
|
|
1618
|
+
// src/commands/fill.ts
|
|
1619
|
+
var params3 = S.Object({
|
|
1620
|
+
text: S.String({ description: "Text to write to the session" }),
|
|
1621
|
+
session: S.Optional(S.String({ short: "s", description: "Session name" }))
|
|
1622
|
+
});
|
|
1623
|
+
var fill = defineCommand({
|
|
1624
|
+
name: "fill",
|
|
1625
|
+
description: "Write text to an active terminal session all at once (replaces \\n with \\r)",
|
|
1626
|
+
scope: ["cli", "mcp", "sdk"],
|
|
1627
|
+
positional: ["text"],
|
|
1628
|
+
params: params3,
|
|
1629
|
+
handler: async ({ params: params17, env, terminalPilotRuntime }) => {
|
|
1630
|
+
const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
|
|
1631
|
+
await namedSession.session.fill(params17.text);
|
|
1632
|
+
return void 0;
|
|
1633
|
+
}
|
|
1634
|
+
});
|
|
1635
|
+
|
|
1636
|
+
// src/commands/get-session.ts
|
|
1637
|
+
var params4 = S.Object({
|
|
1638
|
+
session: S.Optional(S.String({ short: "s", description: "Session name" }))
|
|
1639
|
+
});
|
|
1640
|
+
var getSession = defineCommand({
|
|
1641
|
+
name: "get-session",
|
|
1642
|
+
description: "Get session metadata (name, pid, command, exitCode)",
|
|
1643
|
+
scope: ["cli", "mcp", "sdk"],
|
|
1644
|
+
params: params4,
|
|
1645
|
+
handler: async ({ params: params17, env, terminalPilotRuntime }) => {
|
|
1646
|
+
const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
|
|
1647
|
+
return {
|
|
1648
|
+
session: namedSession.name,
|
|
1649
|
+
pid: namedSession.session.pid,
|
|
1650
|
+
command: namedSession.session.command,
|
|
1651
|
+
exitCode: namedSession.session.exitCode
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
});
|
|
1655
|
+
|
|
1656
|
+
// ../agent-skill-config/src/configs.ts
|
|
1657
|
+
import os from "node:os";
|
|
1658
|
+
import path from "node:path";
|
|
1659
|
+
|
|
1660
|
+
// ../agent-defs/src/agents/claude-code.ts
|
|
1661
|
+
var claudeCodeAgent = {
|
|
1662
|
+
id: "claude-code",
|
|
1663
|
+
name: "claude-code",
|
|
1664
|
+
label: "Claude Code",
|
|
1665
|
+
summary: "Configure Claude Code to route through Poe.",
|
|
1666
|
+
aliases: ["claude"],
|
|
1667
|
+
binaryName: "claude",
|
|
1668
|
+
configPath: "~/.claude/settings.json",
|
|
1669
|
+
branding: {
|
|
1670
|
+
colors: {
|
|
1671
|
+
dark: "#C15F3C",
|
|
1672
|
+
light: "#C15F3C"
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
};
|
|
1676
|
+
|
|
1677
|
+
// ../agent-defs/src/agents/claude-desktop.ts
|
|
1678
|
+
var claudeDesktopAgent = {
|
|
1679
|
+
id: "claude-desktop",
|
|
1680
|
+
name: "claude-desktop",
|
|
1681
|
+
label: "Claude Desktop",
|
|
1682
|
+
summary: "Anthropic's official desktop application for Claude",
|
|
1683
|
+
configPath: "~/.claude/settings.json",
|
|
1684
|
+
branding: {
|
|
1685
|
+
colors: {
|
|
1686
|
+
dark: "#D97757",
|
|
1687
|
+
light: "#D97757"
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
};
|
|
1691
|
+
|
|
1692
|
+
// ../agent-defs/src/agents/codex.ts
|
|
1693
|
+
var codexAgent = {
|
|
1694
|
+
id: "codex",
|
|
1695
|
+
name: "codex",
|
|
1696
|
+
label: "Codex",
|
|
1697
|
+
summary: "Configure Codex to use Poe as the model provider.",
|
|
1698
|
+
binaryName: "codex",
|
|
1699
|
+
configPath: "~/.codex/config.toml",
|
|
1700
|
+
branding: {
|
|
1701
|
+
colors: {
|
|
1702
|
+
dark: "#D5D9DF",
|
|
1703
|
+
light: "#7A7F86"
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
};
|
|
1707
|
+
|
|
1708
|
+
// ../agent-defs/src/agents/opencode.ts
|
|
1709
|
+
var openCodeAgent = {
|
|
1710
|
+
id: "opencode",
|
|
1711
|
+
name: "opencode",
|
|
1712
|
+
label: "OpenCode CLI",
|
|
1713
|
+
summary: "Configure OpenCode CLI to use the Poe API.",
|
|
1714
|
+
binaryName: "opencode",
|
|
1715
|
+
configPath: "~/.config/opencode/config.json",
|
|
1716
|
+
branding: {
|
|
1717
|
+
colors: {
|
|
1718
|
+
dark: "#4A4F55",
|
|
1719
|
+
light: "#2F3338"
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
|
|
1724
|
+
// ../agent-defs/src/agents/kimi.ts
|
|
1725
|
+
var kimiAgent = {
|
|
1726
|
+
id: "kimi",
|
|
1727
|
+
name: "kimi",
|
|
1728
|
+
label: "Kimi",
|
|
1729
|
+
summary: "Configure Kimi CLI to use Poe API",
|
|
1730
|
+
aliases: ["kimi-cli"],
|
|
1731
|
+
binaryName: "kimi",
|
|
1732
|
+
configPath: "~/.kimi/config.toml",
|
|
1733
|
+
branding: {
|
|
1734
|
+
colors: {
|
|
1735
|
+
dark: "#7B68EE",
|
|
1736
|
+
light: "#6A5ACD"
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
};
|
|
1740
|
+
|
|
1741
|
+
// ../agent-defs/src/registry.ts
|
|
1742
|
+
var allAgents = [
|
|
1743
|
+
claudeCodeAgent,
|
|
1744
|
+
claudeDesktopAgent,
|
|
1745
|
+
codexAgent,
|
|
1746
|
+
openCodeAgent,
|
|
1747
|
+
kimiAgent
|
|
1748
|
+
];
|
|
1749
|
+
var lookup = /* @__PURE__ */ new Map();
|
|
1750
|
+
for (const agent of allAgents) {
|
|
1751
|
+
const values = [agent.id, agent.name, ...agent.aliases ?? []];
|
|
1752
|
+
for (const value of values) {
|
|
1753
|
+
const normalized = value.toLowerCase();
|
|
1754
|
+
if (!lookup.has(normalized)) {
|
|
1755
|
+
lookup.set(normalized, agent.id);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
function resolveAgentId(input) {
|
|
1760
|
+
if (!input) {
|
|
1761
|
+
return void 0;
|
|
1762
|
+
}
|
|
1763
|
+
return lookup.get(input.toLowerCase());
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
// ../agent-skill-config/src/configs.ts
|
|
1767
|
+
var agentSkillConfigs = {
|
|
1768
|
+
"claude-code": {
|
|
1769
|
+
globalSkillDir: "~/.claude/skills",
|
|
1770
|
+
localSkillDir: ".claude/skills"
|
|
1771
|
+
},
|
|
1772
|
+
codex: {
|
|
1773
|
+
globalSkillDir: "~/.codex/skills",
|
|
1774
|
+
localSkillDir: ".codex/skills"
|
|
1775
|
+
},
|
|
1776
|
+
opencode: {
|
|
1777
|
+
globalSkillDir: "~/.config/opencode/skills",
|
|
1778
|
+
localSkillDir: ".opencode/skills"
|
|
1779
|
+
}
|
|
1780
|
+
};
|
|
1781
|
+
var supportedAgents = Object.keys(agentSkillConfigs);
|
|
1782
|
+
function resolveAgentSupport(input, registry = agentSkillConfigs) {
|
|
1783
|
+
const resolvedId = resolveAgentId(input);
|
|
1784
|
+
if (!resolvedId) {
|
|
1785
|
+
return { status: "unknown", input };
|
|
1786
|
+
}
|
|
1787
|
+
const config = registry[resolvedId];
|
|
1788
|
+
if (!config) {
|
|
1789
|
+
return { status: "unsupported", input, id: resolvedId };
|
|
1790
|
+
}
|
|
1791
|
+
return { status: "supported", input, id: resolvedId, config };
|
|
1792
|
+
}
|
|
1793
|
+
function getAgentConfig(agentId) {
|
|
1794
|
+
const support = resolveAgentSupport(agentId);
|
|
1795
|
+
return support.status === "supported" ? support.config : void 0;
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
// ../config-mutations/src/mutations/file-mutation.ts
|
|
1799
|
+
function ensureDirectory(options) {
|
|
1800
|
+
return {
|
|
1801
|
+
kind: "ensureDirectory",
|
|
1802
|
+
path: options.path,
|
|
1803
|
+
label: options.label
|
|
1804
|
+
};
|
|
1805
|
+
}
|
|
1806
|
+
function remove(options) {
|
|
1807
|
+
return {
|
|
1808
|
+
kind: "removeFile",
|
|
1809
|
+
target: options.target,
|
|
1810
|
+
whenEmpty: options.whenEmpty,
|
|
1811
|
+
whenContentMatches: options.whenContentMatches,
|
|
1812
|
+
label: options.label
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
function removeDirectory(options) {
|
|
1816
|
+
return {
|
|
1817
|
+
kind: "removeDirectory",
|
|
1818
|
+
path: options.path,
|
|
1819
|
+
force: options.force,
|
|
1820
|
+
label: options.label
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
function chmod(options) {
|
|
1824
|
+
return {
|
|
1825
|
+
kind: "chmod",
|
|
1826
|
+
target: options.target,
|
|
1827
|
+
mode: options.mode,
|
|
1828
|
+
label: options.label
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
function backup(options) {
|
|
1832
|
+
return {
|
|
1833
|
+
kind: "backup",
|
|
1834
|
+
target: options.target,
|
|
1835
|
+
label: options.label
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
var fileMutation = {
|
|
1839
|
+
ensureDirectory,
|
|
1840
|
+
remove,
|
|
1841
|
+
removeDirectory,
|
|
1842
|
+
chmod,
|
|
1843
|
+
backup
|
|
1844
|
+
};
|
|
1845
|
+
|
|
1846
|
+
// ../config-mutations/src/mutations/template-mutation.ts
|
|
1847
|
+
function write(options) {
|
|
1848
|
+
return {
|
|
1849
|
+
kind: "templateWrite",
|
|
1850
|
+
target: options.target,
|
|
1851
|
+
templateId: options.templateId,
|
|
1852
|
+
context: options.context,
|
|
1853
|
+
label: options.label
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
function mergeToml(options) {
|
|
1857
|
+
return {
|
|
1858
|
+
kind: "templateMergeToml",
|
|
1859
|
+
target: options.target,
|
|
1860
|
+
templateId: options.templateId,
|
|
1861
|
+
context: options.context,
|
|
1862
|
+
label: options.label
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
function mergeJson(options) {
|
|
1866
|
+
return {
|
|
1867
|
+
kind: "templateMergeJson",
|
|
1868
|
+
target: options.target,
|
|
1869
|
+
templateId: options.templateId,
|
|
1870
|
+
context: options.context,
|
|
1871
|
+
label: options.label
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
var templateMutation = {
|
|
1875
|
+
write,
|
|
1876
|
+
mergeToml,
|
|
1877
|
+
mergeJson
|
|
1878
|
+
};
|
|
1879
|
+
|
|
1880
|
+
// ../config-mutations/src/execution/apply-mutation.ts
|
|
1881
|
+
import Mustache from "mustache";
|
|
1882
|
+
|
|
1883
|
+
// ../config-mutations/src/formats/json.ts
|
|
1884
|
+
import * as jsonc from "jsonc-parser";
|
|
1885
|
+
function isConfigObject(value) {
|
|
1886
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1887
|
+
}
|
|
1888
|
+
function parse2(content) {
|
|
1889
|
+
if (!content || content.trim() === "") {
|
|
1890
|
+
return {};
|
|
1891
|
+
}
|
|
1892
|
+
const errors = [];
|
|
1893
|
+
const parsed = jsonc.parse(content, errors, {
|
|
1894
|
+
allowTrailingComma: true,
|
|
1895
|
+
disallowComments: false
|
|
1896
|
+
});
|
|
1897
|
+
if (errors.length > 0) {
|
|
1898
|
+
throw new Error(`JSON parse error: ${jsonc.printParseErrorCode(errors[0].error)}`);
|
|
1899
|
+
}
|
|
1900
|
+
if (parsed === null || parsed === void 0) {
|
|
1901
|
+
return {};
|
|
1902
|
+
}
|
|
1903
|
+
if (!isConfigObject(parsed)) {
|
|
1904
|
+
throw new Error("Expected JSON object.");
|
|
1905
|
+
}
|
|
1906
|
+
return parsed;
|
|
1907
|
+
}
|
|
1908
|
+
function serialize(obj) {
|
|
1909
|
+
return `${JSON.stringify(obj, null, 2)}
|
|
1910
|
+
`;
|
|
1911
|
+
}
|
|
1912
|
+
function merge(base, patch) {
|
|
1913
|
+
const result = { ...base };
|
|
1914
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
1915
|
+
if (value === void 0) {
|
|
1916
|
+
continue;
|
|
1917
|
+
}
|
|
1918
|
+
const existing = result[key];
|
|
1919
|
+
if (isConfigObject(existing) && isConfigObject(value)) {
|
|
1920
|
+
result[key] = merge(existing, value);
|
|
1921
|
+
continue;
|
|
1922
|
+
}
|
|
1923
|
+
result[key] = value;
|
|
1924
|
+
}
|
|
1925
|
+
return result;
|
|
1926
|
+
}
|
|
1927
|
+
function prune(obj, shape) {
|
|
1928
|
+
let changed = false;
|
|
1929
|
+
const result = { ...obj };
|
|
1930
|
+
for (const [key, pattern] of Object.entries(shape)) {
|
|
1931
|
+
if (!(key in result)) {
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
const current = result[key];
|
|
1935
|
+
if (isConfigObject(pattern) && Object.keys(pattern).length === 0) {
|
|
1936
|
+
delete result[key];
|
|
1937
|
+
changed = true;
|
|
1938
|
+
continue;
|
|
1939
|
+
}
|
|
1940
|
+
if (isConfigObject(pattern) && isConfigObject(current)) {
|
|
1941
|
+
const { changed: childChanged, result: childResult } = prune(
|
|
1942
|
+
current,
|
|
1943
|
+
pattern
|
|
1944
|
+
);
|
|
1945
|
+
if (childChanged) {
|
|
1946
|
+
changed = true;
|
|
1947
|
+
}
|
|
1948
|
+
if (Object.keys(childResult).length === 0) {
|
|
1949
|
+
delete result[key];
|
|
1950
|
+
} else {
|
|
1951
|
+
result[key] = childResult;
|
|
1952
|
+
}
|
|
1953
|
+
continue;
|
|
1954
|
+
}
|
|
1955
|
+
delete result[key];
|
|
1956
|
+
changed = true;
|
|
1957
|
+
}
|
|
1958
|
+
return { changed, result };
|
|
1959
|
+
}
|
|
1960
|
+
var jsonFormat = {
|
|
1961
|
+
parse: parse2,
|
|
1962
|
+
serialize,
|
|
1963
|
+
merge,
|
|
1964
|
+
prune
|
|
1965
|
+
};
|
|
1966
|
+
|
|
1967
|
+
// ../config-mutations/src/formats/toml.ts
|
|
1968
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
1969
|
+
function isConfigObject2(value) {
|
|
1970
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1971
|
+
}
|
|
1972
|
+
function parse3(content) {
|
|
1973
|
+
if (!content || content.trim() === "") {
|
|
1974
|
+
return {};
|
|
1975
|
+
}
|
|
1976
|
+
const parsed = parseToml(content);
|
|
1977
|
+
if (!isConfigObject2(parsed)) {
|
|
1978
|
+
throw new Error("Expected TOML document to be a table.");
|
|
1979
|
+
}
|
|
1980
|
+
return parsed;
|
|
1981
|
+
}
|
|
1982
|
+
function serialize2(obj) {
|
|
1983
|
+
const serialized = stringifyToml(obj);
|
|
1984
|
+
return serialized.endsWith("\n") ? serialized : `${serialized}
|
|
1985
|
+
`;
|
|
1986
|
+
}
|
|
1987
|
+
function merge2(base, patch) {
|
|
1988
|
+
const result = { ...base };
|
|
1989
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
1990
|
+
if (value === void 0) {
|
|
1991
|
+
continue;
|
|
1992
|
+
}
|
|
1993
|
+
const existing = result[key];
|
|
1994
|
+
if (isConfigObject2(existing) && isConfigObject2(value)) {
|
|
1995
|
+
result[key] = merge2(existing, value);
|
|
1996
|
+
continue;
|
|
1997
|
+
}
|
|
1998
|
+
result[key] = value;
|
|
1999
|
+
}
|
|
2000
|
+
return result;
|
|
2001
|
+
}
|
|
2002
|
+
function prune2(obj, shape) {
|
|
2003
|
+
let changed = false;
|
|
2004
|
+
const result = { ...obj };
|
|
2005
|
+
for (const [key, pattern] of Object.entries(shape)) {
|
|
2006
|
+
if (!(key in result)) {
|
|
2007
|
+
continue;
|
|
2008
|
+
}
|
|
2009
|
+
const current = result[key];
|
|
2010
|
+
if (isConfigObject2(pattern) && Object.keys(pattern).length === 0) {
|
|
2011
|
+
delete result[key];
|
|
2012
|
+
changed = true;
|
|
2013
|
+
continue;
|
|
2014
|
+
}
|
|
2015
|
+
if (isConfigObject2(pattern) && isConfigObject2(current)) {
|
|
2016
|
+
const { changed: childChanged, result: childResult } = prune2(
|
|
2017
|
+
current,
|
|
2018
|
+
pattern
|
|
2019
|
+
);
|
|
2020
|
+
if (childChanged) {
|
|
2021
|
+
changed = true;
|
|
2022
|
+
}
|
|
2023
|
+
if (Object.keys(childResult).length === 0) {
|
|
2024
|
+
delete result[key];
|
|
2025
|
+
} else {
|
|
2026
|
+
result[key] = childResult;
|
|
2027
|
+
}
|
|
2028
|
+
continue;
|
|
2029
|
+
}
|
|
2030
|
+
delete result[key];
|
|
2031
|
+
changed = true;
|
|
2032
|
+
}
|
|
2033
|
+
return { changed, result };
|
|
2034
|
+
}
|
|
2035
|
+
var tomlFormat = {
|
|
2036
|
+
parse: parse3,
|
|
2037
|
+
serialize: serialize2,
|
|
2038
|
+
merge: merge2,
|
|
2039
|
+
prune: prune2
|
|
2040
|
+
};
|
|
2041
|
+
|
|
2042
|
+
// ../config-mutations/src/formats/index.ts
|
|
2043
|
+
var formatRegistry = {
|
|
2044
|
+
json: jsonFormat,
|
|
2045
|
+
toml: tomlFormat
|
|
2046
|
+
};
|
|
2047
|
+
var extensionMap = {
|
|
2048
|
+
".json": "json",
|
|
2049
|
+
".toml": "toml"
|
|
2050
|
+
};
|
|
2051
|
+
function getConfigFormat(pathOrFormat) {
|
|
2052
|
+
if (pathOrFormat in formatRegistry) {
|
|
2053
|
+
return formatRegistry[pathOrFormat];
|
|
2054
|
+
}
|
|
2055
|
+
const ext = getExtension(pathOrFormat);
|
|
2056
|
+
const formatName = extensionMap[ext];
|
|
2057
|
+
if (!formatName) {
|
|
2058
|
+
throw new Error(
|
|
2059
|
+
`Unsupported config format. Cannot detect format from "${pathOrFormat}". Supported extensions: ${Object.keys(extensionMap).join(", ")}. Supported format names: ${Object.keys(formatRegistry).join(", ")}.`
|
|
2060
|
+
);
|
|
2061
|
+
}
|
|
2062
|
+
return formatRegistry[formatName];
|
|
2063
|
+
}
|
|
2064
|
+
function detectFormat(path4) {
|
|
2065
|
+
const ext = getExtension(path4);
|
|
2066
|
+
return extensionMap[ext];
|
|
2067
|
+
}
|
|
2068
|
+
function getExtension(path4) {
|
|
2069
|
+
const lastDot = path4.lastIndexOf(".");
|
|
2070
|
+
if (lastDot === -1) {
|
|
2071
|
+
return "";
|
|
2072
|
+
}
|
|
2073
|
+
return path4.slice(lastDot).toLowerCase();
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
// ../config-mutations/src/execution/path-utils.ts
|
|
2077
|
+
import path2 from "node:path";
|
|
2078
|
+
function expandHome(targetPath, homeDir) {
|
|
2079
|
+
if (!targetPath?.startsWith("~")) {
|
|
2080
|
+
return targetPath;
|
|
2081
|
+
}
|
|
2082
|
+
if (targetPath.startsWith("~./")) {
|
|
2083
|
+
targetPath = `~/.${targetPath.slice(3)}`;
|
|
2084
|
+
}
|
|
2085
|
+
let remainder = targetPath.slice(1);
|
|
2086
|
+
if (remainder.startsWith("/") || remainder.startsWith("\\")) {
|
|
2087
|
+
remainder = remainder.slice(1);
|
|
2088
|
+
} else if (remainder.startsWith(".")) {
|
|
2089
|
+
remainder = remainder.slice(1);
|
|
2090
|
+
if (remainder.startsWith("/") || remainder.startsWith("\\")) {
|
|
2091
|
+
remainder = remainder.slice(1);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
return remainder.length === 0 ? homeDir : path2.join(homeDir, remainder);
|
|
2095
|
+
}
|
|
2096
|
+
function validateHomePath(targetPath) {
|
|
2097
|
+
if (typeof targetPath !== "string" || targetPath.length === 0) {
|
|
2098
|
+
throw new Error("Target path must be a non-empty string.");
|
|
2099
|
+
}
|
|
2100
|
+
if (!targetPath.startsWith("~")) {
|
|
2101
|
+
throw new Error(
|
|
2102
|
+
`All target paths must be home-relative (start with ~). Received: "${targetPath}"`
|
|
2103
|
+
);
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
function resolvePath(rawPath, homeDir, pathMapper) {
|
|
2107
|
+
validateHomePath(rawPath);
|
|
2108
|
+
const expanded = expandHome(rawPath, homeDir);
|
|
2109
|
+
if (!pathMapper) {
|
|
2110
|
+
return expanded;
|
|
2111
|
+
}
|
|
2112
|
+
const rawDirectory = path2.dirname(expanded);
|
|
2113
|
+
const mappedDirectory = pathMapper.mapTargetDirectory({
|
|
2114
|
+
targetDirectory: rawDirectory
|
|
2115
|
+
});
|
|
2116
|
+
const filename = path2.basename(expanded);
|
|
2117
|
+
return filename.length === 0 ? mappedDirectory : path2.join(mappedDirectory, filename);
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
// ../config-mutations/src/fs-utils.ts
|
|
2121
|
+
function isNotFound(error) {
|
|
2122
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2123
|
+
}
|
|
2124
|
+
async function readFileIfExists(fs, target) {
|
|
2125
|
+
try {
|
|
2126
|
+
return await fs.readFile(target, "utf8");
|
|
2127
|
+
} catch (error) {
|
|
2128
|
+
if (isNotFound(error)) {
|
|
2129
|
+
return null;
|
|
2130
|
+
}
|
|
2131
|
+
throw error;
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
async function pathExists(fs, target) {
|
|
2135
|
+
try {
|
|
2136
|
+
await fs.stat(target);
|
|
2137
|
+
return true;
|
|
2138
|
+
} catch (error) {
|
|
2139
|
+
if (isNotFound(error)) {
|
|
2140
|
+
return false;
|
|
2141
|
+
}
|
|
2142
|
+
throw error;
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
function createTimestamp() {
|
|
2146
|
+
return (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-").replaceAll(".", "-");
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
// ../config-mutations/src/execution/apply-mutation.ts
|
|
2150
|
+
function resolveValue(resolver, options) {
|
|
2151
|
+
if (typeof resolver === "function") {
|
|
2152
|
+
return resolver(options);
|
|
2153
|
+
}
|
|
2154
|
+
return resolver;
|
|
2155
|
+
}
|
|
2156
|
+
function createInvalidDocumentBackupPath(targetPath) {
|
|
2157
|
+
const ext = targetPath.includes(".") ? targetPath.split(".").pop() : "bak";
|
|
2158
|
+
return `${targetPath}.invalid-${createTimestamp()}.${ext}`;
|
|
2159
|
+
}
|
|
2160
|
+
async function backupInvalidDocument(fs, targetPath, content) {
|
|
2161
|
+
const backupPath = createInvalidDocumentBackupPath(targetPath);
|
|
2162
|
+
await fs.writeFile(backupPath, content, { encoding: "utf8" });
|
|
2163
|
+
}
|
|
2164
|
+
function describeMutation(kind, targetPath) {
|
|
2165
|
+
const displayPath = targetPath ?? "target";
|
|
2166
|
+
switch (kind) {
|
|
2167
|
+
case "ensureDirectory":
|
|
2168
|
+
return `Create ${displayPath}`;
|
|
2169
|
+
case "removeDirectory":
|
|
2170
|
+
return `Remove directory ${displayPath}`;
|
|
2171
|
+
case "backup":
|
|
2172
|
+
return `Backup ${displayPath}`;
|
|
2173
|
+
case "templateWrite":
|
|
2174
|
+
return `Write ${displayPath}`;
|
|
2175
|
+
case "chmod":
|
|
2176
|
+
return `Set permissions on ${displayPath}`;
|
|
2177
|
+
case "removeFile":
|
|
2178
|
+
return `Remove ${displayPath}`;
|
|
2179
|
+
case "configMerge":
|
|
2180
|
+
case "configPrune":
|
|
2181
|
+
case "configTransform":
|
|
2182
|
+
case "templateMergeToml":
|
|
2183
|
+
case "templateMergeJson":
|
|
2184
|
+
return `Update ${displayPath}`;
|
|
2185
|
+
default:
|
|
2186
|
+
return "Operation";
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
function pruneKeysByPrefix(table, prefix) {
|
|
2190
|
+
const result = {};
|
|
2191
|
+
for (const [key, value] of Object.entries(table)) {
|
|
2192
|
+
if (!key.startsWith(prefix)) {
|
|
2193
|
+
result[key] = value;
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
return result;
|
|
2197
|
+
}
|
|
2198
|
+
function isConfigObject3(value) {
|
|
2199
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2200
|
+
}
|
|
2201
|
+
function mergeWithPruneByPrefix(base, patch, pruneByPrefix) {
|
|
2202
|
+
const result = { ...base };
|
|
2203
|
+
const prefixMap = pruneByPrefix ?? {};
|
|
2204
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
2205
|
+
const current = result[key];
|
|
2206
|
+
const prefix = prefixMap[key];
|
|
2207
|
+
if (isConfigObject3(current) && isConfigObject3(value)) {
|
|
2208
|
+
if (prefix) {
|
|
2209
|
+
const pruned = pruneKeysByPrefix(current, prefix);
|
|
2210
|
+
result[key] = { ...pruned, ...value };
|
|
2211
|
+
} else {
|
|
2212
|
+
result[key] = mergeWithPruneByPrefix(
|
|
2213
|
+
current,
|
|
2214
|
+
value,
|
|
2215
|
+
prefixMap
|
|
2216
|
+
);
|
|
2217
|
+
}
|
|
2218
|
+
continue;
|
|
2219
|
+
}
|
|
2220
|
+
result[key] = value;
|
|
2221
|
+
}
|
|
2222
|
+
return result;
|
|
2223
|
+
}
|
|
2224
|
+
async function applyMutation(mutation, context, options) {
|
|
2225
|
+
switch (mutation.kind) {
|
|
2226
|
+
case "ensureDirectory":
|
|
2227
|
+
return applyEnsureDirectory(mutation, context, options);
|
|
2228
|
+
case "removeDirectory":
|
|
2229
|
+
return applyRemoveDirectory(mutation, context, options);
|
|
2230
|
+
case "removeFile":
|
|
2231
|
+
return applyRemoveFile(mutation, context, options);
|
|
2232
|
+
case "chmod":
|
|
2233
|
+
return applyChmod(mutation, context, options);
|
|
2234
|
+
case "backup":
|
|
2235
|
+
return applyBackup(mutation, context, options);
|
|
2236
|
+
case "configMerge":
|
|
2237
|
+
return applyConfigMerge(mutation, context, options);
|
|
2238
|
+
case "configPrune":
|
|
2239
|
+
return applyConfigPrune(mutation, context, options);
|
|
2240
|
+
case "configTransform":
|
|
2241
|
+
return applyConfigTransform(mutation, context, options);
|
|
2242
|
+
case "templateWrite":
|
|
2243
|
+
return applyTemplateWrite(mutation, context, options);
|
|
2244
|
+
case "templateMergeToml":
|
|
2245
|
+
return applyTemplateMerge(mutation, context, options, "toml");
|
|
2246
|
+
case "templateMergeJson":
|
|
2247
|
+
return applyTemplateMerge(mutation, context, options, "json");
|
|
2248
|
+
default: {
|
|
2249
|
+
const never = mutation;
|
|
2250
|
+
throw new Error(`Unknown mutation kind: ${never.kind}`);
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
async function applyEnsureDirectory(mutation, context, options) {
|
|
2255
|
+
const rawPath = resolveValue(mutation.path, options);
|
|
2256
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
2257
|
+
const details = {
|
|
2258
|
+
kind: mutation.kind,
|
|
2259
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
2260
|
+
targetPath
|
|
2261
|
+
};
|
|
2262
|
+
const existed = await pathExists(context.fs, targetPath);
|
|
2263
|
+
if (!context.dryRun) {
|
|
2264
|
+
await context.fs.mkdir(targetPath, { recursive: true });
|
|
2265
|
+
}
|
|
2266
|
+
return {
|
|
2267
|
+
outcome: {
|
|
2268
|
+
changed: !existed,
|
|
2269
|
+
effect: "mkdir",
|
|
2270
|
+
detail: existed ? "noop" : "create"
|
|
2271
|
+
},
|
|
2272
|
+
details
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
async function applyRemoveDirectory(mutation, context, options) {
|
|
2276
|
+
const rawPath = resolveValue(mutation.path, options);
|
|
2277
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
2278
|
+
const details = {
|
|
2279
|
+
kind: mutation.kind,
|
|
2280
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
2281
|
+
targetPath
|
|
2282
|
+
};
|
|
2283
|
+
const existed = await pathExists(context.fs, targetPath);
|
|
2284
|
+
if (!existed) {
|
|
2285
|
+
return {
|
|
2286
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2287
|
+
details
|
|
2288
|
+
};
|
|
2289
|
+
}
|
|
2290
|
+
if (typeof context.fs.rm !== "function") {
|
|
2291
|
+
return {
|
|
2292
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2293
|
+
details
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
if (mutation.force) {
|
|
2297
|
+
if (!context.dryRun) {
|
|
2298
|
+
await context.fs.rm(targetPath, { recursive: true, force: true });
|
|
2299
|
+
}
|
|
2300
|
+
return {
|
|
2301
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
2302
|
+
details
|
|
2303
|
+
};
|
|
2304
|
+
}
|
|
2305
|
+
const entries = await context.fs.readdir(targetPath);
|
|
2306
|
+
if (entries.length > 0) {
|
|
2307
|
+
return {
|
|
2308
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2309
|
+
details
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
if (!context.dryRun) {
|
|
2313
|
+
await context.fs.rm(targetPath, { recursive: true, force: true });
|
|
2314
|
+
}
|
|
2315
|
+
return {
|
|
2316
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
2317
|
+
details
|
|
2318
|
+
};
|
|
2319
|
+
}
|
|
2320
|
+
async function applyRemoveFile(mutation, context, options) {
|
|
2321
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
2322
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
2323
|
+
const details = {
|
|
2324
|
+
kind: mutation.kind,
|
|
2325
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
2326
|
+
targetPath
|
|
2327
|
+
};
|
|
2328
|
+
try {
|
|
2329
|
+
const content = await context.fs.readFile(targetPath, "utf8");
|
|
2330
|
+
const trimmed = content.trim();
|
|
2331
|
+
if (mutation.whenContentMatches && !mutation.whenContentMatches.test(trimmed)) {
|
|
2332
|
+
return {
|
|
2333
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2334
|
+
details
|
|
2335
|
+
};
|
|
2336
|
+
}
|
|
2337
|
+
if (mutation.whenEmpty && trimmed.length > 0) {
|
|
2338
|
+
return {
|
|
2339
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2340
|
+
details
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2343
|
+
if (!context.dryRun) {
|
|
2344
|
+
await context.fs.unlink(targetPath);
|
|
2345
|
+
}
|
|
2346
|
+
return {
|
|
2347
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
2348
|
+
details
|
|
2349
|
+
};
|
|
2350
|
+
} catch (error) {
|
|
2351
|
+
if (isNotFound(error)) {
|
|
2352
|
+
return {
|
|
2353
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2354
|
+
details
|
|
2355
|
+
};
|
|
2356
|
+
}
|
|
2357
|
+
throw error;
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
async function applyChmod(mutation, context, options) {
|
|
2361
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
2362
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
2363
|
+
const details = {
|
|
2364
|
+
kind: mutation.kind,
|
|
2365
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
2366
|
+
targetPath
|
|
2367
|
+
};
|
|
2368
|
+
if (typeof context.fs.chmod !== "function") {
|
|
2369
|
+
return {
|
|
2370
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2371
|
+
details
|
|
2372
|
+
};
|
|
2373
|
+
}
|
|
2374
|
+
try {
|
|
2375
|
+
const stat = await context.fs.stat(targetPath);
|
|
2376
|
+
const currentMode = typeof stat.mode === "number" ? stat.mode & 511 : null;
|
|
2377
|
+
if (currentMode === mutation.mode) {
|
|
2378
|
+
return {
|
|
2379
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2380
|
+
details
|
|
2381
|
+
};
|
|
2382
|
+
}
|
|
2383
|
+
if (!context.dryRun) {
|
|
2384
|
+
await context.fs.chmod(targetPath, mutation.mode);
|
|
2385
|
+
}
|
|
2386
|
+
return {
|
|
2387
|
+
outcome: { changed: true, effect: "chmod", detail: "update" },
|
|
2388
|
+
details
|
|
2389
|
+
};
|
|
2390
|
+
} catch (error) {
|
|
2391
|
+
if (isNotFound(error)) {
|
|
2392
|
+
return {
|
|
2393
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2394
|
+
details
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
throw error;
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
async function applyBackup(mutation, context, options) {
|
|
2401
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
2402
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
2403
|
+
const details = {
|
|
2404
|
+
kind: mutation.kind,
|
|
2405
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
2406
|
+
targetPath
|
|
2407
|
+
};
|
|
2408
|
+
const content = await readFileIfExists(context.fs, targetPath);
|
|
2409
|
+
if (content === null) {
|
|
2410
|
+
return {
|
|
2411
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2412
|
+
details
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
if (!context.dryRun) {
|
|
2416
|
+
const backupPath = `${targetPath}.backup-${createTimestamp()}`;
|
|
2417
|
+
await context.fs.writeFile(backupPath, content, { encoding: "utf8" });
|
|
2418
|
+
}
|
|
2419
|
+
return {
|
|
2420
|
+
outcome: { changed: true, effect: "copy", detail: "backup" },
|
|
2421
|
+
details
|
|
2422
|
+
};
|
|
2423
|
+
}
|
|
2424
|
+
async function applyConfigMerge(mutation, context, options) {
|
|
2425
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
2426
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
2427
|
+
const details = {
|
|
2428
|
+
kind: mutation.kind,
|
|
2429
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
2430
|
+
targetPath
|
|
2431
|
+
};
|
|
2432
|
+
const formatName = mutation.format ?? detectFormat(rawPath);
|
|
2433
|
+
if (!formatName) {
|
|
2434
|
+
throw new Error(
|
|
2435
|
+
`Cannot detect config format for "${rawPath}". Provide explicit format option.`
|
|
2436
|
+
);
|
|
2437
|
+
}
|
|
2438
|
+
const format = getConfigFormat(formatName);
|
|
2439
|
+
const rawContent = await readFileIfExists(context.fs, targetPath);
|
|
2440
|
+
let current;
|
|
2441
|
+
try {
|
|
2442
|
+
current = rawContent === null ? {} : format.parse(rawContent);
|
|
2443
|
+
} catch {
|
|
2444
|
+
if (rawContent !== null) {
|
|
2445
|
+
await backupInvalidDocument(context.fs, targetPath, rawContent);
|
|
2446
|
+
}
|
|
2447
|
+
current = {};
|
|
2448
|
+
}
|
|
2449
|
+
const value = resolveValue(mutation.value, options);
|
|
2450
|
+
let merged;
|
|
2451
|
+
if (mutation.pruneByPrefix) {
|
|
2452
|
+
merged = mergeWithPruneByPrefix(current, value, mutation.pruneByPrefix);
|
|
2453
|
+
} else {
|
|
2454
|
+
merged = format.merge(current, value);
|
|
2455
|
+
}
|
|
2456
|
+
const serialized = format.serialize(merged);
|
|
2457
|
+
const changed = serialized !== rawContent;
|
|
2458
|
+
if (changed && !context.dryRun) {
|
|
2459
|
+
await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
|
|
2460
|
+
}
|
|
2461
|
+
return {
|
|
2462
|
+
outcome: {
|
|
2463
|
+
changed,
|
|
2464
|
+
effect: changed ? "write" : "none",
|
|
2465
|
+
detail: changed ? rawContent === null ? "create" : "update" : "noop"
|
|
2466
|
+
},
|
|
2467
|
+
details
|
|
2468
|
+
};
|
|
2469
|
+
}
|
|
2470
|
+
async function applyConfigPrune(mutation, context, options) {
|
|
2471
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
2472
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
2473
|
+
const details = {
|
|
2474
|
+
kind: mutation.kind,
|
|
2475
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
2476
|
+
targetPath
|
|
2477
|
+
};
|
|
2478
|
+
const rawContent = await readFileIfExists(context.fs, targetPath);
|
|
2479
|
+
if (rawContent === null) {
|
|
2480
|
+
return {
|
|
2481
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2482
|
+
details
|
|
2483
|
+
};
|
|
2484
|
+
}
|
|
2485
|
+
const formatName = mutation.format ?? detectFormat(rawPath);
|
|
2486
|
+
if (!formatName) {
|
|
2487
|
+
throw new Error(
|
|
2488
|
+
`Cannot detect config format for "${rawPath}". Provide explicit format option.`
|
|
2489
|
+
);
|
|
2490
|
+
}
|
|
2491
|
+
const format = getConfigFormat(formatName);
|
|
2492
|
+
let current;
|
|
2493
|
+
try {
|
|
2494
|
+
current = format.parse(rawContent);
|
|
2495
|
+
} catch {
|
|
2496
|
+
return {
|
|
2497
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2498
|
+
details
|
|
2499
|
+
};
|
|
2500
|
+
}
|
|
2501
|
+
if (mutation.onlyIf && !mutation.onlyIf(current, options)) {
|
|
2502
|
+
return {
|
|
2503
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2504
|
+
details
|
|
2505
|
+
};
|
|
2506
|
+
}
|
|
2507
|
+
const shape = resolveValue(mutation.shape, options);
|
|
2508
|
+
const { changed, result } = format.prune(current, shape);
|
|
2509
|
+
if (!changed) {
|
|
2510
|
+
return {
|
|
2511
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2512
|
+
details
|
|
2513
|
+
};
|
|
2514
|
+
}
|
|
2515
|
+
if (Object.keys(result).length === 0) {
|
|
2516
|
+
if (!context.dryRun) {
|
|
2517
|
+
await context.fs.unlink(targetPath);
|
|
2518
|
+
}
|
|
2519
|
+
return {
|
|
2520
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
2521
|
+
details
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2524
|
+
const serialized = format.serialize(result);
|
|
2525
|
+
if (!context.dryRun) {
|
|
2526
|
+
await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
|
|
2527
|
+
}
|
|
2528
|
+
return {
|
|
2529
|
+
outcome: { changed: true, effect: "write", detail: "update" },
|
|
2530
|
+
details
|
|
2531
|
+
};
|
|
2532
|
+
}
|
|
2533
|
+
async function applyConfigTransform(mutation, context, options) {
|
|
2534
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
2535
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
2536
|
+
const details = {
|
|
2537
|
+
kind: mutation.kind,
|
|
2538
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
2539
|
+
targetPath
|
|
2540
|
+
};
|
|
2541
|
+
const formatName = mutation.format ?? detectFormat(rawPath);
|
|
2542
|
+
if (!formatName) {
|
|
2543
|
+
throw new Error(
|
|
2544
|
+
`Cannot detect config format for "${rawPath}". Provide explicit format option.`
|
|
2545
|
+
);
|
|
2546
|
+
}
|
|
2547
|
+
const format = getConfigFormat(formatName);
|
|
2548
|
+
const rawContent = await readFileIfExists(context.fs, targetPath);
|
|
2549
|
+
let current;
|
|
2550
|
+
try {
|
|
2551
|
+
current = rawContent === null ? {} : format.parse(rawContent);
|
|
2552
|
+
} catch {
|
|
2553
|
+
if (rawContent !== null) {
|
|
2554
|
+
await backupInvalidDocument(context.fs, targetPath, rawContent);
|
|
2555
|
+
}
|
|
2556
|
+
current = {};
|
|
2557
|
+
}
|
|
2558
|
+
const { content: transformed, changed } = mutation.transform(current, options);
|
|
2559
|
+
if (!changed) {
|
|
2560
|
+
return {
|
|
2561
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2562
|
+
details
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2565
|
+
if (transformed === null) {
|
|
2566
|
+
if (rawContent === null) {
|
|
2567
|
+
return {
|
|
2568
|
+
outcome: { changed: false, effect: "none", detail: "noop" },
|
|
2569
|
+
details
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
if (!context.dryRun) {
|
|
2573
|
+
await context.fs.unlink(targetPath);
|
|
2574
|
+
}
|
|
2575
|
+
return {
|
|
2576
|
+
outcome: { changed: true, effect: "delete", detail: "delete" },
|
|
2577
|
+
details
|
|
2578
|
+
};
|
|
2579
|
+
}
|
|
2580
|
+
const serialized = format.serialize(transformed);
|
|
2581
|
+
if (!context.dryRun) {
|
|
2582
|
+
await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
|
|
2583
|
+
}
|
|
2584
|
+
return {
|
|
2585
|
+
outcome: {
|
|
2586
|
+
changed: true,
|
|
2587
|
+
effect: "write",
|
|
2588
|
+
detail: rawContent === null ? "create" : "update"
|
|
2589
|
+
},
|
|
2590
|
+
details
|
|
2591
|
+
};
|
|
2592
|
+
}
|
|
2593
|
+
async function applyTemplateWrite(mutation, context, options) {
|
|
2594
|
+
if (!context.templates) {
|
|
2595
|
+
throw new Error(
|
|
2596
|
+
"Template mutations require a templates loader. Provide templates function to runMutations context."
|
|
2597
|
+
);
|
|
2598
|
+
}
|
|
2599
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
2600
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
2601
|
+
const details = {
|
|
2602
|
+
kind: mutation.kind,
|
|
2603
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
2604
|
+
targetPath
|
|
2605
|
+
};
|
|
2606
|
+
const template = await context.templates(mutation.templateId);
|
|
2607
|
+
const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
|
|
2608
|
+
const rendered = Mustache.render(template, templateContext);
|
|
2609
|
+
const existed = await pathExists(context.fs, targetPath);
|
|
2610
|
+
if (!context.dryRun) {
|
|
2611
|
+
await context.fs.writeFile(targetPath, rendered, { encoding: "utf8" });
|
|
2612
|
+
}
|
|
2613
|
+
return {
|
|
2614
|
+
outcome: {
|
|
2615
|
+
changed: true,
|
|
2616
|
+
effect: "write",
|
|
2617
|
+
detail: existed ? "update" : "create"
|
|
2618
|
+
},
|
|
2619
|
+
details
|
|
2620
|
+
};
|
|
2621
|
+
}
|
|
2622
|
+
async function applyTemplateMerge(mutation, context, options, formatName) {
|
|
2623
|
+
if (!context.templates) {
|
|
2624
|
+
throw new Error(
|
|
2625
|
+
"Template mutations require a templates loader. Provide templates function to runMutations context."
|
|
2626
|
+
);
|
|
2627
|
+
}
|
|
2628
|
+
const rawPath = resolveValue(mutation.target, options);
|
|
2629
|
+
const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
|
|
2630
|
+
const details = {
|
|
2631
|
+
kind: mutation.kind,
|
|
2632
|
+
label: mutation.label ?? describeMutation(mutation.kind, targetPath),
|
|
2633
|
+
targetPath
|
|
2634
|
+
};
|
|
2635
|
+
const format = getConfigFormat(formatName);
|
|
2636
|
+
const template = await context.templates(mutation.templateId);
|
|
2637
|
+
const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
|
|
2638
|
+
const rendered = Mustache.render(template, templateContext);
|
|
2639
|
+
let templateDoc;
|
|
2640
|
+
try {
|
|
2641
|
+
templateDoc = format.parse(rendered);
|
|
2642
|
+
} catch (error) {
|
|
2643
|
+
throw new Error(
|
|
2644
|
+
`Failed to parse rendered template "${mutation.templateId}" as ${formatName.toUpperCase()}: ${error}`,
|
|
2645
|
+
{ cause: error }
|
|
2646
|
+
);
|
|
2647
|
+
}
|
|
2648
|
+
const rawContent = await readFileIfExists(context.fs, targetPath);
|
|
2649
|
+
let current;
|
|
2650
|
+
try {
|
|
2651
|
+
current = rawContent === null ? {} : format.parse(rawContent);
|
|
2652
|
+
} catch {
|
|
2653
|
+
if (rawContent !== null) {
|
|
2654
|
+
await backupInvalidDocument(context.fs, targetPath, rawContent);
|
|
2655
|
+
}
|
|
2656
|
+
current = {};
|
|
2657
|
+
}
|
|
2658
|
+
const merged = format.merge(current, templateDoc);
|
|
2659
|
+
const serialized = format.serialize(merged);
|
|
2660
|
+
const changed = serialized !== rawContent;
|
|
2661
|
+
if (changed && !context.dryRun) {
|
|
2662
|
+
await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
|
|
2663
|
+
}
|
|
2664
|
+
return {
|
|
2665
|
+
outcome: {
|
|
2666
|
+
changed,
|
|
2667
|
+
effect: changed ? "write" : "none",
|
|
2668
|
+
detail: changed ? rawContent === null ? "create" : "update" : "noop"
|
|
2669
|
+
},
|
|
2670
|
+
details
|
|
2671
|
+
};
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
// ../config-mutations/src/execution/run-mutations.ts
|
|
2675
|
+
async function runMutations(mutations, context, options) {
|
|
2676
|
+
const effects = [];
|
|
2677
|
+
let anyChanged = false;
|
|
2678
|
+
const resolverOptions = options ?? {};
|
|
2679
|
+
for (const mutation of mutations) {
|
|
2680
|
+
const { outcome } = await executeMutation(
|
|
2681
|
+
mutation,
|
|
2682
|
+
context,
|
|
2683
|
+
resolverOptions
|
|
2684
|
+
);
|
|
2685
|
+
effects.push(outcome);
|
|
2686
|
+
if (outcome.changed) {
|
|
2687
|
+
anyChanged = true;
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
return {
|
|
2691
|
+
changed: anyChanged,
|
|
2692
|
+
effects
|
|
2693
|
+
};
|
|
2694
|
+
}
|
|
2695
|
+
async function executeMutation(mutation, context, options) {
|
|
2696
|
+
context.observers?.onStart?.({
|
|
2697
|
+
kind: mutation.kind,
|
|
2698
|
+
label: mutation.label ?? mutation.kind,
|
|
2699
|
+
targetPath: void 0
|
|
2700
|
+
// Will be resolved during apply
|
|
2701
|
+
});
|
|
2702
|
+
try {
|
|
2703
|
+
const { outcome, details } = await applyMutation(mutation, context, options);
|
|
2704
|
+
context.observers?.onComplete?.(details, outcome);
|
|
2705
|
+
return { outcome, details };
|
|
2706
|
+
} catch (error) {
|
|
2707
|
+
context.observers?.onError?.(
|
|
2708
|
+
{
|
|
2709
|
+
kind: mutation.kind,
|
|
2710
|
+
label: mutation.label ?? mutation.kind,
|
|
2711
|
+
targetPath: void 0
|
|
2712
|
+
},
|
|
2713
|
+
error
|
|
2714
|
+
);
|
|
2715
|
+
throw error;
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2719
|
+
// ../config-mutations/src/template/render.ts
|
|
2720
|
+
import Mustache2 from "mustache";
|
|
2721
|
+
var originalEscape = Mustache2.escape;
|
|
2722
|
+
|
|
2723
|
+
// ../agent-skill-config/src/templates.ts
|
|
2724
|
+
import { readFile } from "node:fs/promises";
|
|
2725
|
+
|
|
2726
|
+
// ../agent-skill-config/src/apply.ts
|
|
2727
|
+
var UnsupportedAgentError = class extends Error {
|
|
2728
|
+
constructor(agentId) {
|
|
2729
|
+
super(`Unsupported agent: ${agentId}`);
|
|
2730
|
+
this.name = "UnsupportedAgentError";
|
|
2731
|
+
}
|
|
2732
|
+
};
|
|
2733
|
+
function toHomeRelative(localSkillDir) {
|
|
2734
|
+
if (localSkillDir.startsWith("~/") || localSkillDir === "~") {
|
|
2735
|
+
return localSkillDir;
|
|
2736
|
+
}
|
|
2737
|
+
const normalized = localSkillDir.startsWith("./") ? localSkillDir.slice(2) : localSkillDir;
|
|
2738
|
+
return `~/${normalized}`;
|
|
2739
|
+
}
|
|
2740
|
+
var SKILL_TEMPLATE_ID = "__skill_content__";
|
|
2741
|
+
async function installSkill(agentId, skill, options) {
|
|
2742
|
+
const support = resolveAgentSupport(agentId);
|
|
2743
|
+
if (support.status !== "supported") {
|
|
2744
|
+
throw new UnsupportedAgentError(agentId);
|
|
2745
|
+
}
|
|
2746
|
+
const scope = options.scope ?? "local";
|
|
2747
|
+
const config = support.config;
|
|
2748
|
+
const skillDir = scope === "global" ? config.globalSkillDir : toHomeRelative(config.localSkillDir);
|
|
2749
|
+
const skillFolderPath = `${skillDir}/${skill.name}`;
|
|
2750
|
+
const skillFilePath = `${skillFolderPath}/SKILL.md`;
|
|
2751
|
+
const displayPath = `${scope === "global" ? config.globalSkillDir : config.localSkillDir}/${skill.name}/SKILL.md`;
|
|
2752
|
+
await runMutations(
|
|
2753
|
+
[
|
|
2754
|
+
fileMutation.ensureDirectory({
|
|
2755
|
+
path: skillFolderPath,
|
|
2756
|
+
label: `Ensure skill directory ${skill.name}`
|
|
2757
|
+
}),
|
|
2758
|
+
templateMutation.write({
|
|
2759
|
+
target: skillFilePath,
|
|
2760
|
+
templateId: SKILL_TEMPLATE_ID,
|
|
2761
|
+
label: `Write skill ${skill.name}`
|
|
2762
|
+
})
|
|
2763
|
+
],
|
|
2764
|
+
{
|
|
2765
|
+
fs: options.fs,
|
|
2766
|
+
homeDir: scope === "global" ? options.homeDir : options.cwd,
|
|
2767
|
+
dryRun: options.dryRun,
|
|
2768
|
+
observers: options.observers,
|
|
2769
|
+
templates: async (templateId) => {
|
|
2770
|
+
if (templateId === SKILL_TEMPLATE_ID) {
|
|
2771
|
+
return skill.content;
|
|
2772
|
+
}
|
|
2773
|
+
throw new Error(`Unknown template: ${templateId}`);
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
);
|
|
2777
|
+
return { skillPath: skillFilePath, displayPath };
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
// src/commands/installer.ts
|
|
2781
|
+
import os2 from "node:os";
|
|
2782
|
+
import path3 from "node:path";
|
|
2783
|
+
import * as nodeFs from "node:fs/promises";
|
|
2784
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
2785
|
+
var DEFAULT_INSTALL_AGENT = "claude-code";
|
|
2786
|
+
var DEFAULT_INSTALL_SCOPE = "local";
|
|
2787
|
+
var TERMINAL_PILOT_SKILL_NAME = "terminal-pilot";
|
|
2788
|
+
var installableAgents = supportedAgents;
|
|
2789
|
+
function isNotFoundError(error) {
|
|
2790
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2791
|
+
}
|
|
2792
|
+
function resolveInstallerServices(installer) {
|
|
2793
|
+
return {
|
|
2794
|
+
fs: installer?.fs ?? nodeFs,
|
|
2795
|
+
cwd: installer?.cwd ?? process.cwd(),
|
|
2796
|
+
homeDir: installer?.homeDir ?? os2.homedir(),
|
|
2797
|
+
platform: installer?.platform ?? process.platform
|
|
2798
|
+
};
|
|
2799
|
+
}
|
|
2800
|
+
function throwUnsupportedAgent(agent) {
|
|
2801
|
+
throw new UserError(`Unsupported agent: ${agent}`);
|
|
2802
|
+
}
|
|
2803
|
+
function resolveInstallableAgent(agent) {
|
|
2804
|
+
const skillSupport = resolveAgentSupport(agent);
|
|
2805
|
+
if (skillSupport.status !== "supported" || !skillSupport.id) {
|
|
2806
|
+
throwUnsupportedAgent(agent);
|
|
2807
|
+
}
|
|
2808
|
+
return skillSupport.id;
|
|
2809
|
+
}
|
|
2810
|
+
function resolveInstallScope(input) {
|
|
2811
|
+
if (input.local && input.global) {
|
|
2812
|
+
throw new UserError("Use either --local or --global, not both.");
|
|
2813
|
+
}
|
|
2814
|
+
if (input.local) {
|
|
2815
|
+
return "local";
|
|
2816
|
+
}
|
|
2817
|
+
if (input.global) {
|
|
2818
|
+
return "global";
|
|
2819
|
+
}
|
|
2820
|
+
return DEFAULT_INSTALL_SCOPE;
|
|
2821
|
+
}
|
|
2822
|
+
var terminalPilotTemplateCache;
|
|
2823
|
+
async function loadTerminalPilotTemplate() {
|
|
2824
|
+
if (terminalPilotTemplateCache !== void 0) {
|
|
2825
|
+
return terminalPilotTemplateCache;
|
|
2826
|
+
}
|
|
2827
|
+
const candidates = [
|
|
2828
|
+
new URL("./templates/terminal-pilot.md", import.meta.url),
|
|
2829
|
+
new URL("../templates/terminal-pilot.md", import.meta.url),
|
|
2830
|
+
new URL("../../../agent-skill-config/src/templates/terminal-pilot.md", import.meta.url)
|
|
2831
|
+
];
|
|
2832
|
+
for (const candidate of candidates) {
|
|
2833
|
+
try {
|
|
2834
|
+
terminalPilotTemplateCache = await readFile2(candidate, "utf8");
|
|
2835
|
+
return terminalPilotTemplateCache;
|
|
2836
|
+
} catch (error) {
|
|
2837
|
+
if (!isNotFoundError(error)) {
|
|
2838
|
+
throw error;
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
throw new UserError("terminal-pilot skill template is missing.");
|
|
2843
|
+
}
|
|
2844
|
+
function resolveHomeRelativePath(targetPath, homeDir) {
|
|
2845
|
+
if (targetPath === "~") {
|
|
2846
|
+
return homeDir;
|
|
2847
|
+
}
|
|
2848
|
+
if (targetPath.startsWith("~/")) {
|
|
2849
|
+
return path3.join(homeDir, targetPath.slice(2));
|
|
2850
|
+
}
|
|
2851
|
+
return targetPath;
|
|
2852
|
+
}
|
|
2853
|
+
function getSkillFolderWithHome(agent, scope, cwd, homeDir) {
|
|
2854
|
+
const config = getAgentConfig(agent);
|
|
2855
|
+
if (!config) {
|
|
2856
|
+
throwUnsupportedAgent(agent);
|
|
2857
|
+
}
|
|
2858
|
+
return {
|
|
2859
|
+
displayPath: path3.join(
|
|
2860
|
+
scope === "global" ? config.globalSkillDir : config.localSkillDir,
|
|
2861
|
+
TERMINAL_PILOT_SKILL_NAME
|
|
2862
|
+
),
|
|
2863
|
+
fullPath: path3.join(
|
|
2864
|
+
scope === "global" ? resolveHomeRelativePath(config.globalSkillDir, homeDir) : path3.resolve(cwd, config.localSkillDir),
|
|
2865
|
+
TERMINAL_PILOT_SKILL_NAME
|
|
2866
|
+
)
|
|
2867
|
+
};
|
|
2868
|
+
}
|
|
2869
|
+
async function removeSkillFolder(fs, folderPath) {
|
|
2870
|
+
try {
|
|
2871
|
+
await fs.stat(folderPath);
|
|
2872
|
+
} catch (error) {
|
|
2873
|
+
if (isNotFoundError(error)) {
|
|
2874
|
+
return false;
|
|
2875
|
+
}
|
|
2876
|
+
throw error;
|
|
2877
|
+
}
|
|
2878
|
+
if (typeof fs.rm !== "function") {
|
|
2879
|
+
throw new UserError("The configured filesystem does not support removing directories.");
|
|
2880
|
+
}
|
|
2881
|
+
await fs.rm(folderPath, { recursive: true, force: true });
|
|
2882
|
+
return true;
|
|
2883
|
+
}
|
|
2884
|
+
|
|
2885
|
+
// src/commands/install.ts
|
|
2886
|
+
var params5 = S.Object({
|
|
2887
|
+
agent: S.Enum(installableAgents, {
|
|
2888
|
+
description: "Agent to install terminal-pilot for",
|
|
2889
|
+
default: DEFAULT_INSTALL_AGENT
|
|
2890
|
+
}),
|
|
2891
|
+
local: S.Optional(S.Boolean({ description: "Install the skill in the current project" })),
|
|
2892
|
+
global: S.Optional(S.Boolean({ description: "Install the skill in the user home directory" }))
|
|
2893
|
+
});
|
|
2894
|
+
var install = defineCommand({
|
|
2895
|
+
name: "install",
|
|
2896
|
+
description: "Install the terminal-pilot CLI skill.",
|
|
2897
|
+
scope: ["cli"],
|
|
2898
|
+
positional: ["agent"],
|
|
2899
|
+
params: params5,
|
|
2900
|
+
handler: async ({ params: params17, terminalPilotInstaller }) => {
|
|
2901
|
+
const services = resolveInstallerServices(terminalPilotInstaller);
|
|
2902
|
+
const agent = resolveInstallableAgent(params17.agent);
|
|
2903
|
+
const scope = resolveInstallScope(params17);
|
|
2904
|
+
const template = await loadTerminalPilotTemplate();
|
|
2905
|
+
const skillResult = await installSkill(
|
|
2906
|
+
agent,
|
|
2907
|
+
{
|
|
2908
|
+
name: TERMINAL_PILOT_SKILL_NAME,
|
|
2909
|
+
content: template
|
|
2910
|
+
},
|
|
2911
|
+
{
|
|
2912
|
+
fs: services.fs,
|
|
2913
|
+
cwd: services.cwd,
|
|
2914
|
+
homeDir: services.homeDir,
|
|
2915
|
+
scope
|
|
2916
|
+
}
|
|
2917
|
+
);
|
|
2918
|
+
return {
|
|
2919
|
+
agent,
|
|
2920
|
+
scope,
|
|
2921
|
+
skillPath: skillResult.displayPath
|
|
2922
|
+
};
|
|
2923
|
+
}
|
|
2924
|
+
});
|
|
2925
|
+
|
|
2926
|
+
// src/commands/list-sessions.ts
|
|
2927
|
+
var params6 = S.Object({});
|
|
2928
|
+
var listSessions = defineCommand({
|
|
2929
|
+
name: "list-sessions",
|
|
2930
|
+
description: "List active terminal sessions",
|
|
2931
|
+
scope: ["cli", "mcp", "sdk"],
|
|
2932
|
+
params: params6,
|
|
2933
|
+
handler: async ({ terminalPilotRuntime }) => {
|
|
2934
|
+
const sessions = await getTerminalPilotRuntime(terminalPilotRuntime).listSessions();
|
|
2935
|
+
return {
|
|
2936
|
+
sessions: sessions.map((namedSession) => ({
|
|
2937
|
+
session: namedSession.name,
|
|
2938
|
+
command: namedSession.session.command,
|
|
2939
|
+
pid: namedSession.session.pid
|
|
2940
|
+
}))
|
|
2941
|
+
};
|
|
2942
|
+
}
|
|
2943
|
+
});
|
|
2944
|
+
|
|
2945
|
+
// src/commands/press-key.ts
|
|
2946
|
+
var params7 = S.Object({
|
|
2947
|
+
key: S.String({ description: "Named key to press" }),
|
|
2948
|
+
session: S.Optional(S.String({ short: "s", description: "Session name" }))
|
|
2949
|
+
});
|
|
2950
|
+
var pressKey = defineCommand({
|
|
2951
|
+
name: "press-key",
|
|
2952
|
+
description: "Send a named key press to an active terminal session",
|
|
2953
|
+
scope: ["cli", "mcp", "sdk"],
|
|
2954
|
+
positional: ["key"],
|
|
2955
|
+
params: params7,
|
|
2956
|
+
handler: async ({ params: params17, env, terminalPilotRuntime }) => {
|
|
2957
|
+
const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
|
|
2958
|
+
await namedSession.session.press(params17.key);
|
|
2959
|
+
return void 0;
|
|
2960
|
+
}
|
|
2961
|
+
});
|
|
2962
|
+
|
|
2963
|
+
// src/commands/read-history.ts
|
|
2964
|
+
var params8 = S.Object({
|
|
2965
|
+
session: S.Optional(S.String({ short: "s", description: "Session name" })),
|
|
2966
|
+
last: S.Optional(S.Number({ short: "n", description: "Return only the last N lines" }))
|
|
2967
|
+
});
|
|
2968
|
+
var readHistory = defineCommand({
|
|
2969
|
+
name: "read-history",
|
|
2970
|
+
description: "Read terminal output history",
|
|
2971
|
+
scope: ["cli", "mcp", "sdk"],
|
|
2972
|
+
params: params8,
|
|
2973
|
+
handler: async ({ params: params17, env, terminalPilotRuntime }) => {
|
|
2974
|
+
const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
|
|
2975
|
+
const lines = await namedSession.session.history({ last: params17.last });
|
|
2976
|
+
return { lines, exitCode: namedSession.session.exitCode };
|
|
2977
|
+
}
|
|
2978
|
+
});
|
|
2979
|
+
|
|
2980
|
+
// src/commands/read-screen.ts
|
|
2981
|
+
var params9 = S.Object({
|
|
2982
|
+
session: S.Optional(S.String({ short: "s", description: "Session name" }))
|
|
2983
|
+
});
|
|
2984
|
+
var readScreen = defineCommand({
|
|
2985
|
+
name: "read-screen",
|
|
2986
|
+
description: "Read the current visible terminal screen",
|
|
2987
|
+
scope: ["cli", "mcp", "sdk"],
|
|
2988
|
+
params: params9,
|
|
2989
|
+
handler: async ({ params: params17, env, terminalPilotRuntime }) => {
|
|
2990
|
+
const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
|
|
2991
|
+
const screen = await namedSession.session.screen();
|
|
2992
|
+
return {
|
|
2993
|
+
lines: [...screen.lines],
|
|
2994
|
+
cursor: { ...screen.cursor },
|
|
2995
|
+
size: { ...screen.size },
|
|
2996
|
+
exitCode: namedSession.session.exitCode
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2999
|
+
});
|
|
3000
|
+
|
|
3001
|
+
// src/commands/resize.ts
|
|
3002
|
+
var params10 = S.Object({
|
|
3003
|
+
cols: S.Number({ description: "Terminal width in columns" }),
|
|
3004
|
+
rows: S.Number({ description: "Terminal height in rows" }),
|
|
3005
|
+
session: S.Optional(S.String({ short: "s", description: "Session name" }))
|
|
3006
|
+
});
|
|
3007
|
+
var resize = defineCommand({
|
|
3008
|
+
name: "resize",
|
|
3009
|
+
description: "Resize an active terminal session",
|
|
3010
|
+
scope: ["cli", "mcp", "sdk"],
|
|
3011
|
+
params: params10,
|
|
3012
|
+
handler: async ({ params: params17, env, terminalPilotRuntime }) => {
|
|
3013
|
+
const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
|
|
3014
|
+
await namedSession.session.resize(params17.cols, params17.rows);
|
|
3015
|
+
return void 0;
|
|
3016
|
+
}
|
|
3017
|
+
});
|
|
3018
|
+
|
|
3019
|
+
// ../terminal-png/src/index.ts
|
|
3020
|
+
import { writeFile } from "node:fs/promises";
|
|
3021
|
+
|
|
3022
|
+
// ../terminal-png/src/ansi-parser.ts
|
|
3023
|
+
var ESC2 = "\x1B";
|
|
3024
|
+
function createDefaultStyle() {
|
|
3025
|
+
return {
|
|
3026
|
+
fg: null,
|
|
3027
|
+
bg: null,
|
|
3028
|
+
bold: false,
|
|
3029
|
+
italic: false,
|
|
3030
|
+
underline: false,
|
|
3031
|
+
strikethrough: false,
|
|
3032
|
+
dim: false
|
|
3033
|
+
};
|
|
3034
|
+
}
|
|
3035
|
+
function cloneStyle(style) {
|
|
3036
|
+
return { ...style };
|
|
3037
|
+
}
|
|
3038
|
+
function stylesEqual(left, right) {
|
|
3039
|
+
return left.bold === right.bold && left.italic === right.italic && left.underline === right.underline && left.strikethrough === right.strikethrough && left.dim === right.dim && colorsEqual(left.fg, right.fg) && colorsEqual(left.bg, right.bg);
|
|
3040
|
+
}
|
|
3041
|
+
function colorsEqual(left, right) {
|
|
3042
|
+
if (left === right) {
|
|
3043
|
+
return true;
|
|
3044
|
+
}
|
|
3045
|
+
if (!left || !right || left.type !== right.type) {
|
|
3046
|
+
return false;
|
|
3047
|
+
}
|
|
3048
|
+
if (left.type === "rgb" && right.type === "rgb") {
|
|
3049
|
+
return left.r === right.r && left.g === right.g && left.b === right.b;
|
|
3050
|
+
}
|
|
3051
|
+
if ((left.type === "ansi4" || left.type === "ansi8") && left.type === right.type) {
|
|
3052
|
+
return left.index === right.index;
|
|
3053
|
+
}
|
|
3054
|
+
return false;
|
|
3055
|
+
}
|
|
3056
|
+
function pushRun(runs, style, text) {
|
|
3057
|
+
if (text.length === 0) {
|
|
3058
|
+
return;
|
|
3059
|
+
}
|
|
3060
|
+
const previous = runs.at(-1);
|
|
3061
|
+
if (previous && text !== "\n" && previous.text !== "\n" && stylesEqual(previous, style)) {
|
|
3062
|
+
previous.text += text;
|
|
3063
|
+
return;
|
|
3064
|
+
}
|
|
3065
|
+
runs.push({
|
|
3066
|
+
text,
|
|
3067
|
+
fg: style.fg,
|
|
3068
|
+
bg: style.bg,
|
|
3069
|
+
bold: style.bold,
|
|
3070
|
+
italic: style.italic,
|
|
3071
|
+
underline: style.underline,
|
|
3072
|
+
strikethrough: style.strikethrough,
|
|
3073
|
+
dim: style.dim
|
|
3074
|
+
});
|
|
3075
|
+
}
|
|
3076
|
+
function parseCsi(input, start) {
|
|
3077
|
+
let index = start + 2;
|
|
3078
|
+
while (index < input.length) {
|
|
3079
|
+
const code = input.charCodeAt(index);
|
|
3080
|
+
if (code >= 64 && code <= 126) {
|
|
3081
|
+
return {
|
|
3082
|
+
end: index + 1,
|
|
3083
|
+
final: input[index],
|
|
3084
|
+
params: input.slice(start + 2, index)
|
|
3085
|
+
};
|
|
3086
|
+
}
|
|
3087
|
+
index += 1;
|
|
3088
|
+
}
|
|
3089
|
+
return {
|
|
3090
|
+
end: input.length,
|
|
3091
|
+
final: null,
|
|
3092
|
+
params: input.slice(start + 2)
|
|
3093
|
+
};
|
|
3094
|
+
}
|
|
3095
|
+
function toInteger(value) {
|
|
3096
|
+
if (!value || value.length === 0) {
|
|
3097
|
+
return null;
|
|
3098
|
+
}
|
|
3099
|
+
for (const char of value) {
|
|
3100
|
+
if (char < "0" || char > "9") {
|
|
3101
|
+
return null;
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
return Number.parseInt(value, 10);
|
|
3105
|
+
}
|
|
3106
|
+
function clampByte(value) {
|
|
3107
|
+
if (value < 0) {
|
|
3108
|
+
return 0;
|
|
3109
|
+
}
|
|
3110
|
+
if (value > 255) {
|
|
3111
|
+
return 255;
|
|
3112
|
+
}
|
|
3113
|
+
return value;
|
|
3114
|
+
}
|
|
3115
|
+
function applyExtendedColor(params17, start) {
|
|
3116
|
+
const mode = params17[start];
|
|
3117
|
+
if (mode === 5) {
|
|
3118
|
+
const index = params17[start + 1];
|
|
3119
|
+
if (index === void 0) {
|
|
3120
|
+
return null;
|
|
3121
|
+
}
|
|
3122
|
+
return {
|
|
3123
|
+
color: { type: "ansi8", index: clampByte(index) },
|
|
3124
|
+
consumed: 2
|
|
3125
|
+
};
|
|
3126
|
+
}
|
|
3127
|
+
if (mode === 2) {
|
|
3128
|
+
const r = params17[start + 1];
|
|
3129
|
+
const g = params17[start + 2];
|
|
3130
|
+
const b = params17[start + 3];
|
|
3131
|
+
if (r === void 0 || g === void 0 || b === void 0) {
|
|
3132
|
+
return null;
|
|
3133
|
+
}
|
|
3134
|
+
return {
|
|
3135
|
+
color: { type: "rgb", r: clampByte(r), g: clampByte(g), b: clampByte(b) },
|
|
3136
|
+
consumed: 4
|
|
3137
|
+
};
|
|
3138
|
+
}
|
|
3139
|
+
return null;
|
|
3140
|
+
}
|
|
3141
|
+
function applySgr(style, paramsText) {
|
|
3142
|
+
const nextStyle = cloneStyle(style);
|
|
3143
|
+
const rawParams = paramsText.length === 0 ? ["0"] : paramsText.split(";");
|
|
3144
|
+
const params17 = [];
|
|
3145
|
+
for (const rawParam of rawParams) {
|
|
3146
|
+
const value = toInteger(rawParam);
|
|
3147
|
+
if (value === null) {
|
|
3148
|
+
return nextStyle;
|
|
3149
|
+
}
|
|
3150
|
+
params17.push(value);
|
|
3151
|
+
}
|
|
3152
|
+
for (let index = 0; index < params17.length; index += 1) {
|
|
3153
|
+
const value = params17[index];
|
|
3154
|
+
if (value === 0) {
|
|
3155
|
+
Object.assign(nextStyle, createDefaultStyle());
|
|
3156
|
+
continue;
|
|
3157
|
+
}
|
|
3158
|
+
if (value === 1) {
|
|
3159
|
+
nextStyle.bold = true;
|
|
3160
|
+
continue;
|
|
3161
|
+
}
|
|
3162
|
+
if (value === 2) {
|
|
3163
|
+
nextStyle.dim = true;
|
|
3164
|
+
continue;
|
|
3165
|
+
}
|
|
3166
|
+
if (value === 22) {
|
|
3167
|
+
nextStyle.bold = false;
|
|
3168
|
+
nextStyle.dim = false;
|
|
3169
|
+
continue;
|
|
3170
|
+
}
|
|
3171
|
+
if (value === 3) {
|
|
3172
|
+
nextStyle.italic = true;
|
|
3173
|
+
continue;
|
|
3174
|
+
}
|
|
3175
|
+
if (value === 23) {
|
|
3176
|
+
nextStyle.italic = false;
|
|
3177
|
+
continue;
|
|
3178
|
+
}
|
|
3179
|
+
if (value === 4) {
|
|
3180
|
+
nextStyle.underline = true;
|
|
3181
|
+
continue;
|
|
3182
|
+
}
|
|
3183
|
+
if (value === 24) {
|
|
3184
|
+
nextStyle.underline = false;
|
|
3185
|
+
continue;
|
|
3186
|
+
}
|
|
3187
|
+
if (value === 9) {
|
|
3188
|
+
nextStyle.strikethrough = true;
|
|
3189
|
+
continue;
|
|
3190
|
+
}
|
|
3191
|
+
if (value === 29) {
|
|
3192
|
+
nextStyle.strikethrough = false;
|
|
3193
|
+
continue;
|
|
3194
|
+
}
|
|
3195
|
+
if (value === 39) {
|
|
3196
|
+
nextStyle.fg = null;
|
|
3197
|
+
continue;
|
|
3198
|
+
}
|
|
3199
|
+
if (value === 49) {
|
|
3200
|
+
nextStyle.bg = null;
|
|
3201
|
+
continue;
|
|
3202
|
+
}
|
|
3203
|
+
if (value >= 30 && value <= 37) {
|
|
3204
|
+
nextStyle.fg = { type: "ansi4", index: value - 30 };
|
|
3205
|
+
continue;
|
|
3206
|
+
}
|
|
3207
|
+
if (value >= 90 && value <= 97) {
|
|
3208
|
+
nextStyle.fg = { type: "ansi4", index: value - 90 + 8 };
|
|
3209
|
+
continue;
|
|
3210
|
+
}
|
|
3211
|
+
if (value >= 40 && value <= 47) {
|
|
3212
|
+
nextStyle.bg = { type: "ansi4", index: value - 40 };
|
|
3213
|
+
continue;
|
|
3214
|
+
}
|
|
3215
|
+
if (value >= 100 && value <= 107) {
|
|
3216
|
+
nextStyle.bg = { type: "ansi4", index: value - 100 + 8 };
|
|
3217
|
+
continue;
|
|
3218
|
+
}
|
|
3219
|
+
if (value === 38 || value === 48) {
|
|
3220
|
+
const extended = applyExtendedColor(params17, index + 1);
|
|
3221
|
+
if (!extended) {
|
|
3222
|
+
continue;
|
|
3223
|
+
}
|
|
3224
|
+
if (value === 38) {
|
|
3225
|
+
nextStyle.fg = extended.color;
|
|
3226
|
+
} else {
|
|
3227
|
+
nextStyle.bg = extended.color;
|
|
3228
|
+
}
|
|
3229
|
+
index += extended.consumed;
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
return nextStyle;
|
|
3233
|
+
}
|
|
3234
|
+
function parseAnsi(input) {
|
|
3235
|
+
const runs = [];
|
|
3236
|
+
let style = createDefaultStyle();
|
|
3237
|
+
let textStart = 0;
|
|
3238
|
+
let index = 0;
|
|
3239
|
+
while (index < input.length) {
|
|
3240
|
+
const char = input[index];
|
|
3241
|
+
if (char === "\n") {
|
|
3242
|
+
pushRun(runs, style, input.slice(textStart, index));
|
|
3243
|
+
pushRun(runs, style, "\n");
|
|
3244
|
+
index += 1;
|
|
3245
|
+
textStart = index;
|
|
3246
|
+
continue;
|
|
3247
|
+
}
|
|
3248
|
+
if (char === ESC2 && input[index + 1] === "[") {
|
|
3249
|
+
pushRun(runs, style, input.slice(textStart, index));
|
|
3250
|
+
const sequence = parseCsi(input, index);
|
|
3251
|
+
if (sequence.final === "m") {
|
|
3252
|
+
style = applySgr(style, sequence.params);
|
|
3253
|
+
}
|
|
3254
|
+
index = sequence.end;
|
|
3255
|
+
textStart = index;
|
|
3256
|
+
continue;
|
|
3257
|
+
}
|
|
3258
|
+
index += 1;
|
|
3259
|
+
}
|
|
3260
|
+
pushRun(runs, style, input.slice(textStart));
|
|
3261
|
+
return runs;
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
// ../terminal-png/src/png-renderer.ts
|
|
3265
|
+
import { Resvg } from "@resvg/resvg-js";
|
|
3266
|
+
|
|
3267
|
+
// ../terminal-png/src/font.ts
|
|
3268
|
+
import { readFileSync } from "node:fs";
|
|
3269
|
+
import { createRequire } from "node:module";
|
|
3270
|
+
import { dirname, join } from "node:path";
|
|
3271
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3272
|
+
var require2 = createRequire(import.meta.url);
|
|
3273
|
+
var fontPackageRoot = dirname(require2.resolve("jetbrains-mono/package.json"));
|
|
3274
|
+
var webfontRoot = join(fontPackageRoot, "fonts/webfonts");
|
|
3275
|
+
function readWebfontBase64(filename) {
|
|
3276
|
+
return readFileSync(join(webfontRoot, filename)).toString("base64");
|
|
3277
|
+
}
|
|
3278
|
+
function resolveAssetPath(filename) {
|
|
3279
|
+
return fileURLToPath2(new URL(`../assets/${filename}`, import.meta.url));
|
|
3280
|
+
}
|
|
3281
|
+
function createFontFace(base64, weight, style) {
|
|
3282
|
+
return `@font-face {
|
|
3283
|
+
font-family: 'JetBrains Mono';
|
|
3284
|
+
font-style: ${style};
|
|
3285
|
+
font-weight: ${weight};
|
|
3286
|
+
src: url('data:font/woff2;base64,${base64}') format('woff2');
|
|
3287
|
+
}`;
|
|
3288
|
+
}
|
|
3289
|
+
var JETBRAINS_MONO_BASE64 = readWebfontBase64("JetBrainsMono-Regular.woff2");
|
|
3290
|
+
var JETBRAINS_MONO_FONT_FILES = [
|
|
3291
|
+
resolveAssetPath("jetbrains-mono-400-normal.ttf"),
|
|
3292
|
+
resolveAssetPath("jetbrains-mono-700-normal.ttf"),
|
|
3293
|
+
resolveAssetPath("jetbrains-mono-400-italic.ttf"),
|
|
3294
|
+
resolveAssetPath("jetbrains-mono-700-italic.ttf")
|
|
3295
|
+
];
|
|
3296
|
+
var JETBRAINS_MONO_TTF_PATH = JETBRAINS_MONO_FONT_FILES[0];
|
|
3297
|
+
var FONT_FACE_CSS = [
|
|
3298
|
+
createFontFace(JETBRAINS_MONO_BASE64, 400, "normal"),
|
|
3299
|
+
createFontFace(readWebfontBase64("JetBrainsMono-Bold.woff2"), 700, "normal"),
|
|
3300
|
+
createFontFace(readWebfontBase64("JetBrainsMono-Italic.woff2"), 400, "italic"),
|
|
3301
|
+
createFontFace(readWebfontBase64("JetBrainsMono-BoldItalic.woff2"), 700, "italic")
|
|
3302
|
+
].join("\n");
|
|
3303
|
+
|
|
3304
|
+
// ../terminal-png/src/png-renderer.ts
|
|
3305
|
+
function renderPng(svg) {
|
|
3306
|
+
const resvg = new Resvg(svg, {
|
|
3307
|
+
font: {
|
|
3308
|
+
defaultFontFamily: "JetBrains Mono",
|
|
3309
|
+
fontFiles: [...JETBRAINS_MONO_FONT_FILES],
|
|
3310
|
+
loadSystemFonts: false,
|
|
3311
|
+
monospaceFamily: "JetBrains Mono"
|
|
3312
|
+
},
|
|
3313
|
+
fitTo: {
|
|
3314
|
+
mode: "zoom",
|
|
3315
|
+
value: 4
|
|
3316
|
+
}
|
|
3317
|
+
});
|
|
3318
|
+
const png = resvg.render();
|
|
3319
|
+
return Buffer.from(png.asPng());
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
// ../terminal-png/src/svg-renderer.ts
|
|
3323
|
+
var ANSI_16_PALETTE = [
|
|
3324
|
+
"#282a2e",
|
|
3325
|
+
"#D74E6F",
|
|
3326
|
+
"#31BB71",
|
|
3327
|
+
"#D3E561",
|
|
3328
|
+
"#8056FF",
|
|
3329
|
+
"#ED61D7",
|
|
3330
|
+
"#04D7D7",
|
|
3331
|
+
"#C5C8C6",
|
|
3332
|
+
"#4B4B4B",
|
|
3333
|
+
"#FE5F86",
|
|
3334
|
+
"#00D787",
|
|
3335
|
+
"#EBFF71",
|
|
3336
|
+
"#8F69FF",
|
|
3337
|
+
"#FF7AEA",
|
|
3338
|
+
"#00FEFE",
|
|
3339
|
+
"#FFFFFF"
|
|
3340
|
+
];
|
|
3341
|
+
var XTERM_256_PALETTE = [
|
|
3342
|
+
"#000000",
|
|
3343
|
+
"#800000",
|
|
3344
|
+
"#008000",
|
|
3345
|
+
"#808000",
|
|
3346
|
+
"#000080",
|
|
3347
|
+
"#800080",
|
|
3348
|
+
"#008080",
|
|
3349
|
+
"#c0c0c0",
|
|
3350
|
+
"#808080",
|
|
3351
|
+
"#ff0000",
|
|
3352
|
+
"#00ff00",
|
|
3353
|
+
"#ffff00",
|
|
3354
|
+
"#0000ff",
|
|
3355
|
+
"#ff00ff",
|
|
3356
|
+
"#00ffff",
|
|
3357
|
+
"#ffffff",
|
|
3358
|
+
"#000000",
|
|
3359
|
+
"#00005f",
|
|
3360
|
+
"#000087",
|
|
3361
|
+
"#0000af",
|
|
3362
|
+
"#0000d7",
|
|
3363
|
+
"#0000ff",
|
|
3364
|
+
"#005f00",
|
|
3365
|
+
"#005f5f",
|
|
3366
|
+
"#005f87",
|
|
3367
|
+
"#005faf",
|
|
3368
|
+
"#005fd7",
|
|
3369
|
+
"#005fff",
|
|
3370
|
+
"#008700",
|
|
3371
|
+
"#00875f",
|
|
3372
|
+
"#008787",
|
|
3373
|
+
"#0087af",
|
|
3374
|
+
"#0087d7",
|
|
3375
|
+
"#0087ff",
|
|
3376
|
+
"#00af00",
|
|
3377
|
+
"#00af5f",
|
|
3378
|
+
"#00af87",
|
|
3379
|
+
"#00afaf",
|
|
3380
|
+
"#00afd7",
|
|
3381
|
+
"#00afff",
|
|
3382
|
+
"#00d700",
|
|
3383
|
+
"#00d75f",
|
|
3384
|
+
"#00d787",
|
|
3385
|
+
"#00d7af",
|
|
3386
|
+
"#00d7d7",
|
|
3387
|
+
"#00d7ff",
|
|
3388
|
+
"#00ff00",
|
|
3389
|
+
"#00ff5f",
|
|
3390
|
+
"#00ff87",
|
|
3391
|
+
"#00ffaf",
|
|
3392
|
+
"#00ffd7",
|
|
3393
|
+
"#00ffff",
|
|
3394
|
+
"#5f0000",
|
|
3395
|
+
"#5f005f",
|
|
3396
|
+
"#5f0087",
|
|
3397
|
+
"#5f00af",
|
|
3398
|
+
"#5f00d7",
|
|
3399
|
+
"#5f00ff",
|
|
3400
|
+
"#5f5f00",
|
|
3401
|
+
"#5f5f5f",
|
|
3402
|
+
"#5f5f87",
|
|
3403
|
+
"#5f5faf",
|
|
3404
|
+
"#5f5fd7",
|
|
3405
|
+
"#5f5fff",
|
|
3406
|
+
"#5f8700",
|
|
3407
|
+
"#5f875f",
|
|
3408
|
+
"#5f8787",
|
|
3409
|
+
"#5f87af",
|
|
3410
|
+
"#5f87d7",
|
|
3411
|
+
"#5f87ff",
|
|
3412
|
+
"#5faf00",
|
|
3413
|
+
"#5faf5f",
|
|
3414
|
+
"#5faf87",
|
|
3415
|
+
"#5fafaf",
|
|
3416
|
+
"#5fafd7",
|
|
3417
|
+
"#5fafff",
|
|
3418
|
+
"#5fd700",
|
|
3419
|
+
"#5fd75f",
|
|
3420
|
+
"#5fd787",
|
|
3421
|
+
"#5fd7af",
|
|
3422
|
+
"#5fd7d7",
|
|
3423
|
+
"#5fd7ff",
|
|
3424
|
+
"#5fff00",
|
|
3425
|
+
"#5fff5f",
|
|
3426
|
+
"#5fff87",
|
|
3427
|
+
"#5fffaf",
|
|
3428
|
+
"#5fffd7",
|
|
3429
|
+
"#5fffff",
|
|
3430
|
+
"#870000",
|
|
3431
|
+
"#87005f",
|
|
3432
|
+
"#870087",
|
|
3433
|
+
"#8700af",
|
|
3434
|
+
"#8700d7",
|
|
3435
|
+
"#8700ff",
|
|
3436
|
+
"#875f00",
|
|
3437
|
+
"#875f5f",
|
|
3438
|
+
"#875f87",
|
|
3439
|
+
"#875faf",
|
|
3440
|
+
"#875fd7",
|
|
3441
|
+
"#875fff",
|
|
3442
|
+
"#878700",
|
|
3443
|
+
"#87875f",
|
|
3444
|
+
"#878787",
|
|
3445
|
+
"#8787af",
|
|
3446
|
+
"#8787d7",
|
|
3447
|
+
"#8787ff",
|
|
3448
|
+
"#87af00",
|
|
3449
|
+
"#87af5f",
|
|
3450
|
+
"#87af87",
|
|
3451
|
+
"#87afaf",
|
|
3452
|
+
"#87afd7",
|
|
3453
|
+
"#87afff",
|
|
3454
|
+
"#87d700",
|
|
3455
|
+
"#87d75f",
|
|
3456
|
+
"#87d787",
|
|
3457
|
+
"#87d7af",
|
|
3458
|
+
"#87d7d7",
|
|
3459
|
+
"#87d7ff",
|
|
3460
|
+
"#87ff00",
|
|
3461
|
+
"#87ff5f",
|
|
3462
|
+
"#87ff87",
|
|
3463
|
+
"#87ffaf",
|
|
3464
|
+
"#87ffd7",
|
|
3465
|
+
"#87ffff",
|
|
3466
|
+
"#af0000",
|
|
3467
|
+
"#af005f",
|
|
3468
|
+
"#af0087",
|
|
3469
|
+
"#af00af",
|
|
3470
|
+
"#af00d7",
|
|
3471
|
+
"#af00ff",
|
|
3472
|
+
"#af5f00",
|
|
3473
|
+
"#af5f5f",
|
|
3474
|
+
"#af5f87",
|
|
3475
|
+
"#af5faf",
|
|
3476
|
+
"#af5fd7",
|
|
3477
|
+
"#af5fff",
|
|
3478
|
+
"#af8700",
|
|
3479
|
+
"#af875f",
|
|
3480
|
+
"#af8787",
|
|
3481
|
+
"#af87af",
|
|
3482
|
+
"#af87d7",
|
|
3483
|
+
"#af87ff",
|
|
3484
|
+
"#afaf00",
|
|
3485
|
+
"#afaf5f",
|
|
3486
|
+
"#afaf87",
|
|
3487
|
+
"#afafaf",
|
|
3488
|
+
"#afafd7",
|
|
3489
|
+
"#afafff",
|
|
3490
|
+
"#afd700",
|
|
3491
|
+
"#afd75f",
|
|
3492
|
+
"#afd787",
|
|
3493
|
+
"#afd7af",
|
|
3494
|
+
"#afd7d7",
|
|
3495
|
+
"#afd7ff",
|
|
3496
|
+
"#afff00",
|
|
3497
|
+
"#afff5f",
|
|
3498
|
+
"#afff87",
|
|
3499
|
+
"#afffaf",
|
|
3500
|
+
"#afffd7",
|
|
3501
|
+
"#afffff",
|
|
3502
|
+
"#d70000",
|
|
3503
|
+
"#d7005f",
|
|
3504
|
+
"#d70087",
|
|
3505
|
+
"#d700af",
|
|
3506
|
+
"#d700d7",
|
|
3507
|
+
"#d700ff",
|
|
3508
|
+
"#d75f00",
|
|
3509
|
+
"#d75f5f",
|
|
3510
|
+
"#d75f87",
|
|
3511
|
+
"#d75faf",
|
|
3512
|
+
"#d75fd7",
|
|
3513
|
+
"#d75fff",
|
|
3514
|
+
"#d78700",
|
|
3515
|
+
"#d7875f",
|
|
3516
|
+
"#d78787",
|
|
3517
|
+
"#d787af",
|
|
3518
|
+
"#d787d7",
|
|
3519
|
+
"#d787ff",
|
|
3520
|
+
"#d7af00",
|
|
3521
|
+
"#d7af5f",
|
|
3522
|
+
"#d7af87",
|
|
3523
|
+
"#d7afaf",
|
|
3524
|
+
"#d7afd7",
|
|
3525
|
+
"#d7afff",
|
|
3526
|
+
"#d7d700",
|
|
3527
|
+
"#d7d75f",
|
|
3528
|
+
"#d7d787",
|
|
3529
|
+
"#d7d7af",
|
|
3530
|
+
"#d7d7d7",
|
|
3531
|
+
"#d7d7ff",
|
|
3532
|
+
"#d7ff00",
|
|
3533
|
+
"#d7ff5f",
|
|
3534
|
+
"#d7ff87",
|
|
3535
|
+
"#d7ffaf",
|
|
3536
|
+
"#d7ffd7",
|
|
3537
|
+
"#d7ffff",
|
|
3538
|
+
"#ff0000",
|
|
3539
|
+
"#ff005f",
|
|
3540
|
+
"#ff0087",
|
|
3541
|
+
"#ff00af",
|
|
3542
|
+
"#ff00d7",
|
|
3543
|
+
"#ff00ff",
|
|
3544
|
+
"#ff5f00",
|
|
3545
|
+
"#ff5f5f",
|
|
3546
|
+
"#ff5f87",
|
|
3547
|
+
"#ff5faf",
|
|
3548
|
+
"#ff5fd7",
|
|
3549
|
+
"#ff5fff",
|
|
3550
|
+
"#ff8700",
|
|
3551
|
+
"#ff875f",
|
|
3552
|
+
"#ff8787",
|
|
3553
|
+
"#ff87af",
|
|
3554
|
+
"#ff87d7",
|
|
3555
|
+
"#ff87ff",
|
|
3556
|
+
"#ffaf00",
|
|
3557
|
+
"#ffaf5f",
|
|
3558
|
+
"#ffaf87",
|
|
3559
|
+
"#ffafaf",
|
|
3560
|
+
"#ffafd7",
|
|
3561
|
+
"#ffafff",
|
|
3562
|
+
"#ffd700",
|
|
3563
|
+
"#ffd75f",
|
|
3564
|
+
"#ffd787",
|
|
3565
|
+
"#ffd7af",
|
|
3566
|
+
"#ffd7d7",
|
|
3567
|
+
"#ffd7ff",
|
|
3568
|
+
"#ffff00",
|
|
3569
|
+
"#ffff5f",
|
|
3570
|
+
"#ffff87",
|
|
3571
|
+
"#ffffaf",
|
|
3572
|
+
"#ffffd7",
|
|
3573
|
+
"#ffffff",
|
|
3574
|
+
"#080808",
|
|
3575
|
+
"#121212",
|
|
3576
|
+
"#1c1c1c",
|
|
3577
|
+
"#262626",
|
|
3578
|
+
"#303030",
|
|
3579
|
+
"#3a3a3a",
|
|
3580
|
+
"#444444",
|
|
3581
|
+
"#4e4e4e",
|
|
3582
|
+
"#585858",
|
|
3583
|
+
"#606060",
|
|
3584
|
+
"#666666",
|
|
3585
|
+
"#767676",
|
|
3586
|
+
"#808080",
|
|
3587
|
+
"#8a8a8a",
|
|
3588
|
+
"#949494",
|
|
3589
|
+
"#9e9e9e",
|
|
3590
|
+
"#a8a8a8",
|
|
3591
|
+
"#b2b2b2",
|
|
3592
|
+
"#bcbcbc",
|
|
3593
|
+
"#c6c6c6",
|
|
3594
|
+
"#d0d0d0",
|
|
3595
|
+
"#dadada",
|
|
3596
|
+
"#e4e4e4",
|
|
3597
|
+
"#eeeeee"
|
|
3598
|
+
];
|
|
3599
|
+
var BACKGROUND = "#171717";
|
|
3600
|
+
var DEFAULT_FOREGROUND = "#c4c4c4";
|
|
3601
|
+
var FONT_FAMILY = "JetBrains Mono";
|
|
3602
|
+
var FONT_SIZE = 14;
|
|
3603
|
+
var LINE_HEIGHT = 1.2;
|
|
3604
|
+
var CHARACTER_WIDTH = 8.412666666666667;
|
|
3605
|
+
var TEXT_BOTTOM_PADDING = 11.6;
|
|
3606
|
+
var DEFAULT_PADDING = {
|
|
3607
|
+
top: 20,
|
|
3608
|
+
right: 40,
|
|
3609
|
+
bottom: 20,
|
|
3610
|
+
left: 20
|
|
3611
|
+
};
|
|
3612
|
+
var WINDOW_BAR_HEIGHT = 15;
|
|
3613
|
+
function renderSvg(runs, options = {}) {
|
|
3614
|
+
const padding = options.padding === void 0 ? DEFAULT_PADDING : {
|
|
3615
|
+
top: options.padding,
|
|
3616
|
+
right: options.padding,
|
|
3617
|
+
bottom: options.padding,
|
|
3618
|
+
left: options.padding
|
|
3619
|
+
};
|
|
3620
|
+
const showWindow = options.window ?? true;
|
|
3621
|
+
const titleBarHeight = showWindow ? WINDOW_BAR_HEIGHT : 0;
|
|
3622
|
+
const textStartX = padding.left;
|
|
3623
|
+
const lineHeightPx = FONT_SIZE * LINE_HEIGHT;
|
|
3624
|
+
const textStartY = titleBarHeight + padding.top + lineHeightPx;
|
|
3625
|
+
const lines = splitIntoLines(runs);
|
|
3626
|
+
const contentWidth = measureLines(lines);
|
|
3627
|
+
const width = padding.left + contentWidth + padding.right;
|
|
3628
|
+
const height = titleBarHeight + padding.top + lines.length * lineHeightPx + padding.bottom + TEXT_BOTTOM_PADDING;
|
|
3629
|
+
const svgWidth = formatNumber(width);
|
|
3630
|
+
const svgHeight = formatNumber(height);
|
|
3631
|
+
const viewBox = `0 0 ${svgWidth} ${svgHeight}`;
|
|
3632
|
+
const textElements = renderLines(lines, textStartX, textStartY, lineHeightPx);
|
|
3633
|
+
return [
|
|
3634
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${svgWidth}" height="${svgHeight}" viewBox="${viewBox}">`,
|
|
3635
|
+
"<defs>",
|
|
3636
|
+
"<style><![CDATA[",
|
|
3637
|
+
FONT_FACE_CSS,
|
|
3638
|
+
"]]></style>",
|
|
3639
|
+
"</defs>",
|
|
3640
|
+
`<rect x="0" y="0" width="${svgWidth}" height="${svgHeight}" fill="${BACKGROUND}" />`,
|
|
3641
|
+
showWindow ? renderWindowControls() : "",
|
|
3642
|
+
`<g font-family="${FONT_FAMILY}" font-size="${formatNumber(FONT_SIZE)}px" fill="${DEFAULT_FOREGROUND}">`,
|
|
3643
|
+
textElements,
|
|
3644
|
+
"</g>",
|
|
3645
|
+
"</svg>"
|
|
3646
|
+
].join("");
|
|
3647
|
+
}
|
|
3648
|
+
function splitIntoLines(runs) {
|
|
3649
|
+
const lines = [[]];
|
|
3650
|
+
for (const run of runs) {
|
|
3651
|
+
if (run.text === "\n") {
|
|
3652
|
+
lines.push([]);
|
|
3653
|
+
continue;
|
|
3654
|
+
}
|
|
3655
|
+
lines[lines.length - 1]?.push(run);
|
|
3656
|
+
}
|
|
3657
|
+
return lines;
|
|
3658
|
+
}
|
|
3659
|
+
function measureLines(lines) {
|
|
3660
|
+
return Math.max(
|
|
3661
|
+
...lines.map((line) => line.reduce((width, run) => width + displayWidth(run.text) * CHARACTER_WIDTH, 0)),
|
|
3662
|
+
0
|
|
3663
|
+
);
|
|
3664
|
+
}
|
|
3665
|
+
function displayWidth(text) {
|
|
3666
|
+
const segmenter = new Intl.Segmenter();
|
|
3667
|
+
let width = 0;
|
|
3668
|
+
for (const { segment } of segmenter.segment(text)) {
|
|
3669
|
+
const cp = segment.codePointAt(0);
|
|
3670
|
+
width += cp !== void 0 && isWideCodePoint(cp) ? 2 : 1;
|
|
3671
|
+
}
|
|
3672
|
+
return width;
|
|
3673
|
+
}
|
|
3674
|
+
function isWideCodePoint(cp) {
|
|
3675
|
+
return cp >= 4352 && cp <= 4447 || // Hangul Jamo
|
|
3676
|
+
cp === 9001 || cp === 9002 || // Angle brackets
|
|
3677
|
+
cp >= 11904 && cp <= 12350 || // CJK Radicals, Kangxi
|
|
3678
|
+
cp >= 12353 && cp <= 13247 || // Hiragana, Katakana, CJK symbols
|
|
3679
|
+
cp >= 13312 && cp <= 19903 || // CJK Extension A
|
|
3680
|
+
cp >= 19968 && cp <= 42191 || // CJK Unified Ideographs
|
|
3681
|
+
cp >= 43360 && cp <= 43391 || // Hangul Jamo Extended-A
|
|
3682
|
+
cp >= 44032 && cp <= 55215 || // Hangul Syllables
|
|
3683
|
+
cp >= 63744 && cp <= 64255 || // CJK Compatibility Ideographs
|
|
3684
|
+
cp >= 65040 && cp <= 65049 || // Vertical Forms
|
|
3685
|
+
cp >= 65072 && cp <= 65135 || // CJK Compatibility Forms
|
|
3686
|
+
cp >= 65280 && cp <= 65376 || // Fullwidth Latin, Halfwidth Katakana
|
|
3687
|
+
cp >= 65504 && cp <= 65510 || // Fullwidth Signs
|
|
3688
|
+
cp >= 110592 && cp <= 110847 || // Kana Supplement
|
|
3689
|
+
cp >= 126980 && cp <= 126980 || // Mahjong tile
|
|
3690
|
+
cp >= 127183 && cp <= 127183 || // Playing card black joker
|
|
3691
|
+
cp >= 127488 && cp <= 131069 || // Enclosed CJK + Emoji
|
|
3692
|
+
cp >= 131072 && cp <= 196605 || // CJK Extension B–F
|
|
3693
|
+
cp >= 196608 && cp <= 262141;
|
|
3694
|
+
}
|
|
3695
|
+
function renderLines(lines, textStartX, textStartY, lineHeightPx) {
|
|
3696
|
+
return lines.map((line, index) => {
|
|
3697
|
+
const y = formatNumber(textStartY + index * lineHeightPx);
|
|
3698
|
+
if (line.length === 0) {
|
|
3699
|
+
return `<text x="${formatNumber(textStartX)}" y="${y}" xml:space="preserve"/>`;
|
|
3700
|
+
}
|
|
3701
|
+
return [
|
|
3702
|
+
`<text x="${formatNumber(textStartX)}" y="${y}" xml:space="preserve">`,
|
|
3703
|
+
line.map(renderRun).join(""),
|
|
3704
|
+
"</text>"
|
|
3705
|
+
].join("");
|
|
3706
|
+
}).join("");
|
|
3707
|
+
}
|
|
3708
|
+
function renderRun(run) {
|
|
3709
|
+
const attributes = ['xml:space="preserve"'];
|
|
3710
|
+
const color = resolveColor(run.fg);
|
|
3711
|
+
const textDecorations = [];
|
|
3712
|
+
if (color !== DEFAULT_FOREGROUND) {
|
|
3713
|
+
attributes.push(`fill="${escapeXmlAttribute(color)}"`);
|
|
3714
|
+
}
|
|
3715
|
+
if (run.bold) {
|
|
3716
|
+
attributes.push('font-weight="bold"');
|
|
3717
|
+
}
|
|
3718
|
+
if (run.italic) {
|
|
3719
|
+
attributes.push('font-style="italic"');
|
|
3720
|
+
}
|
|
3721
|
+
if (run.underline) {
|
|
3722
|
+
textDecorations.push("underline");
|
|
3723
|
+
}
|
|
3724
|
+
if (run.strikethrough) {
|
|
3725
|
+
textDecorations.push("line-through");
|
|
3726
|
+
}
|
|
3727
|
+
if (textDecorations.length > 0) {
|
|
3728
|
+
attributes.push(`text-decoration="${textDecorations.join(" ")}"`);
|
|
3729
|
+
}
|
|
3730
|
+
if (run.dim) {
|
|
3731
|
+
attributes.push('opacity="0.7"');
|
|
3732
|
+
}
|
|
3733
|
+
return `<tspan ${attributes.join(" ")}>${escapeXmlText(run.text)}</tspan>`;
|
|
3734
|
+
}
|
|
3735
|
+
function renderWindowControls() {
|
|
3736
|
+
return [
|
|
3737
|
+
'<circle cx="13.5" cy="12" r="5.5" fill="#FF5A54" />',
|
|
3738
|
+
'<circle cx="32.5" cy="12" r="5.5" fill="#E6BF29" />',
|
|
3739
|
+
'<circle cx="51.5" cy="12" r="5.5" fill="#52C12B" />'
|
|
3740
|
+
].join("");
|
|
3741
|
+
}
|
|
3742
|
+
function resolveColor(color) {
|
|
3743
|
+
if (color === null) {
|
|
3744
|
+
return DEFAULT_FOREGROUND;
|
|
3745
|
+
}
|
|
3746
|
+
if (color.type === "ansi4") {
|
|
3747
|
+
return ANSI_16_PALETTE[color.index] ?? DEFAULT_FOREGROUND;
|
|
3748
|
+
}
|
|
3749
|
+
if (color.type === "ansi8") {
|
|
3750
|
+
if (color.index < ANSI_16_PALETTE.length) {
|
|
3751
|
+
return ANSI_16_PALETTE[color.index] ?? DEFAULT_FOREGROUND;
|
|
3752
|
+
}
|
|
3753
|
+
return XTERM_256_PALETTE[color.index] ?? DEFAULT_FOREGROUND;
|
|
3754
|
+
}
|
|
3755
|
+
return `rgb(${color.r},${color.g},${color.b})`;
|
|
3756
|
+
}
|
|
3757
|
+
function escapeXmlText(value) {
|
|
3758
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
3759
|
+
}
|
|
3760
|
+
function escapeXmlAttribute(value) {
|
|
3761
|
+
return escapeXmlText(value).replaceAll('"', """).replaceAll("'", "'");
|
|
3762
|
+
}
|
|
3763
|
+
function formatNumber(value) {
|
|
3764
|
+
return value.toFixed(2);
|
|
3765
|
+
}
|
|
3766
|
+
|
|
3767
|
+
// ../terminal-png/src/index.ts
|
|
3768
|
+
async function renderTerminalPng(ansiText, options = {}) {
|
|
3769
|
+
const runs = parseAnsi(ansiText);
|
|
3770
|
+
const svg = renderSvg(runs, {
|
|
3771
|
+
padding: options.padding,
|
|
3772
|
+
window: options.window
|
|
3773
|
+
});
|
|
3774
|
+
const png = renderPng(svg);
|
|
3775
|
+
if (options.output) {
|
|
3776
|
+
await writeFile(options.output, png);
|
|
3777
|
+
}
|
|
3778
|
+
return png;
|
|
3779
|
+
}
|
|
3780
|
+
|
|
3781
|
+
// src/commands/screenshot.ts
|
|
3782
|
+
var params11 = S.Object({
|
|
3783
|
+
session: S.Optional(S.String({ short: "s", description: "Session name" })),
|
|
3784
|
+
output: S.String({ short: "o", description: "Path to the output PNG file" }),
|
|
3785
|
+
window: S.Optional(S.Boolean({ description: "Include terminal window chrome", default: true })),
|
|
3786
|
+
padding: S.Optional(S.Number({ short: "p", description: "Padding around terminal content" }))
|
|
3787
|
+
});
|
|
3788
|
+
var screenshot = defineCommand({
|
|
3789
|
+
name: "screenshot",
|
|
3790
|
+
description: "Capture the current terminal screen as a PNG image",
|
|
3791
|
+
scope: ["cli"],
|
|
3792
|
+
params: params11,
|
|
3793
|
+
handler: async ({ params: params17, env, terminalPilotRuntime }) => {
|
|
3794
|
+
const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
|
|
3795
|
+
const screen = await namedSession.session.screen();
|
|
3796
|
+
await renderTerminalPng(screen.rawLines.join("\n"), {
|
|
3797
|
+
output: params17.output,
|
|
3798
|
+
window: params17.window,
|
|
3799
|
+
padding: params17.padding
|
|
3800
|
+
});
|
|
3801
|
+
return void 0;
|
|
3802
|
+
}
|
|
3803
|
+
});
|
|
3804
|
+
|
|
3805
|
+
// src/commands/send-signal.ts
|
|
3806
|
+
var params12 = S.Object({
|
|
3807
|
+
signal: S.String({ description: "Signal to send to the session process" }),
|
|
3808
|
+
session: S.Optional(S.String({ short: "s", description: "Session name" }))
|
|
3809
|
+
});
|
|
3810
|
+
var sendSignal = defineCommand({
|
|
3811
|
+
name: "send-signal",
|
|
3812
|
+
description: "Send a process signal to an active terminal session",
|
|
3813
|
+
scope: ["cli", "mcp", "sdk"],
|
|
3814
|
+
positional: ["signal"],
|
|
3815
|
+
params: params12,
|
|
3816
|
+
handler: async ({ params: params17, env, terminalPilotRuntime }) => {
|
|
3817
|
+
const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
|
|
3818
|
+
await namedSession.session.signal(params17.signal);
|
|
3819
|
+
return void 0;
|
|
3820
|
+
}
|
|
3821
|
+
});
|
|
3822
|
+
|
|
3823
|
+
// src/commands/type.ts
|
|
3824
|
+
var params13 = S.Object({
|
|
3825
|
+
text: S.String({ description: "Text to write to the session" }),
|
|
3826
|
+
session: S.Optional(S.String({ short: "s", description: "Session name" }))
|
|
3827
|
+
});
|
|
3828
|
+
var type = defineCommand({
|
|
3829
|
+
name: "type",
|
|
3830
|
+
description: "Write text to an active terminal session character-by-character with delay",
|
|
3831
|
+
scope: ["cli", "mcp", "sdk"],
|
|
3832
|
+
positional: ["text"],
|
|
3833
|
+
params: params13,
|
|
3834
|
+
handler: async ({ params: params17, env, terminalPilotRuntime }) => {
|
|
3835
|
+
const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
|
|
3836
|
+
await namedSession.session.type(params17.text);
|
|
3837
|
+
return void 0;
|
|
3838
|
+
}
|
|
3839
|
+
});
|
|
3840
|
+
|
|
3841
|
+
// src/commands/uninstall.ts
|
|
3842
|
+
var params14 = S.Object({
|
|
3843
|
+
agent: S.Enum(installableAgents, {
|
|
3844
|
+
description: "Agent to uninstall terminal-pilot from",
|
|
3845
|
+
default: DEFAULT_INSTALL_AGENT
|
|
3846
|
+
})
|
|
3847
|
+
});
|
|
3848
|
+
var uninstall = defineCommand({
|
|
3849
|
+
name: "uninstall",
|
|
3850
|
+
description: "Remove the terminal-pilot CLI skill.",
|
|
3851
|
+
scope: ["cli"],
|
|
3852
|
+
positional: ["agent"],
|
|
3853
|
+
params: params14,
|
|
3854
|
+
handler: async ({ params: params17, terminalPilotInstaller }) => {
|
|
3855
|
+
const services = resolveInstallerServices(terminalPilotInstaller);
|
|
3856
|
+
const agent = resolveInstallableAgent(params17.agent);
|
|
3857
|
+
const localSkill = getSkillFolderWithHome(
|
|
3858
|
+
agent,
|
|
3859
|
+
"local",
|
|
3860
|
+
services.cwd,
|
|
3861
|
+
services.homeDir
|
|
3862
|
+
);
|
|
3863
|
+
const globalSkill = getSkillFolderWithHome(
|
|
3864
|
+
agent,
|
|
3865
|
+
"global",
|
|
3866
|
+
services.cwd,
|
|
3867
|
+
services.homeDir
|
|
3868
|
+
);
|
|
3869
|
+
const removedSkillPaths = [];
|
|
3870
|
+
if (await removeSkillFolder(services.fs, localSkill.fullPath)) {
|
|
3871
|
+
removedSkillPaths.push(localSkill.displayPath);
|
|
3872
|
+
}
|
|
3873
|
+
if (await removeSkillFolder(services.fs, globalSkill.fullPath)) {
|
|
3874
|
+
removedSkillPaths.push(globalSkill.displayPath);
|
|
3875
|
+
}
|
|
3876
|
+
return {
|
|
3877
|
+
agent,
|
|
3878
|
+
removedSkillPaths
|
|
3879
|
+
};
|
|
3880
|
+
}
|
|
3881
|
+
});
|
|
3882
|
+
|
|
3883
|
+
// src/commands/wait-for.ts
|
|
3884
|
+
var params15 = S.Object({
|
|
3885
|
+
pattern: S.String({ description: "Regular expression pattern to wait for" }),
|
|
3886
|
+
session: S.Optional(S.String({ short: "s", description: "Session name" })),
|
|
3887
|
+
timeout: S.Optional(S.Number({ short: "t", description: "Maximum wait time in milliseconds" })),
|
|
3888
|
+
literal: S.Optional(
|
|
3889
|
+
S.Boolean({
|
|
3890
|
+
short: "l",
|
|
3891
|
+
description: "When true, treat pattern as a literal string instead of a regex"
|
|
3892
|
+
})
|
|
3893
|
+
)
|
|
3894
|
+
});
|
|
3895
|
+
var waitFor = defineCommand({
|
|
3896
|
+
name: "wait-for",
|
|
3897
|
+
description: "Wait for terminal output to match a pattern",
|
|
3898
|
+
scope: ["cli", "mcp", "sdk"],
|
|
3899
|
+
positional: ["pattern"],
|
|
3900
|
+
params: params15,
|
|
3901
|
+
handler: async ({ params: params17, env, terminalPilotRuntime }) => {
|
|
3902
|
+
const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
|
|
3903
|
+
const pattern = params17.literal === true ? params17.pattern : new RegExp(params17.pattern);
|
|
3904
|
+
const line = params17.timeout === void 0 ? await namedSession.session.waitFor(pattern) : await namedSession.session.waitFor(pattern, { timeout: params17.timeout });
|
|
3905
|
+
return { matched: true, line };
|
|
3906
|
+
}
|
|
3907
|
+
});
|
|
3908
|
+
|
|
3909
|
+
// src/commands/wait-for-exit.ts
|
|
3910
|
+
var params16 = S.Object({
|
|
3911
|
+
session: S.Optional(S.String({ short: "s", description: "Session name" })),
|
|
3912
|
+
timeout: S.Optional(S.Number({ short: "t", description: "Maximum wait time in milliseconds" }))
|
|
3913
|
+
});
|
|
3914
|
+
var waitForExit2 = defineCommand({
|
|
3915
|
+
name: "wait-for-exit",
|
|
3916
|
+
description: "Wait for a terminal session process to finish",
|
|
3917
|
+
scope: ["cli", "mcp", "sdk"],
|
|
3918
|
+
params: params16,
|
|
3919
|
+
handler: async ({ params: params17, env, terminalPilotRuntime }) => {
|
|
3920
|
+
const namedSession = await getTerminalPilotRuntime(terminalPilotRuntime).resolveSession(params17.session, env);
|
|
3921
|
+
const exitCode = await namedSession.session.waitForExit(
|
|
3922
|
+
params17.timeout === void 0 ? void 0 : { timeout: params17.timeout }
|
|
3923
|
+
);
|
|
3924
|
+
return { exitCode };
|
|
3925
|
+
}
|
|
3926
|
+
});
|
|
3927
|
+
|
|
3928
|
+
// src/commands/index.ts
|
|
3929
|
+
var children = [
|
|
3930
|
+
install,
|
|
3931
|
+
uninstall,
|
|
3932
|
+
createSession,
|
|
3933
|
+
fill,
|
|
3934
|
+
type,
|
|
3935
|
+
pressKey,
|
|
3936
|
+
sendSignal,
|
|
3937
|
+
waitFor,
|
|
3938
|
+
waitForExit2,
|
|
3939
|
+
readScreen,
|
|
3940
|
+
screenshot,
|
|
3941
|
+
readHistory,
|
|
3942
|
+
resize,
|
|
3943
|
+
closeSession,
|
|
3944
|
+
getSession,
|
|
3945
|
+
listSessions
|
|
3946
|
+
];
|
|
3947
|
+
var terminalPilotGroup = defineGroup({
|
|
3948
|
+
name: "terminal-pilot",
|
|
3949
|
+
scope: ["cli", "mcp", "sdk"],
|
|
3950
|
+
children
|
|
3951
|
+
});
|
|
3952
|
+
export {
|
|
3953
|
+
SESSION_ENV_VAR,
|
|
3954
|
+
closeSession,
|
|
3955
|
+
createSession,
|
|
3956
|
+
createTerminalPilotRuntime,
|
|
3957
|
+
fill,
|
|
3958
|
+
getSession,
|
|
3959
|
+
install,
|
|
3960
|
+
listSessions,
|
|
3961
|
+
pressKey,
|
|
3962
|
+
readHistory,
|
|
3963
|
+
readScreen,
|
|
3964
|
+
resize,
|
|
3965
|
+
screenshot,
|
|
3966
|
+
sendSignal,
|
|
3967
|
+
terminalPilotGroup,
|
|
3968
|
+
type,
|
|
3969
|
+
uninstall,
|
|
3970
|
+
waitFor,
|
|
3971
|
+
waitForExit2 as waitForExit
|
|
3972
|
+
};
|
|
3973
|
+
//# sourceMappingURL=index.js.map
|