@agentshouse/kit 0.1.0-alpha.9 → 0.2.1-alpha.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 (93) hide show
  1. package/README.md +14 -1740
  2. package/dist/acp.js +120 -0
  3. package/dist/agents.js +90 -0
  4. package/dist/api.js +45 -0
  5. package/dist/blocks.js +51 -0
  6. package/dist/bridge.js +124 -0
  7. package/dist/clis.js +110 -0
  8. package/dist/conversations.js +538 -0
  9. package/dist/files.js +21 -0
  10. package/dist/home.js +24 -0
  11. package/dist/house-main.js +92 -0
  12. package/dist/instructions.js +28 -0
  13. package/dist/kit-main.js +16 -0
  14. package/dist/login.js +112 -0
  15. package/dist/resident.js +66 -0
  16. package/dist/secret-input.js +16 -0
  17. package/dist/sign-in.js +83 -0
  18. package/dist/stream.js +47 -0
  19. package/package.json +34 -10
  20. package/bin/connect-linux.sh +0 -134
  21. package/bin/kit.js +0 -1023
  22. package/bootstrap.json +0 -17
  23. package/src/acp.js +0 -99
  24. package/src/adapter-mcp.js +0 -44
  25. package/src/adapter.js +0 -252
  26. package/src/agent-logins.js +0 -65
  27. package/src/agents.js +0 -504
  28. package/src/apply.js +0 -29
  29. package/src/attach.js +0 -52
  30. package/src/binding.js +0 -90
  31. package/src/blocks.js +0 -156
  32. package/src/bridge.js +0 -449
  33. package/src/bytes.js +0 -89
  34. package/src/channel.js +0 -37
  35. package/src/command.js +0 -109
  36. package/src/configuration.js +0 -465
  37. package/src/connector-login.js +0 -91
  38. package/src/connector.js +0 -107
  39. package/src/continuation.js +0 -61
  40. package/src/contract.js +0 -363
  41. package/src/conversation-originals.js +0 -143
  42. package/src/custom/authorization.js +0 -63
  43. package/src/declaration.js +0 -35
  44. package/src/delivery.js +0 -191
  45. package/src/directory.js +0 -102
  46. package/src/draft.js +0 -89
  47. package/src/google/calendar.js +0 -177
  48. package/src/google/declared.js +0 -146
  49. package/src/google/drive.js +0 -435
  50. package/src/google/gateway.js +0 -166
  51. package/src/google/gmail.js +0 -249
  52. package/src/google/id.js +0 -1
  53. package/src/google/oauth.js +0 -273
  54. package/src/google/source.js +0 -25
  55. package/src/hook.js +0 -376
  56. package/src/host.js +0 -121
  57. package/src/house-main.js +0 -79
  58. package/src/house.js +0 -1090
  59. package/src/inbound.js +0 -175
  60. package/src/instance/google.js +0 -1
  61. package/src/kit-work-stream.js +0 -84
  62. package/src/launcher-main.js +0 -19
  63. package/src/launcher.js +0 -276
  64. package/src/local-files.js +0 -113
  65. package/src/login.js +0 -274
  66. package/src/mcp.js +0 -71
  67. package/src/migrator.js +0 -248
  68. package/src/mount.js +0 -582
  69. package/src/operation.js +0 -20
  70. package/src/original.js +0 -631
  71. package/src/peer.js +0 -124
  72. package/src/plan.js +0 -57
  73. package/src/question.js +0 -316
  74. package/src/refusal.js +0 -79
  75. package/src/repository.js +0 -521
  76. package/src/session-bin/house +0 -2
  77. package/src/settlement.js +0 -57
  78. package/src/setup.js +0 -190
  79. package/src/source-originals.js +0 -116
  80. package/src/source.js +0 -286
  81. package/src/starter-set/grilling/LICENSE +0 -21
  82. package/src/starter-set/grilling/SKILL.md +0 -30
  83. package/src/starter-set/grilling/agents/openai.yaml +0 -3
  84. package/src/starter-set.js +0 -17
  85. package/src/state.js +0 -157
  86. package/src/sync-library.js +0 -293
  87. package/src/sync-state.js +0 -120
  88. package/src/sync.js +0 -482
  89. package/src/telegram.js +0 -1045
  90. package/src/terminal.js +0 -57
  91. package/src/tmux.js +0 -119
  92. package/src/worker.js +0 -925
  93. package/src/workspace.js +0 -500
