@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
package/dist/acp.js ADDED
@@ -0,0 +1,120 @@
1
+ import { ndJsonStream, PROTOCOL_VERSION, } from '@agentclientprotocol/sdk';
2
+ import { spawn } from 'node:child_process';
3
+ import { readdirSync, readFileSync } from 'node:fs';
4
+ import { Readable, Writable } from 'node:stream';
5
+ import { CLIS, KIT_VERSION, cliCommand } from './clis.js';
6
+ export const TURN_STARTED = 'kit/turn_started';
7
+ export const TURN_ENDED = 'kit/turn_ended';
8
+ export async function startAdapter(kind, cwd, app, jobs = () => undefined, env = process.env) {
9
+ const child = spawn(cliCommand(kind), CLIS[kind].args, {
10
+ cwd,
11
+ env,
12
+ stdio: ['pipe', 'pipe', 'pipe'],
13
+ detached: true,
14
+ });
15
+ let stderr = '';
16
+ child.stderr.on('data', (chunk) => {
17
+ stderr = (stderr + chunk.toString('utf8')).slice(-4000);
18
+ });
19
+ const exited = new Promise((resolve) => {
20
+ child.on('error', (error) => resolve(`the CLI did not start: ${error.message}`));
21
+ child.on('exit', (code, signal) => {
22
+ const last = stderr.trim().split('\n').at(-1);
23
+ resolve(`the CLI exited with ${code ?? signal}${last ? `: ${last}` : ''}`);
24
+ });
25
+ });
26
+ const stream = ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
27
+ const connection = app.connect({
28
+ writable: stream.writable,
29
+ readable: stream.readable.pipeThrough(new TransformStream({
30
+ transform(message, controller) {
31
+ const notice = message;
32
+ const job = CLIS[kind].job(notice);
33
+ if (job !== null)
34
+ jobs(job);
35
+ if (!notice.params?.update?.sessionUpdate?.startsWith('async_task_'))
36
+ controller.enqueue(message);
37
+ if (CLIS[kind].turnStarted(notice))
38
+ controller.enqueue({ jsonrpc: '2.0', method: TURN_STARTED });
39
+ if (CLIS[kind].turnEnded(notice))
40
+ controller.enqueue({ jsonrpc: '2.0', method: TURN_ENDED });
41
+ },
42
+ })),
43
+ });
44
+ void exited.then((cause) => connection.close(new Error(cause)));
45
+ try {
46
+ const initialized = await connection.agent.request('initialize', {
47
+ protocolVersion: PROTOCOL_VERSION,
48
+ clientCapabilities: {
49
+ fs: { readTextFile: false, writeTextFile: false },
50
+ terminal: false,
51
+ elicitation: { form: {} },
52
+ _meta: { jetbrains: { air: { version: 1, capabilities: ['asyncTasks'] } } },
53
+ },
54
+ clientInfo: { name: '@agentshouse/kit', version: KIT_VERSION },
55
+ });
56
+ return { child, connection, initialized, exited };
57
+ }
58
+ catch (error) {
59
+ child.kill('SIGKILL');
60
+ throw error;
61
+ }
62
+ }
63
+ function parents() {
64
+ const children = new Map();
65
+ for (const entry of readdirSync('/proc')) {
66
+ if (!/^\d+$/.test(entry))
67
+ continue;
68
+ try {
69
+ const stat = readFileSync(`/proc/${entry}/stat`, 'utf8');
70
+ const parent = Number(stat.slice(stat.lastIndexOf(')') + 2).split(' ')[1]);
71
+ children.set(parent, [...(children.get(parent) ?? []), Number(entry)]);
72
+ }
73
+ catch { }
74
+ }
75
+ return children;
76
+ }
77
+ function descendants(roots) {
78
+ const children = parents();
79
+ const tree = [];
80
+ const pending = roots.flatMap((root) => children.get(root) ?? []);
81
+ while (pending.length > 0) {
82
+ const pid = pending.pop();
83
+ tree.push(pid);
84
+ pending.push(...(children.get(pid) ?? []));
85
+ }
86
+ return tree;
87
+ }
88
+ function killAll(pids) {
89
+ for (const pid of pids) {
90
+ for (const target of [-pid, pid]) {
91
+ try {
92
+ process.kill(target, 'SIGKILL');
93
+ }
94
+ catch { }
95
+ }
96
+ }
97
+ }
98
+ function marked(marker) {
99
+ return readdirSync('/proc')
100
+ .filter((entry) => {
101
+ try {
102
+ return /^\d+$/.test(entry) && readFileSync(`/proc/${entry}/environ`, 'utf8').split('\0').includes(marker);
103
+ }
104
+ catch {
105
+ return false;
106
+ }
107
+ })
108
+ .map(Number);
109
+ }
110
+ export function killTree(child) {
111
+ killAll([child.pid, ...descendants([child.pid])]);
112
+ }
113
+ export function killMarked(marker) {
114
+ const pids = marked(marker);
115
+ killAll([...pids, ...descendants(pids)]);
116
+ }
117
+ export function killDescendants() {
118
+ const pids = marked(`HOUSE_KIT_RESIDENT=${process.pid}`);
119
+ killAll([...pids, ...descendants([process.pid, ...pids])]);
120
+ }
package/dist/agents.js ADDED
@@ -0,0 +1,90 @@
1
+ import { client } from '@agentclientprotocol/sdk';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { startAdapter } from './acp.js';
4
+ import { CLIS, KIT_VERSION, cliCommand, install, run } from './clis.js';
5
+ import { kitHome } from './home.js';
6
+ async function signedIn(kind) {
7
+ const probe = CLIS[kind].signedIn;
8
+ if ('command' in probe) {
9
+ return (await run(cliCommand(kind), probe.command, 30_000)).status === 0;
10
+ }
11
+ try {
12
+ const adapter = await startAdapter(kind, kitHome(), client({ name: '@agentshouse/kit' }));
13
+ adapter.child.kill('SIGKILL');
14
+ const method = adapter.initialized._meta?.[probe.initializeMeta];
15
+ return typeof method === 'string' && method.length > 0;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
21
+ async function operatingSystem() {
22
+ const release = await readFile('/etc/os-release', 'utf8');
23
+ return /^PRETTY_NAME="?([^"\n]*)"?$/m.exec(release)?.[1] ?? release;
24
+ }
25
+ export class Agents {
26
+ desired = { agents: [], routes: [] };
27
+ house;
28
+ reading = Promise.resolve();
29
+ installing = Promise.resolve();
30
+ releases = new Map();
31
+ reporting = null;
32
+ again = false;
33
+ constructor(house) {
34
+ this.house = house;
35
+ }
36
+ read() {
37
+ return this.reading.then(() => undefined, () => undefined);
38
+ }
39
+ installed() {
40
+ return this.installing.catch(() => undefined);
41
+ }
42
+ route(agentId) {
43
+ return this.desired.routes.find((route) => route.agent_id === agentId);
44
+ }
45
+ refresh() {
46
+ const reading = this.read().then(async () => {
47
+ this.desired = await this.house.deliver('/kit/agents/desired', {});
48
+ return this.desired;
49
+ });
50
+ this.reading = reading;
51
+ const installing = this.installed()
52
+ .then(() => reading)
53
+ .then(({ agents }) => this.install(agents));
54
+ this.installing = installing;
55
+ if (this.reporting === null)
56
+ this.reporting = this.reports();
57
+ else
58
+ this.again = true;
59
+ return Promise.all([installing, this.reporting]).then(() => undefined);
60
+ }
61
+ async report() {
62
+ const agents = [];
63
+ for (const [kind, release] of this.releases) {
64
+ agents.push({ kind, release, signed_in: release !== null && (await signedIn(kind)) });
65
+ }
66
+ await this.house.deliver('/kit/agents/report', {
67
+ os: await operatingSystem(),
68
+ kit_version: KIT_VERSION,
69
+ agents,
70
+ });
71
+ }
72
+ async install(kinds) {
73
+ const releases = new Map();
74
+ for (const kind of kinds)
75
+ releases.set(kind, await install(kind));
76
+ this.releases = releases;
77
+ }
78
+ async reports() {
79
+ try {
80
+ do {
81
+ this.again = false;
82
+ await this.installing;
83
+ await this.report();
84
+ } while (this.again);
85
+ }
86
+ finally {
87
+ this.reporting = null;
88
+ }
89
+ }
90
+ }
package/dist/api.js ADDED
@@ -0,0 +1,45 @@
1
+ import { readEnrolment } from './home.js';
2
+ export class HouseRefusal extends Error {
3
+ status;
4
+ constructor(path, status, text) {
5
+ super(`House refused ${path} with ${status}: ${text}`);
6
+ this.status = status;
7
+ }
8
+ }
9
+ export async function post(house, path, body, credential, signal) {
10
+ const answer = await fetch(new URL(path, house), {
11
+ method: 'POST',
12
+ headers: {
13
+ 'content-type': 'application/json',
14
+ ...(credential === undefined ? {} : { authorization: `Bearer ${credential}` }),
15
+ },
16
+ body: JSON.stringify(body),
17
+ signal,
18
+ });
19
+ const text = await answer.text();
20
+ if (!answer.ok)
21
+ throw new HouseRefusal(path, answer.status, text);
22
+ return JSON.parse(text);
23
+ }
24
+ async function enrolledPost(path, body, signal) {
25
+ const { house, credential } = (await readEnrolment());
26
+ return post(house, path, body, credential, signal);
27
+ }
28
+ export const house = {
29
+ post: enrolledPost,
30
+ deliver: async (path, body, current = () => true) => {
31
+ for (let attempt = 0;; attempt++) {
32
+ try {
33
+ return await enrolledPost(path, body);
34
+ }
35
+ catch (error) {
36
+ if (error instanceof HouseRefusal && error.status < 500)
37
+ throw error;
38
+ process.stderr.write(`kit: ${path} did not reach House: ${error.message}\n`);
39
+ await new Promise((resolve) => setTimeout(resolve, Math.min(30_000, 1000 * 2 ** attempt)));
40
+ if (!current())
41
+ throw error;
42
+ }
43
+ }
44
+ },
45
+ };
package/dist/blocks.js ADDED
@@ -0,0 +1,51 @@
1
+ const FENCE = /^\s*(`{3,}|~{3,})\s*(\S*)/;
2
+ export function blocksOf(text) {
3
+ const blocks = [];
4
+ let paragraph = [];
5
+ let fence = null;
6
+ const closeParagraph = () => {
7
+ const joined = paragraph.join('\n').trim();
8
+ if (joined.length > 0)
9
+ blocks.push({ type: 'paragraph', text: joined });
10
+ paragraph = [];
11
+ };
12
+ const closeFence = () => {
13
+ const joined = fence.lines.join('\n');
14
+ if (joined.length > 0)
15
+ blocks.push({ type: 'code', language: fence.language, text: joined });
16
+ fence = null;
17
+ };
18
+ for (const line of text.split('\n')) {
19
+ if (fence !== null) {
20
+ if (line.trim().startsWith(fence.marker))
21
+ closeFence();
22
+ else
23
+ fence.lines.push(line);
24
+ continue;
25
+ }
26
+ const opening = FENCE.exec(line);
27
+ if (opening !== null) {
28
+ closeParagraph();
29
+ fence = { marker: opening[1], language: opening[2] || null, lines: [] };
30
+ }
31
+ else if (line.trim().length === 0) {
32
+ closeParagraph();
33
+ }
34
+ else {
35
+ paragraph.push(line);
36
+ }
37
+ }
38
+ if (fence !== null)
39
+ closeFence();
40
+ closeParagraph();
41
+ return blocks;
42
+ }
43
+ export function firstChange(before, after) {
44
+ let index = 0;
45
+ while (index < before.length &&
46
+ index < after.length &&
47
+ JSON.stringify(before[index]) === JSON.stringify(after[index])) {
48
+ index++;
49
+ }
50
+ return index;
51
+ }
package/dist/bridge.js ADDED
@@ -0,0 +1,124 @@
1
+ import { randomBytes, randomUUID } from 'node:crypto';
2
+ import { once } from 'node:events';
3
+ import { mkdir } from 'node:fs/promises';
4
+ import { createServer, request as httpRequest } from 'node:http';
5
+ import { request as httpsRequest } from 'node:https';
6
+ import { join } from 'node:path';
7
+ import { KIT_VERSION } from './clis.js';
8
+ import { kitHome, readEnrolment } from './home.js';
9
+ const MCP_PROTOCOL_VERSION = '2026-07-28';
10
+ const FORWARDED = new Set(['/', '/kit/attachments/upload']);
11
+ function operationId() {
12
+ const bytes = randomBytes(16);
13
+ bytes.writeUIntBE(Date.now(), 0, 6);
14
+ bytes[6] = 0x70 | (bytes[6] & 0x0f);
15
+ bytes[8] = 0x80 | (bytes[8] & 0x3f);
16
+ const hex = bytes.toString('hex');
17
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
18
+ }
19
+ async function received(request) {
20
+ let text = '';
21
+ request.setEncoding('utf8');
22
+ for await (const chunk of request)
23
+ text += chunk;
24
+ return text;
25
+ }
26
+ export async function openBridge(house, conversationId, signal) {
27
+ const origin = (await readEnrolment()).house;
28
+ const { credential } = await house.post(`/kit/conversations/${conversationId}/credential`, {}, signal);
29
+ const closed = new AbortController();
30
+ const mcp = async (message) => {
31
+ const params = message.params ?? {};
32
+ const tool = message.method === 'tools/call' ? String(params.name) : null;
33
+ const writes = tool !== null &&
34
+ (await house.post('/kit/tools/mutations', {}, closed.signal)).tools.some((listed) => listed.name === tool && listed.writes);
35
+ return {
36
+ headers: {
37
+ accept: 'application/json, text/event-stream',
38
+ 'mcp-protocol-version': MCP_PROTOCOL_VERSION,
39
+ 'mcp-method': message.method,
40
+ ...(tool === null ? {} : { 'mcp-name': tool }),
41
+ },
42
+ body: {
43
+ ...message,
44
+ params: {
45
+ ...params,
46
+ _meta: {
47
+ 'io.modelcontextprotocol/protocolVersion': MCP_PROTOCOL_VERSION,
48
+ 'io.modelcontextprotocol/clientInfo': { name: '@agentshouse/kit', version: KIT_VERSION },
49
+ 'io.modelcontextprotocol/clientCapabilities': {},
50
+ ...(writes ? { 'agents.house/agent-operation': operationId() } : {}),
51
+ },
52
+ },
53
+ },
54
+ };
55
+ };
56
+ const forward = async (path, text) => {
57
+ const call = path === '/' ? await mcp(JSON.parse(text)) : null;
58
+ const answer = await fetch(new URL(path, origin), {
59
+ method: 'POST',
60
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${credential}`, ...call?.headers },
61
+ body: call === null ? text : JSON.stringify(call.body),
62
+ signal: closed.signal,
63
+ });
64
+ return { status: answer.status, text: await answer.text() };
65
+ };
66
+ const sockets = join(kitHome(), 'bridges');
67
+ await mkdir(sockets, { recursive: true, mode: 0o700 });
68
+ const socketPath = join(sockets, `${randomUUID()}.sock`);
69
+ const bridge = createServer(async (request, response) => {
70
+ try {
71
+ const path = request.url ?? '';
72
+ const forwarded = FORWARDED.has(path) ? await forward(path, await received(request)) : { status: 404, text: '' };
73
+ response.writeHead(forwarded.status, { 'content-type': 'application/json' }).end(forwarded.text);
74
+ }
75
+ catch (error) {
76
+ response.writeHead(502).end(error instanceof Error ? error.message : String(error));
77
+ }
78
+ });
79
+ bridge.listen(socketPath);
80
+ await once(bridge, 'listening');
81
+ const app = new URL('/app/', origin).href;
82
+ const authorization = `Basic ${Buffer.from(`house:${credential}`).toString('base64')}`;
83
+ const git = createServer((request, response) => {
84
+ const target = new URL(request.url ?? '', app);
85
+ if (!target.href.startsWith(app)) {
86
+ response.writeHead(404).end();
87
+ return;
88
+ }
89
+ const send = target.protocol === 'https:' ? httpsRequest : httpRequest;
90
+ const forwarded = send(target, { method: request.method, headers: { ...request.headers, host: target.host, authorization } }, (answer) => {
91
+ response.writeHead(answer.statusCode, answer.headers);
92
+ answer.pipe(response);
93
+ });
94
+ forwarded.on('error', () => response.destroy());
95
+ request.pipe(forwarded);
96
+ });
97
+ git.listen(0, '127.0.0.1');
98
+ await once(git, 'listening');
99
+ const proxy = `http://127.0.0.1:${git.address().port}`;
100
+ return {
101
+ env: {
102
+ HOUSE_BRIDGE: socketPath,
103
+ GIT_CONFIG_COUNT: '1',
104
+ GIT_CONFIG_KEY_0: `url.${proxy}/app/.insteadOf`,
105
+ GIT_CONFIG_VALUE_0: app,
106
+ },
107
+ tool: async (name, args) => {
108
+ const forwarded = await forward('/', JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name, arguments: args } }));
109
+ if (forwarded.status !== 200)
110
+ throw new Error(`House refused ${name} with ${forwarded.status}: ${forwarded.text}`);
111
+ const answer = JSON.parse(forwarded.text);
112
+ if (answer.error !== undefined)
113
+ throw new Error(`House refused ${name}: ${answer.error.message}`);
114
+ return answer.result;
115
+ },
116
+ close: () => {
117
+ closed.abort();
118
+ bridge.close();
119
+ bridge.closeAllConnections();
120
+ git.close();
121
+ git.closeAllConnections();
122
+ },
123
+ };
124
+ }
package/dist/clis.js ADDED
@@ -0,0 +1,110 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { readFileSync } from 'node:fs';
3
+ import { readFile } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { kitHome } from './home.js';
6
+ const RUNNING_JOB = new Set(['running', 'paused']);
7
+ function threadStatus({ method, params }) {
8
+ if (method !== 'session/update' || params?.update?.sessionUpdate !== 'session_info_update')
9
+ return undefined;
10
+ return params.update._meta?.codex?.threadStatus?.type;
11
+ }
12
+ function asyncTask({ method, params }) {
13
+ const update = params?.update;
14
+ if (method !== 'session/update' || update?.asyncTaskId === undefined)
15
+ return null;
16
+ if (update.sessionUpdate === 'async_task_spawned')
17
+ return { id: update.asyncTaskId, running: true };
18
+ if (update.sessionUpdate === 'async_task_state_update' && !RUNNING_JOB.has(update.state ?? '')) {
19
+ return { id: update.asyncTaskId, running: false };
20
+ }
21
+ return null;
22
+ }
23
+ export const CLIS = {
24
+ 'codex-acp': {
25
+ package: '@agentclientprotocol/codex-acp',
26
+ bin: 'codex-acp',
27
+ args: [],
28
+ signedIn: { command: ['cli', 'login', 'status'] },
29
+ login: { args: ['cli', 'login', '--device-auth'], code: 'show' },
30
+ queues: false,
31
+ turnStarted: (notice) => threadStatus(notice) === 'active',
32
+ turnEnded: (notice) => threadStatus(notice) === 'idle',
33
+ job: asyncTask,
34
+ },
35
+ 'claude-agent-acp': {
36
+ package: '@agentclientprotocol/claude-agent-acp',
37
+ bin: 'claude-agent-acp',
38
+ args: [],
39
+ signedIn: { command: ['--cli', 'auth', 'status'] },
40
+ login: { args: ['--cli', 'auth', 'login', '--claudeai'], code: 'collect' },
41
+ queues: false,
42
+ turnStarted: () => false,
43
+ turnEnded: ({ method, params }) => method === 'session/update' &&
44
+ params?.update?.sessionUpdate === 'usage_update' &&
45
+ params.update._meta?.['_claude/origin'] !== undefined,
46
+ job: asyncTask,
47
+ },
48
+ 'grok-build': {
49
+ package: '@xai-official/grok',
50
+ bin: 'grok',
51
+ args: ['agent', '--no-leader', 'stdio'],
52
+ signedIn: { initializeMeta: 'defaultAuthMethodId' },
53
+ login: { args: ['login', '--device-auth'], code: 'show' },
54
+ queues: true,
55
+ turnStarted: ({ method, params }) => method === '_x.ai/queue/changed' && params?.runningPromptId !== undefined,
56
+ turnEnded: ({ method, params }) => method === '_x.ai/session_notification' && params?.update?.sessionUpdate === 'turn_completed',
57
+ job: ({ method, params }) => {
58
+ if (method === '_x.ai/task_backgrounded')
59
+ return { id: params.update.task_id, running: true };
60
+ if (method === '_x.ai/task_completed')
61
+ return { id: params.update.task_snapshot.task_id, running: false };
62
+ return null;
63
+ },
64
+ },
65
+ };
66
+ const KIT_PACKAGE = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
67
+ export const KIT_VERSION = KIT_PACKAGE.version;
68
+ export function pinned(kind) {
69
+ return KIT_PACKAGE.peerDependencies[CLIS[kind].package];
70
+ }
71
+ function prefix(kind) {
72
+ return join(kitHome(), 'agents', kind);
73
+ }
74
+ export function cliCommand(kind) {
75
+ return join(prefix(kind), 'node_modules', '.bin', CLIS[kind].bin);
76
+ }
77
+ export async function installedRelease(kind) {
78
+ try {
79
+ const manifest = join(prefix(kind), 'node_modules', CLIS[kind].package, 'package.json');
80
+ return JSON.parse(await readFile(manifest, 'utf8')).version;
81
+ }
82
+ catch {
83
+ return null;
84
+ }
85
+ }
86
+ export function run(command, args, timeoutMs) {
87
+ return new Promise((resolve) => {
88
+ const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'], timeout: timeoutMs });
89
+ let output = '';
90
+ child.stdout.on('data', (chunk) => {
91
+ output += chunk.toString('utf8');
92
+ });
93
+ child.stderr.on('data', (chunk) => {
94
+ output += chunk.toString('utf8');
95
+ });
96
+ child.on('error', (error) => resolve({ status: null, output: error.message }));
97
+ child.on('close', (status) => resolve({ status, output }));
98
+ });
99
+ }
100
+ export async function install(kind) {
101
+ const release = pinned(kind);
102
+ if ((await installedRelease(kind)) === release)
103
+ return release;
104
+ const ran = await run('npm', ['install', '--prefix', prefix(kind), '--no-save', '--no-audit', '--no-fund', `${CLIS[kind].package}@${release}`], 15 * 60_000);
105
+ if (ran.status !== 0) {
106
+ process.stderr.write(`kit: ${kind} did not install: ${ran.output.trim()}\n`);
107
+ return null;
108
+ }
109
+ return installedRelease(kind);
110
+ }