@agentshouse/kit 0.2.1-c2c600f2d2b30 → 0.2.1-c2fb1c24bcdd5

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 (135) hide show
  1. package/README.md +42 -41
  2. package/dist/acp.js +50 -106
  3. package/dist/agents.js +149 -43
  4. package/dist/api.js +26 -4
  5. package/dist/app-git.js +48 -0
  6. package/dist/authority-main.js +2 -2
  7. package/dist/bridge.js +37 -59
  8. package/dist/building.js +46 -0
  9. package/dist/calls.js +63 -0
  10. package/dist/chatgpt.js +32 -0
  11. package/dist/clis-main.js +2 -2
  12. package/dist/clis.js +156 -78
  13. package/dist/codex-main.js +58 -0
  14. package/dist/connection.js +126 -0
  15. package/dist/conversations.js +623 -325
  16. package/dist/copies.js +188 -126
  17. package/dist/end.js +24 -5
  18. package/dist/enrol-main.js +7 -1
  19. package/dist/events.js +525 -0
  20. package/dist/files.js +139 -17
  21. package/dist/git.js +13 -11
  22. package/dist/home.js +96 -7
  23. package/dist/house-cli.js +299 -0
  24. package/dist/house-main.js +7 -159
  25. package/dist/instructions.js +7 -9
  26. package/dist/kit-cli.js +68 -0
  27. package/dist/kit-main.js +8 -19
  28. package/dist/login.js +117 -104
  29. package/dist/logout.js +14 -13
  30. package/dist/mcp.js +4 -2
  31. package/dist/parts.js +82 -0
  32. package/dist/placement.js +39 -0
  33. package/dist/refusals.js +12 -0
  34. package/dist/relay.js +33 -2
  35. package/dist/resident.js +112 -50
  36. package/dist/scope.js +206 -0
  37. package/dist/secret-input.js +2 -2
  38. package/dist/service-main.js +9 -0
  39. package/dist/service.js +55 -0
  40. package/dist/shell.js +3 -0
  41. package/dist/sign-in.js +40 -33
  42. package/dist/skills.js +20 -32
  43. package/dist/transcription.js +129 -43
  44. package/dist/translate.js +2 -8
  45. package/dist/update.js +98 -53
  46. package/dist/web-wizard.js +101 -0
  47. package/dist/wire.js +22 -0
  48. package/dist/work.js +346 -0
  49. package/package.json +15 -15
  50. package/dist/blocks.js +0 -51
  51. package/dist/bootstrap.json +0 -24
  52. package/dist/stream.js +0 -72
  53. package/how-we-work.md +0 -23
  54. package/skills/ask-matt/LICENSE +0 -21
  55. package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
  56. package/skills/ask-matt/SKILL.md +0 -91
  57. package/skills/ask-matt/agents/openai.yaml +0 -4
  58. package/skills/code-review/LICENSE +0 -21
  59. package/skills/code-review/SKILL.md +0 -89
  60. package/skills/code-review/agents/openai.yaml +0 -3
  61. package/skills/codebase-design/DEEPENING.md +0 -37
  62. package/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
  63. package/skills/codebase-design/LICENSE +0 -21
  64. package/skills/codebase-design/SKILL.md +0 -116
  65. package/skills/codebase-design/agents/openai.yaml +0 -3
  66. package/skills/diagnosing-bugs/LICENSE +0 -21
  67. package/skills/diagnosing-bugs/SKILL.md +0 -140
  68. package/skills/diagnosing-bugs/agents/openai.yaml +0 -3
  69. package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
  70. package/skills/domain-modeling/ADR-FORMAT.md +0 -47
  71. package/skills/domain-modeling/CONTEXT-FORMAT.md +0 -60
  72. package/skills/domain-modeling/LICENSE +0 -21
  73. package/skills/domain-modeling/SKILL.md +0 -76
  74. package/skills/domain-modeling/agents/openai.yaml +0 -3
  75. package/skills/grill-me/LICENSE +0 -21
  76. package/skills/grill-me/SKILL.md +0 -8
  77. package/skills/grill-me/agents/openai.yaml +0 -4
  78. package/skills/grill-with-docs/LICENSE +0 -21
  79. package/skills/grill-with-docs/SKILL.md +0 -8
  80. package/skills/grill-with-docs/agents/openai.yaml +0 -4
  81. package/skills/grilling/LICENSE +0 -21
  82. package/skills/grilling/SKILL.md +0 -30
  83. package/skills/grilling/agents/openai.yaml +0 -3
  84. package/skills/handoff/LICENSE +0 -21
  85. package/skills/handoff/SKILL.md +0 -17
  86. package/skills/handoff/agents/openai.yaml +0 -4
  87. package/skills/implement/LICENSE +0 -21
  88. package/skills/implement/SKILL.md +0 -16
  89. package/skills/implement/agents/openai.yaml +0 -4
  90. package/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
  91. package/skills/improve-codebase-architecture/LICENSE +0 -21
  92. package/skills/improve-codebase-architecture/SKILL.md +0 -72
  93. package/skills/improve-codebase-architecture/agents/openai.yaml +0 -4
  94. package/skills/prototype/LICENSE +0 -21
  95. package/skills/prototype/LOGIC.md +0 -67
  96. package/skills/prototype/SKILL.md +0 -28
  97. package/skills/prototype/UI.md +0 -112
  98. package/skills/prototype/agents/openai.yaml +0 -3
  99. package/skills/research/LICENSE +0 -21
  100. package/skills/research/SKILL.md +0 -14
  101. package/skills/research/agents/openai.yaml +0 -3
  102. package/skills/resolving-merge-conflicts/LICENSE +0 -21
  103. package/skills/resolving-merge-conflicts/SKILL.md +0 -16
  104. package/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
  105. package/skills/tdd/LICENSE +0 -21
  106. package/skills/tdd/SKILL.md +0 -40
  107. package/skills/tdd/agents/openai.yaml +0 -3
  108. package/skills/tdd/mocking.md +0 -59
  109. package/skills/tdd/tests.md +0 -77
  110. package/skills/teach/GLOSSARY-FORMAT.md +0 -35
  111. package/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
  112. package/skills/teach/LICENSE +0 -21
  113. package/skills/teach/MISSION-FORMAT.md +0 -31
  114. package/skills/teach/RESOURCES-FORMAT.md +0 -32
  115. package/skills/teach/SKILL.md +0 -141
  116. package/skills/teach/agents/openai.yaml +0 -4
  117. package/skills/to-questionnaire/LICENSE +0 -21
  118. package/skills/to-questionnaire/SKILL.md +0 -55
  119. package/skills/to-questionnaire/agents/openai.yaml +0 -4
  120. package/skills/to-spec/LICENSE +0 -21
  121. package/skills/to-spec/SKILL.md +0 -76
  122. package/skills/to-spec/agents/openai.yaml +0 -4
  123. package/skills/to-tickets/LICENSE +0 -21
  124. package/skills/to-tickets/SKILL.md +0 -106
  125. package/skills/to-tickets/agents/openai.yaml +0 -4
  126. package/skills/wait-what/LICENSE +0 -21
  127. package/skills/wait-what/SKILL.md +0 -8
  128. package/skills/wait-what/agents/openai.yaml +0 -4
  129. package/skills/wayfinder/LICENSE +0 -21
  130. package/skills/wayfinder/SKILL.md +0 -129
  131. package/skills/wayfinder/agents/openai.yaml +0 -4
  132. package/skills/writing-for-agents/LICENSE +0 -21
  133. package/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
  134. package/skills/writing-for-agents/SKILL.md +0 -83
  135. package/skills/writing-for-agents/agents/openai.yaml +0 -3
