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.
Files changed (79) hide show
  1. package/.env.example +25 -0
  2. package/CHANGELOG.md +402 -0
  3. package/LICENSE +21 -0
  4. package/LOOM.md +235 -0
  5. package/README.md +433 -0
  6. package/bin/loom-tui.js +43 -0
  7. package/bin/loom.js +44 -0
  8. package/docs/acp.md +151 -0
  9. package/docs/web.md +205 -0
  10. package/package.json +97 -0
  11. package/scripts/acp-smoke.js +146 -0
  12. package/src/acp/acp-server.js +287 -0
  13. package/src/config/provider-cmd.js +37 -0
  14. package/src/config/settings.js +164 -0
  15. package/src/core/agents.js +361 -0
  16. package/src/core/background-tasks.js +103 -0
  17. package/src/core/cli.js +579 -0
  18. package/src/core/custom-commands.js +70 -0
  19. package/src/core/errors.js +29 -0
  20. package/src/core/events.js +24 -0
  21. package/src/core/file-diffs.js +282 -0
  22. package/src/core/format.js +206 -0
  23. package/src/core/graph.js +257 -0
  24. package/src/core/hooks.js +82 -0
  25. package/src/core/lsp.js +385 -0
  26. package/src/core/memory.js +87 -0
  27. package/src/core/model-router.js +87 -0
  28. package/src/core/permissions.js +327 -0
  29. package/src/core/platform.js +33 -0
  30. package/src/core/plugin-cmd.js +380 -0
  31. package/src/core/restore.js +207 -0
  32. package/src/core/session-store.js +167 -0
  33. package/src/core/session.js +910 -0
  34. package/src/core/subagent-log.js +134 -0
  35. package/src/core/tokens.js +31 -0
  36. package/src/core/update.js +6 -0
  37. package/src/core/usage.js +166 -0
  38. package/src/index.js +41 -0
  39. package/src/mcp/mcp-client.js +201 -0
  40. package/src/mcp/mcp-manager.js +193 -0
  41. package/src/providers/anthropic.js +243 -0
  42. package/src/providers/google.js +29 -0
  43. package/src/providers/index.js +175 -0
  44. package/src/providers/local.js +27 -0
  45. package/src/providers/nvidia.js +85 -0
  46. package/src/providers/openai-compat.js +269 -0
  47. package/src/providers/openai.js +35 -0
  48. package/src/providers/openrouter.js +43 -0
  49. package/src/providers/registry.js +196 -0
  50. package/src/providers/tokenrouter.js +19 -0
  51. package/src/skills/skill-matcher.js +133 -0
  52. package/src/skills/skills-manager.js +213 -0
  53. package/src/tools/index.js +543 -0
  54. package/src/tui/App.tsx +1578 -0
  55. package/src/tui/components/BreadcrumbBar.tsx +34 -0
  56. package/src/tui/components/ChatArea.tsx +518 -0
  57. package/src/tui/components/InputBar.tsx +354 -0
  58. package/src/tui/components/MdText.tsx +105 -0
  59. package/src/tui/components/Modals.tsx +851 -0
  60. package/src/tui/components/PermissionPopup.tsx +264 -0
  61. package/src/tui/components/Sidebar.tsx +182 -0
  62. package/src/tui/components/SplashScreen.tsx +51 -0
  63. package/src/tui/components/SubagentPanel.tsx +217 -0
  64. package/src/tui/components/ToastOverlay.tsx +34 -0
  65. package/src/tui/keybinds.ts +318 -0
  66. package/src/tui/mcp-presets.ts +189 -0
  67. package/src/tui/md-render.ts +228 -0
  68. package/src/tui/store.ts +714 -0
  69. package/src/tui/suite-home.ts +20 -0
  70. package/src/tui/theme.ts +313 -0
  71. package/src/tui/themes.generated.ts +968 -0
  72. package/src/tui/tool-display.ts +176 -0
  73. package/src/tui/toolname.ts +60 -0
  74. package/src/tui/tui-config.ts +28 -0
  75. package/src/tui-open.tsx +51 -0
  76. package/src/web/attach.js +242 -0
  77. package/src/web/graph-view.html +262 -0
  78. package/src/web/index.html +824 -0
  79. package/src/web/web-server.js +470 -0
