tablewalk 0.0.1
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/LICENSE +21 -0
- package/README.md +553 -0
- package/dist/adapters/adapter.js +372 -0
- package/dist/adapters/connect.js +33 -0
- package/dist/adapters/mysql.js +951 -0
- package/dist/adapters/postgres.js +1000 -0
- package/dist/adapters/sqlite.js +781 -0
- package/dist/client/agent.js +262 -0
- package/dist/client/app.js +973 -0
- package/dist/client/arrange.js +254 -0
- package/dist/client/ask.js +133 -0
- package/dist/client/breakdown.js +317 -0
- package/dist/client/clauses.js +390 -0
- package/dist/client/columns.js +98 -0
- package/dist/client/complete.js +437 -0
- package/dist/client/compose.js +166 -0
- package/dist/client/composer.css +495 -0
- package/dist/client/composer.js +1972 -0
- package/dist/client/connections.js +234 -0
- package/dist/client/connmanager.js +962 -0
- package/dist/client/connurl.js +188 -0
- package/dist/client/core.js +893 -0
- package/dist/client/deeplink.js +270 -0
- package/dist/client/delete.js +144 -0
- package/dist/client/diagram.js +885 -0
- package/dist/client/dropdown.js +279 -0
- package/dist/client/export.js +456 -0
- package/dist/client/features.css +524 -0
- package/dist/client/findvalue.js +169 -0
- package/dist/client/grid.js +205 -0
- package/dist/client/handoff.js +153 -0
- package/dist/client/help.css +145 -0
- package/dist/client/help.js +881 -0
- package/dist/client/history.js +222 -0
- package/dist/client/index.html +116 -0
- package/dist/client/insert.js +151 -0
- package/dist/client/menu.js +160 -0
- package/dist/client/nested.js +255 -0
- package/dist/client/page.css +713 -0
- package/dist/client/page.js +1345 -0
- package/dist/client/pagebuilder.js +1222 -0
- package/dist/client/pagemarks.js +95 -0
- package/dist/client/palette.js +374 -0
- package/dist/client/peek.js +254 -0
- package/dist/client/picker.js +139 -0
- package/dist/client/pins.js +140 -0
- package/dist/client/prompt.js +129 -0
- package/dist/client/record.js +707 -0
- package/dist/client/schemaexport.js +242 -0
- package/dist/client/schematext.js +125 -0
- package/dist/client/shape.js +178 -0
- package/dist/client/shapecheck.js +129 -0
- package/dist/client/skeleton.js +139 -0
- package/dist/client/sql.css +126 -0
- package/dist/client/sql.js +398 -0
- package/dist/client/sqlcomplete.js +163 -0
- package/dist/client/sqlsaved.js +107 -0
- package/dist/client/style.css +2711 -0
- package/dist/client/summary.js +259 -0
- package/dist/client/table.js +1035 -0
- package/dist/client/template.js +539 -0
- package/dist/client/theme.js +74 -0
- package/dist/client/tour.js +324 -0
- package/dist/client/undo.js +105 -0
- package/dist/client/url.js +166 -0
- package/dist/client/value.js +223 -0
- package/dist/client/views.js +215 -0
- package/dist/client/virtual.js +176 -0
- package/dist/client/welcome.js +170 -0
- package/dist/client/write.js +414 -0
- package/dist/server/changeimpact.js +195 -0
- package/dist/server/connections.js +615 -0
- package/dist/server/constraints.js +62 -0
- package/dist/server/credentials.js +230 -0
- package/dist/server/fixture.js +199 -0
- package/dist/server/graph.js +194 -0
- package/dist/server/impact.js +48 -0
- package/dist/server/index.js +2204 -0
- package/dist/server/journal.js +173 -0
- package/dist/server/layouts.js +128 -0
- package/dist/server/mcp.js +2840 -0
- package/dist/server/shapeonly.js +91 -0
- package/dist/shared/breakdown.js +231 -0
- package/dist/shared/breakdowntext.js +257 -0
- package/dist/shared/diff.js +130 -0
- package/dist/shared/like.js +29 -0
- package/dist/shared/lint.js +149 -0
- package/dist/shared/order.js +133 -0
- package/dist/shared/page.js +932 -0
- package/dist/shared/query.js +831 -0
- package/dist/shared/recordview.js +343 -0
- package/dist/shared/schema.js +377 -0
- package/dist/shared/sqlsaved.js +67 -0
- package/dist/shared/view.js +981 -0
- package/dist/shared/viewtext.js +273 -0
- package/dist/shared/vocabulary.js +164 -0
- package/package.json +57 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The agent console: the MCP tools, driven by hand.
|
|
3
|
+
*
|
|
4
|
+
* MCP is invisible by design — newline-delimited JSON between two programs —
|
|
5
|
+
* which leaves nowhere to *watch* it. This is that place: the tool list an
|
|
6
|
+
* agent's client would receive, a form for each tool's arguments, and the
|
|
7
|
+
* answer exactly as an agent gets it, stats and all. The claim is structural
|
|
8
|
+
* rather than aspirational: the endpoint underneath calls the same runTool
|
|
9
|
+
* the stdio loop calls.
|
|
10
|
+
*
|
|
11
|
+
* What it is for: tuning tool descriptions by reading them the way a model
|
|
12
|
+
* would, checking what a call costs before an agent pays it on every step,
|
|
13
|
+
* and debugging "why did the agent do that" by replaying its calls.
|
|
14
|
+
*/
|
|
15
|
+
import { api, el, state, toast, trapFocus } from './core.js';
|
|
16
|
+
|
|
17
|
+
let panel = null;
|
|
18
|
+
/** Undoes the focus trap, and hands the keyboard back where it came from. */
|
|
19
|
+
let release = null;
|
|
20
|
+
let tools = [];
|
|
21
|
+
/** Calls made while the console has been open, newest first. */
|
|
22
|
+
let transcript = [];
|
|
23
|
+
|
|
24
|
+
export function openAgentConsole() {
|
|
25
|
+
if (panel) return;
|
|
26
|
+
panel = el('div', { class: 'conn-manager agent-console', role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Agent console' });
|
|
27
|
+
document.body.append(panel);
|
|
28
|
+
release = trapFocus(panel);
|
|
29
|
+
document.addEventListener('keydown', onKey, true);
|
|
30
|
+
panel.replaceChildren(el('div', { class: 'conn-inner' }, [el('p', { class: 'loading', text: 'Reading the tools…' })]));
|
|
31
|
+
void load();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function close() {
|
|
35
|
+
release?.();
|
|
36
|
+
release = null;
|
|
37
|
+
panel?.remove();
|
|
38
|
+
panel = null;
|
|
39
|
+
transcript = [];
|
|
40
|
+
document.removeEventListener('keydown', onKey, true);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function onKey(e) {
|
|
44
|
+
if (e.key !== 'Escape') return;
|
|
45
|
+
const tag = document.activeElement?.tagName;
|
|
46
|
+
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') { document.activeElement.blur(); return; }
|
|
47
|
+
e.preventDefault();
|
|
48
|
+
close();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function load() {
|
|
52
|
+
try {
|
|
53
|
+
tools = (await api('/api/agent/tools')).tools ?? [];
|
|
54
|
+
} catch (err) {
|
|
55
|
+
toast(err.message, 'error');
|
|
56
|
+
close();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
paint(tools[0]?.name);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** ~4 characters per token is the rule of thumb the readout says it is. */
|
|
63
|
+
const cost = (text) => `${text.length.toLocaleString()} chars · ~${Math.ceil(text.length / 4).toLocaleString()} tokens`;
|
|
64
|
+
|
|
65
|
+
function badges(tool) {
|
|
66
|
+
const a = tool.annotations ?? {};
|
|
67
|
+
const badge = (text, tone) => el('span', { class: `agent-badge tone-${tone}`, text });
|
|
68
|
+
if (a.readOnlyHint) return [badge('read-only', 'ok')];
|
|
69
|
+
return [
|
|
70
|
+
badge('writes', 'warn'),
|
|
71
|
+
a.destructiveHint ? badge('destructive', 'bad') : null,
|
|
72
|
+
].filter(Boolean);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function paint(selectedName) {
|
|
76
|
+
const tool = tools.find((t) => t.name === selectedName) ?? tools[0];
|
|
77
|
+
const result = el('div', { class: 'agent-result' });
|
|
78
|
+
|
|
79
|
+
const list = el('div', { class: 'agent-tools', role: 'listbox', 'aria-label': 'Tools' }, tools.map((t) =>
|
|
80
|
+
el('button', {
|
|
81
|
+
type: 'button',
|
|
82
|
+
class: `agent-tool${t.name === tool?.name ? ' active' : ''}`,
|
|
83
|
+
role: 'option',
|
|
84
|
+
'aria-selected': String(t.name === tool?.name),
|
|
85
|
+
onclick: () => paint(t.name),
|
|
86
|
+
}, [
|
|
87
|
+
el('span', { class: 'agent-tool-name', text: t.name }),
|
|
88
|
+
el('span', { class: 'agent-tool-badges' }, badges(t)),
|
|
89
|
+
])));
|
|
90
|
+
|
|
91
|
+
panel.replaceChildren(el('div', { class: 'conn-inner' }, [
|
|
92
|
+
el('div', { class: 'conn-head' }, [
|
|
93
|
+
el('div', { class: 'conn-head-titles' }, [
|
|
94
|
+
el('div', { class: 'conn-brand', 'aria-hidden': 'true' }, [
|
|
95
|
+
el('span', { class: 'brand-mark' }),
|
|
96
|
+
el('span', { class: 'conn-brand-name', text: 'tablewalk' }),
|
|
97
|
+
]),
|
|
98
|
+
el('h2', { text: 'Agent console' }),
|
|
99
|
+
el('p', {
|
|
100
|
+
class: 'conn-lede',
|
|
101
|
+
text: 'The MCP tools, driven by hand. Answers are exactly what an agent receives — same code path, stats and all.',
|
|
102
|
+
}),
|
|
103
|
+
]),
|
|
104
|
+
el('div', { class: 'conn-head-actions' }, [
|
|
105
|
+
el('button', { type: 'button', text: 'Done', onclick: close }),
|
|
106
|
+
]),
|
|
107
|
+
]),
|
|
108
|
+
el('div', { class: 'agent-body' }, [
|
|
109
|
+
list,
|
|
110
|
+
el('div', { class: 'agent-main' }, tool ? [
|
|
111
|
+
toolForm(tool, result),
|
|
112
|
+
result,
|
|
113
|
+
transcriptPanel(),
|
|
114
|
+
] : [el('p', { class: 'note', text: 'No tools. Is the server running?' })]),
|
|
115
|
+
]),
|
|
116
|
+
]));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function toolForm(tool, result) {
|
|
120
|
+
const inputs = new Map();
|
|
121
|
+
const properties = tool.inputSchema?.properties ?? {};
|
|
122
|
+
const required = new Set(tool.inputSchema?.required ?? []);
|
|
123
|
+
|
|
124
|
+
const field = (name, spec) => {
|
|
125
|
+
const types = Array.isArray(spec.type) ? spec.type : [spec.type];
|
|
126
|
+
/* Objects arrive as JSON, because a key or a set of values is a shape
|
|
127
|
+
the caller composes — a form that flattened it would be guessing. */
|
|
128
|
+
const isObject = types.includes('object');
|
|
129
|
+
const input = isObject
|
|
130
|
+
? el('textarea', {
|
|
131
|
+
class: 'agent-input agent-json', rows: '2', spellcheck: 'false',
|
|
132
|
+
placeholder: spec.description ?? '{ }',
|
|
133
|
+
'aria-label': `${name} (json)`,
|
|
134
|
+
})
|
|
135
|
+
: el('input', {
|
|
136
|
+
type: 'text', class: 'agent-input', autocomplete: 'off', spellcheck: 'false',
|
|
137
|
+
placeholder: name === 'connection'
|
|
138
|
+
? `${state.activeConnection ?? 'active'} (active)`
|
|
139
|
+
: spec.description ?? '',
|
|
140
|
+
'aria-label': name,
|
|
141
|
+
});
|
|
142
|
+
inputs.set(name, { input, spec, isObject });
|
|
143
|
+
return el('div', { class: 'agent-field' }, [
|
|
144
|
+
el('label', {}, [
|
|
145
|
+
document.createTextNode(name),
|
|
146
|
+
required.has(name) ? el('span', { class: 'req', text: 'required' }) : null,
|
|
147
|
+
].filter(Boolean)),
|
|
148
|
+
input,
|
|
149
|
+
]);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const run = async () => {
|
|
153
|
+
const args = {};
|
|
154
|
+
for (const [name, { input, spec, isObject }] of inputs) {
|
|
155
|
+
const raw = input.value.trim();
|
|
156
|
+
if (!raw) continue;
|
|
157
|
+
if (isObject) {
|
|
158
|
+
try {
|
|
159
|
+
args[name] = JSON.parse(raw);
|
|
160
|
+
} catch {
|
|
161
|
+
result.replaceChildren(el('p', { class: 'agent-error', text: `"${name}" is not JSON: ${raw.slice(0, 60)}` }));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
} else if ((Array.isArray(spec.type) ? spec.type : [spec.type]).includes('number') && raw !== '' && !Number.isNaN(Number(raw))) {
|
|
165
|
+
args[name] = Number(raw);
|
|
166
|
+
} else if (spec.type === 'boolean') {
|
|
167
|
+
args[name] = raw === 'true' || raw === '1' || raw === 'yes';
|
|
168
|
+
} else {
|
|
169
|
+
args[name] = raw;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
result.replaceChildren(el('p', { class: 'loading', text: `Calling ${tool.name}…` }));
|
|
174
|
+
let answer;
|
|
175
|
+
try {
|
|
176
|
+
answer = await api('/api/agent/call', { name: tool.name, arguments: args });
|
|
177
|
+
} catch (err) {
|
|
178
|
+
result.replaceChildren(el('p', { class: 'agent-error', text: err.message }));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
transcript.unshift({ tool: tool.name, args, answer });
|
|
182
|
+
renderAnswer(result, answer);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
return el('div', { class: 'agent-form' }, [
|
|
186
|
+
el('div', { class: 'agent-desc-row' }, [
|
|
187
|
+
el('h3', { text: tool.annotations?.title ?? tool.name }),
|
|
188
|
+
...badges(tool),
|
|
189
|
+
]),
|
|
190
|
+
/* The description, shown whole: this text is the tool's entire pitch to
|
|
191
|
+
a model deciding whether to call it, and reading it here is how it
|
|
192
|
+
gets tuned. */
|
|
193
|
+
el('p', { class: 'agent-desc', text: tool.description }),
|
|
194
|
+
...Object.entries(properties).map(([name, spec]) => field(name, spec)),
|
|
195
|
+
el('div', { class: 'agent-run-row' }, [
|
|
196
|
+
el('button', { type: 'button', class: 'primary', text: 'Call tool', onclick: () => void run() }),
|
|
197
|
+
]),
|
|
198
|
+
]);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function renderAnswer(result, answer) {
|
|
202
|
+
const text = answer.structuredContent
|
|
203
|
+
? JSON.stringify(answer.structuredContent, null, 2)
|
|
204
|
+
: (answer.content?.[0]?.text ?? '');
|
|
205
|
+
const stats = answer.structuredContent?.stats ?? {};
|
|
206
|
+
|
|
207
|
+
/* Every deep link in the answer, surfaced as a real door: the console and
|
|
208
|
+
the browser are the same app, and "see that row" should cost one click. */
|
|
209
|
+
const links = [];
|
|
210
|
+
const walk = (value) => {
|
|
211
|
+
if (typeof value === 'string' && /^[#/]\/?\w/.test(value) && !value.startsWith('//')) links.push(value);
|
|
212
|
+
else if (Array.isArray(value)) value.forEach(walk);
|
|
213
|
+
else if (value && typeof value === 'object') Object.values(value).forEach(walk);
|
|
214
|
+
};
|
|
215
|
+
walk(answer.structuredContent ?? {});
|
|
216
|
+
|
|
217
|
+
result.replaceChildren(
|
|
218
|
+
el('div', { class: 'agent-stats' }, [
|
|
219
|
+
answer.isError ? el('span', { class: 'agent-badge tone-bad', text: 'isError' }) : null,
|
|
220
|
+
...Object.entries(stats).map(([k, v]) => el('span', { class: 'stat', text: `${k} ${v}` })),
|
|
221
|
+
el('span', { class: 'stat', text: cost(text) }),
|
|
222
|
+
].filter(Boolean)),
|
|
223
|
+
el('pre', { class: `agent-answer${answer.isError ? ' agent-answer-error' : ''}`, text }),
|
|
224
|
+
links.length
|
|
225
|
+
? el('div', { class: 'agent-links' }, links.slice(0, 8).map((hash) =>
|
|
226
|
+
el('button', {
|
|
227
|
+
type: 'button', class: 'ghost', text: `open ${decodeURIComponent(hash).slice(0, 60)}`,
|
|
228
|
+
onclick: () => {
|
|
229
|
+
close();
|
|
230
|
+
/* A path is navigated, not assigned to `location.hash`. Written
|
|
231
|
+
with pushState so the console's own history entry survives. */
|
|
232
|
+
window.history.pushState(null, '', hash.replace(/^#/, ''));
|
|
233
|
+
window.dispatchEvent(new window.PopStateEvent('popstate'));
|
|
234
|
+
},
|
|
235
|
+
})))
|
|
236
|
+
: null,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function transcriptPanel() {
|
|
241
|
+
if (!transcript.length) return el('div');
|
|
242
|
+
return el('div', { class: 'agent-transcript' }, [
|
|
243
|
+
el('h4', {
|
|
244
|
+
text: `This session — ${transcript.length} call${transcript.length === 1 ? '' : 's'} · `
|
|
245
|
+
+ `${transcript.reduce((n, e) => n + (e.answer.structuredContent?.stats?.rowsRead ?? 0), 0).toLocaleString()} rows read`,
|
|
246
|
+
}),
|
|
247
|
+
...transcript.slice(0, 12).map((entry) => {
|
|
248
|
+
const size = entry.answer.content?.[0]?.text?.length ?? 0;
|
|
249
|
+
return el('button', {
|
|
250
|
+
type: 'button', class: 'agent-past',
|
|
251
|
+
onclick: () => {
|
|
252
|
+
const result = panel?.querySelector('.agent-result');
|
|
253
|
+
if (result) renderAnswer(result, entry.answer);
|
|
254
|
+
},
|
|
255
|
+
}, [
|
|
256
|
+
el('span', { class: 'agent-past-tool', text: entry.tool }),
|
|
257
|
+
el('span', { class: 'agent-past-args', text: JSON.stringify(entry.args).slice(0, 60) }),
|
|
258
|
+
el('span', { class: 'agent-past-cost', text: `${entry.answer.structuredContent?.stats?.ms ?? '–'} ms · ${size.toLocaleString()} ch` }),
|
|
259
|
+
]);
|
|
260
|
+
}),
|
|
261
|
+
]);
|
|
262
|
+
}
|