package/dist/api.js CHANGED
@@ -1,6 +1,9 @@
1
- import { end, SIGN_IN_AGAIN } from './end.js';
1
+ import { setTimeout as delay } from 'node:timers/promises';
2
+ import { signInAgain } from './end.js';
2
3
  import { readEnrolment } from './home.js';
4
+ import { retryable } from './refusals.js';
3
5
  import { relayed } from './relay.js';
6
+ import { endUnserved, KIT_WIRE, KIT_WIRE_HEADER } from './wire.js';
4
7
  export class HouseRefusal extends Error {
5
8
  status;
6
9
  text;
@@ -10,21 +13,40 @@ export class HouseRefusal extends Error {
10
13
  this.text = text;
11
14
  }
12
15
  }
16
+ export function finalRefusal(error) {
17
+ return error instanceof HouseRefusal && retryable(error.text) === false;
18
+ }
13
19
  export function retryDelay(attempt) {
14
- // Full jitter, doubling from one second to a 30-second cap, spreads the Kits one outage dropped and retries within half a minute of House's return.
20
+ // Full jitter, doubling from one second to a 30-second cap, spreads the Kit processes one outage dropped and retries within half a minute of House's return.
15
21
  return Math.random() * Math.min(30_000, 1000 * 2 ** attempt);
16
22
  }