@@ -1,25 +0,0 @@
1
- import { KitRefusal } from "../refusal.js";
2
- import { gmailPage } from "./gmail.js";
3
- import { calendarPage } from "./calendar.js";
4
- import { drivePage } from "./drive.js";
5
- import { declaredPage } from "./declared.js";
6
-
7
- export async function googlePage(options, dependencies = {}) {
8
- const common = {
9
- directory: options.directory,
10
- instance: options.instance,
11
- client: options.client,
12
- selection: options.service.selection,
13
- cursor: options.cursor,
14
- limit: options.limit,
15
- backfillFloor: options.backfillFloor,
16
- signal: options.signal,
17
- };
18
- if (options.service.service === "gmail") return gmailPage(common, dependencies);
19
- if (options.service.service === "calendar") return calendarPage(common, dependencies);
20
- if (options.service.service === "drive") return drivePage(common, dependencies);
21
- if (options.service.declaration !== null) {
22
- return declaredPage({ ...common, service: options.service }, dependencies);
23
- }
24
- throw new KitRefusal("google_service_unsupported", options.service.service);
25
- }
package/src/hook.js DELETED
@@ -1,376 +0,0 @@
1
- import { readFile, readdir, readlink, rm, stat } from "node:fs/promises";
2
- import { dirname, isAbsolute, join, resolve, sep } from "node:path";
3
- import { DECLARED_ID } from "./contract.js";
4
- import { declarationReader } from "./declaration.js";
5
- import { KitRefusal } from "./refusal.js";
6
- import { readNote, writeNote, writeText } from "./state.js";
7
-
8
- const SESSION_START_EVENT = "SessionStart";
9
- const HOOK_INSTRUCTION_MAX = 16 * 1024;
10
- const SETTINGS_MODE = 0o600;
11
- const LINK_BUDGET = 32;
12
-
13
- const { refuse, closed, text } = declarationReader("kit_hook_rejected");
14
-
15
- function servedSessionStartHook(value) {
16
- const served = closed(value, ["id", "version", "event", "instruction"], "session_start_hook");
17
- const id = text(served.id, "session_start_hook.id", 128);
18
- if (!DECLARED_ID.test(id)) refuse("session_start_hook.id");
19
- if (served.event !== SESSION_START_EVENT) refuse("session_start_hook.event");
20
- return {
21
- id,
22
- version: text(served.version, "session_start_hook.version", 64),
23
- event: SESSION_START_EVENT,
24
- instruction: text(served.instruction, "session_start_hook.instruction", HOOK_INSTRUCTION_MAX),
25
- };
26
- }
27
-
28
- function hooksNamespace(directory) {
29
- return join(directory, "state", "hooks");
30
- }
31
-
32
- function recordPath(directory) {
33
- return join(directory, "state", "hooks.json");
34
- }
35
-
36
- function shellQuoted(value) {
37
- return `'${value.replaceAll("'", "'\\''")}'`;
38
- }
39
-
40
- function instructionCommand(path) {
41
- return `cat ${shellQuoted(path)}`;
42
- }
43
-
44
- function active(signal) {
45
- if (signal?.aborted === true) throw new KitRefusal("kit_command_cancelled");
46
- }
47
-
48
- function plainDocument(value) {
49
- return typeof value === "object" && value !== null && !Array.isArray(value);
50
- }
51
-
52
- function pathSegments(path) {
53
- return path.split(sep).filter((segment) => segment.length > 0);
54
- }
55
-
56
- async function linkTarget(path, declared) {
57
- try {
58
- return await readlink(path);
59
- } catch (error) {
60
- if (error.code === "EINVAL" || error.code === "ENOENT" || error.code === "ENOTDIR") return null;
61
- throw new KitRefusal("kit_client_settings_unreadable", declared);
62
- }
63
- }
64
-
65
- async function canonicalPath(declared) {
66
- let remaining = pathSegments(resolve(declared));
67
- let resolved = sep;
68
- let links = LINK_BUDGET;
69
- while (remaining.length > 0) {
70
- const segment = remaining.shift();
71
- if (segment === ".") continue;
72
- if (segment === "..") {
73
- resolved = dirname(resolved);
74
- continue;
75
- }
76
- const candidate = join(resolved, segment);
77
- const target = await linkTarget(candidate, declared);
78
- if (target === null) {
79
- resolved = candidate;
80
- continue;
81
- }
82
- links -= 1;
83
- if (links < 0) throw new KitRefusal("kit_client_settings_refused", declared);
84
- remaining = [...pathSegments(target), ...remaining];
85
- if (isAbsolute(target)) resolved = sep;
86
- }
87
- return resolved;
88
- }
89
-
90
- function within(path, root) {
91
- return path === root || path.startsWith(`${root}${sep}`);
92
- }
93
-
94
- async function declaredDestination(installation, client, taken) {
95
- const path = await canonicalPath(client.settings);
96
- if (within(path, installation.home) || path === installation.configuration || taken.has(path)) {
97
- throw new KitRefusal("kit_client_settings_refused", client.settings);
98
- }
99
- taken.add(path);
100
- return { kind: client.kind, declared: client.settings, path, desired: true };
101
- }
102
-
103
- async function recordedDestination(installation, recorded) {
104
- const path = await canonicalPath(recorded.path);
105
- if (path !== recorded.path || within(path, installation.home)) {
106
- throw new KitRefusal("kit_client_settings_refused", recorded.path);
107
- }
108
- return { kind: null, declared: recorded.path, path, desired: false };
109
- }
110
-
111
- function hookSections(document, declared) {
112
- const hooks = document.hooks === undefined ? {} : document.hooks;
113
- if (!plainDocument(hooks)) throw new KitRefusal("kit_client_settings_unreadable", declared);
114
- const groups = hooks[SESSION_START_EVENT] === undefined ? [] : hooks[SESSION_START_EVENT];
115
- if (!Array.isArray(groups)) throw new KitRefusal("kit_client_settings_unreadable", declared);
116
- return { hooks, groups };
117
- }
118
-
119
- function identityOf(status) {
120
- return { device: status.dev, node: status.ino };
121
- }
122
-
123
- async function readSettings(destination) {
124
- let status;
125
- try {
126
- status = await stat(destination.path);
127
- } catch (error) {
128
- if (error.code === "ENOENT") {
129
- return { document: {}, contents: null, mode: SETTINGS_MODE, identity: null, present: false };
130
- }
131
- throw new KitRefusal("kit_client_settings_unreadable", destination.declared);
132
- }
133
- if (!status.isFile()) throw new KitRefusal("kit_client_settings_refused", destination.declared);
134
- let stored;
135
- try {
136
- stored = await readFile(destination.path, "utf8");
137
- } catch {
138
- throw new KitRefusal("kit_client_settings_unreadable", destination.declared);
139
- }
140
- let document;
141
- try {
142
- document = JSON.parse(stored);
143
- } catch {
144
- throw new KitRefusal("kit_client_settings_unreadable", destination.declared);
145
- }
146
- if (!plainDocument(document)) {
147
- throw new KitRefusal("kit_client_settings_unreadable", destination.declared);
148
- }
149
- hookSections(document, destination.declared);
150
- return {
151
- document,
152
- contents: stored,
153
- mode: status.mode & 0o777,
154
- identity: identityOf(status),
155
- present: true,
156
- };
157
- }
158
-
159
- async function prepared(destination, guard) {
160
- const path = await canonicalPath(destination.declared);
161
- if (path !== destination.path || within(path, guard.home) || path === guard.configuration) {
162
- return false;
163
- }
164
- let status = null;
165
- try {
166
- status = await stat(path);
167
- } catch (error) {
168
- if (error.code !== "ENOENT") return false;
169
- }
170
- if (status === null) return !destination.present;
171
- return (
172
- destination.present &&
173
- status.isFile() &&
174
- status.dev === destination.identity.device &&
175
- status.ino === destination.identity.node
176
- );
177
- }
178
-
179
- async function readRecord(directory) {
180
- const path = recordPath(directory);
181
- const stored = await readNote(path, null);
182
- if (stored === null) return [];
183
- const unreadable = () => {
184
- throw new KitRefusal("kit_state_unreadable", path);
185
- };
186
- if (!plainDocument(stored) || !Array.isArray(stored.destinations)) unreadable();
187
- return stored.destinations.map((entry) => {
188
- if (!plainDocument(entry) || typeof entry.settings !== "string") unreadable();
189
- if (!Array.isArray(entry.commands)) unreadable();
190
- if (entry.commands.some((command) => typeof command !== "string")) unreadable();
191
- return { path: entry.settings, commands: entry.commands };
192
- });
193
- }
194
-
195
- function recordOf(destinations) {
196
- return {
197
- destinations: destinations.map((destination) => ({
198
- settings: destination.path,
199
- commands: [...destination.commands],
200
- })),
201
- };
202
- }
203
-
204
- function sameRecord(one, other) {
205
- return JSON.stringify(one) === JSON.stringify(other);
206
- }
207
-
208
- function ownedEntry(entry, commands) {
209
- return (
210
- plainDocument(entry) &&
211
- entry.type === "command" &&
212
- typeof entry.command === "string" &&
213
- commands.has(entry.command)
214
- );
215
- }
216
-
217
- function keptGroup(group, commands) {
218
- if (!plainDocument(group) || !Array.isArray(group.hooks)) return group;
219
- const kept = group.hooks.filter((entry) => !ownedEntry(entry, commands));
220
- if (kept.length === group.hooks.length) return group;
221
- if (kept.length === 0) return null;
222
- return { ...group, hooks: kept };
223
- }
224
-
225
- function reconciledSettings(destination, command) {
226
- const settings = destination.document;
227
- const { hooks, groups: declared } = hookSections(settings, destination.declared);
228
- const kept = declared
229
- .map((group) => keptGroup(group, destination.commands))
230
- .filter((group) => group !== null);
231
- const groups =
232
- command === null ? kept : [...kept, { hooks: [{ type: "command", command }] }];
233
- if (groups.length > 0) {
234
- return { ...settings, hooks: { ...hooks, [SESSION_START_EVENT]: groups } };
235
- }
236
- const { [SESSION_START_EVENT]: _sessionStart, ...remaining } = hooks;
237
- if (Object.keys(remaining).length > 0) return { ...settings, hooks: remaining };
238
- const { hooks: _hooks, ...document } = settings;
239
- return document;
240
- }
241
-
242
- function instructionPath(directory, id) {
243
- return join(hooksNamespace(directory), `${id}.txt`);
244
- }
245
-
246
- async function pruneInstructions(directory, hook, signal) {
247
- active(signal);
248
- const namespace = hooksNamespace(directory);
249
- if (hook === null) {
250
- await rm(namespace, { recursive: true, force: true });
251
- return;
252
- }
253
- const current = `${hook.id}.txt`;
254
- for (const present of await readdir(namespace)) {
255
- if (present !== current) await rm(join(namespace, present), { recursive: true });
256
- }
257
- }
258
-
259
- async function restored(written) {
260
- const unrestored = [];
261
- for (const target of [...written].reverse()) {
262
- try {
263
- if (target.present) await writeText(target.path, target.contents, undefined, target.mode);
264
- else await rm(target.path, { force: true });
265
- } catch {
266
- unrestored.push(target.path);
267
- }
268
- }
269
- if (unrestored.length > 0) {
270
- throw new KitRefusal("kit_client_settings_unrestored", unrestored.join(" "));
271
- }
272
- }
273
-
274
- async function writeSettings(targets, guard, signal) {
275
- const written = [];
276
- try {
277
- for (const target of targets) {
278
- if (!target.desired && !target.present) continue;
279
- active(signal);
280
- if (!(await prepared(target, guard))) {
281
- throw new KitRefusal("kit_client_settings_refused", target.declared);
282
- }
283
- await writeNote(target.path, target.next, signal, target.mode);
284
- written.push(target);
285
- }
286
- } catch (error) {
287
- await restored(written);
288
- throw error;
289
- }
290
- }
291
-
292
- async function writeReconciliation(plan, targets, hook, command, signal) {
293
- const { directory, recorded, guard } = plan;
294
- const intent = recordOf(targets);
295
- if (!sameRecord(intent, recordOf(recorded))) {
296
- active(signal);
297
- await writeNote(recordPath(directory), intent, signal);
298
- }
299
- if (hook !== null) {
300
- active(signal);
301
- await writeText(instructionPath(directory, hook.id), `${hook.instruction}\n`, signal);
302
- }
303
- await writeSettings(targets, guard, signal);
304
- const desired = targets.filter((target) => target.desired);
305
- active(signal);
306
- if (desired.length === 0) {
307
- await rm(recordPath(directory), { force: true });
308
- } else {
309
- await writeNote(
310
- recordPath(directory),
311
- recordOf(desired.map((target) => ({ path: target.path, commands: new Set([command]) }))),
312
- signal,
313
- );
314
- }
315
- await pruneInstructions(directory, hook, signal);
316
- }
317
-
318
- export async function planSessionStartHook(installation, clients, signal) {
319
- const recorded = await readRecord(installation.directory);
320
- const plan = {
321
- directory: installation.directory,
322
- declared: clients.length,
323
- recorded,
324
- targets: [],
325
- guard: null,
326
- };
327
- if (clients.length === 0 && recorded.length === 0) return plan;
328
- const home = await canonicalPath(installation.directory);
329
- const configuration = await canonicalPath(installation.configuration);
330
- plan.guard = { home, configuration };
331
- const taken = new Set();
332
- const destinations = new Map();
333
- for (const client of clients) {
334
- active(signal);
335
- const destination = await declaredDestination({ home, configuration }, client, taken);
336
- destinations.set(destination.path, { ...destination, commands: new Set() });
337
- }
338
- for (const entry of recorded) {
339
- active(signal);
340
- if (!destinations.has(entry.path)) {
341
- const destination = await recordedDestination({ home }, entry);
342
- destinations.set(destination.path, { ...destination, commands: new Set() });
343
- }
344
- const known = destinations.get(entry.path);
345
- for (const previous of entry.commands) known.commands.add(previous);
346
- }
347
- plan.targets = [...destinations.values()];
348
- for (const target of plan.targets) {
349
- active(signal);
350
- Object.assign(target, await readSettings(target));
351
- }
352
- return plan;
353
- }
354
-
355
- export async function commitSessionStartHook(plan, served, signal) {
356
- if (plan.declared === 0 && plan.recorded.length === 0) return { installed: [], removed: [] };
357
- const hook = plan.declared === 0 ? null : servedSessionStartHook(served);
358
- const command =
359
- hook === null ? null : instructionCommand(instructionPath(plan.directory, hook.id));
360
- for (const target of plan.targets) {
361
- if (target.desired && command !== null) target.commands.add(command);
362
- target.next = reconciledSettings(target, target.desired ? command : null);
363
- }
364
- await writeReconciliation(plan, plan.targets, hook, command, signal);
365
- return {
366
- installed: plan.targets
367
- .filter((target) => target.desired)
368
- .map((target) => ({
369
- kind: target.kind,
370
- settings: target.declared,
371
- id: hook.id,
372
- version: hook.version,
373
- })),
374
- removed: plan.targets.filter((target) => !target.desired).map((target) => target.path),
375
- };
376
- }
package/src/host.js DELETED
@@ -1,121 +0,0 @@
1
- import { readFile, statfs } from "node:fs/promises";
2
- import { readConfiguration } from "./configuration.js";
3
- import { HOST_CPU_SAMPLE_MS } from "./contract.js";
4
- import { openSession, reportHostCapacity } from "./house.js";
5
- import { KitRefusal } from "./refusal.js";
6
-
7
- const PROC_STAT = "/proc/stat";
8
- const PROC_MEMINFO = "/proc/meminfo";
9
- const PROCESSOR_LINE = /^cpu[0-9]+\s/;
10
- const AGGREGATE_LINE = /^cpu\s+(.*)$/;
11
- const MEMINFO_LINE = /^(\w+):\s+(\d+)\s+kB$/;
12
-
13
- function unmeasurable(source) {
14
- throw new KitRefusal("kit_host_unmeasurable", source);
15
- }
16
-
17
- async function readSource(read, path) {
18
- try {
19
- return await read(path);
20
- } catch {
21
- return unmeasurable(path);
22
- }
23
- }
24
-
25
- function processorTime(document) {
26
- const lines = document.split("\n");
27
- const aggregate = lines.map((line) => AGGREGATE_LINE.exec(line)).find((match) => match !== null);
28
- if (aggregate === null || aggregate === undefined) unmeasurable(PROC_STAT);
29
- const fields = aggregate[1].trim().split(/\s+/).slice(0, 8).map(Number);
30
- if (fields.length < 5 || fields.some((field) => !Number.isFinite(field))) {
31
- unmeasurable(PROC_STAT);
32
- }
33
- const processors = lines.filter((line) => PROCESSOR_LINE.test(line)).length;
34
- if (processors === 0) unmeasurable(PROC_STAT);
35
- return {
36
- processors,
37
- busy: fields.reduce((total, field) => total + field, 0) - fields[3] - fields[4],
38
- idle: fields[3] + fields[4],
39
- };
40
- }
41
-
42
- async function processors(read, wait) {
43
- const first = processorTime(await readSource(read, PROC_STAT));
44
- await wait(HOST_CPU_SAMPLE_MS);
45
- const second = processorTime(await readSource(read, PROC_STAT));
46
- const elapsed = second.busy + second.idle - first.busy - first.idle;
47
- if (elapsed <= 0 || second.processors !== first.processors) unmeasurable(PROC_STAT);
48
- const total = second.processors * 1000;
49
- const used = Math.min(total, Math.round(((second.busy - first.busy) / elapsed) * total));
50
- return { total, used, available: total - used };
51
- }
52
-
53
- function memory(document) {
54
- const named = new Map(
55
- document
56
- .split("\n")
57
- .map((line) => MEMINFO_LINE.exec(line))
58
- .filter((match) => match !== null)
59
- .map((match) => [match[1], Number(match[2]) * 1024]),
60
- );
61
- const total = named.get("MemTotal");
62
- const available = named.get("MemAvailable");
63
- if (total === undefined || available === undefined || total < 1 || available > total) {
64
- unmeasurable(PROC_MEMINFO);
65
- }
66
- return { total, used: total - available, available };
67
- }
68
-
69
- function workspaceDisk(path, statistics) {
70
- const total = statistics.blocks * statistics.bsize;
71
- const used = (statistics.blocks - statistics.bfree) * statistics.bsize;
72
- const available = statistics.bavail * statistics.bsize;
73
- if (!Number.isSafeInteger(total) || total < 1 || used < 0 || available > total) {
74
- unmeasurable(path);
75
- }
76
- return { path, total, used, available };
77
- }
78
-
79
- export async function measureHost(workspace, dependencies = {}) {
80
- const read = dependencies.readFile ?? ((path) => readFile(path, "utf8"));
81
- const statistics = dependencies.statfs ?? statfs;
82
- const wait = dependencies.wait ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
83
- const now = dependencies.now ?? (() => new Date());
84
- const cpu = await processors(read, wait);
85
- const ram = memory(await readSource(read, PROC_MEMINFO));
86
- let filesystem;
87
- try {
88
- filesystem = await statistics(workspace);
89
- } catch {
90
- return unmeasurable(workspace);
91
- }
92
- return {
93
- observed_at: now().toISOString(),
94
- cpu,
95
- memory: ram,
96
- disk: workspaceDisk(workspace, filesystem),
97
- };
98
- }
99
-
100
- function declaredMaxima(maxima) {
101
- return {
102
- ...(maxima.cpuMillicores === null ? {} : { cpu_millicores: maxima.cpuMillicores }),
103
- ...(maxima.memoryBytes === null ? {} : { memory_bytes: maxima.memoryBytes }),
104
- ...(maxima.diskBytes === null ? {} : { disk_bytes: maxima.diskBytes }),
105
- };
106
- }
107
-
108
- export async function reportHost(options, dependencies = {}) {
109
- const configuration = await readConfiguration(options.configuration);
110
- if (configuration.host === null) {
111
- throw new KitRefusal("kit_host_unconfigured", options.configuration);
112
- }
113
- const observed = await measureHost(configuration.host.workspace, dependencies);
114
- const declaration =
115
- configuration.host.maxima === null
116
- ? observed
117
- : { ...observed, maxima: declaredMaxima(configuration.host.maxima) };
118
- const session = await openSession(options, dependencies);
119
- await reportHostCapacity(session, declaration);
120
- return declaration;
121
- }
package/src/house-main.js DELETED
@@ -1,79 +0,0 @@
1
- import { resolve } from "node:path";
2
- import { BRIDGE_NONCE_ENVIRONMENT, BRIDGE_SOCKET_ENVIRONMENT } from "./contract.js";
3
- import { bridgeCall } from "./mcp.js";
4
- import { KitRefusal } from "./refusal.js";
5
-
6
- const HELP = ["--help", "-h"];
7
- const usage = (verb) => `usage: house ${verb} ['<arguments as one JSON object>' | - for standard input]`;
8
-
9
- async function answered(method, params) {
10
- const socket = process.env[BRIDGE_SOCKET_ENVIRONMENT];
11
- const nonce = process.env[BRIDGE_NONCE_ENVIRONMENT];
12
- if (socket === undefined || nonce === undefined) throw new KitRefusal("kit_bridge_unavailable");
13
- const frame = await bridgeCall({ socket, nonce }, { method, params });
14
- if (frame.error !== undefined) throw new KitRefusal(frame.error.message ?? "kit_house_refused");
15
- return frame.result;
16
- }
17
-
18
- async function listed() {
19
- const tools = (await answered("tools/list", {}))?.tools;
20
- if (!Array.isArray(tools)) throw new KitRefusal("kit_house_listing_unreadable");
21
- return tools;
22
- }
23
-
24
- async function standardInput() {
25
- let text = "";
26
- process.stdin.setEncoding("utf8");
27
- for await (const chunk of process.stdin) text += chunk;
28
- return text;
29
- }
30
-
31
- async function argumentsOf(verb, text) {
32
- if (text === undefined) return {};
33
- let parsed = null;
34
- try {
35
- parsed = JSON.parse(text === "-" ? await standardInput() : text);
36
- } catch {}
37
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
38
- throw new KitRefusal("kit_house_arguments_invalid", usage(verb));
39
- }
40
- return parsed;
41
- }
42
-
43
- function located(verb, args) {
44
- const local = (path) => (typeof path === "string" ? resolve(path) : path);
45
- if (verb === "upload_attachment") return { ...args, path: local(args.path) };
46
- if (verb === "append_record" && Array.isArray(args.attachments)) {
47
- return { ...args, attachments: args.attachments.map(local) };
48
- }
49
- return args;
50
- }
51
-
52
- async function house([verb, argument, ...rest]) {
53
- if (verb === undefined || HELP.includes(verb)) {
54
- const tools = await listed();
55
- return { out: [usage("<verb>"), ...tools.map((tool) => `${tool.name}\n${tool.description}`)].join("\n\n") };
56
- }
57
- if (HELP.includes(argument)) {
58
- const tool = (await listed()).find((one) => one.name === verb);
59
- if (tool === undefined) throw new KitRefusal("kit_house_verb_unknown", verb);
60
- return { out: [usage(verb), tool.description, JSON.stringify(tool.inputSchema, null, 2)].join("\n\n") };
61
- }
62
- if (rest.length > 0) throw new KitRefusal("kit_house_arguments_invalid", usage(verb));
63
- const args = located(verb, await argumentsOf(verb, argument));
64
- const result = await answered("tools/call", { name: verb, arguments: args });
65
- const text = (result?.content ?? []).filter((item) => item.type === "text").map((item) => item.text).join("\n");
66
- return result?.isError === true ? { err: text, status: 1 } : { out: text };
67
- }
68
-
69
- house(process.argv.slice(2)).then(
70
- ({ out, err, status = 0 }) => {
71
- if (out !== undefined) process.stdout.write(`${out}\n`);
72
- if (err !== undefined) process.stderr.write(`${err}\n`);
73
- process.exitCode = status;
74
- },
75
- (refusal) => {
76
- process.stderr.write(`${refusal instanceof Error ? refusal.message : "kit_house_failed"}\n`);
77
- process.exitCode = 1;
78
- },
79
- );