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.
Files changed (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +553 -0
  3. package/dist/adapters/adapter.js +372 -0
  4. package/dist/adapters/connect.js +33 -0
  5. package/dist/adapters/mysql.js +951 -0
  6. package/dist/adapters/postgres.js +1000 -0
  7. package/dist/adapters/sqlite.js +781 -0
  8. package/dist/client/agent.js +262 -0
  9. package/dist/client/app.js +973 -0
  10. package/dist/client/arrange.js +254 -0
  11. package/dist/client/ask.js +133 -0
  12. package/dist/client/breakdown.js +317 -0
  13. package/dist/client/clauses.js +390 -0
  14. package/dist/client/columns.js +98 -0
  15. package/dist/client/complete.js +437 -0
  16. package/dist/client/compose.js +166 -0
  17. package/dist/client/composer.css +495 -0
  18. package/dist/client/composer.js +1972 -0
  19. package/dist/client/connections.js +234 -0
  20. package/dist/client/connmanager.js +962 -0
  21. package/dist/client/connurl.js +188 -0
  22. package/dist/client/core.js +893 -0
  23. package/dist/client/deeplink.js +270 -0
  24. package/dist/client/delete.js +144 -0
  25. package/dist/client/diagram.js +885 -0
  26. package/dist/client/dropdown.js +279 -0
  27. package/dist/client/export.js +456 -0
  28. package/dist/client/features.css +524 -0
  29. package/dist/client/findvalue.js +169 -0
  30. package/dist/client/grid.js +205 -0
  31. package/dist/client/handoff.js +153 -0
  32. package/dist/client/help.css +145 -0
  33. package/dist/client/help.js +881 -0
  34. package/dist/client/history.js +222 -0
  35. package/dist/client/index.html +116 -0
  36. package/dist/client/insert.js +151 -0
  37. package/dist/client/menu.js +160 -0
  38. package/dist/client/nested.js +255 -0
  39. package/dist/client/page.css +713 -0
  40. package/dist/client/page.js +1345 -0
  41. package/dist/client/pagebuilder.js +1222 -0
  42. package/dist/client/pagemarks.js +95 -0
  43. package/dist/client/palette.js +374 -0
  44. package/dist/client/peek.js +254 -0
  45. package/dist/client/picker.js +139 -0
  46. package/dist/client/pins.js +140 -0
  47. package/dist/client/prompt.js +129 -0
  48. package/dist/client/record.js +707 -0
  49. package/dist/client/schemaexport.js +242 -0
  50. package/dist/client/schematext.js +125 -0
  51. package/dist/client/shape.js +178 -0
  52. package/dist/client/shapecheck.js +129 -0
  53. package/dist/client/skeleton.js +139 -0
  54. package/dist/client/sql.css +126 -0
  55. package/dist/client/sql.js +398 -0
  56. package/dist/client/sqlcomplete.js +163 -0
  57. package/dist/client/sqlsaved.js +107 -0
  58. package/dist/client/style.css +2711 -0
  59. package/dist/client/summary.js +259 -0
  60. package/dist/client/table.js +1035 -0
  61. package/dist/client/template.js +539 -0
  62. package/dist/client/theme.js +74 -0
  63. package/dist/client/tour.js +324 -0
  64. package/dist/client/undo.js +105 -0
  65. package/dist/client/url.js +166 -0
  66. package/dist/client/value.js +223 -0
  67. package/dist/client/views.js +215 -0
  68. package/dist/client/virtual.js +176 -0
  69. package/dist/client/welcome.js +170 -0
  70. package/dist/client/write.js +414 -0
  71. package/dist/server/changeimpact.js +195 -0
  72. package/dist/server/connections.js +615 -0
  73. package/dist/server/constraints.js +62 -0
  74. package/dist/server/credentials.js +230 -0
  75. package/dist/server/fixture.js +199 -0
  76. package/dist/server/graph.js +194 -0
  77. package/dist/server/impact.js +48 -0
  78. package/dist/server/index.js +2204 -0
  79. package/dist/server/journal.js +173 -0
  80. package/dist/server/layouts.js +128 -0
  81. package/dist/server/mcp.js +2840 -0
  82. package/dist/server/shapeonly.js +91 -0
  83. package/dist/shared/breakdown.js +231 -0
  84. package/dist/shared/breakdowntext.js +257 -0
  85. package/dist/shared/diff.js +130 -0
  86. package/dist/shared/like.js +29 -0
  87. package/dist/shared/lint.js +149 -0
  88. package/dist/shared/order.js +133 -0
  89. package/dist/shared/page.js +932 -0
  90. package/dist/shared/query.js +831 -0
  91. package/dist/shared/recordview.js +343 -0
  92. package/dist/shared/schema.js +377 -0
  93. package/dist/shared/sqlsaved.js +67 -0
  94. package/dist/shared/view.js +981 -0
  95. package/dist/shared/viewtext.js +273 -0
  96. package/dist/shared/vocabulary.js +164 -0
  97. 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
+ }