@ocis/myagent-cli 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +357 -0
  2. package/dist/agent/context.d.ts +33 -0
  3. package/dist/agent/context.js +169 -0
  4. package/dist/agent/modes.d.ts +21 -0
  5. package/dist/agent/modes.js +84 -0
  6. package/dist/agent/prompt-builder.d.ts +9 -0
  7. package/dist/agent/prompt-builder.js +31 -0
  8. package/dist/agent/sessions.d.ts +38 -0
  9. package/dist/agent/sessions.js +130 -0
  10. package/dist/agent/todo.d.ts +18 -0
  11. package/dist/agent/todo.js +61 -0
  12. package/dist/agent/turn.d.ts +309 -0
  13. package/dist/agent/turn.js +1253 -0
  14. package/dist/approval/policy.d.ts +81 -0
  15. package/dist/approval/policy.js +157 -0
  16. package/dist/config.d.ts +49 -0
  17. package/dist/config.js +156 -0
  18. package/dist/git/status.d.ts +89 -0
  19. package/dist/git/status.js +226 -0
  20. package/dist/headless.d.ts +72 -0
  21. package/dist/headless.js +330 -0
  22. package/dist/index.d.ts +60 -0
  23. package/dist/index.js +511 -0
  24. package/dist/protocol/client.d.ts +123 -0
  25. package/dist/protocol/client.js +250 -0
  26. package/dist/protocol/sse-frames.d.ts +6 -0
  27. package/dist/protocol/sse-frames.js +75 -0
  28. package/dist/protocol/types.d.ts +200 -0
  29. package/dist/protocol/types.js +8 -0
  30. package/dist/runtime.d.ts +38 -0
  31. package/dist/runtime.js +166 -0
  32. package/dist/sanitize.d.ts +1 -0
  33. package/dist/sanitize.js +21 -0
  34. package/dist/skills/discovery.d.ts +24 -0
  35. package/dist/skills/discovery.js +109 -0
  36. package/dist/tools/binary.d.ts +2 -0
  37. package/dist/tools/binary.js +22 -0
  38. package/dist/tools/diff.d.ts +1 -0
  39. package/dist/tools/diff.js +49 -0
  40. package/dist/tools/find.d.ts +2 -0
  41. package/dist/tools/find.js +61 -0
  42. package/dist/tools/fs.d.ts +2 -0
  43. package/dist/tools/fs.js +276 -0
  44. package/dist/tools/glob.d.ts +6 -0
  45. package/dist/tools/glob.js +131 -0
  46. package/dist/tools/grep.d.ts +3 -0
  47. package/dist/tools/grep.js +228 -0
  48. package/dist/tools/paths.d.ts +27 -0
  49. package/dist/tools/paths.js +124 -0
  50. package/dist/tools/registry.d.ts +13 -0
  51. package/dist/tools/registry.js +38 -0
  52. package/dist/tools/shell.d.ts +2 -0
  53. package/dist/tools/shell.js +136 -0
  54. package/dist/tools/skills.d.ts +2 -0
  55. package/dist/tools/skills.js +36 -0
  56. package/dist/tools/todo.d.ts +2 -0
  57. package/dist/tools/todo.js +43 -0
  58. package/dist/tools/transfer.d.ts +2 -0
  59. package/dist/tools/transfer.js +145 -0
  60. package/dist/tools/truncate.d.ts +12 -0
  61. package/dist/tools/truncate.js +46 -0
  62. package/dist/tools/types.d.ts +85 -0
  63. package/dist/tools/types.js +63 -0
  64. package/dist/ui/app.d.ts +39 -0
  65. package/dist/ui/app.js +1061 -0
  66. package/dist/ui/colors.d.ts +100 -0
  67. package/dist/ui/colors.js +169 -0
  68. package/dist/ui/components.d.ts +267 -0
  69. package/dist/ui/components.js +811 -0
  70. package/dist/ui/diff.d.ts +37 -0
  71. package/dist/ui/diff.js +143 -0
  72. package/dist/ui/format.d.ts +28 -0
  73. package/dist/ui/format.js +76 -0
  74. package/dist/ui/help.d.ts +6 -0
  75. package/dist/ui/help.js +45 -0
  76. package/dist/ui/highlight.d.ts +20 -0
  77. package/dist/ui/highlight.js +210 -0
  78. package/dist/ui/logo.d.ts +24 -0
  79. package/dist/ui/logo.js +106 -0
  80. package/dist/ui/model-list.d.ts +10 -0
  81. package/dist/ui/model-list.js +33 -0
  82. package/dist/ui/quit-confirm.d.ts +8 -0
  83. package/dist/ui/quit-confirm.js +40 -0
  84. package/dist/ui/select-popup.d.ts +30 -0
  85. package/dist/ui/select-popup.js +54 -0
  86. package/dist/ui/theme.d.ts +4 -0
  87. package/dist/ui/theme.js +41 -0
  88. package/dist/ui/tool-view.d.ts +20 -0
  89. package/dist/ui/tool-view.js +326 -0
  90. package/package.json +44 -0
  91. package/skills/git-commit/SKILL.md +27 -0