@@ -0,0 +1,262 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Loom Graph</title>
6
+ <style>
7
+ :root { color-scheme: dark; }
8
+ * { box-sizing: border-box; }
9
+ html, body { margin: 0; height: 100%; overflow: hidden;
10
+ background: #0f1117; color: #cbd2e0; font: 13px/1.45 ui-monospace, Consolas, monospace; }
11
+ #app { display: flex; height: 100%; }
12
+ #side { width: 260px; min-width: 260px; border-right: 1px solid #232838;
13
+ background: #141722; padding: 12px; overflow-y: auto; }
14
+ #side h1 { font-size: 15px; margin: 0 0 2px; color: #fff; }
15
+ #side .sub { color: #7c86a0; margin-bottom: 12px; }
16
+ #side h3 { font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
17
+ color: #7c86a0; margin: 14px 0 6px; }
18
+ #search { width: 100%; padding: 6px 8px; border: 1px solid #2b3146; border-radius: 4px;
19
+ background: #0f1117; color: #e6e9f2; outline: none; }
20
+ #filters label { display: flex; align-items: center; gap: 7px; padding: 3px 0;
21
+ cursor: pointer; }
22
+ #filters input { accent-color: #4f9cf9; }
23
+ .swatch { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
24
+ #main { flex: 1; position: relative; }
25
+ #canvas { width: 100%; height: 100%; display: block; cursor: grab; }
26
+ #canvas.dragging { cursor: grabbing; }
27
+ #panel { position: absolute; left: 16px; right: 16px; bottom: 16px;
28
+ background: #161a26; border: 1px solid #2b3146; border-radius: 6px;
29
+ padding: 12px 14px; display: none; max-height: 38%; overflow: auto; }
30
+ #panel h2 { margin: 0 0 4px; font-size: 14px; color: #fff; }
31
+ #panel .meta { color: #7c86a0; margin-bottom: 6px; }
32
+ #panel pre { margin: 0; white-space: pre-wrap; color: #aeb7cc; }
33
+ #stats { position: absolute; top: 10px; right: 14px; color: #7c86a0; font-size: 12px; }
34
+ #hint { position: absolute; top: 10px; left: 14px; color: #7c86a0; font-size: 12px; }
35
+ .pill { display: inline-block; padding: 1px 7px; margin: 1px 3px 1px 0;
36
+ background: #232838; border-radius: 9px; color: #9fb0d0; font-size: 11px; }
37
+ .empty { color: #7c86a0; padding: 8px 2px; }
38
+ </style>
39
+ </head>
40
+ <body>
41
+ <div id="app">
42
+ <div id="side">
43
+ <h1>Loom Graph</h1>
44
+ <div class="sub" id="statsSide"></div>
45
+ <input id="search" placeholder="Search nodes..." autocomplete="off">
46
+ <h3>Types</h3>
47
+ <div id="filters"></div>
48
+ <h3>Selected</h3>
49
+ <div id="selInfo" class="empty">Click a node to inspect it.</div>
50
+ </div>
51
+ <div id="main">
52
+ <div id="hint">drag nodes &middot; wheel to zoom &middot; drag background to pan</div>
53
+ <div id="stats"></div>
54
+ <canvas id="canvas"></canvas>
55
+ </div>
56
+ </div>
57
+ <script>
58
+ "use strict";
59
+ /* Graph data is injected by the Loom server (loom graph open). */
60
+ var GRAPH = window.__GRAPH__ || { nodes: [], edges: [] };
61
+ var TYPE_COLORS = {
62
+ decision: "#4f9cf9", convention: "#3ecf8e", bugfix: "#f98f4f",
63
+ superseded: "#6b7080", note: "#8b93a7",
64
+ };
65
+ function colorOf(t) { return TYPE_COLORS[t] || TYPE_COLORS.note; }
66
+
67
+ /* ---- force-directed simulation (license-free vanilla) ---- */
68
+ var W, H;
69
+ var nodes = [];
70
+ var links = [];
71
+ var byId = {};
72
+ var selected = null;
73
+ var dragging = null;
74
+ var zoom = 1, panX = 0, panY = 0;
75
+
76
+ function init() {
77
+ var seen = {};
78
+ GRAPH.nodes.forEach(function (n, i) {
79
+ var o = { id: n.id, label: n.title || n.id, type: n.type || "note",
80
+ confidence: typeof n.confidence === "number" ? n.confidence : 0.5,
81
+ tags: n.tags || [], body: n.body || "", source: n.source || "",
82
+ x: 0, y: 0, vx: 0, vy: 0, r: 8 + 14 * (n.confidence || 0.5), fx: null, fy: null };
83
+ if (!seen[o.id]) { seen[o.id] = 1; nodes.push(o); byId[o.id] = o; }
84
+ });
85
+ GRAPH.edges.forEach(function (e) {
86
+ if (byId[e.source] && byId[e.target] && e.source !== e.target) {
87
+ links.push({ a: byId[e.source], b: byId[e.target], type: e.type || "link" });
88
+ }
89
+ });
90
+ var n = nodes.length;
91
+ var R = Math.min(W, H) * 0.35;
92
+ nodes.forEach(function (o, i) {
93
+ var a = (i / Math.max(n, 1)) * Math.PI * 2;
94
+ o.x = W / 2 + R * Math.cos(a);
95
+ o.y = H / 2 + R * Math.sin(a);
96
+ });
97
+ buildFilters();
98
+ tick();
99
+ }
100
+
101
+ var tickTimer;
102
+ function tick() {
103
+ var rep = 2400, spring = 0.04, grav = 0.02, damp = 0.85;
104
+ nodes.forEach(function (a) {
105
+ a.vx *= damp; a.vy *= damp;
106
+ nodes.forEach(function (b) {
107
+ if (a === b) return;
108
+ var dx = a.x - b.x, dy = a.y - b.y;
109
+ var d2 = dx * dx + dy * dy + 1;
110
+ var f = rep / d2;
111
+ var d = Math.sqrt(d2);
112
+ a.vx += (dx / d) * f; a.vy += (dy / d) * f;
113
+ });
114
+ a.vx -= (a.x - W / 2) * grav; a.vy -= (a.y - H / 2) * grav;
115
+ });
116
+ links.forEach(function (l) {
117
+ var dx = l.b.x - l.a.x, dy = l.b.y - l.a.y;
118
+ var d = Math.sqrt(dx * dx + dy * dy) || 1;
119
+ var want = 90, f = (d - want) * spring;
120
+ var ux = dx / d, uy = dy / d;
121
+ l.a.vx += ux * f; l.a.vy += uy * f;
122
+ l.b.vx -= ux * f; l.b.vy -= uy * f;
123
+ });
124
+ nodes.forEach(function (a) {
125
+ if (a.fx !== null) { a.x = a.fx; a.y = a.fy; a.vx = a.vy = 0; return; }
126
+ a.x += a.vx; a.y += a.vy;
127
+ a.x = Math.max(20, Math.min(W - 20, a.x));
128
+ a.y = Math.max(20, Math.min(H - 20, a.y));
129
+ });
130
+ draw();
131
+ tickTimer = requestAnimationFrame(tick);
132
+ }
133
+
134
+ /* ---- rendering ---- */
135
+ var canvas = document.getElementById("canvas");
136
+ var ctx = canvas.getContext("2d");
137
+ function resize() {
138
+ W = canvas.clientWidth; H = canvas.clientHeight;
139
+ canvas.width = W * devicePixelRatio; canvas.height = H * devicePixelRatio;
140
+ ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0);
141
+ }
142
+ window.addEventListener("resize", resize); resize();
143
+
144
+ function tx(x, y) { return { x: x * zoom + panX, y: y * zoom + panY }; }
145
+
146
+ function visible(o) {
147
+ var q = document.getElementById("search").value.trim().toLowerCase();
148
+ if (q && (o.label.toLowerCase().indexOf(q) < 0)) return false;
149
+ var f = document.getElementById("f-" + o.type);
150
+ return !f || f.checked;
151
+ }
152
+
153
+ function draw() {
154
+ ctx.clearRect(0, 0, W, H);
155
+ ctx.strokeStyle = "#2b3146"; ctx.lineWidth = 1;
156
+ links.forEach(function (l) {
157
+ if (!visible(l.a) || !visible(l.b)) return;
158
+ var p1 = tx(l.a.x, l.a.y), p2 = tx(l.b.x, l.b.y);
159
+ ctx.beginPath(); ctx.moveTo(p1.x, p1.y); ctx.lineTo(p2.x, p2.y); ctx.stroke();
160
+ });
161
+ nodes.forEach(function (o) {
162
+ if (!visible(o)) return;
163
+ var p = tx(o.x, o.y), r = o.r * zoom;
164
+ ctx.beginPath(); ctx.arc(p.x, p.y, r, 0, Math.PI * 2);
165
+ ctx.fillStyle = colorOf(o.type); ctx.fill();
166
+ ctx.strokeStyle = o === selected ? "#ffffff" : "#0f1117"; ctx.lineWidth = o === selected ? 2 : 1;
167
+ ctx.stroke();
168
+ ctx.fillStyle = "#e6e9f2"; ctx.font = "11px ui-monospace, Consolas, monospace";
169
+ ctx.textAlign = "center";
170
+ var label = o.label.length > 26 ? o.label.slice(0, 25) + ".." : o.label;
171
+ ctx.fillText(label, p.x, p.y + r + 12);
172
+ });
173
+ var vis = nodes.filter(visible).length;
174
+ document.getElementById("stats").textContent = vis + " / " + nodes.length + " nodes - " + links.length + " links";
175
+ document.getElementById("statsSide").textContent = nodes.length + " nodes, " + links.length + " links";
176
+ }
177
+
178
+ /* ---- interaction ---- */
179
+ function hitTest(mx, my) {
180
+ for (var i = nodes.length - 1; i >= 0; i--) {
181
+ var o = nodes[i];
182
+ if (!visible(o)) continue;
183
+ var p = tx(o.x, o.y);
184
+ var dx = mx - p.x, dy = my - p.y;
185
+ if (dx * dx + dy * dy <= (o.r * zoom + 4) * (o.r * zoom + 4)) return o;
186
+ }
187
+ return null;
188
+ }
189
+
190
+ canvas.addEventListener("mousedown", function (e) {
191
+ var r = canvas.getBoundingClientRect();
192
+ var mx = e.clientX - r.left, my = e.clientY - r.top;
193
+ var o = hitTest(mx, my);
194
+ if (o) {
195
+ dragging = o;
196
+ canvas.classList.add("dragging");
197
+ o.fx = o.x; o.fy = o.y;
198
+ select(o);
199
+ } else {
200
+ dragging = null;
201
+ lastPan = { x: mx, y: my };
202
+ }
203
+ });
204
+ var lastPan = null;
205
+ window.addEventListener("mousemove", function (e) {
206
+ var r = canvas.getBoundingClientRect();
207
+ var mx = e.clientX - r.left, my = e.clientY - r.top;
208
+ if (dragging) {
209
+ dragging.fx = (mx - panX) / zoom; dragging.fy = (my - panY) / zoom;
210
+ } else if (lastPan) {
211
+ panX += mx - lastPan.x; panY += my - lastPan.y;
212
+ lastPan = { x: mx, y: my };
213
+ }
214
+ });
215
+ window.addEventListener("mouseup", function () {
216
+ if (dragging) { dragging.fx = null; dragging.fy = null; dragging = null; }
217
+ lastPan = null;
218
+ canvas.classList.remove("dragging");
219
+ });
220
+ canvas.addEventListener("wheel", function (e) {
221
+ e.preventDefault();
222
+ var f = e.deltaY < 0 ? 1.1 : 0.9;
223
+ zoom = Math.max(0.2, Math.min(3, zoom * f));
224
+ }, { passive: false });
225
+
226
+ /* ---- side panel + filters ---- */
227
+ function select(o) {
228
+ selected = o;
229
+ var el = document.getElementById("selInfo");
230
+ var html = '<div style="color:#fff;font-size:13px"><b>' + esc(o.label) + "</b></div>";
231
+ html += '<div class="meta">' + esc(o.type) +
232
+ (typeof o.confidence === "number" ? " &middot; confidence " + Math.round(o.confidence * 100) + "%" : "") + "</div>";
233
+ if (o.tags && o.tags.length) html += o.tags.map(function (t) { return '<span class="pill">#' + esc(t) + "</span>"; }).join("");
234
+ if (o.source) html += '<div class="meta" style="margin-top:6px">source: ' + esc(o.source) + "</div>";
235
+ if (o.body) {
236
+ var b = o.body.length > 600 ? o.body.slice(0, 600) + " …" : o.body;
237
+ html += "<pre>" + esc(b) + "</pre>";
238
+ }
239
+ el.innerHTML = html;
240
+ }
241
+ function esc(s) {
242
+ return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
243
+ }
244
+ function buildFilters() {
245
+ var types = {};
246
+ nodes.forEach(function (o) { types[o.type] = (types[o.type] || 0) + 1; });
247
+ var html = "";
248
+ Object.keys(types).sort().forEach(function (t) {
249
+ html += '<label><input type="checkbox" id="f-' + esc(t) + '" checked> ' +
250
+ '<span class="swatch" style="background:' + colorOf(t) + '"></span> ' +
251
+ esc(t) + " (" + types[t] + ")</label>";
252
+ });
253
+ document.getElementById("filters").innerHTML = html;
254
+ var boxes = document.querySelectorAll("#filters input");
255
+ for (var i = 0; i < boxes.length; i++) boxes[i].addEventListener("change", draw);
256
+ }
257
+ document.getElementById("search").addEventListener("input", draw);
258
+
259
+ init();
260
+ </script>
261
+ </body>
262
+ </html>