@thoth-agents/pi-thoth-theme 0.1.0 → 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 (41) hide show
  1. package/README.md +11 -5
  2. package/dist/index.ts +3686 -0
  3. package/package.json +7 -9
  4. package/src/index.ts +0 -53
  5. package/src/input-box/decorate.ts +0 -304
  6. package/src/input-box/frame.ts +0 -182
  7. package/src/input-box/gradient.ts +0 -92
  8. package/src/input-box/state.ts +0 -90
  9. package/src/render-kit/index.ts +0 -305
  10. package/src/render-kit/working.ts +0 -25
  11. package/src/shared/cache.ts +0 -21
  12. package/src/shared/config.ts +0 -86
  13. package/src/shared/duration.ts +0 -1
  14. package/src/shared/icons.ts +0 -89
  15. package/src/status-line/cost.ts +0 -56
  16. package/src/status-line/index.ts +0 -242
  17. package/src/status-line/layout.ts +0 -272
  18. package/src/status-line/snapshot.ts +0 -11
  19. package/src/status-line/throughput.ts +0 -61
  20. package/src/status-line/tokens.ts +0 -51
  21. package/src/tools/bash.ts +0 -237
  22. package/src/tools/border.ts +0 -21
  23. package/src/tools/box.ts +0 -258
  24. package/src/tools/edit.ts +0 -228
  25. package/src/tools/file-icons.ts +0 -139
  26. package/src/tools/find.ts +0 -227
  27. package/src/tools/frame.ts +0 -166
  28. package/src/tools/generic.ts +0 -195
  29. package/src/tools/grep.ts +0 -371
  30. package/src/tools/index.ts +0 -135
  31. package/src/tools/ls.ts +0 -231
  32. package/src/tools/ownership.ts +0 -70
  33. package/src/tools/powershell.ts +0 -1
  34. package/src/tools/read.ts +0 -194
  35. package/src/tools/ticker.ts +0 -101
  36. package/src/tools/write.ts +0 -188
  37. package/src/welcome/age.ts +0 -11
  38. package/src/welcome/index.ts +0 -132
  39. package/src/welcome/logo.ts +0 -35
  40. package/src/welcome/render.ts +0 -323
  41. package/src/welcome/resources.ts +0 -251