@@ -0,0 +1,37 @@
1
+ export type DiffLineKind = "meta" | "hunk" | "add" | "del" | "context";
2
+ export interface DiffLine {
3
+ kind: DiffLineKind;
4
+ text: string;
5
+ /** 1-based line number in the pre-image (deletions and context). */
6
+ oldLine?: number;
7
+ /** 1-based line number in the post-image (additions and context). */
8
+ newLine?: number;
9
+ }
10
+ /** A pair row: `full` spans both columns; otherwise left/right cells align. */
11
+ export type DiffRow = {
12
+ full: DiffLine;
13
+ } | {
14
+ left: DiffLine | null;
15
+ right: DiffLine | null;
16
+ };
17
+ /**
18
+ * Classify the lines of a unified diff (blank/trailing lines are dropped) and
19
+ * track the pre/post-image line numbers from the hunk headers.
20
+ */
21
+ export declare function parseDiffLines(raw: string): DiffLine[];
22
+ /**
23
+ * Pack a parsed diff into rows: a run of deletions is paired with the
24
+ * following run of additions (the shorter side pads with empty cells); meta,
25
+ * hunk and context lines span the full row.
26
+ */
27
+ export declare function toPairRows(lines: readonly DiffLine[]): DiffRow[];
28
+ /** Width of the line-number gutter for a set of diff lines. */
29
+ export declare function diffNumberWidth(lines: readonly DiffLine[]): number;
30
+ /** Pad a line number to the gutter width (dim; blank when absent). */
31
+ export declare function diffNumber(value: number | undefined, width: number): string;
32
+ /**
33
+ * Lay a parsed diff out as side-by-side rows: each side gets its own number
34
+ * gutter and text column, and full-span rows (context/meta) keep the gutters
35
+ * blank for alignment. `paint` styles one line for the caller's palette.
36
+ */
37
+ export declare function renderPairRows(lines: readonly DiffLine[], width: number, paint: (line: DiffLine) => string): string[];
@@ -0,0 +1,143 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Unified-diff parsing and side-by-side row rendering.
3
+ //
4
+ // Pure and synchronous: classify each line of a unified diff, pack the
5
+ // removed/added runs into aligned side-by-side rows, and lay those rows out
6
+ // with old/new line-number gutters. Callers supply the per-line paint function
7
+ // (the transcript and the popup viewer use different palettes).
8
+ // ---------------------------------------------------------------------------
9
+ import { truncateToWidth } from "@earendil-works/pi-tui";
10
+ import { style } from "./colors.js";
11
+ const HUNK_PATTERN = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
12
+ /**
13
+ * Classify the lines of a unified diff (blank/trailing lines are dropped) and
14
+ * track the pre/post-image line numbers from the hunk headers.
15
+ */
16
+ export function parseDiffLines(raw) {
17
+ const lines = [];
18
+ let oldLine;
19
+ let newLine;
20
+ for (const text of raw.split("\n")) {
21
+ if (text.length === 0)
22
+ continue;
23
+ let kind;
24
+ let oldNumber;
25
+ let newNumber;
26
+ if (text.startsWith("@@")) {
27
+ kind = "hunk";
28
+ const match = HUNK_PATTERN.exec(text);
29
+ if (match) {
30
+ oldLine = Number(match[1]);
31
+ newLine = Number(match[2]);
32
+ }
33
+ }
34
+ else if (text.startsWith("+++") || text.startsWith("---")) {
35
+ kind = "meta";
36
+ }
37
+ else if (text.startsWith("+")) {
38
+ kind = "add";
39
+ newNumber = newLine;
40
+ if (newLine !== undefined)
41
+ newLine += 1;
42
+ }
43
+ else if (text.startsWith("-")) {
44
+ kind = "del";
45
+ oldNumber = oldLine;
46
+ if (oldLine !== undefined)
47
+ oldLine += 1;
48
+ }
49
+ else if (text.startsWith(" ")) {
50
+ kind = "context";
51
+ oldNumber = oldLine;
52
+ newNumber = newLine;
53
+ if (oldLine !== undefined)
54
+ oldLine += 1;
55
+ if (newLine !== undefined)
56
+ newLine += 1;
57
+ }
58
+ else {
59
+ kind = "meta";
60
+ }
61
+ const line = { kind, text };
62
+ if (oldNumber !== undefined)
63
+ line.oldLine = oldNumber;
64
+ if (newNumber !== undefined)
65
+ line.newLine = newNumber;
66
+ lines.push(line);
67
+ }
68
+ return lines;
69
+ }
70
+ /**
71
+ * Pack a parsed diff into rows: a run of deletions is paired with the
72
+ * following run of additions (the shorter side pads with empty cells); meta,
73
+ * hunk and context lines span the full row.
74
+ */
75
+ export function toPairRows(lines) {
76
+ const rows = [];
77
+ let dels = [];
78
+ let adds = [];
79
+ const flush = () => {
80
+ const count = Math.max(dels.length, adds.length);
81
+ for (let i = 0; i < count; i++) {
82
+ rows.push({ left: dels[i] ?? null, right: adds[i] ?? null });
83
+ }
84
+ dels = [];
85
+ adds = [];
86
+ };
87
+ for (const line of lines) {
88
+ if (line.kind === "del") {
89
+ dels.push(line);
90
+ }
91
+ else if (line.kind === "add") {
92
+ adds.push(line);
93
+ }
94
+ else {
95
+ flush();
96
+ rows.push({ full: line });
97
+ }
98
+ }
99
+ flush();
100
+ return rows;
101
+ }
102
+ /** Width of the line-number gutter for a set of diff lines. */
103
+ export function diffNumberWidth(lines) {
104
+ let max = 0;
105
+ for (const line of lines) {
106
+ max = Math.max(max, line.oldLine ?? 0, line.newLine ?? 0);
107
+ }
108
+ return Math.min(6, Math.max(2, String(max).length));
109
+ }
110
+ /** Pad a line number to the gutter width (dim; blank when absent). */
111
+ export function diffNumber(value, width) {
112
+ return style.dim((value === undefined ? "" : String(value)).padStart(width));
113
+ }
114
+ /**
115
+ * Lay a parsed diff out as side-by-side rows: each side gets its own number
116
+ * gutter and text column, and full-span rows (context/meta) keep the gutters
117
+ * blank for alignment. `paint` styles one line for the caller's palette.
118
+ */
119
+ export function renderPairRows(lines, width, paint) {
120
+ const nums = diffNumberWidth(lines);
121
+ const gutter = 3; // " │ "
122
+ const minCell = nums + 4;
123
+ let leftWidth = Math.max(minCell, Math.floor((width - gutter) / 2));
124
+ let rightWidth = Math.max(minCell, width - gutter - leftWidth);
125
+ if (leftWidth + gutter + rightWidth > width) {
126
+ // Narrow terminal: the comfortable minimum would overflow the popup's
127
+ // width budget — shrink below it (cells degrade to heavy truncation)
128
+ // rather than render rows wider than the caller asked for.
129
+ const available = Math.max(2, width - gutter);
130
+ leftWidth = Math.floor(available / 2);
131
+ rightWidth = available - leftWidth;
132
+ }
133
+ const cell = (line, number, cellWidth) => {
134
+ const text = line ? paint(line) : "";
135
+ return `${diffNumber(number, nums)} ${style.dim("│")} ${truncateToWidth(text, Math.max(1, cellWidth - nums - 3), "…", true)}`;
136
+ };
137
+ return toPairRows(lines).map((row) => {
138
+ if ("full" in row) {
139
+ return truncateToWidth(`${" ".repeat(nums)} ${style.dim("│")} ${paint(row.full)}`, width, "…", true);
140
+ }
141
+ return `${cell(row.left, row.left?.oldLine, leftWidth)}${style.dim(" │ ")}${cell(row.right, row.right?.newLine, rightWidth)}`;
142
+ });
143
+ }
@@ -0,0 +1,28 @@
1
+ import type { AgentUsage } from "../protocol/types.js";
2
+ import type { TodoStatus } from "../agent/todo.js";
3
+ /** 999 → "999", 12345 → "12.3k", 1234567 → "1.2M". */
4
+ export declare function formatTokens(n: number): string;
5
+ /** Cost with adaptive precision; empty string when no cost data. */
6
+ export declare function formatCost(usage: AgentUsage | null): string;
7
+ /**
8
+ * Share of input tokens served from the prompt cache ("87%"), or "—" when no
9
+ * input has been reported yet. `input` counts non-cached tokens only, so the
10
+ * total input is `input + cacheRead + cacheWrite`.
11
+ */
12
+ export declare function formatCacheHit(usage: AgentUsage | null): string;
13
+ /** Percentage of the context window in use ("42%"), or "—" when unknown. */
14
+ export declare function formatContextPercent(used: number, window: number): string;
15
+ /** "84.0k / 200k" style context numbers. */
16
+ export declare function formatContextNumbers(used: number, window: number): string;
17
+ /** Clamp a line to `width` visible cells (never exceeds it). */
18
+ export declare function fit(line: string, width: number): string;
19
+ /** Remove ANSI SGR sequences — content matching on styled lines. */
20
+ export declare function stripAnsi(line: string): string;
21
+ /** Plain-text checkbox for a todo status. */
22
+ export declare function todoMark(status: TodoStatus): string;
23
+ /**
24
+ * Render a `local_update_todo` request's args as display lines — the task
25
+ * list lives in the tool request, not in the system prompt. Empty for
26
+ * malformed args (the tool call itself surfaces the validation error).
27
+ */
28
+ export declare function formatTodoArgs(args: Record<string, unknown>): string[];
@@ -0,0 +1,76 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Pure formatting helpers shared by the sidebar, footer and transcript.
3
+ // ---------------------------------------------------------------------------
4
+ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
5
+ import { parseTodoItems } from "../agent/todo.js";
6
+ /** 999 → "999", 12345 → "12.3k", 1234567 → "1.2M". */
7
+ export function formatTokens(n) {
8
+ if (!Number.isFinite(n))
9
+ return "0";
10
+ const abs = Math.abs(n);
11
+ if (abs < 1000)
12
+ return String(Math.round(n));
13
+ if (abs < 10_000)
14
+ return `${(n / 1000).toFixed(1)}k`;
15
+ if (abs < 1_000_000)
16
+ return `${Math.round(n / 1000)}k`;
17
+ return `${(n / 1_000_000).toFixed(1)}M`;
18
+ }
19
+ /** Cost with adaptive precision; empty string when no cost data. */
20
+ export function formatCost(usage) {
21
+ const total = usage?.cost?.total;
22
+ if (total === undefined || Number.isNaN(total))
23
+ return "";
24
+ return `$${total >= 1 ? total.toFixed(2) : total.toFixed(4)}`;
25
+ }
26
+ /**
27
+ * Share of input tokens served from the prompt cache ("87%"), or "—" when no
28
+ * input has been reported yet. `input` counts non-cached tokens only, so the
29
+ * total input is `input + cacheRead + cacheWrite`.
30
+ */
31
+ export function formatCacheHit(usage) {
32
+ const read = usage?.cacheRead ?? 0;
33
+ const totalInput = (usage?.input ?? 0) + read + (usage?.cacheWrite ?? 0);
34
+ if (totalInput <= 0)
35
+ return "—";
36
+ return `${Math.round((read / totalInput) * 100)}%`;
37
+ }
38
+ /** Percentage of the context window in use ("42%"), or "—" when unknown. */
39
+ export function formatContextPercent(used, window) {
40
+ if (!window || window <= 0)
41
+ return "—";
42
+ return `${Math.round(Math.max(0, Math.min(1, used / window)) * 100)}%`;
43
+ }
44
+ /** "84.0k / 200k" style context numbers. */
45
+ export function formatContextNumbers(used, window) {
46
+ if (!window || window <= 0)
47
+ return formatTokens(used);
48
+ return `${formatTokens(used)} / ${formatTokens(window)}`;
49
+ }
50
+ /** Clamp a line to `width` visible cells (never exceeds it). */
51
+ export function fit(line, width) {
52
+ return visibleWidth(line) > width ? truncateToWidth(line, width) : line;
53
+ }
54
+ /** Remove ANSI SGR sequences — content matching on styled lines. */
55
+ export function stripAnsi(line) {
56
+ return line.replace(/\x1b\[[0-9;]*m/g, "");
57
+ }
58
+ /** Plain-text checkbox for a todo status. */
59
+ export function todoMark(status) {
60
+ return status === "completed" ? "[x]" : status === "in_progress" ? "[>]" : "[ ]";
61
+ }
62
+ /**
63
+ * Render a `local_update_todo` request's args as display lines — the task
64
+ * list lives in the tool request, not in the system prompt. Empty for
65
+ * malformed args (the tool call itself surfaces the validation error).
66
+ */
67
+ export function formatTodoArgs(args) {
68
+ let items;
69
+ try {
70
+ items = parseTodoItems(args.todos);
71
+ }
72
+ catch {
73
+ return [];
74
+ }
75
+ return items.map((t) => `${todoMark(t.status)} ${t.content}`);
76
+ }
@@ -0,0 +1,6 @@
1
+ /** [leftLabel, leftDescription, rightLabel, rightDescription]. */
2
+ export type HelpPair = [string, string, string, string];
3
+ /** One section table: bold title, then aligned cyan labels + gray descriptions. */
4
+ export declare function helpTable(title: string, rows: readonly HelpPair[]): string[];
5
+ /** Body lines for the /help popup. */
6
+ export declare function helpLines(): string[];
@@ -0,0 +1,45 @@
1
+ // -------------------------------------------------------------------
2
+ // /help content: compact two-pair tables (label + description, twice per
3
+ // row) sized to fit an 80-column terminal inside the info popup.
4
+ // -------------------------------------------------------------------
5
+ import { visibleWidth } from "@earendil-works/pi-tui";
6
+ import { GRAY_COLOR, style } from "./colors.js";
7
+ function pad(text, width) {
8
+ return text + " ".repeat(Math.max(0, width - visibleWidth(text)));
9
+ }
10
+ /** One section table: bold title, then aligned cyan labels + gray descriptions. */
11
+ export function helpTable(title, rows) {
12
+ const leftLabel = rows.reduce((widest, row) => Math.max(widest, visibleWidth(row[0])), 0);
13
+ const leftDescription = rows.reduce((widest, row) => Math.max(widest, visibleWidth(row[1])), 0);
14
+ const rightLabel = rows.reduce((widest, row) => Math.max(widest, visibleWidth(row[2])), 0);
15
+ const lines = [style.bold(title)];
16
+ for (const [leftKey, leftText, rightKey, rightText] of rows) {
17
+ lines.push(` ${style.cyan(pad(leftKey, leftLabel))} ${GRAY_COLOR(pad(leftText, leftDescription))} ` +
18
+ `${style.cyan(pad(rightKey, rightLabel))} ${GRAY_COLOR(rightText)}`);
19
+ }
20
+ return lines;
21
+ }
22
+ const COMMANDS = [
23
+ ["/plan /coding", "modes", "/mode", "cycle"],
24
+ ["/cowork", "general tasks", "/edit", "AUTO/ASK"],
25
+ ["/new", "new session", "/session [all]", "list · switch"],
26
+ ["/thinking /model", "level · model", "/compact /stop", "compact · stop"],
27
+ ["/reload /export", "reload · export", "/config /quit", "info · exit"],
28
+ ];
29
+ const KEYS = [
30
+ ["Tab", "plan/coding/cowork", "Shift+Tab", "AUTO/ASK"],
31
+ ["Enter", "send · steer", "Ctrl+P", "sidebar"],
32
+ ["Shift+Enter", "newline (also Alt+Enter)", "Ctrl+E", "changed files"],
33
+ ["Ctrl+Y / Ctrl+N", "approve · deny", "Ctrl+O", "tool output"],
34
+ ["Esc", "force-stop", "Ctrl+T", "thinking"],
35
+ ["Ctrl+C", "twice quits (idle)", "Alt+↑/↓", "panel scroll"],
36
+ ["@ /", "mention · commands", "", ""],
37
+ ];
38
+ const MOUSE = [
39
+ ["Click", "mode/model/thinking chips", "Click", "ASK/AUTO badge"],
40
+ ["Click", "picker rows", "Wheel", "scroll"],
41
+ ];
42
+ /** Body lines for the /help popup. */
43
+ export function helpLines() {
44
+ return [...helpTable("Commands", COMMANDS), "", ...helpTable("Keys", KEYS), "", ...helpTable("Mouse", MOUSE)];
45
+ }
@@ -0,0 +1,20 @@
1
+ type TokenKind = "plain" | "comment" | "string" | "number" | "keyword";
2
+ interface Token {
3
+ kind: TokenKind;
4
+ text: string;
5
+ }
6
+ /** Language id for a file path, or undefined when it has no known extension. */
7
+ export declare function languageForPath(path: string): string | undefined;
8
+ /** Language id for a fenced code block's info string (e.g. "ts title=x"). */
9
+ export declare function languageForFence(language: string | undefined): string | undefined;
10
+ /**
11
+ * Split one line into highlightable tokens: comments, strings, numbers and
12
+ * keywords, with everything else left plain. Line-oriented — an unterminated
13
+ * block comment colors to the end of the line.
14
+ */
15
+ export declare function tokenizeLine(language: string | undefined, line: string): Token[];
16
+ /** Apply the syntax palette to one line, optionally tinting the plain runs. */
17
+ export declare function highlightLine(language: string | undefined, line: string, base?: (text: string) => string): string;
18
+ /** Markdown `highlightCode` hook: one highlighted line per source line. */
19
+ export declare function highlightCodeBlock(code: string, language?: string): string[];
20
+ export {};
@@ -0,0 +1,210 @@
1
+ // -------------------------------------------------------------------
2
+ // Regex syntax highlighting for tool output (file reads).
3
+ //
4
+ // Line-oriented and dependency-free: enough color for common source files
5
+ // without pulling in a parser. Tokenization is pure and separate from color
6
+ // application, so it stays testable without a TTY.
7
+ // -------------------------------------------------------------------
8
+ import { syntaxComment, syntaxKeyword, syntaxNumber, syntaxString } from "./colors.js";
9
+ const JS_KEYWORDS = [
10
+ "await", "async", "break", "case", "catch", "class", "const", "continue", "debugger", "default",
11
+ "delete", "do", "else", "export", "extends", "false", "finally", "for", "from", "function", "get",
12
+ "if", "import", "in", "instanceof", "let", "new", "null", "of", "return", "set", "static", "super",
13
+ "switch", "this", "throw", "true", "try", "typeof", "undefined", "var", "void", "while", "with", "yield",
14
+ ];
15
+ const TS_KEYWORDS = [
16
+ ...JS_KEYWORDS,
17
+ "abstract", "any", "as", "bigint", "boolean", "declare", "enum", "implements", "infer", "interface",
18
+ "is", "keyof", "namespace", "never", "number", "object", "private", "protected", "public", "readonly",
19
+ "satisfies", "string", "symbol", "type", "unique", "unknown",
20
+ ];
21
+ const PYTHON_KEYWORDS = [
22
+ "and", "as", "assert", "async", "await", "break", "class", "continue", "def", "del", "elif", "else",
23
+ "except", "False", "finally", "for", "from", "global", "if", "import", "in", "is", "lambda", "None",
24
+ "nonlocal", "not", "or", "pass", "raise", "return", "True", "try", "while", "with", "yield",
25
+ ];
26
+ const RUST_KEYWORDS = [
27
+ "as", "async", "await", "break", "const", "continue", "crate", "dyn", "else", "enum", "extern",
28
+ "false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref",
29
+ "return", "self", "Self", "static", "struct", "super", "trait", "true", "type", "unsafe", "use",
30
+ "where", "while",
31
+ ];
32
+ const GO_KEYWORDS = [
33
+ "break", "case", "chan", "const", "continue", "default", "defer", "else", "fallthrough", "false",
34
+ "for", "func", "go", "goto", "if", "import", "interface", "iota", "map", "nil", "package", "range",
35
+ "return", "select", "struct", "switch", "true", "type", "var",
36
+ ];
37
+ const SH_KEYWORDS = [
38
+ "case", "do", "done", "elif", "else", "esac", "export", "fi", "for", "function", "if", "in",
39
+ "local", "readonly", "return", "set", "shift", "then", "trap", "unset", "until", "while",
40
+ ];
41
+ /** SQL keywords in both cases — queries are written upper- or lowercase. */
42
+ const SQL_KEYWORDS = [
43
+ "add", "alter", "and", "as", "asc", "begin", "between", "by", "case", "commit", "constraint",
44
+ "create", "cross", "default", "delete", "desc", "distinct", "drop", "else", "end", "exists",
45
+ "foreign", "from", "full", "group", "having", "if", "in", "index", "inner", "insert", "into",
46
+ "is", "join", "key", "left", "like", "limit", "not", "null", "offset", "on", "or", "order",
47
+ "outer", "primary", "references", "right", "rollback", "select", "set", "table", "then",
48
+ "transaction", "union", "unique", "update", "values", "when", "where", "with",
49
+ ].flatMap((k) => [k, k.toUpperCase()]);
50
+ /** Dockerfile instructions — conventionally uppercase, lowercase accepted. */
51
+ const DOCKERFILE_KEYWORDS = [
52
+ "add", "arg", "cmd", "copy", "entrypoint", "env", "expose", "from", "healthcheck", "label",
53
+ "onbuild", "run", "shell", "stopsignal", "user", "volume", "workdir",
54
+ ].flatMap((k) => [k, k.toUpperCase()]);
55
+ const JAVA_KEYWORDS = [
56
+ "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const",
57
+ "continue", "default", "do", "double", "else", "enum", "extends", "false", "final", "finally",
58
+ "float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long",
59
+ "native", "new", "null", "package", "private", "protected", "public", "record", "return", "short",
60
+ "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient",
61
+ "true", "try", "var", "void", "volatile", "while",
62
+ ];
63
+ const C_KEYWORDS = [
64
+ "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum",
65
+ "extern", "false", "float", "for", "goto", "if", "inline", "int", "long", "NULL", "register",
66
+ "restrict", "return", "short", "signed", "sizeof", "static", "struct", "switch", "true", "typedef",
67
+ "union", "unsigned", "void", "volatile", "while",
68
+ ];
69
+ const CPP_KEYWORDS = [
70
+ ...C_KEYWORDS,
71
+ "bool", "catch", "class", "constexpr", "delete", "explicit", "friend", "namespace", "new", "noexcept",
72
+ "nullptr", "operator", "private", "protected", "public", "template", "this", "throw", "try",
73
+ "typename", "using", "virtual",
74
+ ];
75
+ const LANGS = {
76
+ js: { lineComments: ["//"], blockComment: ["/*", "*/"], quotes: ['"', "'", "`"], keywords: JS_KEYWORDS },
77
+ ts: { lineComments: ["//"], blockComment: ["/*", "*/"], quotes: ['"', "'", "`"], keywords: TS_KEYWORDS },
78
+ json: { lineComments: [], quotes: ['"'], keywords: ["true", "false", "null"] },
79
+ python: { lineComments: ["#"], quotes: ['"', "'"], keywords: PYTHON_KEYWORDS },
80
+ rust: { lineComments: ["//"], blockComment: ["/*", "*/"], quotes: ['"', "'"], keywords: RUST_KEYWORDS },
81
+ go: { lineComments: ["//"], blockComment: ["/*", "*/"], quotes: ['"', "`"], keywords: GO_KEYWORDS },
82
+ sh: { lineComments: ["#"], quotes: ['"', "'"], keywords: SH_KEYWORDS },
83
+ java: { lineComments: ["//"], blockComment: ["/*", "*/"], quotes: ['"', "'"], keywords: JAVA_KEYWORDS },
84
+ c: { lineComments: ["//"], blockComment: ["/*", "*/"], quotes: ['"', "'"], keywords: C_KEYWORDS },
85
+ cpp: { lineComments: ["//"], blockComment: ["/*", "*/"], quotes: ['"', "'"], keywords: CPP_KEYWORDS },
86
+ css: { lineComments: [], blockComment: ["/*", "*/"], quotes: ['"', "'"], keywords: [] },
87
+ yaml: { lineComments: ["#"], quotes: ['"', "'"], keywords: [] },
88
+ toml: { lineComments: ["#"], quotes: ['"', "'"], keywords: [] },
89
+ sql: { lineComments: ["--"], quotes: ["'"], keywords: SQL_KEYWORDS },
90
+ dockerfile: { lineComments: ["#"], quotes: ['"', "'"], keywords: DOCKERFILE_KEYWORDS },
91
+ };
92
+ /** Language ids keyed by file extension and fenced-code info string. */
93
+ const LANGUAGES = {
94
+ ts: "ts", typescript: "ts", tsx: "ts", mts: "ts", cts: "ts",
95
+ js: "js", javascript: "js", jsx: "js", mjs: "js", cjs: "js",
96
+ json: "json", jsonc: "json",
97
+ py: "python", pyi: "python", python: "python",
98
+ rs: "rust", rust: "rust",
99
+ go: "go", golang: "go",
100
+ sh: "sh", bash: "sh", zsh: "sh", shell: "sh",
101
+ java: "java",
102
+ c: "c", h: "c",
103
+ cpp: "cpp", "c++": "cpp", cc: "cpp", cxx: "cpp", hpp: "cpp", hh: "cpp",
104
+ css: "css", scss: "css",
105
+ yml: "yaml", yaml: "yaml",
106
+ toml: "toml",
107
+ sql: "sql",
108
+ dockerfile: "dockerfile", docker: "dockerfile",
109
+ };
110
+ /** Language id for a file path, or undefined when it has no known extension. */
111
+ export function languageForPath(path) {
112
+ const dot = path.lastIndexOf(".");
113
+ if (dot < 0)
114
+ return undefined;
115
+ return LANGUAGES[path.slice(dot + 1).toLowerCase()];
116
+ }
117
+ /** Language id for a fenced code block's info string (e.g. "ts title=x"). */
118
+ export function languageForFence(language) {
119
+ const name = language?.trim().split(/\s+/)[0]?.toLowerCase();
120
+ return name ? LANGUAGES[name] : undefined;
121
+ }
122
+ const TOKEN_STYLES = {
123
+ comment: syntaxComment,
124
+ string: syntaxString,
125
+ number: syntaxNumber,
126
+ keyword: syntaxKeyword,
127
+ };
128
+ /**
129
+ * Split one line into highlightable tokens: comments, strings, numbers and
130
+ * keywords, with everything else left plain. Line-oriented — an unterminated
131
+ * block comment colors to the end of the line.
132
+ */
133
+ export function tokenizeLine(language, line) {
134
+ if (line.length === 0)
135
+ return [];
136
+ const spec = language === undefined ? undefined : LANGS[language];
137
+ if (!spec)
138
+ return [{ kind: "plain", text: line }];
139
+ const tokens = [];
140
+ const push = (kind, text) => {
141
+ if (text.length === 0)
142
+ return;
143
+ const last = tokens[tokens.length - 1];
144
+ if (kind === "plain" && last?.kind === "plain")
145
+ last.text += text;
146
+ else
147
+ tokens.push({ kind, text });
148
+ };
149
+ let index = 0;
150
+ while (index < line.length) {
151
+ const rest = line.slice(index);
152
+ if (spec.blockComment && rest.startsWith(spec.blockComment[0])) {
153
+ const end = rest.indexOf(spec.blockComment[1], spec.blockComment[0].length);
154
+ const text = end === -1 ? rest : rest.slice(0, end + spec.blockComment[1].length);
155
+ push("comment", text);
156
+ index += text.length;
157
+ continue;
158
+ }
159
+ const lineComment = spec.lineComments.find((prefix) => rest.startsWith(prefix));
160
+ if (lineComment) {
161
+ push("comment", rest);
162
+ break;
163
+ }
164
+ const quote = spec.quotes.find((q) => rest.startsWith(q));
165
+ if (quote) {
166
+ let end = index + 1;
167
+ while (end < line.length) {
168
+ if (line[end] === "\\") {
169
+ end += 2;
170
+ continue;
171
+ }
172
+ if (line[end] === quote) {
173
+ end += 1;
174
+ break;
175
+ }
176
+ end += 1;
177
+ }
178
+ const text = line.slice(index, Math.min(end, line.length));
179
+ push("string", text);
180
+ index += text.length;
181
+ continue;
182
+ }
183
+ const number = /^\d[\w.]*/.exec(rest);
184
+ if (number) {
185
+ push("number", number[0]);
186
+ index += number[0].length;
187
+ continue;
188
+ }
189
+ const word = /^[A-Za-z_$][\w$]*/.exec(rest);
190
+ if (word) {
191
+ push(spec.keywords.includes(word[0]) ? "keyword" : "plain", word[0]);
192
+ index += word[0].length;
193
+ continue;
194
+ }
195
+ push("plain", line[index]);
196
+ index += 1;
197
+ }
198
+ return tokens;
199
+ }
200
+ /** Apply the syntax palette to one line, optionally tinting the plain runs. */
201
+ export function highlightLine(language, line, base) {
202
+ return tokenizeLine(language, line)
203
+ .map((token) => (token.kind === "plain" ? (base ? base(token.text) : token.text) : TOKEN_STYLES[token.kind](token.text)))
204
+ .join("");
205
+ }
206
+ /** Markdown `highlightCode` hook: one highlighted line per source line. */
207
+ export function highlightCodeBlock(code, language) {
208
+ const resolved = languageForFence(language);
209
+ return code.split("\n").map((line) => highlightLine(resolved, line));
210
+ }
@@ -0,0 +1,24 @@
1
+ import type { Component, TUI } from "@earendil-works/pi-tui";
2
+ export declare const LOGO: string[];
3
+ export declare const TAGLINE = " \u2726 your machine, in the hands of the agent \u2726";
4
+ /** Paint the logo for a given gradient phase; returns the 5 colored rows. */
5
+ export declare function paintLogo(phase: number): string[];
6
+ /**
7
+ * Transcript component rendering the logo. `start()` kicks off the one-shot
8
+ * shimmer; the component stops animating after the final frame.
9
+ */
10
+ export declare class LogoComponent implements Component {
11
+ private readonly options;
12
+ private phase;
13
+ private timer;
14
+ private frame;
15
+ readonly frames = 22;
16
+ constructor(options?: {
17
+ animate: boolean;
18
+ tagline?: boolean;
19
+ });
20
+ start(tui: TUI): void;
21
+ stop(): void;
22
+ invalidate(): void;
23
+ render(width: number): string[];
24
+ }