23
+ export async function retrying(sent, signal, pause = retryDelay) {
24
+ for (let attempt = 0;; attempt++) {
25
+ try {
26
+ return await sent();
27
+ }
28
+ catch (error) {
29
+ if (!(error instanceof HouseRefusal) || retryable(error.text) !== true)
30
+ throw error;
31
+ }
32
+ // A request House refused as retryable is sent again after the caller's pause, the jittered curve unless it names its own.
33
+ await delay(pause(attempt), undefined, { signal });
34
+ }
35
+ }
17
36
  export async function post(house, path, body, credential, signal) {
18
37
  const answer = await fetch(relayed(new URL(path, house)), {
19
38
  method: 'POST',
20
39
  headers: {
21
40
  'content-type': 'application/json',
41
+ [KIT_WIRE_HEADER]: String(KIT_WIRE),
22
42
  ...(credential === undefined ? {} : { authorization: `Bearer ${credential}` }),
23
43
  },
24
44
  body: JSON.stringify(body),
25
45
  signal,
26
46
  });
27
47
  const text = await answer.text();
48
+ if (!answer.ok)
49
+ endUnserved(text);
28
50
  if (!answer.ok)
29
51
  throw new HouseRefusal(path, answer.status, text);
30
52
  return JSON.parse(text);
@@ -36,7 +58,7 @@ async function enrolledPost(path, body, signal) {
36
58
  }
37
59
  catch (error) {
38
60
  if (error instanceof HouseRefusal && error.status === 401)
39
- end(SIGN_IN_AGAIN);
61
+ await signInAgain(credential);
40
62
  throw error;
41
63
  }
42
64
  }
@@ -48,7 +70,7 @@ export const house = {
48
70
  return await enrolledPost(path, body);
49
71
  }
