ucode-agent 1.64.0 → 1.67.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 (63) hide show
  1. package/LICENSE +662 -21
  2. package/LICENSE-DOCS +404 -0
  3. package/NOTICE +47 -0
  4. package/README.md +104 -13
  5. package/package.json +4 -3
  6. package/skills/LICENSE +19 -0
  7. package/src/core/attach.js +1 -0
  8. package/src/core/commands.js +1 -0
  9. package/src/core/context.js +1 -0
  10. package/src/core/csscheck.js +1 -0
  11. package/src/core/doctor.js +1 -0
  12. package/src/core/failure.js +1 -0
  13. package/src/core/genericcheck.js +1 -0
  14. package/src/core/git.js +1 -0
  15. package/src/core/headless.js +1 -0
  16. package/src/core/history.js +1 -0
  17. package/src/core/htmlcheck.js +1 -0
  18. package/src/core/jslogic.js +1 -0
  19. package/src/core/lessons.js +1 -0
  20. package/src/core/livelog.js +1 -0
  21. package/src/core/login.js +1 -0
  22. package/src/core/loop.js +4064 -3887
  23. package/src/core/mcp.js +1 -0
  24. package/src/core/mcpcli.js +1 -0
  25. package/src/core/openable.js +1 -0
  26. package/src/core/opener.js +1 -0
  27. package/src/core/provider.js +1 -0
  28. package/src/core/relink.js +1 -0
  29. package/src/core/scope.js +1 -0
  30. package/src/core/settings.js +1 -0
  31. package/src/core/skills.js +1 -0
  32. package/src/core/snapshot.js +1 -0
  33. package/src/core/stuck.js +1 -0
  34. package/src/core/terminal.js +257 -0
  35. package/src/core/tests.js +1 -0
  36. package/src/core/undo.js +1 -0
  37. package/src/core/updater.js +1 -0
  38. package/src/core/version.js +1 -0
  39. package/src/core/voice.js +1 -0
  40. package/src/core/window.js +1 -0
  41. package/src/tools/blocks.js +1 -0
  42. package/src/tools/browser.js +1 -0
  43. package/src/tools/cache.js +1 -0
  44. package/src/tools/deploy.js +1 -0
  45. package/src/tools/files.js +1 -0
  46. package/src/tools/fuzzy.js +1 -0
  47. package/src/tools/index.js +5 -0
  48. package/src/tools/rename.js +1 -0
  49. package/src/tools/scaffold.js +1 -0
  50. package/src/tools/search.js +1 -0
  51. package/src/tools/shared.js +2 -1
  52. package/src/tools/shell.js +1 -0
  53. package/src/tools/symbols.js +1 -0
  54. package/src/tools/types.js +1 -0
  55. package/src/tools/web.js +1 -0
  56. package/src/ui/activity.js +3 -2
  57. package/src/ui/markdown.js +1 -0
  58. package/src/ui/plain.js +4 -3
  59. package/src/ui/screen.js +2012 -1833
  60. package/src/ui/theme.js +151 -11
  61. package/templates/LICENSE +23 -0
  62. package/ucode.js +1 -0
  63. package/LICENSE-APACHE +0 -202
