killeros 2.0.3 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/Killeros.ts +4 -2
- package/README.md +9 -7
- package/killeros/commands.ts +2 -8
- package/killeros/concise.ts +12 -8
- package/killeros/goals.ts +0 -2
- package/killeros/hooks.ts +49 -17
- package/killeros/init-evidence.ts +240 -0
- package/killeros/init-target.ts +289 -0
- package/killeros/init.ts +139 -356
- package/killeros/runtime.ts +18 -6
- package/killeros/shell-ui.ts +17 -141
- package/package.json +1 -1
package/killeros/shell-ui.ts
CHANGED
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
} from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import {
|
|
13
13
|
Container,
|
|
14
|
-
CURSOR_MARKER,
|
|
15
14
|
Text,
|
|
16
15
|
truncateToWidth,
|
|
17
16
|
visibleWidth,
|
|
@@ -19,7 +18,6 @@ import {
|
|
|
19
18
|
type EditorTheme,
|
|
20
19
|
type TUI,
|
|
21
20
|
} from "@earendil-works/pi-tui";
|
|
22
|
-
import { availableCommandNames } from "./commands.ts";
|
|
23
21
|
import { formatCwd, padRight } from "./display.ts";
|
|
24
22
|
import { reportError } from "./errors.ts";
|
|
25
23
|
import { formatModel } from "./footer.ts";
|
|
@@ -151,113 +149,6 @@ class PiStartupHeader {
|
|
|
151
149
|
}
|
|
152
150
|
|
|
153
151
|
const ANSI_REGEX = /\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
|
|
154
|
-
const ANSI_SEQUENCE_AT_START = /^\x1b\[[0-?]*[ -/]*[@-~]/u;
|
|
155
|
-
const COMMAND_TOKEN_PATTERN = /(^|[ \t])(\/[A-Za-z0-9:_-]*)/gu;
|
|
156
|
-
|
|
157
|
-
function controlSequenceAt(text: string, index: number): string | undefined {
|
|
158
|
-
if (text.startsWith(CURSOR_MARKER, index)) return CURSOR_MARKER;
|
|
159
|
-
return text.slice(index).match(ANSI_SEQUENCE_AT_START)?.[0];
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
interface CommandToken {
|
|
163
|
-
text: string;
|
|
164
|
-
start: number;
|
|
165
|
-
end: number;
|
|
166
|
-
valid: boolean;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
interface EditorVisualLine {
|
|
170
|
-
logicalLine: number;
|
|
171
|
-
startCol: number;
|
|
172
|
-
length: number;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function commandTokens(text: string, normalizedNames: readonly string[]): CommandToken[] {
|
|
176
|
-
return [...text.matchAll(COMMAND_TOKEN_PATTERN)].map((match) => {
|
|
177
|
-
const token = match[2] ?? "";
|
|
178
|
-
const prefix = token.slice(1).toLocaleLowerCase();
|
|
179
|
-
const start = (match.index ?? 0) + (match[1]?.length ?? 0);
|
|
180
|
-
return {
|
|
181
|
-
text: token,
|
|
182
|
-
start,
|
|
183
|
-
end: start + token.length,
|
|
184
|
-
valid: normalizedNames.some((name) => name.startsWith(prefix)),
|
|
185
|
-
};
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function highlightTextRanges(
|
|
190
|
-
text: string,
|
|
191
|
-
ranges: Array<{ start: number; end: number }>,
|
|
192
|
-
color: (value: string) => string,
|
|
193
|
-
): string {
|
|
194
|
-
if (ranges.length === 0) return text;
|
|
195
|
-
|
|
196
|
-
let output = "";
|
|
197
|
-
let buffer = "";
|
|
198
|
-
let bufferHighlighted: boolean | undefined;
|
|
199
|
-
let plainIndex = 0;
|
|
200
|
-
const flush = (): void => {
|
|
201
|
-
if (!buffer) return;
|
|
202
|
-
output += bufferHighlighted ? color(buffer) : buffer;
|
|
203
|
-
buffer = "";
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
for (let index = 0; index < text.length;) {
|
|
207
|
-
const control = controlSequenceAt(text, index);
|
|
208
|
-
if (control) {
|
|
209
|
-
flush();
|
|
210
|
-
output += control;
|
|
211
|
-
index += control.length;
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const highlighted = ranges.some((range) => plainIndex >= range.start && plainIndex < range.end);
|
|
216
|
-
if (bufferHighlighted !== highlighted) {
|
|
217
|
-
flush();
|
|
218
|
-
bufferHighlighted = highlighted;
|
|
219
|
-
}
|
|
220
|
-
buffer += text[index];
|
|
221
|
-
plainIndex += 1;
|
|
222
|
-
index += 1;
|
|
223
|
-
}
|
|
224
|
-
flush();
|
|
225
|
-
return output;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function highlightEditorLines(
|
|
229
|
-
lines: string[],
|
|
230
|
-
sourceLines: string[],
|
|
231
|
-
visualLines: EditorVisualLine[],
|
|
232
|
-
scrollOffset: number,
|
|
233
|
-
commandNames: ReadonlySet<string>,
|
|
234
|
-
color: (value: string) => string,
|
|
235
|
-
): { lines: string[]; bottomBorderIndex: number } {
|
|
236
|
-
let bottomBorderIndex = -1;
|
|
237
|
-
for (let index = lines.length - 1; index >= 1; index -= 1) {
|
|
238
|
-
if (isBorderLine(lines[index] ?? "")) {
|
|
239
|
-
bottomBorderIndex = index;
|
|
240
|
-
break;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
if (bottomBorderIndex < 0) bottomBorderIndex = lines.length - 1;
|
|
244
|
-
|
|
245
|
-
const normalizedNames = [...commandNames].map((name) => name.toLocaleLowerCase());
|
|
246
|
-
for (let index = 1; index < bottomBorderIndex; index += 1) {
|
|
247
|
-
const visualLine = visualLines[scrollOffset + index - 1];
|
|
248
|
-
if (!visualLine) continue;
|
|
249
|
-
const visibleStart = visualLine.startCol;
|
|
250
|
-
const visibleEnd = visibleStart + visualLine.length;
|
|
251
|
-
const ranges = commandTokens(sourceLines[visualLine.logicalLine] ?? "", normalizedNames)
|
|
252
|
-
.filter((token) => token.valid && token.start < visibleEnd && token.end > visibleStart)
|
|
253
|
-
.map((token) => ({
|
|
254
|
-
start: Math.max(token.start, visibleStart) - visibleStart,
|
|
255
|
-
end: Math.min(token.end, visibleEnd) - visibleStart,
|
|
256
|
-
}));
|
|
257
|
-
lines[index] = highlightTextRanges(lines[index] ?? "", ranges, color);
|
|
258
|
-
}
|
|
259
|
-
return { lines, bottomBorderIndex };
|
|
260
|
-
}
|
|
261
152
|
|
|
262
153
|
function stripAnsi(text: string): string {
|
|
263
154
|
return text.replace(ANSI_REGEX, "").trim();
|
|
@@ -276,19 +167,16 @@ function isScrolledTopBorder(line: string): boolean {
|
|
|
276
167
|
class PiCodeEditor extends CustomEditor {
|
|
277
168
|
private readonly appKeybindings: KeybindingsManager;
|
|
278
169
|
private readonly runtimeTheme: Theme;
|
|
279
|
-
private readonly getCommandNames: () => ReadonlySet<string>;
|
|
280
170
|
|
|
281
171
|
constructor(
|
|
282
172
|
tui: TUI,
|
|
283
173
|
theme: EditorTheme,
|
|
284
174
|
appKeybindings: KeybindingsManager,
|
|
285
175
|
runtimeTheme: Theme,
|
|
286
|
-
getCommandNames: () => ReadonlySet<string>,
|
|
287
176
|
) {
|
|
288
177
|
super(tui, theme, appKeybindings);
|
|
289
178
|
this.appKeybindings = appKeybindings;
|
|
290
179
|
this.runtimeTheme = runtimeTheme;
|
|
291
|
-
this.getCommandNames = getCommandNames;
|
|
292
180
|
}
|
|
293
181
|
|
|
294
182
|
override handleInput(data: string): void {
|
|
@@ -305,37 +193,19 @@ class PiCodeEditor extends CustomEditor {
|
|
|
305
193
|
super.handleInput(data);
|
|
306
194
|
}
|
|
307
195
|
|
|
308
|
-
private renderWithCommandHighlighting(
|
|
309
|
-
width: number,
|
|
310
|
-
color: (value: string) => string,
|
|
311
|
-
): { lines: string[]; bottomBorderIndex: number } {
|
|
312
|
-
const lines = super.render(width);
|
|
313
|
-
const internals = this as unknown as {
|
|
314
|
-
lastWidth: number;
|
|
315
|
-
scrollOffset: number;
|
|
316
|
-
buildVisualLineMap: (layoutWidth: number) => EditorVisualLine[];
|
|
317
|
-
};
|
|
318
|
-
return highlightEditorLines(
|
|
319
|
-
lines,
|
|
320
|
-
this.getLines(),
|
|
321
|
-
internals.buildVisualLineMap(internals.lastWidth),
|
|
322
|
-
internals.scrollOffset,
|
|
323
|
-
this.getCommandNames(),
|
|
324
|
-
color,
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
196
|
override render(width: number): string[] {
|
|
329
197
|
if (width <= 0) return [];
|
|
330
|
-
|
|
331
|
-
if (width < 4) {
|
|
332
|
-
return this.renderWithCommandHighlighting(width, colorCommand)
|
|
333
|
-
.lines.map((line) => truncateToWidth(line, width, ""));
|
|
334
|
-
}
|
|
198
|
+
if (width < 4) return super.render(width).map((line) => truncateToWidth(line, width, ""));
|
|
335
199
|
const innerWidth = width - 2;
|
|
336
|
-
const
|
|
337
|
-
const { lines, bottomBorderIndex } = highlighted;
|
|
200
|
+
const lines = super.render(innerWidth);
|
|
338
201
|
if (lines.length < 2) return lines.map((line) => truncateToWidth(line, width, ""));
|
|
202
|
+
let bottomBorderIndex = lines.length - 1;
|
|
203
|
+
for (let index = lines.length - 1; index >= 1; index -= 1) {
|
|
204
|
+
if (isBorderLine(lines[index] ?? "")) {
|
|
205
|
+
bottomBorderIndex = index;
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
339
209
|
|
|
340
210
|
const gray = (text: string): string => this.runtimeTheme.fg("dim", text);
|
|
341
211
|
const framed: string[] = [];
|
|
@@ -381,6 +251,8 @@ function formatActivityMessage(word: string, theme: Theme): string {
|
|
|
381
251
|
return `${theme.fg("accent", `${word}…`)} ${theme.fg("dim", `(${theme.bold("esc")} to interrupt · thinking)`)}`;
|
|
382
252
|
}
|
|
383
253
|
|
|
254
|
+
let killerosEditorFactory: ReturnType<ExtensionContext["ui"]["getEditorComponent"]>;
|
|
255
|
+
|
|
384
256
|
export function registerShellUi(pi: ExtensionAPI): void {
|
|
385
257
|
let activeHeader: PiStartupHeader | undefined;
|
|
386
258
|
let activityDeck: string[] = [];
|
|
@@ -428,8 +300,12 @@ export function registerShellUi(pi: ExtensionAPI): void {
|
|
|
428
300
|
intervalMs: ACTIVITY_FRAME_INTERVAL_MS,
|
|
429
301
|
});
|
|
430
302
|
ctx.ui.setHiddenThinkingLabel("└ Thinking…");
|
|
431
|
-
ctx.ui.
|
|
432
|
-
|
|
303
|
+
const existingEditorFactory = ctx.ui.getEditorComponent?.();
|
|
304
|
+
if (!existingEditorFactory || existingEditorFactory === killerosEditorFactory) {
|
|
305
|
+
killerosEditorFactory = (tui, editorTheme, keybindings) =>
|
|
306
|
+
new PiCodeEditor(tui, editorTheme, keybindings, ctx.ui.theme);
|
|
307
|
+
ctx.ui.setEditorComponent(killerosEditorFactory);
|
|
308
|
+
}
|
|
433
309
|
} catch (error) {
|
|
434
310
|
reportError(ctx, "Killeros UI failed to initialize", error);
|
|
435
311
|
}
|