50
72
  catch (error) {
51
- if (error instanceof HouseRefusal && error.status < 500)
73
+ if (finalRefusal(error))
52
74
  throw error;
53
75
  process.stderr.write(`kit: ${path} did not reach House: ${error.message}\n`);
54
76
  // A delivery House did not take is sent again on the jittered curve for as long as it is still current.
@@ -0,0 +1,48 @@
1
+ import { run } from './git.js';
2
+ import { kitHome, readOwnAgent } from './home.js';
3
+ const HELPER = '!kit git-credential';
4
+ function section(house) {
5
+ return `credential.${new URL('/app/', house).href}`;
6
+ }
7
+ async function configured(args) {
8
+ try {
9
+ return await run(kitHome(), ['config', '--global', ...args]);
10
+ }
11
+ catch (error) {
12
+ if (error.code === 'ENOENT')
13
+ return null;
14
+ throw error;
15
+ }
16
+ }
17
+ export async function placeAppGit(ownAgent) {
18
+ if (ownAgent === null)
19
+ return;
20
+ await configured(['--remove-section', section(ownAgent.house)]);
21
+ for (const value of ['', HELPER]) {
22
+ const ran = await configured(['--add', `${section(ownAgent.house)}.helper`, value]);
23
+ if (ran === null)
24
+ return;
25
+ if (ran.status !== 0)
26
+ throw new Error(`git config failed: ${ran.stderr.trim()}`);
27
+ }
28
+ }
29
+ export async function answerGitCredential(operation) {
30
+ let asked = '';
31
+ for await (const chunk of process.stdin)
32
+ asked += String(chunk);
33
+ if (operation !== 'get')
34
+ return;
35
+ const attributes = new Map(asked
36
+ .split('\n')
37
+ .filter((line) => line.includes('='))
38
+ .map((line) => [line.slice(0, line.indexOf('=')), line.slice(line.indexOf('=') + 1)]));
39
+ const ownAgent = await readOwnAgent();
40
+ if (ownAgent === null) {
41
+ process.stderr.write(`kit: Kit home ${kitHome()} is not signed in; run kit login\n`);
42
+ return;
43
+ }
44
+ const house = new URL(ownAgent.house);
45
+ if (attributes.get('protocol') !== house.protocol.slice(0, -1) || attributes.get('host') !== house.host)
46
+ return;
47
+ process.stdout.write(`username=house\npassword=${ownAgent.credential}\n`);
48
+ }
@@ -1,10 +1,10 @@
1
- import { setGlobalProxyFromEnv } from 'node:http';
2
1
  import { HouseRefusal, post } from './api.js';
3
2
  import { readEnrolment } from './home.js';
3
+ import { proxyUnlessRelayed } from './relay.js';
4
4
  function unanswered(error) {
5
5
  return error instanceof HouseRefusal ? error.status >= 500 : error instanceof TypeError && error.message === 'fetch failed';
6
6
  }
7
- setGlobalProxyFromEnv();
7
+ proxyUnlessRelayed();
8
8
  try {
9
9
  const { house, credential } = (await readEnrolment());
10
10
  try {
package/dist/bridge.js CHANGED
@@ -1,21 +1,19 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
2
  import { once } from 'node:events';
3
3
  import { setTimeout as delay } from 'node:timers/promises';
4
- import { mkdir } from 'node:fs/promises';
5
4
  import { createServer, request as httpRequest } from 'node:http';
6
5
  import { request as httpsRequest } from 'node:https';
7
6
  import { delimiter, dirname, join } from 'node:path';
8
7
  import { parse } from 'yaml';
9
- import { HouseRefusal, retryDelay } from './api.js';
8
+ import { finalRefusal, HouseRefusal, retryDelay, retrying } from './api.js';
10
9
  import { loginPath } from './clis.js';
11
10
  import { changedPaths, } from './copies.js';
12
- import { kitHome, readEnrolment } from './home.js';
11
+ import { kitHome, readEnrolment, socketDirectory } from './home.js';
13
12
  import { callHouse, mcpBody, rpc, UNREACHABLE } from './mcp.js';
14
- import { operationId, uuidOf } from './operation.js';
15
- import { refusalOf } from './refusals.js';
13
+ import { operationId } from './operation.js';
14
+ import { refusalOf, retryable } from './refusals.js';
16
15
  import { relayed } from './relay.js';
17
- const MCP_REQUEST_BYTES = 4 * 1024 * 1024;
18
- const ATTACHMENTS = '/kit/attachments/upload';
16
+ const MCP_REQUEST_BYTES = 4_000_000;
19
17
  export async function received(request) {
20
18
  let text = '';
21
19
  request.setEncoding('utf8');
@@ -40,8 +38,6 @@ function refusalIn(text, status) {
40
38
  return refusalOf(text) ?? { code: '', text: text === '' ? `House refused it with ${status}` : text, conflicts: [] };
41
39
  }
42
40
  function answered(forwarded) {
43
- if (forwarded.status >= 500)
44
- throw new HouseRefusal('/', forwarded.status, forwarded.text);
45
41
  if (forwarded.status !== 200)
46
42
  return { refused: refusalIn(forwarded.text, forwarded.status) };
47
43
  const answer = JSON.parse(forwarded.text);
@@ -65,16 +61,11 @@ function pendingSeconds(forwarded) {
65
61
  function resendAfter(forwarded, attempt) {
66
62
  if (forwarded === null)
67
63
  return retryDelay(attempt);
68
- if (forwarded.status >= 500)
69
- return refusalOf(forwarded.text) === null ? retryDelay(attempt) : null;
64
+ if (forwarded.status !== 200)
65
+ return retryable(forwarded.text) === false ? null : retryDelay(attempt);
70
66
  const seconds = pendingSeconds(forwarded);
71
67
  return seconds === null ? null : seconds * 1000;
72
68
  }
73
- function versioned(conversationId, text) {
74
- const file = JSON.parse(text);
75
- const identity = JSON.stringify([conversationId, file.room_ref ?? null, file.name, file.sha256]);
76
- return JSON.stringify({ ...file, version: uuidOf(createHash('sha256').update(identity).digest(), 8) });
77
- }
78
69
  function editAnswer(forwarded) {
79
70
  const answer = answered(forwarded);
80
71
  if ('refused' in answer)
@@ -90,7 +81,7 @@ function editAnswer(forwarded) {
90
81
  export async function openBridge(house, copies, conversationId, signal) {
91
82
  const path = [join(kitHome(), 'shim'), dirname(process.execPath), await loginPath()].join(delimiter);
92
83
  const origin = (await readEnrolment()).house;
93
- const { credential } = await house.post(`/kit/conversations/${conversationId}/credential`, {}, signal);
84
+ const { credential } = await retrying(() => house.post(`/kit/conversations/${conversationId}/credential`, {}, signal), signal);
94
85
  const closed = new AbortController();
95
86
  const sent = (message, operation) => callHouse(origin, credential, message, operation, closed.signal);
96
87
  const send = async (message, operation) => {
@@ -105,7 +96,7 @@ export async function openBridge(house, copies, conversationId, signal) {
105
96
  const after = resendAfter(forwarded, attempt);
106
97
  if (after === null)
107
98
  return forwarded;
108
- // A write House did not settle is sent again on the jittered curve, or after the wait House names for a pending one.
99
+ // A Tool call House did not settle is sent again on the jittered curve, or after the wait House names for a pending one.
109
100
  await delay(after, undefined, { signal: closed.signal });
110
101
  }
111
102
  };
@@ -120,46 +111,32 @@ export async function openBridge(house, copies, conversationId, signal) {
120
111
  if ('refused' in prepared)
121
112
  return prepared;
122
113
  const { transfer } = parse(prepared.text);
123
- const posted = await fetch(relayed(transfer.url), {
124
- method: 'POST',
125
- headers: { 'content-type': 'text/plain; charset=utf-8' },
126
- body: Buffer.concat([Buffer.from(`${transfer.operation}\n`), bytes]),
127
- signal: closed.signal,
128
- });
129
- const text = await posted.text();
130
- if (posted.status >= 500)
131
- throw new HouseRefusal('/', posted.status, text);
132
- if (!posted.ok)
133
- return { refused: refusalIn(text, posted.status) };
134
- const held = JSON.parse(text).transfer;
135
- return { url: held.url, operation: held.operation, bytes: bytes.length, sha256 };
136
- };
137
- let mutations = null;
138
- const writes = async (tool) => {
139
- mutations ??= house
140
- .post('/kit/tools/mutations', {}, closed.signal)
141
- .then(({ tools }) => new Set(tools.filter((listed) => listed.writes).map((listed) => listed.name)));
114
+ let text;
142
115
  try {
143
- return (await mutations).has(tool);
116
+ text = await retrying(async () => {
117
+ const posted = await fetch(relayed(transfer.url), {
118
+ method: 'POST',
119
+ headers: { 'content-type': 'text/plain; charset=utf-8' },
120
+ body: Buffer.concat([Buffer.from(`${transfer.operation}\n`), bytes]),
121
+ signal: closed.signal,
122
+ });
123
+ const answer = await posted.text();
124
+ if (!posted.ok)
125
+ throw new HouseRefusal('/', posted.status, answer);
126
+ return answer;
127
+ }, closed.signal);
144
128
  }
145
129
  catch (error) {
146
- mutations = null;
147
- throw error;
130
+ if (!finalRefusal(error))
131
+ throw error;
132
+ return { refused: refusalIn(error.text, error.status) };
148
133
  }
134
+ const held = JSON.parse(text).transfer;
135
+ return { url: held.url, operation: held.operation, bytes: bytes.length, sha256 };
149
136
  };
150
- const forward = async (path, text) => {
151
- if (path === '/') {
152
- const message = JSON.parse(text);
153
- const tool = message.method === 'tools/call' ? String(message.params.name) : null;
154
- return send(message, tool !== null && (await writes(tool)) ? operationId() : null);
155
- }
156
- const answer = await fetch(relayed(new URL(path, origin)), {
157
- method: 'POST',
158
- headers: { 'content-type': 'application/json', authorization: `Bearer ${credential}` },
159
- body: versioned(conversationId, text),
160
- signal: closed.signal,
161
- });
162
- return { status: answer.status, text: await answer.text() };
137
+ const forward = (text) => {
138
+ const message = JSON.parse(text);
139
+ return send(message, message.method === 'tools/call' ? operationId() : null);
163
140
  };
164
141
  const caller = {
165
142
  key: `conversation:${conversationId}`,
@@ -181,15 +158,15 @@ export async function openBridge(house, copies, conversationId, signal) {
181
158
  const { cwd, commit } = JSON.parse(text);
182
159
  return { status: 200, text: JSON.stringify(await copies.push(cwd, commit, caller)) };
183
160
  }
161
+ if (path === '/git/fetch')
162
+ return { status: 200, text: JSON.stringify(await copies.fetch()) };
184
163
  if (path === '/git/observed') {
185
164
  await copies.observed(JSON.parse(text));
186
165
  return { status: 200, text: '{}' };
187
166
  }
188
- return path === '/' || path === ATTACHMENTS ? forward(path, text) : { status: 404, text: '' };
167
+ return path === '/' ? forward(text) : { status: 404, text: '' };
189
168
  };
190
- const sockets = join(kitHome(), 'bridges');
191
- await mkdir(sockets, { recursive: true, mode: 0o700 });
192
- const socketPath = join(sockets, `${randomUUID()}.sock`);
169
+ const socketPath = join(socketDirectory(), `${randomUUID()}.sock`);
193
170
  const bridge = createServer(async (request, response) => {
194
171
  try {
195
172
  const path = request.url ?? '';
@@ -205,7 +182,8 @@ export async function openBridge(house, copies, conversationId, signal) {
205
182
  response.writeHead(error.status, { 'content-type': 'application/json' }).end(error.text);
206
183
  return;
207
184
  }
208
- response.writeHead(502).end(request.url === '/git/push' && error instanceof Error ? error.message : UNREACHABLE);
185
+ const verb = request.url === '/git/push' || request.url === '/git/fetch';
186
+ response.writeHead(502).end(verb && error instanceof Error ? error.message : UNREACHABLE);
209
187
  }
210
188
  });
211
189
  bridge.listen(socketPath);
@@ -232,7 +210,7 @@ export async function openBridge(house, copies, conversationId, signal) {
232
210
  GIT_CONFIG_VALUE_0: app,
233
211
  },
234
212
  tool: async (name, args) => {
235
- const forwarded = await forward('/', JSON.stringify(call(name, args)));
213
+ const forwarded = await forward(JSON.stringify(call(name, args)));
236
214
  if (forwarded.status !== 200)
237
215
  throw new Error(`House refused ${name} with ${forwarded.status}: ${forwarded.text}`);
238
216
  const answer = JSON.parse(forwarded.text);
@@ -0,0 +1,46 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { createWriteStream } from 'node:fs';
3
+ import { rm } from 'node:fs/promises';
4
+ import { Transform } from 'node:stream';
5
+ import { pipeline } from 'node:stream/promises';
6
+ import { readEnrolment } from './home.js';
7
+ import { relayed } from './relay.js';
8
+ const MANIFEST = 'bootstrap.json';
9
+ async function served(name, signal) {
10
+ const enrolment = await readEnrolment();
11
+ if (enrolment === null)
12
+ throw new Error('this Kit is connected to no House');
13
+ const answer = await fetch(relayed(new URL(`/kit-installer/${encodeURIComponent(name)}`, enrolment.house)), { signal });
14
+ if (answer.status === 200 && answer.body !== null)
15
+ return answer;
16
+ await answer.body?.cancel();
17
+ throw new Error(`House answered ${answer.status} for ${name}`);
18
+ }
19
+ export async function servedDigest(name, signal) {
20
+ const manifest = (await (await served(MANIFEST, signal)).json());
21
+ const sha256 = manifest.files?.[name]?.sha256;
22
+ return typeof sha256 === 'string' ? sha256 : null;
23
+ }
24
+ export async function downloadServed(name, sha256, destination, signal, most = Infinity) {
25
+ const answer = await served(name, signal);
26
+ const digest = createHash('sha256');
27
+ let bytes = 0;
28
+ const hashed = new Transform({
29
+ transform(chunk, _encoding, done) {
30
+ bytes += chunk.length;
31
+ if (bytes > most)
32
+ return done(new Error(`${name} is larger than its pinned ${most} bytes`));
33
+ digest.update(chunk);
34
+ done(null, chunk);
35
+ },
36
+ });
37
+ try {
38
+ await pipeline(answer.body, hashed, createWriteStream(destination), { signal });
39
+ if (digest.digest('hex') !== sha256)
40
+ throw new Error(`${name} does not match its sha256`);
41
+ }
42
+ catch (error) {
43
+ await rm(destination, { force: true });
44
+ throw error;
45
+ }
46
+ }
package/dist/calls.js ADDED
@@ -0,0 +1,63 @@
1
+ import { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { kitHome } from './home.js';
4
+ function logged(error) {
5
+ process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
6
+ }
7
+ export class Calls {
8
+ house;
9
+ changed;
10
+ carried = new Map();
11
+ path = join(kitHome(), 'call-results.json');
12
+ results;
13
+ returned = new Set();
14
+ open = 0;
15
+ constructor(house, changed) {
16
+ this.house = house;
17
+ this.changed = changed;
18
+ this.results = new Map(existsSync(this.path) ? Object.entries(JSON.parse(readFileSync(this.path, 'utf8'))) : []);
19
+ }
20
+ keepResult(callId, result) {
21
+ if (this.returned.has(callId))
22
+ return;
23
+ this.results.set(callId, result);
24
+ this.save();
25
+ }
26
+ idle() {
27
+ return this.open === 0;
28
+ }
29
+ carry(callId, work) {
30
+ const known = this.carried.get(callId);
31
+ const result = this.results.get(callId);
32
+ const carried = known ?? (result === undefined ? work() : Promise.resolve({ result }));
33
+ if (known === undefined) {
34
+ this.carried.set(callId, carried);
35
+ carried.catch(() => this.carried.delete(callId));
36
+ }
37
+ this.open++;
38
+ this.changed();
39
+ return carried
40
+ .then(async ({ result, returned }) => {
41
+ try {
42
+ await this.house.deliver(`/kit/inputs/${callId}/ack`, result);
43
+ }
44
+ finally {
45
+ this.returned.add(callId);
46
+ if (this.results.delete(callId))
47
+ this.save();
48
+ }
49
+ if (known === undefined)
50
+ await returned?.();
51
+ })
52
+ .catch(logged)
53
+ .finally(() => {
54
+ this.open--;
55
+ this.changed();
56
+ });
57
+ }
58
+ save() {
59
+ const temporary = `${this.path}.${process.pid}`;
60
+ writeFileSync(temporary, JSON.stringify(Object.fromEntries(this.results)), { mode: 0o600 });
61
+ renameSync(temporary, this.path);
62
+ }
63
+ }
@@ -0,0 +1,32 @@
1
+ import { parseArgs } from 'node:util';
2
+ import { placeAppGit } from './app-git.js';
3
+ import { kitHome, readOwnAgent, writeOwnAgent } from './home.js';
4
+ import { accepted, DEFAULT_HOUSE } from './login.js';
5
+ const KEY_SOURCE = 'New key for a script on Connections';
6
+ const USAGE = `usage: kit chatgpt --key <key> [--house <origin>]
7
+ --key <key> a key from ${KEY_SOURCE}
8
+ --house <origin> the House of the key, ${DEFAULT_HOUSE} unless one is stored
9
+ `;
10
+ export async function chatgpt(argv) {
11
+ const { values } = parseArgs({
12
+ args: argv,
13
+ options: { key: { type: 'string' }, house: { type: 'string' }, help: { type: 'boolean', default: false } },
14
+ });
15
+ if (values.help) {
16
+ process.stdout.write(USAGE);
17
+ return;
18
+ }
19
+ if (values.key === undefined)
20
+ throw new Error(`pass --key with a key from ${KEY_SOURCE}`);
21
+ const before = await readOwnAgent();
22
+ const house = values.house ?? before?.house ?? DEFAULT_HOUSE;
23
+ if (!(await accepted(house, values.key)))
24
+ throw new Error(`${house} does not accept this key; pass one from ${KEY_SOURCE}`);
25
+ const relay = process.env.HOUSE_KIT_RELAY || undefined;
26
+ await writeOwnAgent({ house, ...(relay === undefined ? {} : { relay }), credential: values.key });
27
+ await placeAppGit(await readOwnAgent()).catch((error) => process.stderr.write(`kit: ${error.message}\n`));
28
+ if (before !== null && before.credential !== values.key) {
29
+ process.stdout.write('This replaced the key this Kit home held; give each session its own HOUSE_KIT_HOME to keep its own key.\n');
30
+ }
31
+ process.stdout.write(`house now calls ${house} as you, with the key kept in ${kitHome()}.\n`);
32
+ }
package/dist/clis-main.js CHANGED
@@ -1,8 +1,8 @@
1
- import { setGlobalProxyFromEnv } from 'node:http';
2
1
  import { post } from './api.js';
3
2
  import { CLIS, below, locate, versionOf } from './clis.js';
4
3
  import { readEnrolment } from './home.js';
5
- setGlobalProxyFromEnv();
4
+ import { proxyUnlessRelayed } from './relay.js';
5
+ proxyUnlessRelayed();
6
6
  try {
7
7
  const { house, credential } = (await readEnrolment());
8
8
  const { agents } = await post(house, '/kit/agents/desired', {}, credential);