loom-agent 1.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.
- package/.env.example +25 -0
- package/CHANGELOG.md +402 -0
- package/LICENSE +21 -0
- package/LOOM.md +235 -0
- package/README.md +433 -0
- package/bin/loom-tui.js +43 -0
- package/bin/loom.js +44 -0
- package/docs/acp.md +151 -0
- package/docs/web.md +205 -0
- package/package.json +97 -0
- package/scripts/acp-smoke.js +146 -0
- package/src/acp/acp-server.js +287 -0
- package/src/config/provider-cmd.js +37 -0
- package/src/config/settings.js +164 -0
- package/src/core/agents.js +361 -0
- package/src/core/background-tasks.js +103 -0
- package/src/core/cli.js +579 -0
- package/src/core/custom-commands.js +70 -0
- package/src/core/errors.js +29 -0
- package/src/core/events.js +24 -0
- package/src/core/file-diffs.js +282 -0
- package/src/core/format.js +206 -0
- package/src/core/graph.js +257 -0
- package/src/core/hooks.js +82 -0
- package/src/core/lsp.js +385 -0
- package/src/core/memory.js +87 -0
- package/src/core/model-router.js +87 -0
- package/src/core/permissions.js +327 -0
- package/src/core/platform.js +33 -0
- package/src/core/plugin-cmd.js +380 -0
- package/src/core/restore.js +207 -0
- package/src/core/session-store.js +167 -0
- package/src/core/session.js +910 -0
- package/src/core/subagent-log.js +134 -0
- package/src/core/tokens.js +31 -0
- package/src/core/update.js +6 -0
- package/src/core/usage.js +166 -0
- package/src/index.js +41 -0
- package/src/mcp/mcp-client.js +201 -0
- package/src/mcp/mcp-manager.js +193 -0
- package/src/providers/anthropic.js +243 -0
- package/src/providers/google.js +29 -0
- package/src/providers/index.js +175 -0
- package/src/providers/local.js +27 -0
- package/src/providers/nvidia.js +85 -0
- package/src/providers/openai-compat.js +269 -0
- package/src/providers/openai.js +35 -0
- package/src/providers/openrouter.js +43 -0
- package/src/providers/registry.js +196 -0
- package/src/providers/tokenrouter.js +19 -0
- package/src/skills/skill-matcher.js +133 -0
- package/src/skills/skills-manager.js +213 -0
- package/src/tools/index.js +543 -0
- package/src/tui/App.tsx +1578 -0
- package/src/tui/components/BreadcrumbBar.tsx +34 -0
- package/src/tui/components/ChatArea.tsx +518 -0
- package/src/tui/components/InputBar.tsx +354 -0
- package/src/tui/components/MdText.tsx +105 -0
- package/src/tui/components/Modals.tsx +851 -0
- package/src/tui/components/PermissionPopup.tsx +264 -0
- package/src/tui/components/Sidebar.tsx +182 -0
- package/src/tui/components/SplashScreen.tsx +51 -0
- package/src/tui/components/SubagentPanel.tsx +217 -0
- package/src/tui/components/ToastOverlay.tsx +34 -0
- package/src/tui/keybinds.ts +318 -0
- package/src/tui/mcp-presets.ts +189 -0
- package/src/tui/md-render.ts +228 -0
- package/src/tui/store.ts +714 -0
- package/src/tui/suite-home.ts +20 -0
- package/src/tui/theme.ts +313 -0
- package/src/tui/themes.generated.ts +968 -0
- package/src/tui/tool-display.ts +176 -0
- package/src/tui/toolname.ts +60 -0
- package/src/tui/tui-config.ts +28 -0
- package/src/tui-open.tsx +51 -0
- package/src/web/attach.js +242 -0
- package/src/web/graph-view.html +262 -0
- package/src/web/index.html +824 -0
- package/src/web/web-server.js +470 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Isolate the entire interactive suite from the developer's real home
|
|
2
|
+
// configuration: create a throwaway HOME/USERPROFILE BEFORE the App and its
|
|
3
|
+
// theme/store/keybind modules initialize, so tui.json, keybinds.json, and
|
|
4
|
+
// session prefs are read and written inside the temp dir only.
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import os from "os";
|
|
8
|
+
|
|
9
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "loom-suite-home-"));
|
|
10
|
+
process.env.HOME = dir;
|
|
11
|
+
process.env.USERPROFILE = dir;
|
|
12
|
+
process.env.LOOM_CONFIG_DIR = path.join(dir, ".loom");
|
|
13
|
+
fs.mkdirSync(process.env.LOOM_CONFIG_DIR, { recursive: true });
|
|
14
|
+
// Seed one harmless MCP server: the /mcp popup test toggles servers[0] and
|
|
15
|
+
// the /sessions picker renders the MCP status column — both need a server.
|
|
16
|
+
fs.writeFileSync(path.join(process.env.LOOM_CONFIG_DIR, "mcp.json"), JSON.stringify({
|
|
17
|
+
servers: { "suite-local": { command: "echo", args: ["suite-mcp-ok"], enabled: true } },
|
|
18
|
+
}, null, 2));
|
|
19
|
+
|
|
20
|
+
(globalThis as any).__LOOM_SUITE_HOME = dir;
|
package/src/tui/theme.ts
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
// Loom Code TUI — color themes + palette access.
|
|
2
|
+
// palette() returns a live Proxy: each property is read from the currently
|
|
3
|
+
// selected theme at render time, so switching themes re-renders instantly.
|
|
4
|
+
import { createSignal } from "solid-js";
|
|
5
|
+
import { loadTuiJson, saveTuiJson } from "./tui-config.ts";
|
|
6
|
+
|
|
7
|
+
export type Palette = {
|
|
8
|
+
bg: string; bgMsg: string; bgPanel: string; bgPanelAlt: string; bgInput: string; bgHover: string;
|
|
9
|
+
border: string; borderFocus: string;
|
|
10
|
+
fg: string; fgDim: string; fgMuted: string;
|
|
11
|
+
primary: string; // brand accent
|
|
12
|
+
primarySoft: string;
|
|
13
|
+
secondary: string; // user accent
|
|
14
|
+
accent: string; // tool activity
|
|
15
|
+
success: string; warning: string; error: string;
|
|
16
|
+
thinking: string;
|
|
17
|
+
// Syntax colors for markdown code blocks — each theme ships real VS Code
|
|
18
|
+
// TextMate colors (keyword/string/comment/number/call/base) via fetch-themes.
|
|
19
|
+
syntax: { kw: string; str: string; com: string; num: string; call: string; plain: string };
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type Theme = { id: string; label: string; desc: string; palette: Palette };
|
|
23
|
+
|
|
24
|
+
const LOOM_DARK: Palette = {
|
|
25
|
+
// Deep warm charcoal — terracotta accents
|
|
26
|
+
bg: "#191817",
|
|
27
|
+
bgMsg: "#131211",
|
|
28
|
+
bgPanel: "#211f1e",
|
|
29
|
+
bgPanelAlt: "#2a2725",
|
|
30
|
+
bgInput: "#25221f",
|
|
31
|
+
bgHover: "#322e2a",
|
|
32
|
+
border: "#4a4440",
|
|
33
|
+
borderFocus: "#e07856",
|
|
34
|
+
fg: "#f0ebe4",
|
|
35
|
+
fgDim: "#a89e93",
|
|
36
|
+
fgMuted: "#6e655c",
|
|
37
|
+
primary: "#e07856",
|
|
38
|
+
primarySoft: "#f4a27f",
|
|
39
|
+
secondary: "#b8d0a8",
|
|
40
|
+
accent: "#e8b06f",
|
|
41
|
+
success: "#a3c084",
|
|
42
|
+
warning: "#e0b356",
|
|
43
|
+
error: "#e06c5a",
|
|
44
|
+
thinking: "#d9a35f",
|
|
45
|
+
syntax: {
|
|
46
|
+
kw: "#e07856",
|
|
47
|
+
str: "#a3c084",
|
|
48
|
+
com: "#6e655c",
|
|
49
|
+
num: "#e0b356",
|
|
50
|
+
call: "#e8b06f",
|
|
51
|
+
plain: "#f0ebe4",
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const LIGHT: Palette = {
|
|
56
|
+
// Paper light — warm parchment with terracotta
|
|
57
|
+
bg: "#faf7f2",
|
|
58
|
+
bgMsg: "#efe9df",
|
|
59
|
+
bgPanel: "#f3eee6",
|
|
60
|
+
bgPanelAlt: "#eae3d8",
|
|
61
|
+
bgInput: "#ffffff",
|
|
62
|
+
bgHover: "#e0d6c8",
|
|
63
|
+
border: "#c9bfae",
|
|
64
|
+
borderFocus: "#c2593a",
|
|
65
|
+
fg: "#2b2620",
|
|
66
|
+
fgDim: "#6f675c",
|
|
67
|
+
fgMuted: "#9a9082",
|
|
68
|
+
primary: "#c2593a",
|
|
69
|
+
primarySoft: "#a34a2f",
|
|
70
|
+
secondary: "#5c7a4a",
|
|
71
|
+
accent: "#b0742f",
|
|
72
|
+
success: "#5f8a3d",
|
|
73
|
+
warning: "#a97a1a",
|
|
74
|
+
error: "#b33c2e",
|
|
75
|
+
thinking: "#a5682a",
|
|
76
|
+
syntax: {
|
|
77
|
+
kw: "#c2593a",
|
|
78
|
+
str: "#5f8a3d",
|
|
79
|
+
com: "#9a9082",
|
|
80
|
+
num: "#a97a1a",
|
|
81
|
+
call: "#b0742f",
|
|
82
|
+
plain: "#2b2620",
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const OCEAN: Palette = {
|
|
87
|
+
// Deep navy — cyan accents
|
|
88
|
+
bg: "#0e1a26",
|
|
89
|
+
bgMsg: "#0a141d",
|
|
90
|
+
bgPanel: "#132433",
|
|
91
|
+
bgPanelAlt: "#182c3d",
|
|
92
|
+
bgInput: "#16293a",
|
|
93
|
+
bgHover: "#1e3346",
|
|
94
|
+
border: "#2f4a61",
|
|
95
|
+
borderFocus: "#4fc3f7",
|
|
96
|
+
fg: "#e3eef6",
|
|
97
|
+
fgDim: "#9db4c6",
|
|
98
|
+
fgMuted: "#6d8599",
|
|
99
|
+
primary: "#4fc3f7",
|
|
100
|
+
primarySoft: "#81d4fa",
|
|
101
|
+
secondary: "#80cbc4",
|
|
102
|
+
accent: "#ffb74d",
|
|
103
|
+
success: "#81c784",
|
|
104
|
+
warning: "#ffd54f",
|
|
105
|
+
error: "#e57373",
|
|
106
|
+
thinking: "#ffb74d",
|
|
107
|
+
syntax: {
|
|
108
|
+
kw: "#4fc3f7",
|
|
109
|
+
str: "#81c784",
|
|
110
|
+
com: "#6d8599",
|
|
111
|
+
num: "#ffd54f",
|
|
112
|
+
call: "#ffb74d",
|
|
113
|
+
plain: "#e3eef6",
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const FOREST: Palette = {
|
|
118
|
+
// Dark evergreen — sage accents
|
|
119
|
+
bg: "#0f1a12",
|
|
120
|
+
bgMsg: "#0b130d",
|
|
121
|
+
bgPanel: "#152319",
|
|
122
|
+
bgPanelAlt: "#1b2d20",
|
|
123
|
+
bgInput: "#182a1e",
|
|
124
|
+
bgHover: "#223726",
|
|
125
|
+
border: "#33503a",
|
|
126
|
+
borderFocus: "#7bd88f",
|
|
127
|
+
fg: "#e8f2e9",
|
|
128
|
+
fgDim: "#9db3a0",
|
|
129
|
+
fgMuted: "#6b8270",
|
|
130
|
+
primary: "#7bd88f",
|
|
131
|
+
primarySoft: "#a3e5b2",
|
|
132
|
+
secondary: "#c6d8a8",
|
|
133
|
+
accent: "#e0c568",
|
|
134
|
+
success: "#9ccc65",
|
|
135
|
+
warning: "#d4c34f",
|
|
136
|
+
error: "#e08070",
|
|
137
|
+
thinking: "#c9a95c",
|
|
138
|
+
syntax: {
|
|
139
|
+
kw: "#7bd88f",
|
|
140
|
+
str: "#9ccc65",
|
|
141
|
+
com: "#6b8270",
|
|
142
|
+
num: "#d4c34f",
|
|
143
|
+
call: "#e0c568",
|
|
144
|
+
plain: "#e8f2e9",
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const MIDNIGHT: Palette = {
|
|
149
|
+
// Near-black violet — lavender accents
|
|
150
|
+
bg: "#131019",
|
|
151
|
+
bgMsg: "#0f0c15",
|
|
152
|
+
bgPanel: "#1a1625",
|
|
153
|
+
bgPanelAlt: "#211c30",
|
|
154
|
+
bgInput: "#1d1830",
|
|
155
|
+
bgHover: "#2a2340",
|
|
156
|
+
border: "#3d3356",
|
|
157
|
+
borderFocus: "#a78bfa",
|
|
158
|
+
fg: "#ede9f5",
|
|
159
|
+
fgDim: "#a59bbd",
|
|
160
|
+
fgMuted: "#6f6690",
|
|
161
|
+
primary: "#a78bfa",
|
|
162
|
+
primarySoft: "#c4b5fd",
|
|
163
|
+
secondary: "#94d2bd",
|
|
164
|
+
accent: "#f6ad55",
|
|
165
|
+
success: "#9ae6b4",
|
|
166
|
+
warning: "#fde68a",
|
|
167
|
+
error: "#f87171",
|
|
168
|
+
thinking: "#f0abfc",
|
|
169
|
+
syntax: {
|
|
170
|
+
kw: "#a78bfa",
|
|
171
|
+
str: "#9ae6b4",
|
|
172
|
+
com: "#6f6690",
|
|
173
|
+
num: "#fde68a",
|
|
174
|
+
call: "#f6ad55",
|
|
175
|
+
plain: "#ede9f5",
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const MONO: Palette = {
|
|
180
|
+
// Pure monochrome — greys only
|
|
181
|
+
bg: "#111111",
|
|
182
|
+
bgMsg: "#0d0d0d",
|
|
183
|
+
bgPanel: "#1a1a1a",
|
|
184
|
+
bgPanelAlt: "#242424",
|
|
185
|
+
bgInput: "#1c1c1c",
|
|
186
|
+
bgHover: "#2b2b2b",
|
|
187
|
+
border: "#3c3c3c",
|
|
188
|
+
borderFocus: "#e8e8e8",
|
|
189
|
+
fg: "#e8e8e8",
|
|
190
|
+
fgDim: "#9c9c9c",
|
|
191
|
+
fgMuted: "#666666",
|
|
192
|
+
primary: "#e8e8e8",
|
|
193
|
+
primarySoft: "#c4c4c4",
|
|
194
|
+
secondary: "#b8b8b8",
|
|
195
|
+
accent: "#cccccc",
|
|
196
|
+
success: "#9f9f9f",
|
|
197
|
+
warning: "#bdbdbd",
|
|
198
|
+
error: "#e0e0e0",
|
|
199
|
+
thinking: "#b0b0b0",
|
|
200
|
+
syntax: {
|
|
201
|
+
kw: "#e8e8e8",
|
|
202
|
+
str: "#b8b8b8",
|
|
203
|
+
com: "#666666",
|
|
204
|
+
num: "#bdbdbd",
|
|
205
|
+
call: "#cccccc",
|
|
206
|
+
plain: "#e8e8e8",
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
import { GENERATED_THEMES } from "./themes.generated.ts";
|
|
211
|
+
|
|
212
|
+
export const THEMES: Record<string, Theme> = {
|
|
213
|
+
loom: { id: "loom", label: "Loom Dark", desc: "Warm charcoal + terracotta (default)", palette: LOOM_DARK },
|
|
214
|
+
light: { id: "light", label: "Light", desc: "Parchment light + terracotta", palette: LIGHT },
|
|
215
|
+
ocean: { id: "ocean", label: "Ocean", desc: "Deep navy + cyan", palette: OCEAN },
|
|
216
|
+
forest: { id: "forest", label: "Forest", desc: "Dark evergreen + sage", palette: FOREST },
|
|
217
|
+
midnight: { id: "midnight", label: "Midnight", desc: "Violet-black + lavender", palette: MIDNIGHT },
|
|
218
|
+
mono: { id: "mono", label: "Mono", desc: "Pure greyscale", palette: MONO },
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Safety net: shiki-sourced themes sometimes carry transparent or
|
|
222
|
+
// background-identical border colors, which makes every UI grid line vanish.
|
|
223
|
+
// Accept a border only when it clears a real contrast bar against `bg`;
|
|
224
|
+
// otherwise derive one (lighten dark themes, darken light themes).
|
|
225
|
+
function visibleBorder(bg: string, border: string): string {
|
|
226
|
+
const hex = (h: string) => /^#?([0-9a-f]{6})/i.exec(String(h).trim())?.[1];
|
|
227
|
+
const lum = (h: string) => {
|
|
228
|
+
const m = hex(h);
|
|
229
|
+
if (!m) return 0;
|
|
230
|
+
const n = parseInt(m, 16);
|
|
231
|
+
return 0.299 * (n >> 16 & 255) + 0.587 * (n >> 8 & 255) + 0.114 * (n & 255);
|
|
232
|
+
};
|
|
233
|
+
const base = hex(bg) || "1e1e1e";
|
|
234
|
+
const raw = hex(border);
|
|
235
|
+
if (raw && Math.abs(lum("#" + raw) - lum("#" + base)) >= 28) return border;
|
|
236
|
+
const L = lum("#" + base);
|
|
237
|
+
// Channel shifts run red-green-blue order; a negative factor SUBTRACTS the
|
|
238
|
+
// requested proportion of each channel, and every channel clamps to 0-255
|
|
239
|
+
// so the derived border hex never overflows/underflows.
|
|
240
|
+
const mix = (f: number) => {
|
|
241
|
+
const hex2 = (v: number) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, "0");
|
|
242
|
+
const ch = (s: number) => {
|
|
243
|
+
const c = parseInt(base, 16) >> s & 255;
|
|
244
|
+
return f > 0 ? hex2(c + (255 - c) * f) : hex2(c - c * Math.abs(f));
|
|
245
|
+
};
|
|
246
|
+
return "#" + ch(16) + ch(8) + ch(0);
|
|
247
|
+
};
|
|
248
|
+
return L < 128 ? mix(0.38) : mix(-0.35);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Stitch in the generated (shiki-sourced) themes after the local defaults.
|
|
252
|
+
// Ids from the registry win on collision — the six above always take priority.
|
|
253
|
+
// Each generated palette is validated on a COPY: missing fields (including
|
|
254
|
+
// syntax) are filled from LOOM_DARK and the border is derived on the copy, so
|
|
255
|
+
// the imported generated object is never mutated.
|
|
256
|
+
for (const t of GENERATED_THEMES) {
|
|
257
|
+
if (!THEMES[t.id]) {
|
|
258
|
+
const src: any = (t as Theme).palette || {};
|
|
259
|
+
const p: any = Object.assign({}, LOOM_DARK, src);
|
|
260
|
+
if (!p.syntax || typeof p.syntax !== "object") p.syntax = {};
|
|
261
|
+
p.syntax = Object.assign({}, LOOM_DARK.syntax, p.syntax);
|
|
262
|
+
p.border = visibleBorder(p.bg, p.border);
|
|
263
|
+
THEMES[t.id] = { id: t.id, label: t.label, desc: t.desc, palette: p };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function loadSavedTheme(): string {
|
|
268
|
+
const data = loadTuiJson();
|
|
269
|
+
if (typeof data.theme === "string" && THEMES[data.theme]) return data.theme;
|
|
270
|
+
return "loom";
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function saveThemePref(id: string) {
|
|
274
|
+
saveTuiJson({ theme: id });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const [themeId, setThemeId] = createSignal(loadSavedTheme());
|
|
278
|
+
|
|
279
|
+
export function setTheme(id: string) {
|
|
280
|
+
if (!THEMES[id]) return false;
|
|
281
|
+
setThemeId(id);
|
|
282
|
+
saveThemePref(id);
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function themeName() {
|
|
287
|
+
return themeId();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function themeOptions(): { id: string; label: string; desc: string }[] {
|
|
291
|
+
return Object.values(THEMES).map(t => ({ id: t.id, label: t.label, desc: t.desc }));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function palette(_name?: string): Palette {
|
|
295
|
+
return new Proxy(LOOM_DARK, {
|
|
296
|
+
get(_target, prop) {
|
|
297
|
+
const cur = THEMES[themeId()];
|
|
298
|
+
const p = (cur && cur.palette) || LOOM_DARK;
|
|
299
|
+
return (p as any)[prop as string];
|
|
300
|
+
},
|
|
301
|
+
}) as Palette;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export const VERSION = "1.2.0";
|
|
305
|
+
|
|
306
|
+
export const LOOM_LOGO = [
|
|
307
|
+
" ██╗ ██████╗ ██████╗ ███╗ ███╗ ██████╗ ██████╗ ██████╗ ███████╗",
|
|
308
|
+
" ██║ ██╔═══██╗██╔═══██╗████╗ ████║ ██╔════╝██╔═══██╗██╔══██╗██╔════╝",
|
|
309
|
+
" ██║ ██║ ██║██║ ██║██╔████╔██║ ██║ ██║ ██║██║ ██║█████╗ ",
|
|
310
|
+
" ██║ ██║ ██║██║ ██║██║╚██╔╝██║ ██║ ██║ ██║██║ ██║██╔══╝ ",
|
|
311
|
+
" ███████╗╚██████╔╝╚██████╔╝██║ ╚═╝ ██║ ╚██████╗██████╔╝██████╔╝███████╗",
|
|
312
|
+
" ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚═════╝ ╚═════╝ ╚══════╝",
|
|
313
|
+
];
|