package/src/tools/edit.ts DELETED
@@ -1,228 +0,0 @@
1
- import type {
2
- AgentToolResult,
3
- Theme,
4
- ToolRenderResultOptions,
5
- } from '@earendil-works/pi-coding-agent';
6
- import { toolFooter } from '../render-kit/index.ts';
7
- import type { ThemeConfig } from '../shared/config.ts';
8
- import { icon as semanticIcon, statusIcon } from '../shared/icons.ts';
9
- import { getToolBorderTone } from './border.ts';
10
- import { createComponent, getResultText } from './box.ts';
11
- import { getFileIcon, getToolIcon } from './file-icons.ts';
12
- import {
13
- formatDisplayPath,
14
- hasToolResult,
15
- renderFrameBottom,
16
- renderFrameRow,
17
- renderFrameTop,
18
- } from './frame.ts';
19
- import { type ElapsedRenderContext, syncElapsedTicker } from './ticker.ts';
20
-
21
- const COLLAPSED_DIFF_LINES = 6;
22
-
23
- interface EditArgs {
24
- path?: string;
25
- file_path?: string;
26
- }
27
-
28
- interface EditContext extends ElapsedRenderContext {
29
- cwd?: string;
30
- lastComponent?: { invalidate?: () => void };
31
- state?: {
32
- hasResult?: boolean;
33
- callComponent?: { invalidate?: () => void };
34
- [key: string]: unknown;
35
- };
36
- }
37
-
38
- interface EditDetails {
39
- diff?: string;
40
- }
41
-
42
- function parseDiffStats(diffText: string): {
43
- additions: number;
44
- removals: number;
45
- } {
46
- let additions = 0;
47
- let removals = 0;
48
- const lines = diffText.split('\n');
49
-
50
- for (const line of lines) {
51
- if (line.startsWith('+++') || line.startsWith('---')) continue;
52
- if (line.startsWith('+')) additions++;
53
- else if (line.startsWith('-')) removals++;
54
- }
55
-
56
- return { additions, removals };
57
- }
58
-
59
- function colorDiffLine(line: string, theme: Theme): string {
60
- if (
61
- line.startsWith('+++') ||
62
- line.startsWith('---') ||
63
- line.startsWith('@@')
64
- ) {
65
- return theme.fg('dim', line);
66
- }
67
- if (line.startsWith('+')) {
68
- return theme.fg('toolDiffAdded', line);
69
- }
70
- if (line.startsWith('-')) {
71
- return theme.fg('toolDiffRemoved', line);
72
- }
73
- return theme.fg('toolDiffContext', line);
74
- }
75
-
76
- export function createCustomEditTool(cwd: string, config: ThemeConfig) {
77
- return {
78
- renderShell: 'self' as const,
79
- renderCall(rawArgs: unknown, theme: Theme, context: EditContext) {
80
- syncElapsedTicker(context);
81
- const args = (rawArgs ?? {}) as EditArgs;
82
- const rawPath = String(args.path ?? args.file_path ?? '');
83
- const filePath = formatDisplayPath(rawPath, context?.cwd ?? cwd);
84
- const icon = getFileIcon(filePath, config.icons);
85
- const editIcon = getToolIcon('edit', config.icons);
86
- const borderTone = getToolBorderTone(context);
87
-
88
- const comp = createComponent((width: number) => {
89
- const safeWidth = Math.max(0, width);
90
- const footer =
91
- context?.executionStarted && context.isPartial
92
- ? toolFooter(theme, { status: 'running', context }, config.icons)
93
- : undefined;
94
- const title = `${theme.fg('accent', editIcon)} ${theme.bold ? theme.bold(theme.fg('toolTitle', 'Edit')) : theme.fg('toolTitle', 'Edit')} ${theme.fg('accent', icon)} ${theme.fg('text', filePath)}`;
95
-
96
- if (hasToolResult(context)) {
97
- return renderFrameTop(theme, title, safeWidth, borderTone);
98
- }
99
-
100
- return [
101
- ...renderFrameTop(theme, title, safeWidth, borderTone),
102
- ...renderFrameBottom(theme, footer, safeWidth, borderTone),
103
- ];
104
- });
105
-
106
- if (context?.state) {
107
- context.state.callComponent = comp;
108
- }
109
- return comp;
110
- },
111
-
112
- renderResult(
113
- result: AgentToolResult<unknown>,
114
- options: ToolRenderResultOptions,
115
- theme: Theme,
116
- context: EditContext,
117
- ) {
118
- if (context?.state) {
119
- context.state.hasResult = true;
120
- if (
121
- context.state.callComponent &&
122
- typeof context.state.callComponent.invalidate === 'function'
123
- ) {
124
- context.state.callComponent.invalidate();
125
- }
126
- }
127
- if (
128
- context?.lastComponent &&
129
- typeof context.lastComponent.invalidate === 'function'
130
- ) {
131
- context.lastComponent.invalidate();
132
- }
133
-
134
- const isErr = Boolean(context?.isError);
135
- const status = options?.isPartial
136
- ? 'running'
137
- : isErr
138
- ? 'failed'
139
- : 'completed';
140
- const footerOptions = {
141
- status,
142
- context: { ...context, isPartial: options?.isPartial },
143
- } as const;
144
- syncElapsedTicker(footerOptions.context);
145
- const borderTone = getToolBorderTone({
146
- isError: isErr,
147
- isPartial: options?.isPartial,
148
- });
149
- const details = result.details as EditDetails | undefined;
150
- const diffText = details?.diff ?? getResultText(result);
151
-
152
- if (isErr) {
153
- return createComponent((width: number) => {
154
- const safeWidth = Math.max(0, width);
155
- const errText = `${theme.fg('error', `${statusIcon('warning', config.icons)} `)}${theme.fg('error', diffText || 'Edit failed')}`;
156
- return [
157
- ...renderFrameRow(theme, errText, safeWidth, borderTone),
158
- ...renderFrameBottom(
159
- theme,
160
- toolFooter(theme, footerOptions, config.icons),
161
- safeWidth,
162
- borderTone,
163
- ),
164
- ];
165
- });
166
- }
167
-
168
- const { additions, removals } = parseDiffStats(diffText);
169
- const diffLines = diffText ? diffText.split('\n') : [];
170
-
171
- return createComponent((width: number) => {
172
- const safeWidth = Math.max(0, width);
173
- const addedStr =
174
- additions > 0
175
- ? theme.fg('toolDiffAdded', `+${additions}`)
176
- : theme.fg('dim', '+0');
177
- const removedStr =
178
- removals > 0
179
- ? theme.fg('toolDiffRemoved', `-${removals}`)
180
- : theme.fg('dim', '-0');
181
- const footer = toolFooter(
182
- theme,
183
- {
184
- ...footerOptions,
185
- summary: [`${addedStr} ${removedStr}`, '1 file'],
186
- },
187
- config.icons,
188
- );
189
-
190
- if (diffLines.length === 0) {
191
- return [
192
- ...renderFrameRow(
193
- theme,
194
- theme.fg('dim', '(no changes)'),
195
- safeWidth,
196
- borderTone,
197
- ),
198
- ...renderFrameBottom(theme, footer, safeWidth, borderTone),
199
- ];
200
- }
201
-
202
- const visibleLines = options?.expanded
203
- ? diffLines
204
- : diffLines.slice(0, COLLAPSED_DIFF_LINES);
205
- const bodyLines: string[] = visibleLines.map((l) =>
206
- colorDiffLine(l, theme),
207
- );
208
-
209
- if (!options?.expanded && diffLines.length > COLLAPSED_DIFF_LINES) {
210
- const remaining = diffLines.length - COLLAPSED_DIFF_LINES;
211
- bodyLines.push(
212
- theme.fg(
213
- 'dim',
214
- `${semanticIcon('ellipsis', config.icons)} ${remaining} more lines ${semanticIcon('separator', config.icons)} ctrl+o to expand`,
215
- ),
216
- );
217
- }
218
-
219
- return [
220
- ...bodyLines.flatMap((l) =>
221
- renderFrameRow(theme, l, safeWidth, borderTone),
222
- ),
223
- ...renderFrameBottom(theme, footer, safeWidth, borderTone),
224
- ];
225
- });
226
- },
227
- };
228
- }
@@ -1,139 +0,0 @@
1
- import { basename, extname } from 'node:path';
2
- import type { IconMode } from '../shared/config.ts';
3
- import { icon } from '../shared/icons.ts';
4
-
5
- const NERD_FILE_ICONS: Record<string, string> = {
6
- ts: '\ue628',
7
- tsx: '\ue7ba',
8
- js: '\ue74e',
9
- jsx: '\ue7ba',
10
- mjs: '\ue74e',
11
- cjs: '\ue74e',
12
- py: '\ue73c',
13
- rs: '\ue7a8',
14
- go: '\ue724',
15
- java: '\ue738',
16
- c: '\ue61e',
17
- cpp: '\ue61d',
18
- h: '\ue61e',
19
- hpp: '\ue61d',
20
- cs: '\ue648',
21
- html: '\ue736',
22
- htm: '\ue736',
23
- css: '\ue749',
24
- scss: '\ue749',
25
- sass: '\ue749',
26
- less: '\ue749',
27
- vue: '\ue6a0',
28
- svelte: '\ue697',
29
- json: '\ue60b',
30
- yaml: '\ue6a8',
31
- yml: '\ue6a8',
32
- toml: '\ue6b2',
33
- xml: '\ue619',
34
- md: '\ue73e',
35
- mdx: '\ue73e',
36
- sql: '\ue706',
37
- sh: '\ue795',
38
- bash: '\ue795',
39
- zsh: '\ue795',
40
- ps1: '\ue70f',
41
- psm1: '\ue70f',
42
- lua: '\ue620',
43
- php: '\ue73d',
44
- dart: '\ue798',
45
- png: '\uf1c5',
46
- jpg: '\uf1c5',
47
- jpeg: '\uf1c5',
48
- svg: '\uf1c5',
49
- webp: '\uf1c5',
50
- gif: '\uf1c5',
51
- lock: '\uf023',
52
- env: '\ue615',
53
- dockerfile: '\ue7b0',
54
- };
55
-
56
- const NERD_NAME_ICONS: Record<string, string> = {
57
- 'package.json': '\ue71e',
58
- 'package-lock.json': '\ue71e',
59
- 'tsconfig.json': '\ue628',
60
- '.gitignore': '\ue702',
61
- '.gitattributes': '\ue702',
62
- '.env': '\ue615',
63
- dockerfile: '\ue7b0',
64
- makefile: '\ue615',
65
- 'readme.md': '\ue73e',
66
- license: '\ue60a',
67
- };
68
-
69
- const ASCII_FILE_ICONS: Record<string, string> = {
70
- ts: '[ts]',
71
- tsx: '[ts]',
72
- js: '[js]',
73
- jsx: '[js]',
74
- mjs: '[js]',
75
- cjs: '[js]',
76
- py: '[py]',
77
- rs: '[rs]',
78
- go: '[go]',
79
- json: '{ }',
80
- yaml: '{ }',
81
- yml: '{ }',
82
- toml: '{ }',
83
- xml: '< >',
84
- md: '[md]',
85
- mdx: '[md]',
86
- html: '< >',
87
- htm: '< >',
88
- css: '{ }',
89
- sh: '$',
90
- bash: '$',
91
- zsh: '$',
92
- ps1: 'PS',
93
- psm1: 'PS',
94
- png: '[img]',
95
- jpg: '[img]',
96
- jpeg: '[img]',
97
- svg: '[img]',
98
- gif: '[img]',
99
- webp: '[img]',
100
- lock: '[lock]',
101
- env: '[env]',
102
- };
103
-
104
- export function getFileIcon(filePath: string, mode: IconMode): string {
105
- if (filePath.endsWith('/') || filePath.endsWith('\\')) {
106
- return icon('folder', mode);
107
- }
108
-
109
- const name = basename(filePath).toLowerCase();
110
- const ext = extname(name).replace(/^\./, '').toLowerCase();
111
-
112
- if (mode === 'ascii') {
113
- if (ASCII_FILE_ICONS[ext]) return ASCII_FILE_ICONS[ext];
114
- return icon('file', mode);
115
- }
116
-
117
- if (NERD_NAME_ICONS[name]) return NERD_NAME_ICONS[name];
118
- if (NERD_FILE_ICONS[ext]) return NERD_FILE_ICONS[ext];
119
- return icon('file', mode);
120
- }
121
-
122
- export function getDirIcon(mode: IconMode): string {
123
- return icon('folder', mode);
124
- }
125
-
126
- export function getToolIcon(
127
- tool:
128
- | 'read'
129
- | 'write'
130
- | 'edit'
131
- | 'bash'
132
- | 'powershell'
133
- | 'search'
134
- | 'folder'
135
- | 'file',
136
- mode: IconMode,
137
- ): string {
138
- return icon(tool, mode);
139
- }
package/src/tools/find.ts DELETED
@@ -1,227 +0,0 @@
1
- import type {
2
- AgentToolResult,
3
- Theme,
4
- ToolRenderResultOptions,
5
- } from '@earendil-works/pi-coding-agent';
6
- import { truncateToWidth } from '@earendil-works/pi-tui';
7
- import { toolFooter } from '../render-kit/index.ts';
8
- import type { ThemeConfig } from '../shared/config.ts';
9
- import { icon as semanticIcon, statusIcon } from '../shared/icons.ts';
10
- import { getToolBorderTone } from './border.ts';
11
- import {
12
- createComponent,
13
- escapeControlCharacters,
14
- getRawResultText,
15
- renderBox,
16
- splitResultNotice,
17
- } from './box.ts';
18
- import { getFileIcon, getToolIcon } from './file-icons.ts';
19
- import {
20
- formatDisplayPath,
21
- hasToolResult,
22
- isFramedContext,
23
- renderFrameBottom,
24
- renderFrameRow,
25
- renderFrameTop,
26
- } from './frame.ts';
27
- import { type ElapsedRenderContext, syncElapsedTicker } from './ticker.ts';
28
-
29
- const COLLAPSED_ENTRY_LIMIT = 8;
30
-
31
- interface FindArgs {
32
- pattern?: string;
33
- path?: string;
34
- }
35
-
36
- interface FindContext extends ElapsedRenderContext {
37
- cwd?: string;
38
- lastComponent?: { invalidate?: () => void };
39
- state?: {
40
- hasResult?: boolean;
41
- callComponent?: { invalidate?: () => void };
42
- [key: string]: unknown;
43
- };
44
- toolCallId?: string;
45
- }
46
-
47
- function parseFindOutput(
48
- text: string,
49
- details: unknown,
50
- ): { entries: string[]; notices: string[] } {
51
- const output = splitResultNotice(text, details, 'find');
52
- return {
53
- entries: output.text.split('\n').filter((line) => line.length > 0),
54
- notices: output.notices,
55
- };
56
- }
57
-
58
- export function createCustomFindTool(cwd: string, config: ThemeConfig) {
59
- return {
60
- renderShell: 'self' as const,
61
- renderCall(rawArgs: unknown, theme: Theme, context: FindContext) {
62
- syncElapsedTicker(context);
63
- const args = (rawArgs ?? {}) as FindArgs;
64
- const pattern = escapeControlCharacters(String(args.pattern ?? '*'));
65
- const rawPath = String(args.path ?? '.');
66
- const searchPath = escapeControlCharacters(
67
- formatDisplayPath(rawPath, context?.cwd ?? cwd),
68
- );
69
- const icon = getToolIcon('search', config.icons);
70
- const borderTone = getToolBorderTone(context);
71
-
72
- const comp = createComponent((width: number) => {
73
- const safeWidth = Math.max(0, width);
74
- const footer =
75
- context?.executionStarted && context.isPartial
76
- ? toolFooter(theme, { status: 'running', context }, config.icons)
77
- : undefined;
78
- const title = `${theme.fg('accent', icon)} ${theme.bold ? theme.bold(theme.fg('toolTitle', 'Find')) : theme.fg('toolTitle', 'Find')} ${theme.fg('syntaxString', `"${pattern}"`)} ${theme.fg('dim', 'in')} ${theme.fg('text', searchPath)}`;
79
-
80
- if (!isFramedContext(context)) {
81
- return renderBox(theme, [], safeWidth, {
82
- title,
83
- footer,
84
- isError: borderTone === 'error',
85
- isSuccess: borderTone === 'success',
86
- });
87
- }
88
-
89
- if (hasToolResult(context)) {
90
- return renderFrameTop(theme, title, safeWidth, borderTone);
91
- }
92
-
93
- return [
94
- ...renderFrameTop(theme, title, safeWidth, borderTone),
95
- ...renderFrameBottom(theme, footer, safeWidth, borderTone),
96
- ];
97
- });
98
-
99
- if (context?.state) {
100
- context.state.callComponent = comp;
101
- }
102
- return comp;
103
- },
104
-
105
- renderResult(
106
- result: AgentToolResult<unknown>,
107
- options: ToolRenderResultOptions,
108
- theme: Theme,
109
- context: FindContext,
110
- ) {
111
- if (context?.state) {
112
- context.state.hasResult = true;
113
- if (
114
- context.state.callComponent &&
115
- typeof context.state.callComponent.invalidate === 'function'
116
- ) {
117
- context.state.callComponent.invalidate();
118
- }
119
- }
120
- if (
121
- context?.lastComponent &&
122
- typeof context.lastComponent.invalidate === 'function'
123
- ) {
124
- context.lastComponent.invalidate();
125
- }
126
-
127
- const isErr = Boolean(context?.isError);
128
- const status = options?.isPartial
129
- ? 'running'
130
- : isErr
131
- ? 'failed'
132
- : 'completed';
133
- const footerOptions = {
134
- status,
135
- context: { ...context, isPartial: options?.isPartial },
136
- } as const;
137
- syncElapsedTicker(footerOptions.context);
138
- const borderTone = getToolBorderTone({
139
- isError: isErr,
140
- isPartial: options?.isPartial,
141
- });
142
- const textOutput = getRawResultText(result);
143
-
144
- if (isErr) {
145
- return createComponent((width: number) => {
146
- const safeWidth = Math.max(0, width);
147
- const footer = toolFooter(theme, footerOptions, config.icons);
148
- const errText = `${theme.fg('error', `${statusIcon('warning', config.icons)} `)}${theme.fg('error', escapeControlCharacters(textOutput || 'Find failed'))}`;
149
- if (!isFramedContext(context)) {
150
- return [
151
- truncateToWidth(errText, safeWidth),
152
- ...renderFrameBottom(theme, footer, safeWidth, borderTone),
153
- ];
154
- }
155
- return [
156
- ...renderFrameRow(theme, errText, safeWidth, borderTone),
157
- ...renderFrameBottom(theme, footer, safeWidth, borderTone),
158
- ];
159
- });
160
- }
161
-
162
- const { entries, notices } = parseFindOutput(textOutput, result.details);
163
- const total = entries.length;
164
-
165
- return createComponent((width: number) => {
166
- const safeWidth = Math.max(0, width);
167
- const footer = toolFooter(theme, footerOptions, config.icons);
168
- if (safeWidth === 0) return [];
169
- if (total === 0 && notices.length === 0) {
170
- if (!isFramedContext(context)) {
171
- return [
172
- truncateToWidth(theme.fg('dim', 'no matches found'), safeWidth),
173
- ...renderFrameBottom(theme, footer, safeWidth, borderTone),
174
- ];
175
- }
176
- return [
177
- ...renderFrameRow(
178
- theme,
179
- theme.fg('dim', 'no matches found'),
180
- safeWidth,
181
- borderTone,
182
- ),
183
- ...renderFrameBottom(theme, footer, safeWidth, borderTone),
184
- ];
185
- }
186
-
187
- const rawLines: string[] = [];
188
- const visible = options?.expanded
189
- ? entries
190
- : entries.slice(0, COLLAPSED_ENTRY_LIMIT);
191
- const remaining = total - visible.length;
192
-
193
- for (let i = 0; i < visible.length; i++) {
194
- const entry = visible[i];
195
- const isLast = i === visible.length - 1 && remaining === 0;
196
- const branch = isLast ? '└─' : '├─';
197
- const icon = getFileIcon(entry, config.icons);
198
- const line = `${theme.fg('dim', branch)} ${theme.fg('accent', icon)} ${theme.fg('toolOutput', escapeControlCharacters(entry))}`;
199
- rawLines.push(line);
200
- }
201
-
202
- if (remaining > 0) {
203
- const more = `${theme.fg('dim', '└─')} ${theme.fg('dim', `${semanticIcon('ellipsis', config.icons)} ${remaining} more matches ${semanticIcon('separator', config.icons)} ctrl+o to expand`)}`;
204
- rawLines.push(more);
205
- }
206
-
207
- for (const notice of notices) {
208
- rawLines.push(theme.fg('warning', notice));
209
- }
210
-
211
- if (!isFramedContext(context)) {
212
- return [
213
- ...rawLines.map((line) => truncateToWidth(` ${line}`, safeWidth)),
214
- ...renderFrameBottom(theme, footer, safeWidth, borderTone),
215
- ];
216
- }
217
-
218
- return [
219
- ...rawLines.flatMap((line) =>
220
- renderFrameRow(theme, line, safeWidth, borderTone),
221
- ),
222
- ...renderFrameBottom(theme, footer, safeWidth, borderTone),
223
- ];
224
- });
225
- },
226
- };
227
- }