package/src/core/mcp.js CHANGED
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * mcp.js — tools from MCP servers: GitHub, docs, databases, whatever the user connects.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * mcpcli.js — `ucode mcp add | list | remove`, the way to connect a server
3
4
  * without editing JSON by hand. Servers go in ~/.ucode/mcp.json (every
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * openable.js — a page that works when you double-click it.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * opener.js — showing a finished page or a running server in the user's own
3
4
  * browser.
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * provider.js — the only module that knows a model provider exists.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * relink.js — taking an app's own folder back out of its own links.
3
4
  *
package/src/core/scope.js CHANGED
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * scope.js — keeping a new app to what was asked for.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * settings.js — what ucode may do without asking, and what runs around its work.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * skills.js — instruction packs, disclosed progressively.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * snapshot.js — the whole project as it was before each turn, for /undo.
3
4
  *
package/src/core/stuck.js CHANGED
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * stuck.js — noticing when the model is going round in circles.
3
4
  *
@@ -0,0 +1,257 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
2
+ /**
3
+ * terminal.js — changing the terminal ucode runs in, when the user asks:
4
+ * "make my terminal dark green", "bigger font", "a bit see-through".
5
+ *
6
+ * Each terminal keeps its look somewhere different, so each gets its own way:
7
+ *
8
+ * Windows Terminal its settings.json, which it reloads the moment it is saved.
9
+ * Kept: every new tab looks the same. The file as it was is
10
+ * backed up first, and `reset` puts it back.
11
+ * Terminal.app AppleScript on the window ucode is in: colours, cursor,
12
+ * font and size. That window only; the old values are kept
13
+ * so `reset` can restore them.
14
+ * iTerm2 its escape sequence for colours, this session only.
15
+ * anything else the standard xterm escapes for colours, this session only.
16
+ *
17
+ * Nothing here runs unless the user asked, and the loop asks before it does.
18
+ */
19
+
20
+ import { execFile } from 'node:child_process';
21
+ import { promises as fs, existsSync } from 'node:fs';
22
+ import os from 'node:os';
23
+ import path from 'node:path';
24
+ import { jsonrepair } from 'jsonrepair';
25
+ import { normaliseColour, hexRGB } from '../ui/theme.js';
26
+
27
+ export const BACKUP_DIR = path.join(os.homedir(), '.ucode', 'terminal-backup');
28
+ const backups = () => process.env.UCODE_TERMINAL_BACKUP || BACKUP_DIR;
29
+
30
+ /** Which terminal this is, from what it puts in the environment. */
31
+ export function detectTerminal(env = process.env, platform = process.platform) {
32
+ if (env.WT_SESSION) return 'windows-terminal';
33
+ if (env.TERM_PROGRAM === 'Apple_Terminal') return 'apple-terminal';
34
+ if (env.TERM_PROGRAM === 'iTerm.app' || env.LC_TERMINAL === 'iTerm2') return 'iterm';
35
+ if (env.TERM_PROGRAM === 'vscode') return 'vscode';
36
+ if (platform === 'win32') return 'windows-console';
37
+ return 'other';
38
+ }
39
+
40
+ export const TERMINAL_NAMES = {
41
+ 'windows-terminal': 'Windows Terminal',
42
+ 'apple-terminal': 'Terminal',
43
+ iterm: 'iTerm2',
44
+ vscode: 'the VS Code terminal',
45
+ 'windows-console': 'the Windows console',
46
+ other: 'this terminal',
47
+ };
48
+
49
+ /** Where Windows Terminal keeps its settings: the Store build, Preview, or unpackaged. */
50
+ export function windowsTerminalSettings(env = process.env) {
51
+ const local = env.LOCALAPPDATA ?? path.join(os.homedir(), 'AppData', 'Local');
52
+ const candidates = [
53
+ path.join(local, 'Packages', 'Microsoft.WindowsTerminal_8wekyb3d8bbwe', 'LocalState', 'settings.json'),
54
+ path.join(local, 'Packages', 'Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe', 'LocalState', 'settings.json'),
55
+ path.join(local, 'Microsoft', 'Windows Terminal', 'settings.json'),
56
+ ];
57
+ return candidates.find((f) => existsSync(f)) ?? null;
58
+ }
59
+
60
+ /** What was asked for, cleaned: colours as #rrggbb, a sane size and opacity. */
61
+ export function cleanRequest(req = {}) {
62
+ const out = {};
63
+ for (const key of ['background', 'foreground', 'cursor']) {
64
+ if (req[key] === undefined || req[key] === null || req[key] === '') continue;
65
+ const colour = normaliseColour(req[key]);
66
+ if (!colour) throw new Error(`"${req[key]}" is not a colour ucode can read — use a name like "navy" or a hex like #1e1e2e`);
67
+ out[key] = colour;
68
+ }
69
+ if (req.font_size !== undefined && req.font_size !== null && req.font_size !== '') {
70
+ const size = Number(req.font_size);
71
+ if (!Number.isFinite(size) || size < 6 || size > 48) throw new Error('font_size should be a number from 6 to 48');
72
+ out.font_size = Math.round(size);
73
+ }
74
+ if (req.font) out.font = String(req.font).replace(/["\\\r\n]/g, '').slice(0, 60);
75
+ if (req.opacity !== undefined && req.opacity !== null && req.opacity !== '') {
76
+ const opacity = Number(req.opacity);
77
+ if (!Number.isFinite(opacity) || opacity < 20 || opacity > 100) throw new Error('opacity should be a number from 20 to 100');
78
+ out.opacity = Math.round(opacity);
79
+ }
80
+ return out;
81
+ }
82
+
83
+ const IMAGE_KEYS = ['backgroundImage', 'backgroundImageOpacity', 'backgroundImageStretchMode', 'backgroundImageAlignment'];
84
+
85
+ /** Whether any profile draws a picture behind the text. */
86
+ export const hasBackgroundImage = (settings) => {
87
+ const profiles = Array.isArray(settings?.profiles) ? settings.profiles : [settings?.profiles?.defaults, ...(settings?.profiles?.list ?? [])];
88
+ return profiles.some((p) => p?.backgroundImage);
89
+ };
90
+
91
+ /** Windows Terminal: set the look on the defaults, and on any profile that overrides it. */
92
+ export function applyToWindowsTerminal(settings, want) {
93
+ const next = structuredClone(settings);
94
+ if (Array.isArray(next.profiles)) next.profiles = { defaults: {}, list: next.profiles };
95
+ next.profiles ??= {};
96
+ next.profiles.defaults ??= {};
97
+ const set = (profile) => {
98
+ if (want.background) {
99
+ profile.background = want.background;
100
+ // A background picture is drawn over the colour, so a colour set under
101
+ // one never shows: asked for black, the window stayed the picture's
102
+ // purple while ucode said it had changed. The picture goes, and the
103
+ // backup keeps it for /theme terminal reset.
104
+ for (const key of IMAGE_KEYS) delete profile[key];
105
+ }
106
+ if (want.foreground) profile.foreground = want.foreground;
107
+ if (want.cursor) profile.cursorColor = want.cursor;
108
+ if (want.font || want.font_size) {
109
+ profile.font = { ...(profile.font ?? {}), ...(want.font ? { face: want.font } : {}), ...(want.font_size ? { size: want.font_size } : {}) };
110
+ }
111
+ if (want.opacity !== undefined) {
112
+ profile.opacity = want.opacity;
113
+ profile.useAcrylic = want.opacity < 100;
114
+ }
115
+ };
116
+ set(next.profiles.defaults);
117
+ // A profile with its own value would hide the default, so it changes too.
118
+ const keys = ['background', 'foreground', 'cursorColor', 'font', 'opacity', 'backgroundImage'];
119
+ for (const profile of next.profiles.list ?? []) {
120
+ if (keys.some((k) => profile[k] !== undefined)) set(profile);
121
+ }
122
+ return next;
123
+ }
124
+
125
+ const run = (file, args) => new Promise((resolve) => {
126
+ execFile(file, args, { windowsHide: true, timeout: 15_000 }, (err, stdout, stderr) =>
127
+ resolve({ ok: !err, out: String(stdout ?? '').trim(), err: String(stderr || err?.message || '').trim() }));
128
+ });
129
+
130
+ /** AppleScript's colours run 0-65535. */
131
+ const appleColour = (hex) => `{${hexRGB(hex).map((v) => v * 257).join(', ')}}`;
132
+ const TAB = 'selected tab of front window';
133
+
134
+ async function appleTerminal(want) {
135
+ const backup = path.join(backups(), 'apple-terminal.json');
136
+ if (!existsSync(backup)) {
137
+ const get = await run('osascript', ['-e', `tell application "Terminal" to get {background color, normal text color, cursor color, font name, font size} of ${TAB}`]);
138
+ if (get.ok) {
139
+ await fs.mkdir(backups(), { recursive: true });
140
+ await fs.writeFile(backup, JSON.stringify({ raw: get.out }));
141
+ }
142
+ }
143
+ const lines = [];
144
+ if (want.background) lines.push(`set background color of ${TAB} to ${appleColour(want.background)}`);
145
+ if (want.foreground) lines.push(`set normal text color of ${TAB} to ${appleColour(want.foreground)}`);
146
+ if (want.cursor) lines.push(`set cursor color of ${TAB} to ${appleColour(want.cursor)}`);
147
+ if (want.font) lines.push(`set font name of ${TAB} to "${want.font}"`);
148
+ if (want.font_size) lines.push(`set font size of ${TAB} to ${want.font_size}`);
149
+ if (!lines.length) return { ok: false, message: 'Terminal can change colours, the cursor, the font and its size here — not the opacity.' };
150
+ const script = ['tell application "Terminal"', ...lines.map((l) => ` ${l}`), 'end tell'].join('\n');
151
+ const done = await run('osascript', ['-e', script]);
152
+ return done.ok
153
+ ? { ok: true, message: `Changed this Terminal window${want.opacity !== undefined ? ' (opacity is not something Terminal lets ucode set)' : ''}.` }
154
+ : { ok: false, message: `Terminal refused: ${done.err}. macOS may need ucode's terminal allowed under Privacy & Security > Automation.` };
155
+ }
156
+
157
+ /** The escape sequences for colours: iTerm2's own, or the xterm ones nearly everything reads. */
158
+ export function colourEscapes(kind, want) {
159
+ const hex = (h) => h.slice(1);
160
+ if (kind === 'iterm') {
161
+ return [
162
+ want.background && `\x1b]1337;SetColors=bg=${hex(want.background)}\x07`,
163
+ want.foreground && `\x1b]1337;SetColors=fg=${hex(want.foreground)}\x07`,
164
+ want.cursor && `\x1b]1337;SetColors=curbg=${hex(want.cursor)}\x07`,
165
+ ].filter(Boolean).join('');
166
+ }
167
+ return [
168
+ want.foreground && `\x1b]10;${want.foreground}\x07`,
169
+ want.background && `\x1b]11;${want.background}\x07`,
170
+ want.cursor && `\x1b]12;${want.cursor}\x07`,
171
+ ].filter(Boolean).join('');
172
+ }
173
+
174
+ /**
175
+ * Change the terminal. Resolves to { ok, message } and never throws on a
176
+ * terminal that cannot do it; throws only on a request that makes no sense.
177
+ */
178
+ export async function customizeTerminal(request, { env = process.env, platform = process.platform, write = (s) => process.stdout.write(s) } = {}) {
179
+ const want = cleanRequest(request);
180
+ if (!Object.keys(want).length) throw new Error('Say what to change: background, foreground, cursor, font, font_size or opacity.');
181
+ const kind = detectTerminal(env, platform);
182
+
183
+ if (kind === 'windows-terminal') {
184
+ const file = windowsTerminalSettings(env);
185
+ if (!file) return { ok: false, message: 'Could not find Windows Terminal\'s settings.json.' };
186
+ const text = await fs.readFile(file, 'utf8');
187
+ if (!text.trim()) return { ok: false, message: 'Windows Terminal\'s settings.json is empty, so it was left alone.' };
188
+ let settings;
189
+ try {
190
+ settings = JSON.parse(jsonrepair(text.replace(/^/, '')));
191
+ } catch {
192
+ return { ok: false, message: 'Windows Terminal\'s settings.json could not be read, so it was left alone.' };
193
+ }
194
+ await fs.mkdir(backups(), { recursive: true });
195
+ const backup = path.join(backups(), 'windows-terminal.json');
196
+ if (!existsSync(backup)) await fs.writeFile(backup, JSON.stringify({ file, text }));
197
+ const next = applyToWindowsTerminal(settings, want);
198
+ await fs.writeFile(file, `${JSON.stringify(next, null, 4)}\n`);
199
+ // Read back what Windows Terminal will read, rather than trusting the write.
200
+ const check = JSON.parse(await fs.readFile(file, 'utf8'));
201
+ if (want.background && check.profiles?.defaults?.background !== want.background) {
202
+ return { ok: false, message: 'Windows Terminal\'s settings did not take the change.' };
203
+ }
204
+ const picture = want.background && hasBackgroundImage(settings)
205
+ ? ' Its background picture was taken off so the colour shows (it comes back with /theme terminal reset).'
206
+ : '';
207
+ return { ok: true, message: `Changed Windows Terminal — every tab, and it stays.${picture} "/theme terminal reset" puts it back.` };
208
+ }
209
+
210
+ if (kind === 'apple-terminal') return appleTerminal(want);
211
+
212
+ if (kind === 'windows-console') {
213
+ return { ok: false, message: 'The old Windows console cannot be changed from here. Windows Terminal (free in the Microsoft Store) can.' };
214
+ }
215
+
216
+ const escapes = colourEscapes(kind, want);
217
+ if (!escapes) return { ok: false, message: `${TERMINAL_NAMES[kind]} only lets ucode change its colours, not the font or opacity.` };
218
+ write(escapes);
219
+ const skipped = want.font || want.font_size || want.opacity !== undefined ? ' The font and opacity are not something it lets ucode set.' : '';
220
+ return { ok: true, message: `Changed ${TERMINAL_NAMES[kind]}'s colours for this session.${skipped}` };
221
+ }
222
+
223
+ /** Put the terminal back as it was before ucode changed it. */
224
+ export async function resetTerminal({ env = process.env, platform = process.platform, write = (s) => process.stdout.write(s) } = {}) {
225
+ const kind = detectTerminal(env, platform);
226
+ if (kind === 'windows-terminal') {
227
+ const backup = path.join(backups(), 'windows-terminal.json');
228
+ if (!existsSync(backup)) return { ok: false, message: 'ucode has not changed Windows Terminal, so there is nothing to put back.' };
229
+ const { file, text } = JSON.parse(await fs.readFile(backup, 'utf8'));
230
+ await fs.writeFile(file, text);
231
+ await fs.rm(backup, { force: true });
232
+ return { ok: true, message: 'Windows Terminal is back the way it was.' };
233
+ }
234
+ if (kind === 'apple-terminal') {
235
+ const backup = path.join(backups(), 'apple-terminal.json');
236
+ if (!existsSync(backup)) return { ok: false, message: 'ucode has not changed this Terminal window.' };
237
+ const { raw } = JSON.parse(await fs.readFile(backup, 'utf8'));
238
+ const n = raw.split(/,\s*/);
239
+ // {r,g,b} three times, then the font name and size.
240
+ if (n.length >= 11) {
241
+ const colour = (i) => `{${n.slice(i, i + 3).join(', ')}}`;
242
+ await run('osascript', ['-e', [
243
+ 'tell application "Terminal"',
244
+ ` set background color of ${TAB} to ${colour(0)}`,
245
+ ` set normal text color of ${TAB} to ${colour(3)}`,
246
+ ` set cursor color of ${TAB} to ${colour(6)}`,
247
+ ` set font name of ${TAB} to "${n[9].replace(/"/g, '')}"`,
248
+ ` set font size of ${TAB} to ${Number(n[10]) || 12}`,
249
+ 'end tell',
250
+ ].join('\n')]);
251
+ }
252
+ await fs.rm(backup, { force: true });
253
+ return { ok: true, message: 'This Terminal window is back the way it was.' };
254
+ }
255
+ write(kind === 'iterm' ? '\x1b]1337;SetColors=preset=Default\x07' : '\x1b]110\x07\x1b]111\x07\x1b]112\x07');
256
+ return { ok: true, message: 'The terminal\'s own colours are back.' };
257
+ }
package/src/core/tests.js CHANGED
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * tests.js — running the tests a change actually affects.
3
4
  *
package/src/core/undo.js CHANGED
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * undo.js — what every file looked like before this turn touched it.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * updater.js — staying current without anyone running npm by hand.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * version.js — the version, read from package.json rather than typed twice.
3
4
  *
package/src/core/voice.js CHANGED
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * voice.js — the mic button: say what you want instead of typing it.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * window.js — keeping a long conversation inside the model's context.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * blocks.js — pieces of an app that are already right.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * browser.js — looking at the app the way a person would.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * cache.js — the starter's packages, installed once and reused.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * deploy.js — put the app online on Vercel.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * files.js — reading and changing files.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * fuzzy.js — finding the text an edit meant when it is not in the file as written.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * index.js — the tool registry: schemas, argument checking, dispatch, and the
3
4
  * line the user reads while each one runs.
@@ -722,6 +723,10 @@ export function describe(name, args = {}) {
722
723
  return `Searching the web for ${clip(args.query, 60)}`;
723
724
  case 'load_skill':
724
725
  return `Loading the ${clip(args.name, 40)} skill`;
726
+ case 'change_look':
727
+ return args.reset ? 'Putting ucode\'s look back' : 'Changing how ucode looks';
728
+ case 'change_terminal':
729
+ return args.reset ? 'Putting the terminal back' : 'Changing the terminal';
725
730
  default:
726
731
  return `${name} ${clip(JSON.stringify(args), 60)}`;
727
732
  }
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * rename.js — renaming a name everywhere it is that name.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * scaffold.js — starting an app from a starter that is known to work.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * search.js — finding things: what is in a directory, which files match a
3
4
  * name pattern, and which lines match a regular expression.
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * shared.js — the plumbing every tool sits on.
3
4
  *
@@ -103,7 +104,7 @@ export async function confirm(action, detail, risk = 'write', always = null) {
103
104
  /** A command about to run, put to the user first when they asked for that. */
104
105
  export async function approveCommand(command, where) {
105
106
  if (!policy.askCommands || allowedNow(command)) return;
106
- await confirm(`run ${String(command).trim().slice(0, 160)}`, `in ${where}`, 'command', commandKey(command));
107
+ await confirm(`run ${String(command).trim()}`, `in ${where}`, 'command', commandKey(command));
107
108
  }
108
109
 
109
110
  // ---------------------------------------------------------------------------
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * shell.js — running commands.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * symbols.js — a map of what this codebase declares, so "where is the tip
3
4
  * calculated" is one lookup rather than five greps.
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * types.js — asking the app's own TypeScript what something actually is.
3
4
  *
package/src/tools/web.js CHANGED
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * web.js — looking things up outside the project.
3
4
  *
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * activity.js — what the status row shows while ucode is working.
3
4
  *
@@ -12,7 +13,7 @@
12
13
  */
13
14
 
14
15
  import chalk, { Chalk } from 'chalk';
15
- import { dim, sky, theme, clip, SPINNER, bannerRGB, bannerPaint } from './theme.js';
16
+ import { dim, sky, theme, clip, SPINNER, bannerRGB, bannerPaint, look, hexRGB } from './theme.js';
16
17
 
17
18
  /** One painter per colour level, so a test can ask for truecolour on a pipe. */
18
19
  const painters = new Map();
@@ -289,7 +290,7 @@ export function spinnerGlyph(frame, t, { level = chalk.level } = {}) {
289
290
  const glyph = SPINNER[((frame % SPINNER.length) + SPINNER.length) % SPINNER.length];
290
291
  if (level < 2) return theme.blue(glyph);
291
292
  const k = (Math.sin((Math.max(0, t) / 1000) * Math.PI * 2) + 1) / 2;
292
- const [r, g, b] = mix([0x2f, 0x6f, 0xe0], [0x9f, 0xc6, 0xff], k);
293
+ const [r, g, b] = mix(hexRGB(look.deep), hexRGB(look.light), k);
293
294
  return painter(level).rgb(r, g, b)(glyph);
294
295
  }
295
296
 
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * markdown.js — turning a reply into styled terminal text.
3
4
  *
package/src/ui/plain.js CHANGED
@@ -1,3 +1,4 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only - ucode, made and tested by om dixit. Additional terms: see NOTICE.
1
2
  /**
2
3
  * plain.js — the interface for when there is no terminal to draw on.
3
4
  *
@@ -14,7 +15,7 @@ import chalk from 'chalk';
14
15
  import {
15
16
  theme, blue, sky, dim, boxTop, boxBottom, boxRow,
16
17
  BANNER, BANNER_WIDTH, SPINNER, clip, shortenPath, asLabel, padVis, visLen, planLine, bannerPaint, modeChip, narrationMark, groupKind,
17
- tidyReply, trimAnswer,
18
+ tidyReply, trimAnswer, CREDIT,
18
19
  } from './theme.js';
19
20
  import { formatDuration, doneLine } from './activity.js';
20
21
  import { renderer, render } from './markdown.js';
@@ -101,7 +102,7 @@ export class Plain {
101
102
  ['keys', '/help'],
102
103
  ['', ''],
103
104
  ['', ''],
104
- ['', 'made with ❤ by om dixit'],
105
+ ['', CREDIT],
105
106
  ];
106
107
 
107
108
  // There is no input box to hang the status off here, so it goes on the
@@ -116,7 +117,7 @@ export class Plain {
116
117
  return ` ${bannerPaint(i)(art)} ${right}`;
117
118
  })
118
119
  : [
119
- ` ${blue.bold('U C O D E')} ${dim('terminal coding agent')}`,
120
+ ` ${blue.bold('U C O D E')} ${dim(CREDIT)}`,
120
121
  ...facts
121
122
  .filter(([label]) => label)
122
123
  .map(([label, value]) => ` ${dim(label.padEnd(9))}${chalk.white(clip(value, width - 16))}`),