@agentshouse/kit 0.2.1-cf669c6d1ecbb → 0.2.1-cfd9926e1dac7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -15
- package/dist/agents.js +23 -4
- package/dist/api.js +5 -1
- package/dist/bootstrap.json +4 -4
- package/dist/chatgpt.js +26 -0
- package/dist/clis.js +41 -9
- package/dist/conversations.js +1 -0
- package/dist/copies.js +18 -8
- package/dist/end.js +4 -0
- package/dist/files.js +7 -1
- package/dist/git.js +11 -8
- package/dist/home.js +36 -3
- package/dist/house-main.js +165 -65
- package/dist/kit-main.js +53 -8
- package/dist/login.js +15 -6
- package/dist/placement.js +48 -0
- package/dist/relay.js +1 -1
- package/dist/resident.js +8 -4
- package/dist/skills.js +11 -1
- package/dist/stream.js +5 -2
- package/dist/update.js +28 -6
- package/dist/wire.js +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,26 +33,26 @@ them out; running it again without the flag brings them back.
|
|
|
33
33
|
|
|
34
34
|
`kit login` opens or prints a link you confirm in any browser, on this computer
|
|
35
35
|
or another, continues on its own and keeps the Environment's credential in
|
|
36
|
-
`~/.house-kit`, or in
|
|
36
|
+
`$HOUSE_KIT_HOME` when it is set, else in `~/.house-kit`, or in `house-kit-<uid>` under the temporary directory when your home is read-only. `--environment <id>`
|
|
37
37
|
reconnects an Environment you already have; `--rooms <ref>[,<ref>...]` gives it
|
|
38
|
-
only those Rooms and `--no-private` every Room but your Private Room;
|
|
39
|
-
`--code <code>` connects immediately without a link.
|
|
38
|
+
only those Rooms and `--no-private` every Room but your Private Room; `--code <code>` connects immediately without a link.
|
|
40
39
|
A login also connects your own agent: outside an Agent conversation, `house <tool>` calls
|
|
41
|
-
House as you and `git` clones and pushes your Apps at `<house>/app/` as you,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
agent's connection, so `house` and `git` no
|
|
45
|
-
`kit login` reconnects the same Environment.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
install command in the container. An Agent runs
|
|
40
|
+
House as you and `git` clones and pushes your Apps at `<house>/app/` as you, until you revoke
|
|
41
|
+
that connection on Connections. `house help` lists the Tools. Codex sessions started after a
|
|
42
|
+
login run a lone `house` command outside their sandbox. `kit logout` disconnects this
|
|
43
|
+
computer, stops House Kit and ends your own agent's connection, so `house` and `git` no
|
|
44
|
+
longer act as you here; a later `kit login` reconnects the same Environment.
|
|
45
|
+
In a container you do not control, such as ChatGPT's, `npm i -g @agentshouse/kit` and `kit chatgpt --key <key>` with a key made on Connections let `house <tool>` act as you, starting and configuring nothing else.
|
|
46
|
+
|
|
47
|
+
`kit resident` keeps the Environment connected and, as it connects, copies the text of each Room
|
|
48
|
+
you chose for it as a Git repository in the workspace's `local-copies`. `house fetch` records
|
|
49
|
+
House's changes on each copy's `refs/house/received`; rebase onto it, commit and run
|
|
50
|
+
`house push --owner` in the copy, through the install command in the container. An Agent runs
|
|
51
|
+
both, without `--owner`. `house upload` and `house download` move a Room's other files.
|
|
52
52
|
|
|
53
53
|
House Kit turns each audio file of your message to an Agent into text on this
|
|
54
54
|
computer before the Agent reads it. While transcription is on, the speech
|
|
55
|
-
engine and model use about 425 MB
|
|
55
|
+
engine and model use about 425 MB beside the Environment's credential; turning it off deletes them.
|
|
56
56
|
|
|
57
57
|
## License
|
|
58
58
|
|
package/dist/agents.js
CHANGED
|
@@ -3,8 +3,10 @@ import { readFile, realpath } from 'node:fs/promises';
|
|
|
3
3
|
import { startAdapter } from './acp.js';
|
|
4
4
|
import { CLIS, KIT_VERSION, below, installAdapter, installCli, locate, run, versionOf } from './clis.js';
|
|
5
5
|
import { agentBase, kitHome } from './home.js';
|
|
6
|
+
import { updatesItself } from './placement.js';
|
|
6
7
|
import { killTree } from './scope.js';
|
|
7
8
|
import { holdTranscription, transcriptionReport } from './transcription.js';
|
|
9
|
+
import { KIT_WIRE } from './wire.js';
|
|
8
10
|
const SIGNED_OUT = { signed_in: false, models: [], modes: [] };
|
|
9
11
|
function select(options, category) {
|
|
10
12
|
const option = options.find((candidate) => candidate.category === category);
|
|
@@ -130,8 +132,14 @@ export class Agents {
|
|
|
130
132
|
reads = new Map();
|
|
131
133
|
sending = Promise.resolve();
|
|
132
134
|
sent = null;
|
|
133
|
-
|
|
135
|
+
refreshing = 0;
|
|
136
|
+
changed;
|
|
137
|
+
constructor(house, changed = () => undefined) {
|
|
134
138
|
this.house = house;
|
|
139
|
+
this.changed = changed;
|
|
140
|
+
}
|
|
141
|
+
idle() {
|
|
142
|
+
return this.refreshing === 0;
|
|
135
143
|
}
|
|
136
144
|
read() {
|
|
137
145
|
return this.reading.then(() => undefined, () => undefined);
|
|
@@ -140,19 +148,28 @@ export class Agents {
|
|
|
140
148
|
return this.desired.routes.find((route) => route.agent_id === agentId);
|
|
141
149
|
}
|
|
142
150
|
refresh() {
|
|
151
|
+
this.refreshing++;
|
|
152
|
+
this.changed();
|
|
143
153
|
const reading = this.read().then(async () => {
|
|
144
154
|
this.desired = await this.house.deliver('/kit/agents/desired', {});
|
|
145
155
|
return this.desired;
|
|
146
156
|
});
|
|
147
157
|
this.reading = reading;
|
|
148
|
-
|
|
158
|
+
reading
|
|
159
|
+
.then(({ agents, transcribe }) => Promise.all([
|
|
149
160
|
holdTranscription(transcribe)
|
|
150
161
|
.then((changed) => (changed ? this.report() : undefined))
|
|
151
|
-
.catch(logged)
|
|
162
|
+
.catch(logged),
|
|
152
163
|
Promise.all(agents.map((kind) => this.load(kind)))
|
|
153
164
|
.then(() => this.report())
|
|
154
|
-
.catch(logged)
|
|
165
|
+
.catch(logged),
|
|
166
|
+
]))
|
|
167
|
+
.catch(() => undefined)
|
|
168
|
+
.finally(() => {
|
|
169
|
+
this.refreshing--;
|
|
170
|
+
this.changed();
|
|
155
171
|
});
|
|
172
|
+
return reading.then(() => undefined);
|
|
156
173
|
}
|
|
157
174
|
cli(kind) {
|
|
158
175
|
return this.queue(kind, async () => {
|
|
@@ -270,6 +287,8 @@ export class Agents {
|
|
|
270
287
|
const body = {
|
|
271
288
|
os: await operatingSystem(),
|
|
272
289
|
kit_version: KIT_VERSION,
|
|
290
|
+
wire: KIT_WIRE,
|
|
291
|
+
updates_itself: await updatesItself(),
|
|
273
292
|
agent_base: agentBase(),
|
|
274
293
|
agents,
|
|
275
294
|
transcription: transcriptionReport(),
|
package/dist/api.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
-
import { signInAgain } from './end.js';
|
|
2
|
+
import { signInAgain, updateRequired } from './end.js';
|
|
3
3
|
import { readEnrolment } from './home.js';
|
|
4
4
|
import { retryable } from './refusals.js';
|
|
5
5
|
import { relayed } from './relay.js';
|
|
6
|
+
import { KIT_WIRE, KIT_WIRE_HEADER, tooOld } from './wire.js';
|
|
6
7
|
export class HouseRefusal extends Error {
|
|
7
8
|
status;
|
|
8
9
|
text;
|
|
@@ -37,12 +38,15 @@ export async function post(house, path, body, credential, signal) {
|
|
|
37
38
|
method: 'POST',
|
|
38
39
|
headers: {
|
|
39
40
|
'content-type': 'application/json',
|
|
41
|
+
[KIT_WIRE_HEADER]: String(KIT_WIRE),
|
|
40
42
|
...(credential === undefined ? {} : { authorization: `Bearer ${credential}` }),
|
|
41
43
|
},
|
|
42
44
|
body: JSON.stringify(body),
|
|
43
45
|
signal,
|
|
44
46
|
});
|
|
45
47
|
const text = await answer.text();
|
|
48
|
+
if (!answer.ok && tooOld(text))
|
|
49
|
+
updateRequired(house);
|
|
46
50
|
if (!answer.ok)
|
|
47
51
|
throw new HouseRefusal(path, answer.status, text);
|
|
48
52
|
return JSON.parse(text);
|
package/dist/bootstrap.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
|
-
"version": "0.2.1-
|
|
3
|
+
"version": "0.2.1-cfd9926e1dac7",
|
|
4
4
|
"hosts": {
|
|
5
5
|
"macos": {
|
|
6
6
|
"asset": {
|
|
7
7
|
"name": "connect-linux.sh",
|
|
8
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-
|
|
8
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-cfd9926e1dac7/connect-linux.sh"
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
11
|
"linux": {
|
|
12
12
|
"asset": {
|
|
13
13
|
"name": "connect-linux.sh",
|
|
14
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-
|
|
14
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-cfd9926e1dac7/connect-linux.sh"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"windows": {
|
|
18
18
|
"asset": {
|
|
19
19
|
"name": "connect-windows.ps1",
|
|
20
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-
|
|
20
|
+
"url": "https://github.com/agentshouse/kit/releases/download/v0.2.1-cfd9926e1dac7/connect-windows.ps1"
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
}
|
package/dist/chatgpt.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { kitHome, readOwnAgent, writeOwnAgent } from './home.js';
|
|
3
|
+
import { accepted, DEFAULT_HOUSE } from './login.js';
|
|
4
|
+
const KEY_SOURCE = 'New key for a script on Connections';
|
|
5
|
+
const USAGE = `usage: kit chatgpt --key <key> [--house <origin>]
|
|
6
|
+
--key <key> a key from ${KEY_SOURCE}
|
|
7
|
+
--house <origin> the House of the key, ${DEFAULT_HOUSE} unless one is stored
|
|
8
|
+
`;
|
|
9
|
+
export async function chatgpt(argv) {
|
|
10
|
+
const { values } = parseArgs({
|
|
11
|
+
args: argv,
|
|
12
|
+
options: { key: { type: 'string' }, house: { type: 'string' }, help: { type: 'boolean', default: false } },
|
|
13
|
+
});
|
|
14
|
+
if (values.help) {
|
|
15
|
+
process.stdout.write(USAGE);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (values.key === undefined)
|
|
19
|
+
throw new Error(`pass --key with a key from ${KEY_SOURCE}`);
|
|
20
|
+
const house = values.house ?? (await readOwnAgent())?.house ?? DEFAULT_HOUSE;
|
|
21
|
+
if (!(await accepted(house, values.key)))
|
|
22
|
+
throw new Error(`${house} does not accept this key; pass one from ${KEY_SOURCE}`);
|
|
23
|
+
const relay = process.env.HOUSE_KIT_RELAY || undefined;
|
|
24
|
+
await writeOwnAgent({ house, ...(relay === undefined ? {} : { relay }), credential: values.key });
|
|
25
|
+
process.stdout.write(`house now calls ${house} as you, with the key kept in ${kitHome()}.\n`);
|
|
26
|
+
}
|
package/dist/clis.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { constants, readFileSync } from 'node:fs';
|
|
3
|
-
import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { access, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { homedir, userInfo } from 'node:os';
|
|
5
|
-
import { delimiter, isAbsolute, join } from 'node:path';
|
|
5
|
+
import { delimiter, dirname, isAbsolute, join } from 'node:path';
|
|
6
6
|
import { kitHome } from './home.js';
|
|
7
|
+
const HOUSE_EXECUTABLES = /^host_executable\(name = "house", paths = (\[.*\])\)$/m;
|
|
8
|
+
function houseRule(houses) {
|
|
9
|
+
return `prefix_rule(pattern = ["house"], decision = "allow")\nhost_executable(name = "house", paths = ${JSON.stringify(houses)})\n`;
|
|
10
|
+
}
|
|
7
11
|
const RAW_SUBTYPES = [
|
|
8
12
|
'background_tasks_changed',
|
|
9
13
|
'task_started',
|
|
@@ -22,15 +26,37 @@ async function executable(path) {
|
|
|
22
26
|
return false;
|
|
23
27
|
}
|
|
24
28
|
}
|
|
25
|
-
async function
|
|
29
|
+
async function listedHouses(file) {
|
|
30
|
+
let text;
|
|
31
|
+
try {
|
|
32
|
+
text = await readFile(file, 'utf8');
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error.code === 'ENOENT')
|
|
36
|
+
return [];
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const listed = JSON.parse(HOUSE_EXECUTABLES.exec(text)?.[1] ?? '[]');
|
|
41
|
+
return Array.isArray(listed) ? listed.filter((house) => typeof house === 'string') : [];
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export async function allowHouseInCodex(path) {
|
|
26
48
|
const candidates = [...new Set(path.split(delimiter).filter((directory) => isAbsolute(directory)))].map((directory) => join(directory, 'house'));
|
|
27
|
-
const houses = [];
|
|
28
|
-
for (const candidate of candidates)
|
|
29
|
-
if (await executable(candidate))
|
|
30
|
-
houses.push(candidate);
|
|
31
49
|
const rules = join(process.env.CODEX_HOME || join(homedir(), '.codex'), 'rules');
|
|
50
|
+
const file = join(rules, 'house.rules');
|
|
51
|
+
const houses = [];
|
|
52
|
+
for (const house of [...(await listedHouses(file)), ...candidates]) {
|
|
53
|
+
if (!houses.includes(house) && (await executable(house)))
|
|
54
|
+
houses.push(house);
|
|
55
|
+
}
|
|
32
56
|
await mkdir(rules, { recursive: true });
|
|
33
|
-
|
|
57
|
+
const temporary = `${file}.${process.pid}`;
|
|
58
|
+
await writeFile(temporary, houseRule(houses));
|
|
59
|
+
await rename(temporary, file);
|
|
34
60
|
}
|
|
35
61
|
async function nothing() { }
|
|
36
62
|
function bridgeInClaudeSandbox(bridge) {
|
|
@@ -173,7 +199,7 @@ export async function installAdapter(kind, signal) {
|
|
|
173
199
|
const release = KIT_PACKAGE.peerDependencies[adapter.package];
|
|
174
200
|
if ((await installedAdapter(kind)) === release)
|
|
175
201
|
return null;
|
|
176
|
-
const ran = await run('npm', ['install', '--prefix', prefix(kind), '--no-save', '--no-audit', '--no-fund', '--omit=optional', `${adapter.package}@${release}`], INSTALL_MS, true, signal);
|
|
202
|
+
const ran = await run('npm', ['install', '--prefix', prefix(kind), '--cache', join(kitHome(), 'npm-cache'), '--no-save', '--no-audit', '--no-fund', '--omit=optional', `${adapter.package}@${release}`], INSTALL_MS, true, signal);
|
|
177
203
|
return ran.status === 0 ? null : failureOf(ran, 'npm');
|
|
178
204
|
}
|
|
179
205
|
export async function installCli(kind, signal) {
|
|
@@ -198,6 +224,12 @@ export async function loginPath() {
|
|
|
198
224
|
const ran = await inLoginShell(`printf "${marker}%s\\n" "$PATH"`);
|
|
199
225
|
return ran.stdout.split('\n').findLast((line) => line.startsWith(marker)).slice(marker.length);
|
|
200
226
|
}
|
|
227
|
+
export async function writeCodexRule() {
|
|
228
|
+
const shell = await loginPath().catch(() => '');
|
|
229
|
+
await allowHouseInCodex([dirname(process.execPath), process.env.PATH ?? '', shell].join(delimiter)).catch((error) => {
|
|
230
|
+
throw new Error(`Codex's rule for house could not be written: ${error.message}`);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
201
233
|
export async function versionOf(path, signal) {
|
|
202
234
|
return VERSION.exec((await run(path, ['--version'], READ_MS, false, signal)).stdout)?.[0] ?? null;
|
|
203
235
|
}
|
package/dist/conversations.js
CHANGED
|
@@ -17,6 +17,7 @@ import { createHowWeWork } from './skills.js';
|
|
|
17
17
|
import { transcribe } from './transcription.js';
|
|
18
18
|
import { workFor } from './work.js';
|
|
19
19
|
const KILLED = 'the conversation was killed';
|
|
20
|
+
export const CONVERSATION_INPUTS = new Set(['open', 'message', 'interrupt', 'kill', 'option', 'answer']);
|
|
20
21
|
function causeOf(error) {
|
|
21
22
|
return error instanceof Error ? error.message : String(error);
|
|
22
23
|
}
|
package/dist/copies.js
CHANGED
|
@@ -3,7 +3,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { mkdir, readFile, readdir, realpath, rename, rm, rmdir, stat, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
import { finalRefusal, HouseRefusal, retrying } from './api.js';
|
|
6
|
-
import { blobId, blobText, git, HOUSE_REF, importCommits, localCopies, revision, run, succeeded, tree, } from './git.js';
|
|
6
|
+
import { blobId, blobText, git, isText, HOUSE_REF, importCommits, localCopies, revision, run, succeeded, tree, } from './git.js';
|
|
7
7
|
import { kitHome } from './home.js';
|
|
8
8
|
import { operationId } from './operation.js';
|
|
9
9
|
import { refusalOf } from './refusals.js';
|
|
@@ -29,6 +29,10 @@ const REBASED = new Set([
|
|
|
29
29
|
function rebaseFixes(refusal) {
|
|
30
30
|
return refusal.code === 'edit_conflict' && refusal.conflicts.some((code) => REBASED.has(code));
|
|
31
31
|
}
|
|
32
|
+
function textOnly(paths) {
|
|
33
|
+
const [named, them] = paths.length === 1 ? [`${paths[0]} is`, 'it'] : [`${paths.join(', ')} are`, 'them'];
|
|
34
|
+
return `${named} not text, and house push sends text only; remove ${them} from the commit and place ${them} with house upload`;
|
|
35
|
+
}
|
|
32
36
|
function short(commit) {
|
|
33
37
|
return commit.slice(0, 12);
|
|
34
38
|
}
|
|
@@ -572,7 +576,13 @@ export class LocalCopies {
|
|
|
572
576
|
const after = await tree(copy.repository, commit);
|
|
573
577
|
const moved = movedPaths(copy.moves, before, after);
|
|
574
578
|
const arrived = new Set(moved.values());
|
|
575
|
-
const
|
|
579
|
+
const binary = [];
|
|
580
|
+
for (const [path, entry] of after)
|
|
581
|
+
if (before.get(path) !== entry && !(await isText(copy.repository, entry)))
|
|
582
|
+
binary.push(path);
|
|
583
|
+
if (binary.length > 0)
|
|
584
|
+
throw new Error(textOnly(binary));
|
|
585
|
+
const text = (entry) => blobText(copy.repository, entry);
|
|
576
586
|
const changes = [];
|
|
577
587
|
for (const path of [...new Set([...before.keys(), ...after.keys()])].sort()) {
|
|
578
588
|
if (arrived.has(path))
|
|
@@ -585,7 +595,7 @@ export class LocalCopies {
|
|
|
585
595
|
changes.push({ op: 'rename', path: `${root}/${path}`, to: `${root}/${to}`, base: known });
|
|
586
596
|
const moving = after.get(to);
|
|
587
597
|
if (moving !== prior) {
|
|
588
|
-
changes.push({ op: 'replace', path: `${root}/${to}`, base: known, content: await text(moving
|
|
598
|
+
changes.push({ op: 'replace', path: `${root}/${to}`, base: known, content: await text(moving) });
|
|
589
599
|
}
|
|
590
600
|
continue;
|
|
591
601
|
}
|
|
@@ -594,9 +604,9 @@ export class LocalCopies {
|
|
|
594
604
|
if (next === undefined)
|
|
595
605
|
changes.push({ op: 'remove', path: `${root}/${path}`, base: known });
|
|
596
606
|
else if (prior === undefined)
|
|
597
|
-
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next
|
|
607
|
+
changes.push({ op: 'create', path: `${root}/${path}`, content: await text(next) });
|
|
598
608
|
else
|
|
599
|
-
changes.push(...changed(`${root}/${path}`, path, known, await text(prior
|
|
609
|
+
changes.push(...changed(`${root}/${path}`, path, known, await text(prior), await text(next)));
|
|
600
610
|
}
|
|
601
611
|
return changes;
|
|
602
612
|
}
|
|
@@ -663,7 +673,7 @@ export class LocalCopies {
|
|
|
663
673
|
written.push({
|
|
664
674
|
path: `${root}/${path}`,
|
|
665
675
|
revision: entry === undefined ? null : (revisionAt(current, current.house, path) ?? null),
|
|
666
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
676
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
667
677
|
});
|
|
668
678
|
}
|
|
669
679
|
return this.finished(await this.save({ ...current, push: { ...push, state: 'accepted', written } }));
|
|
@@ -707,7 +717,7 @@ export class LocalCopies {
|
|
|
707
717
|
{
|
|
708
718
|
path: `${rootOf(copy.room_handle)}/${path}`,
|
|
709
719
|
revision: entry === undefined ? null : (revisionAt(copy, copy.house, path) ?? null),
|
|
710
|
-
content: entry === undefined ? null : await blobText(copy.repository, entry
|
|
720
|
+
content: entry === undefined ? null : await blobText(copy.repository, entry),
|
|
711
721
|
},
|
|
712
722
|
];
|
|
713
723
|
}
|
|
@@ -752,7 +762,7 @@ export class LocalCopies {
|
|
|
752
762
|
async landed(repository, changes, house, selected) {
|
|
753
763
|
const text = async (path) => {
|
|
754
764
|
const entry = house.get(relative(path));
|
|
755
|
-
return entry === undefined ? undefined : blobText(repository, entry
|
|
765
|
+
return entry === undefined ? undefined : blobText(repository, entry);
|
|
756
766
|
};
|
|
757
767
|
for (const change of changes) {
|
|
758
768
|
const current = await text(change.path);
|
package/dist/end.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { forgetRejected } from './home.js';
|
|
2
|
+
import { installCommand } from './placement.js';
|
|
2
3
|
import { killDescendants } from './scope.js';
|
|
3
4
|
export const REPLACED = 'another Kit started for this Environment';
|
|
4
5
|
function exit(reason, status) {
|
|
@@ -12,6 +13,9 @@ export function end(reason) {
|
|
|
12
13
|
export function restart(reason) {
|
|
13
14
|
exit(reason, 1);
|
|
14
15
|
}
|
|
16
|
+
export function updateRequired(house) {
|
|
17
|
+
end(`This Kit is too old for House. Update it: ${installCommand(house)}`);
|
|
18
|
+
}
|
|
15
19
|
export async function signInAgain(rejected) {
|
|
16
20
|
await forgetRejected(rejected);
|
|
17
21
|
restart('sign in again');
|
package/dist/files.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import { HouseRefusal, retrying } from './api.js';
|
|
4
5
|
import { relayed } from './relay.js';
|
|
6
|
+
export function verified(bytes, sha256, name) {
|
|
7
|
+
if (createHash('sha256').update(bytes).digest('hex') !== sha256)
|
|
8
|
+
throw new Error(`${name} did not arrive whole; download it again`);
|
|
9
|
+
return bytes;
|
|
10
|
+
}
|
|
5
11
|
export async function placeFiles(house, directory, files, signal) {
|
|
6
12
|
const paths = [];
|
|
7
13
|
for (const file of files) {
|
|
@@ -16,7 +22,7 @@ export async function placeFiles(house, directory, files, signal) {
|
|
|
16
22
|
});
|
|
17
23
|
await mkdir(folder, { recursive: true });
|
|
18
24
|
const path = join(folder, file.name);
|
|
19
|
-
await writeFile(path, Buffer.from(await answer.arrayBuffer()));
|
|
25
|
+
await writeFile(path, verified(Buffer.from(await answer.arrayBuffer()), file.sha256, file.name));
|
|
20
26
|
paths.push(path);
|
|
21
27
|
}
|
|
22
28
|
return paths;
|
package/dist/git.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { isUtf8 } from 'node:buffer';
|
|
2
3
|
import { createHash } from 'node:crypto';
|
|
3
4
|
import { join } from 'node:path';
|
|
4
5
|
import { withoutProxy } from './clis.js';
|
|
@@ -48,14 +49,16 @@ export async function tree(repository, commit) {
|
|
|
48
49
|
}
|
|
49
50
|
return entries;
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
const TEXT_MAX_BYTES = 16 * 1024 * 1024;
|
|
53
|
+
async function blob(repository, object) {
|
|
54
|
+
return (await run(repository, ['cat-file', 'blob', object])).stdout;
|
|
55
|
+
}
|
|
56
|
+
export async function blobText(repository, object) {
|
|
57
|
+
return (await blob(repository, object)).toString('utf8');
|
|
58
|
+
}
|
|
59
|
+
export async function isText(repository, object) {
|
|
60
|
+
const bytes = await blob(repository, object);
|
|
61
|
+
return bytes.length <= TEXT_MAX_BYTES && isUtf8(bytes) && !bytes.includes(0);
|
|
59
62
|
}
|
|
60
63
|
export function blobId(content) {
|
|
61
64
|
const bytes = Buffer.from(content, 'utf8');
|
package/dist/home.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { existsSync, linkSync, readFileSync, renameSync, rmSync } from 'node:fs';
|
|
1
|
+
import { accessSync, chmodSync, constants, existsSync, linkSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync } from 'node:fs';
|
|
2
2
|
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
3
|
+
import { homedir, tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { selectRelay } from './relay.js';
|
|
6
6
|
const ENROLMENT = 'credential.json';
|
|
@@ -8,8 +8,41 @@ const DISCONNECTED = 'disconnected.json';
|
|
|
8
8
|
const CONFIGURATION = 'kit.json';
|
|
9
9
|
const OWN_AGENT = 'own-agent.json';
|
|
10
10
|
const RESTART = 'restart';
|
|
11
|
+
let defaulted;
|
|
12
|
+
function writable(directory) {
|
|
13
|
+
try {
|
|
14
|
+
accessSync(directory, constants.W_OK | constants.X_OK);
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function ownTemporaryHome() {
|
|
22
|
+
const home = join(tmpdir(), `house-kit-${process.getuid()}`);
|
|
23
|
+
try {
|
|
24
|
+
mkdirSync(home, { mode: 0o700 });
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error.code !== 'EEXIST')
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
const found = lstatSync(home);
|
|
31
|
+
if (found.isSymbolicLink())
|
|
32
|
+
throw new Error(`${home} is a symbolic link`);
|
|
33
|
+
if (!found.isDirectory())
|
|
34
|
+
throw new Error(`${home} is not a directory`);
|
|
35
|
+
if (found.uid !== process.getuid())
|
|
36
|
+
throw new Error(`${home} is not owned by this User`);
|
|
37
|
+
chmodSync(home, 0o700);
|
|
38
|
+
return home;
|
|
39
|
+
}
|
|
40
|
+
function defaultHome() {
|
|
41
|
+
const home = join(homedir(), '.house-kit');
|
|
42
|
+
return existsSync(home) || writable(homedir()) ? home : ownTemporaryHome();
|
|
43
|
+
}
|
|
11
44
|
export function kitHome() {
|
|
12
|
-
return process.env.HOUSE_KIT_HOME
|
|
45
|
+
return process.env.HOUSE_KIT_HOME || (defaulted ??= defaultHome());
|
|
13
46
|
}
|
|
14
47
|
export function workspaceRoot() {
|
|
15
48
|
return process.env.HOUSE_KIT_WORKSPACE ?? '/agents/house';
|
package/dist/house-main.js
CHANGED
|
@@ -1,69 +1,105 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { request, setGlobalProxyFromEnv } from 'node:http';
|
|
5
|
-
import { basename, join } from 'node:path';
|
|
5
|
+
import { basename, dirname, extname, join, resolve } from 'node:path';
|
|
6
|
+
import { verified } from './files.js';
|
|
6
7
|
import { HOUSE_REF } from './git.js';
|
|
7
|
-
import { kitHome,
|
|
8
|
+
import { kitHome, readOwnAgent } from './home.js';
|
|
8
9
|
import { callHouse, rpc, UNREACHABLE } from './mcp.js';
|
|
9
10
|
import { refusalOf } from './refusals.js';
|
|
10
|
-
import { relayed } from './relay.js';
|
|
11
|
-
import { parse } from 'yaml';
|
|
11
|
+
import { relayed, selectRelay } from './relay.js';
|
|
12
|
+
import { parse, stringify } from 'yaml';
|
|
12
13
|
const USAGE = "usage: house <tool> ['<arguments as JSON>']";
|
|
13
14
|
const PUSH_USAGE = 'usage: house push [commit] [--owner]';
|
|
14
15
|
const PUSH = "push [commit]: sends the Local copy's committed changes to House";
|
|
15
16
|
const FETCH_USAGE = 'usage: house fetch';
|
|
16
17
|
const FETCH = `fetch: records House's changes on each Local copy's ${HOUSE_REF}`;
|
|
18
|
+
const DOWNLOAD_USAGE = `usage: house download '{"path":"/rooms/<handle>/<path>","file":"<local file>"}', adding "ref" for a revision`;
|
|
19
|
+
const DOWNLOAD = 'download: writes a binary file of a Room to the local file named by file';
|
|
17
20
|
const ARGUMENTS = `the arguments are one JSON object in single quotes, like house search '{"query":"invoice"}'`;
|
|
18
21
|
const CLOSED = "this conversation's House connection is closed; nothing to do from here";
|
|
19
22
|
const SANDBOXED = "your CLI's sandbox kept house from House; run house directly, not through another program";
|
|
20
23
|
const REVOKED = 'this conversation no longer has House access; nothing to do from here';
|
|
21
24
|
const UNCONNECTED = "this computer's own agent has no House connection; run kit login";
|
|
22
25
|
const DISCONNECTED = "House no longer accepts this computer's own agent; run kit login";
|
|
26
|
+
const UNCONFIRMED = 'with no result; a write may or may not have taken effect, so read before you repeat it';
|
|
23
27
|
const socketPath = process.env.HOUSE_BRIDGE;
|
|
24
|
-
|
|
28
|
+
const MEDIA_TYPES = new Map([
|
|
29
|
+
['avif', 'image/avif'],
|
|
30
|
+
['csv', 'text/csv'],
|
|
31
|
+
['docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
|
|
32
|
+
['gif', 'image/gif'],
|
|
33
|
+
['heic', 'image/heic'],
|
|
34
|
+
['html', 'text/html'],
|
|
35
|
+
['jpeg', 'image/jpeg'],
|
|
36
|
+
['jpg', 'image/jpeg'],
|
|
37
|
+
['json', 'application/json'],
|
|
38
|
+
['m4a', 'audio/mp4'],
|
|
39
|
+
['md', 'text/markdown'],
|
|
40
|
+
['mov', 'video/quicktime'],
|
|
41
|
+
['mp3', 'audio/mpeg'],
|
|
42
|
+
['mp4', 'video/mp4'],
|
|
43
|
+
['ogg', 'audio/ogg'],
|
|
44
|
+
['pdf', 'application/pdf'],
|
|
45
|
+
['png', 'image/png'],
|
|
46
|
+
['pptx', 'application/vnd.openxmlformats-officedocument.presentationml.presentation'],
|
|
47
|
+
['svg', 'image/svg+xml'],
|
|
48
|
+
['txt', 'text/plain'],
|
|
49
|
+
['wav', 'audio/wav'],
|
|
50
|
+
['webm', 'video/webm'],
|
|
51
|
+
['webp', 'image/webp'],
|
|
52
|
+
['xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
|
|
53
|
+
['zip', 'application/zip'],
|
|
54
|
+
]);
|
|
55
|
+
function refused(path, method, status, text, local) {
|
|
25
56
|
const refusal = refusalOf(text);
|
|
26
57
|
if (refusal !== null)
|
|
27
58
|
return refusal.text;
|
|
28
59
|
if (status === 401)
|
|
29
60
|
return REVOKED;
|
|
61
|
+
const act = method === 'GET' ? 'download' : 'upload';
|
|
30
62
|
if (URL.canParse(path))
|
|
31
|
-
return `the
|
|
32
|
-
if (status === 502 &&
|
|
63
|
+
return `the ${act} answered ${status}; ${act} the file again`;
|
|
64
|
+
if (status === 502 && local)
|
|
33
65
|
return text;
|
|
34
|
-
return `${path} answered ${status}`;
|
|
66
|
+
return path === '/' ? unconfirmed(status) : `${path} answered ${status}`;
|
|
35
67
|
}
|
|
36
|
-
function
|
|
68
|
+
function unconfirmed(status) {
|
|
69
|
+
return `House answered ${status} ${UNCONFIRMED}`;
|
|
70
|
+
}
|
|
71
|
+
function exchanged(path, method, body, headers = { 'content-type': 'application/json' }, to = socketPath) {
|
|
37
72
|
return new Promise((resolve, reject) => {
|
|
38
|
-
const sent = request({ socketPath: to, path, method
|
|
39
|
-
|
|
40
|
-
answer.
|
|
41
|
-
answer.on('data', (chunk) => {
|
|
42
|
-
text += chunk;
|
|
43
|
-
});
|
|
73
|
+
const sent = request({ socketPath: to, path, method, headers }, (answer) => {
|
|
74
|
+
const chunks = [];
|
|
75
|
+
answer.on('data', (chunk) => chunks.push(chunk));
|
|
44
76
|
answer.on('end', () => {
|
|
77
|
+
const bytes = Buffer.concat(chunks);
|
|
45
78
|
if (answer.statusCode === 200)
|
|
46
|
-
resolve(
|
|
79
|
+
resolve(bytes);
|
|
47
80
|
else
|
|
48
|
-
reject(new Error(refused(path, answer.statusCode,
|
|
81
|
+
reject(new Error(refused(path, method, answer.statusCode, bytes.toString('utf8'), answer.headers['content-type'] === undefined)));
|
|
49
82
|
});
|
|
50
83
|
});
|
|
51
84
|
sent.on('error', (error) => reject(new Error(error.code === 'EPERM' || error.code === 'EACCES' ? SANDBOXED : CLOSED)));
|
|
52
|
-
sent.end(body instanceof Buffer ? body : JSON.stringify(body));
|
|
85
|
+
sent.end(body === undefined ? undefined : body instanceof Buffer ? body : JSON.stringify(body));
|
|
53
86
|
});
|
|
54
87
|
}
|
|
88
|
+
async function bridged(path, body, headers, to = socketPath) {
|
|
89
|
+
return (await exchanged(path, 'POST', body, headers, to)).toString('utf8');
|
|
90
|
+
}
|
|
55
91
|
async function direct(message) {
|
|
56
92
|
const ownAgent = await readOwnAgent();
|
|
57
93
|
if (ownAgent === null)
|
|
58
94
|
throw new Error(UNCONNECTED);
|
|
59
|
-
|
|
95
|
+
selectRelay(process.env.HOUSE_KIT_RELAY || ownAgent.relay);
|
|
60
96
|
setGlobalProxyFromEnv();
|
|
61
97
|
const answer = await callHouse(ownAgent.house, ownAgent.credential, message).catch(() => {
|
|
62
98
|
throw new Error(UNREACHABLE);
|
|
63
99
|
});
|
|
64
100
|
if (answer.status === 200)
|
|
65
101
|
return answer.text;
|
|
66
|
-
throw new Error(answer.status === 401 ? DISCONNECTED : (refusalOf(answer.text)?.text ??
|
|
102
|
+
throw new Error(answer.status === 401 ? DISCONNECTED : (refusalOf(answer.text)?.text ?? unconfirmed(answer.status)));
|
|
67
103
|
}
|
|
68
104
|
async function mcp(method, params) {
|
|
69
105
|
const message = rpc(method, params);
|
|
@@ -85,41 +121,102 @@ async function localFile(path) {
|
|
|
85
121
|
: `${path} is not a readable file`);
|
|
86
122
|
}
|
|
87
123
|
}
|
|
88
|
-
async function
|
|
89
|
-
const headers = { 'content-type': 'application/octet-stream', 'x-house-byte-operation': operation };
|
|
124
|
+
async function transferred(url, method, bytes, headers) {
|
|
90
125
|
if (socketPath !== undefined)
|
|
91
|
-
return
|
|
126
|
+
return exchanged(url, method, bytes, headers);
|
|
92
127
|
setGlobalProxyFromEnv();
|
|
93
|
-
const answer = await fetch(relayed(url), { method:
|
|
128
|
+
const answer = await fetch(relayed(url), { method, ...(headers === undefined ? {} : { headers }), ...(bytes === undefined ? {} : { body: bytes }) }).catch(() => {
|
|
94
129
|
throw new Error(UNREACHABLE);
|
|
95
130
|
});
|
|
96
|
-
const
|
|
131
|
+
const received = Buffer.from(await answer.arrayBuffer());
|
|
97
132
|
if (answer.status === 200)
|
|
98
|
-
return
|
|
99
|
-
throw new Error(refused(url, answer.status,
|
|
100
|
-
}
|
|
101
|
-
async function
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
133
|
+
return received;
|
|
134
|
+
throw new Error(refused(url, method, answer.status, received.toString('utf8'), false));
|
|
135
|
+
}
|
|
136
|
+
async function sent(url, bytes, operation) {
|
|
137
|
+
const headers = { 'content-type': 'application/octet-stream', 'x-house-byte-operation': operation };
|
|
138
|
+
return (await transferred(url, 'POST', bytes, headers)).toString('utf8');
|
|
139
|
+
}
|
|
140
|
+
async function download(args) {
|
|
141
|
+
const { flags, positionals: [argument] } = accepted(args, ['--help'], 1, DOWNLOAD_USAGE);
|
|
142
|
+
if (flags.has('--help'))
|
|
143
|
+
return DOWNLOAD_USAGE;
|
|
144
|
+
const { file, ...selected } = argumentsOf(argument);
|
|
145
|
+
if (typeof file !== 'string' || file === '')
|
|
146
|
+
throw new Error('download needs "file", the local file to write');
|
|
147
|
+
const inspected = parse(await called('inspect', selected));
|
|
148
|
+
if (typeof inspected?.download !== 'string')
|
|
149
|
+
throw new Error(`${String(selected.path)} holds no binary file; shell reads its text`);
|
|
150
|
+
const bytes = verified(await transferred(inspected.download, 'GET'), String(inspected.sha256), String(selected.path));
|
|
151
|
+
const written = resolve(file);
|
|
152
|
+
await mkdir(dirname(written), { recursive: true });
|
|
153
|
+
await writeFile(written, bytes);
|
|
154
|
+
return written;
|
|
155
|
+
}
|
|
156
|
+
async function described(file) {
|
|
157
|
+
const bytes = await localFile(file);
|
|
158
|
+
const mediaType = MEDIA_TYPES.get(extname(file).slice(1).toLowerCase());
|
|
159
|
+
return {
|
|
160
|
+
bytes,
|
|
161
|
+
description: {
|
|
162
|
+
bytes: bytes.length,
|
|
163
|
+
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
164
|
+
...(mediaType === undefined ? {} : { media_type: mediaType }),
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
async function upload({ file, ...destination }) {
|
|
169
|
+
if (typeof file !== 'string' || file === '')
|
|
170
|
+
throw new Error('upload needs "file", a local file');
|
|
171
|
+
const { bytes, description } = await described(file);
|
|
172
|
+
const answer = await called('upload', { ...destination, ...description });
|
|
173
|
+
const declared = parse(answer);
|
|
111
174
|
if (declared.upload === undefined)
|
|
112
|
-
return
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
175
|
+
return answer;
|
|
176
|
+
const written = JSON.parse(await sent(declared.upload.url, bytes, declared.upload.operation));
|
|
177
|
+
return stringify(written, { lineWidth: 0 }).trimEnd();
|
|
178
|
+
}
|
|
179
|
+
async function appendRecord(args) {
|
|
180
|
+
const held = new Map();
|
|
181
|
+
const attachments = Array.isArray(args.attachments)
|
|
182
|
+
? await Promise.all(args.attachments.map(async (attachment) => {
|
|
183
|
+
if (typeof attachment !== 'string')
|
|
184
|
+
return attachment;
|
|
185
|
+
const { bytes, description } = await described(attachment);
|
|
186
|
+
held.set(description.sha256, bytes);
|
|
187
|
+
return { name: basename(attachment), ...description };
|
|
188
|
+
}))
|
|
189
|
+
: args.attachments;
|
|
190
|
+
const record = { ...args, attachments };
|
|
191
|
+
for (;;) {
|
|
192
|
+
const answer = await called('append_record', record);
|
|
193
|
+
const { upload } = parse(answer);
|
|
194
|
+
if (upload === undefined)
|
|
195
|
+
return answer;
|
|
196
|
+
for (const grant of upload)
|
|
197
|
+
await sent(grant.url, held.get(grant.sha256), grant.operation);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function unexpected(argument, usage) {
|
|
201
|
+
return new Error(`unexpected argument ${argument}; ${usage}`);
|
|
202
|
+
}
|
|
203
|
+
function accepted(args, flags, positionals, usage) {
|
|
204
|
+
const given = { flags: new Set(), positionals: [] };
|
|
205
|
+
for (const arg of args) {
|
|
206
|
+
if (flags.includes(arg))
|
|
207
|
+
given.flags.add(arg);
|
|
208
|
+
else if (!arg.startsWith('-') && given.positionals.length < positionals)
|
|
209
|
+
given.positionals.push(arg);
|
|
210
|
+
else
|
|
211
|
+
throw unexpected(arg, usage);
|
|
212
|
+
}
|
|
213
|
+
return given;
|
|
117
214
|
}
|
|
118
215
|
async function push(args) {
|
|
119
|
-
|
|
216
|
+
const { flags, positionals: [commit = 'HEAD'] } = accepted(args, ['--help', '--owner'], 1, PUSH_USAGE);
|
|
217
|
+
if (flags.has('--help'))
|
|
120
218
|
return PUSH_USAGE;
|
|
121
|
-
const owner =
|
|
122
|
-
const commit = args.find((arg) => arg !== '--owner') ?? 'HEAD';
|
|
219
|
+
const owner = flags.has('--owner');
|
|
123
220
|
if (owner && socketPath !== undefined)
|
|
124
221
|
throw new Error('--owner is not for Agent conversations; run house push without it');
|
|
125
222
|
if (!owner && socketPath === undefined)
|
|
@@ -130,7 +227,7 @@ async function push(args) {
|
|
|
130
227
|
return pushed.text;
|
|
131
228
|
}
|
|
132
229
|
async function fetched(args) {
|
|
133
|
-
if (args.
|
|
230
|
+
if (accepted(args, ['--help'], 0, FETCH_USAGE).flags.has('--help'))
|
|
134
231
|
return FETCH_USAGE;
|
|
135
232
|
const answer = JSON.parse(await bridged('/git/fetch', {}, undefined, socketPath ?? join(kitHome(), 'owner.sock')));
|
|
136
233
|
if (answer.refused)
|
|
@@ -151,28 +248,23 @@ function argumentsOf(argument) {
|
|
|
151
248
|
throw new Error(ARGUMENTS);
|
|
152
249
|
return parsed;
|
|
153
250
|
}
|
|
154
|
-
async function house(
|
|
155
|
-
|
|
156
|
-
|
|
251
|
+
async function house(args) {
|
|
252
|
+
const { flags, positionals: [verb, argument] } = accepted(args, ['--help'], 2, USAGE);
|
|
253
|
+
if (verb === 'help' && argument !== undefined)
|
|
254
|
+
throw unexpected(argument, USAGE);
|
|
255
|
+
if (flags.has('--help') && argument !== undefined)
|
|
256
|
+
throw unexpected('--help', USAGE);
|
|
257
|
+
if (verb === undefined || verb === 'help') {
|
|
258
|
+
return [USAGE, 'help', PUSH, FETCH, DOWNLOAD, ...(await tools()).map((tool) => (tool.description === undefined ? tool.name : `${tool.name}: ${tool.description}`))].join('\n');
|
|
157
259
|
}
|
|
158
|
-
if (
|
|
260
|
+
if (flags.has('--help')) {
|
|
159
261
|
const tool = (await tools()).find((listed) => listed.name === verb);
|
|
160
262
|
if (tool === undefined)
|
|
161
263
|
throw new Error(`${verb} is no Tool; house find_command '{"query":"${verb}"}' finds Commands`);
|
|
162
264
|
return [...(tool.description === undefined ? [] : [tool.description]), JSON.stringify(tool.inputSchema)].join('\n');
|
|
163
265
|
}
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
return JSON.stringify({ attachment: await upload(args.path, args.room_ref, 'upload needs "path", a local file') });
|
|
167
|
-
}
|
|
168
|
-
if (verb === 'append_record' && Array.isArray(args.attachments)) {
|
|
169
|
-
const attachments = [];
|
|
170
|
-
for (const path of args.attachments) {
|
|
171
|
-
attachments.push(await upload(path, args.room_ref, 'each attachments entry is the path of a local file'));
|
|
172
|
-
}
|
|
173
|
-
args.attachments = attachments;
|
|
174
|
-
}
|
|
175
|
-
return called(verb, args);
|
|
266
|
+
const parsed = argumentsOf(argument);
|
|
267
|
+
return verb === 'upload' ? upload(parsed) : verb === 'append_record' ? appendRecord(parsed) : called(verb, parsed);
|
|
176
268
|
}
|
|
177
269
|
async function called(name, args) {
|
|
178
270
|
const result = await mcp('tools/call', { name, arguments: args });
|
|
@@ -182,7 +274,15 @@ async function called(name, args) {
|
|
|
182
274
|
return text;
|
|
183
275
|
}
|
|
184
276
|
const argv = process.argv.slice(2);
|
|
185
|
-
|
|
277
|
+
const named = argv.findIndex((arg) => !arg.startsWith('-'));
|
|
278
|
+
const rest = argv.filter((_arg, index) => index !== named);
|
|
279
|
+
(argv[named] === 'push'
|
|
280
|
+
? push(rest)
|
|
281
|
+
: argv[named] === 'fetch'
|
|
282
|
+
? fetched(rest)
|
|
283
|
+
: argv[named] === 'download'
|
|
284
|
+
? download(rest)
|
|
285
|
+
: house(argv)).then((text) => {
|
|
186
286
|
process.stdout.write(`${text}\n`);
|
|
187
287
|
if (argv[0] === 'shell')
|
|
188
288
|
process.exitCode = Number(/^exit: (\d+)(?:\n|$)/.exec(text)?.[1] ?? 0);
|
package/dist/kit-main.js
CHANGED
|
@@ -1,22 +1,67 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { setGlobalProxyFromEnv } from 'node:http';
|
|
3
|
+
import { parseArgs } from 'node:util';
|
|
3
4
|
import { answerGitCredential } from './app-git.js';
|
|
5
|
+
import { chatgpt } from './chatgpt.js';
|
|
4
6
|
import { login } from './login.js';
|
|
5
7
|
import { logout } from './logout.js';
|
|
6
8
|
import { resident } from './resident.js';
|
|
9
|
+
const USAGE = 'usage: kit <login|logout|resident|chatgpt>\n';
|
|
10
|
+
const COMMANDS = new Map([
|
|
11
|
+
[
|
|
12
|
+
'logout',
|
|
13
|
+
{
|
|
14
|
+
usage: 'usage: kit logout',
|
|
15
|
+
about: "disconnects this computer, stops House Kit and ends your own agent's connection",
|
|
16
|
+
operands: 0,
|
|
17
|
+
run: logout,
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
[
|
|
21
|
+
'resident',
|
|
22
|
+
{
|
|
23
|
+
usage: 'usage: kit resident',
|
|
24
|
+
about: "keeps this Environment connected; House Kit's service runs it",
|
|
25
|
+
operands: 0,
|
|
26
|
+
run: resident,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
'git-credential',
|
|
31
|
+
{
|
|
32
|
+
usage: 'usage: kit git-credential <get|store|erase>',
|
|
33
|
+
about: "answers git with your own agent's House credential; git runs it",
|
|
34
|
+
operands: 1,
|
|
35
|
+
run: ([operation]) => answerGitCredential(operation),
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
]);
|
|
7
39
|
const [command, ...argv] = process.argv.slice(2);
|
|
8
40
|
setGlobalProxyFromEnv();
|
|
9
41
|
async function run() {
|
|
10
42
|
if (command === 'login')
|
|
11
43
|
return login(argv);
|
|
12
|
-
if (command === '
|
|
13
|
-
return
|
|
14
|
-
if (command === '
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return
|
|
18
|
-
|
|
19
|
-
|
|
44
|
+
if (command === 'chatgpt')
|
|
45
|
+
return chatgpt(argv);
|
|
46
|
+
if (command === '--help') {
|
|
47
|
+
parseArgs({ args: argv });
|
|
48
|
+
process.stdout.write(USAGE);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const chosen = COMMANDS.get(command ?? '');
|
|
52
|
+
if (chosen === undefined) {
|
|
53
|
+
process.stderr.write(USAGE);
|
|
54
|
+
process.exitCode = 2;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const { values, positionals } = parseArgs({ args: argv, options: { help: { type: 'boolean', default: false } }, allowPositionals: true });
|
|
58
|
+
if (positionals.length > chosen.operands)
|
|
59
|
+
throw new Error(`unexpected argument ${positionals[chosen.operands]}; ${chosen.usage}`);
|
|
60
|
+
if (values.help) {
|
|
61
|
+
process.stdout.write(`${chosen.usage}\n ${chosen.about}\n`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
return chosen.run(positionals);
|
|
20
65
|
}
|
|
21
66
|
run().catch((error) => {
|
|
22
67
|
process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
|
package/dist/login.js
CHANGED
|
@@ -4,11 +4,13 @@ import { hostname } from 'node:os';
|
|
|
4
4
|
import { parseArgs } from 'node:util';
|
|
5
5
|
import { post, retrying } from './api.js';
|
|
6
6
|
import { placeAppGit } from './app-git.js';
|
|
7
|
+
import { writeCodexRule } from './clis.js';
|
|
7
8
|
import { forgetOwnAgent, readDisconnected, readEnrolment, readOwnAgent, writeEnrolment, writeOwnAgent } from './home.js';
|
|
8
9
|
import { callHouse, rpc } from './mcp.js';
|
|
9
10
|
import { selectRelay } from './relay.js';
|
|
10
11
|
import { startService } from './service.js';
|
|
11
|
-
|
|
12
|
+
import { placeSkillSet } from './skills.js';
|
|
13
|
+
export const DEFAULT_HOUSE = 'https://agents.house';
|
|
12
14
|
const OPENER = process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
13
15
|
// Polling every two seconds brings the User's confirmation to the waiting Kit moments after they give it.
|
|
14
16
|
const POLL_INTERVAL_MS = 2000;
|
|
@@ -17,7 +19,7 @@ function open(link) {
|
|
|
17
19
|
.on('error', () => undefined)
|
|
18
20
|
.unref();
|
|
19
21
|
}
|
|
20
|
-
async function accepted(house, credential) {
|
|
22
|
+
export async function accepted(house, credential) {
|
|
21
23
|
const checked = await callHouse(house, credential, rpc('tools/list', {}));
|
|
22
24
|
if (checked.status === 200)
|
|
23
25
|
return true;
|
|
@@ -25,7 +27,8 @@ async function accepted(house, credential) {
|
|
|
25
27
|
return false;
|
|
26
28
|
throw new Error(`House answered ${checked.status} when Kit checked this computer's own agent's connection: ${checked.text}`);
|
|
27
29
|
}
|
|
28
|
-
const USAGE = `usage: kit login [--house <origin>] [--environment <id>] [--rooms <ref>[,<ref>...] | --no-private]
|
|
30
|
+
const USAGE = `usage: kit login [--house <origin>] [--environment <id>] [--rooms <ref>[,<ref>...] | --no-private]
|
|
31
|
+
kit login [--house <origin>] --code <code>
|
|
29
32
|
--house <origin> the House to connect to, ${DEFAULT_HOUSE} unless one is stored
|
|
30
33
|
--environment <id> reconnect this Environment
|
|
31
34
|
--rooms <ref>[,<ref>...] give this Environment only these Rooms
|
|
@@ -93,21 +96,27 @@ export async function login(argv) {
|
|
|
93
96
|
open(started.start_url);
|
|
94
97
|
exchanged = await retrying(() => post(house, '/kit/token', { login: started.login, code_verifier: verifier }), undefined, () => POLL_INTERVAL_MS);
|
|
95
98
|
}
|
|
99
|
+
await placeSkillSet();
|
|
100
|
+
await writeCodexRule().catch((error) => process.stderr.write(`kit: ${error.message}\n`));
|
|
101
|
+
const relayRecord = relay === undefined ? {} : { relay };
|
|
96
102
|
await writeEnrolment({
|
|
97
103
|
house,
|
|
98
|
-
...
|
|
104
|
+
...relayRecord,
|
|
99
105
|
environment: exchanged.environment,
|
|
100
106
|
credential: exchanged.credential,
|
|
101
107
|
});
|
|
102
108
|
process.stdout.write(`Environment ${exchanged.environment} is connected to ${house}.\n`);
|
|
103
109
|
if (exchanged.headless_credential !== undefined) {
|
|
104
|
-
await writeOwnAgent({ house, user: exchanged.user, credential: exchanged.headless_credential });
|
|
110
|
+
await writeOwnAgent({ house, ...relayRecord, user: exchanged.user, credential: exchanged.headless_credential });
|
|
105
111
|
}
|
|
106
112
|
else if (held && ownAgent !== null && ownAgent.user !== exchanged.user) {
|
|
107
113
|
await forgetOwnAgent();
|
|
108
114
|
process.stdout.write("This computer's own agent was another User's; run kit login again to connect it to you.\n");
|
|
109
115
|
}
|
|
110
|
-
|
|
116
|
+
else if (held && ownAgent !== null) {
|
|
117
|
+
await writeOwnAgent({ house, ...relayRecord, user: ownAgent.user, credential: ownAgent.credential });
|
|
118
|
+
}
|
|
119
|
+
await placeAppGit(ownAgent, await readOwnAgent()).catch((error) => process.stderr.write(`kit: ${error.message}\n`));
|
|
111
120
|
try {
|
|
112
121
|
startService();
|
|
113
122
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { run } from './clis.js';
|
|
4
|
+
import { kitHome } from './home.js';
|
|
5
|
+
import { relayOrigin } from './relay.js';
|
|
6
|
+
export function service() {
|
|
7
|
+
if (process.platform !== 'linux' || !process.env.INVOCATION_ID)
|
|
8
|
+
return null;
|
|
9
|
+
return process.env.MANAGERPID ? 'user' : 'system';
|
|
10
|
+
}
|
|
11
|
+
export function throughSudo() {
|
|
12
|
+
return service() === 'system' && process.getuid() !== 0;
|
|
13
|
+
}
|
|
14
|
+
export async function updatesItself() {
|
|
15
|
+
if (service() === null)
|
|
16
|
+
return false;
|
|
17
|
+
if (!throughSudo())
|
|
18
|
+
return true;
|
|
19
|
+
// sudo answers at once, so ten seconds only bounds a stuck sudo.
|
|
20
|
+
return (await run('sudo', ['-n', 'true'], 10_000)).status === 0;
|
|
21
|
+
}
|
|
22
|
+
function installed() {
|
|
23
|
+
try {
|
|
24
|
+
return readFileSync(join(kitHome(), 'placement'), 'utf8').trim();
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error.code === 'ENOENT')
|
|
28
|
+
return null;
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function entries(house) {
|
|
33
|
+
const host = new URL(house).host;
|
|
34
|
+
const relay = relayOrigin();
|
|
35
|
+
if (relay === undefined)
|
|
36
|
+
return [`curl -fsSL ${host}/kit | sh`, `irm ${host}/kit.ps1 | iex`];
|
|
37
|
+
const entry = `${relay}/${host}/kit`;
|
|
38
|
+
return [`curl -fsSL ${entry} | HOUSE_KIT_RELAY=${relay} sh`, `$env:HOUSE_KIT_RELAY='${relay}'; irm ${entry}.ps1 | iex`];
|
|
39
|
+
}
|
|
40
|
+
export function installCommand(house) {
|
|
41
|
+
const [shell, windows] = entries(house);
|
|
42
|
+
const placement = installed();
|
|
43
|
+
if (placement === 'native')
|
|
44
|
+
return shell;
|
|
45
|
+
if (placement === 'server')
|
|
46
|
+
return `${shell} -s -- --linux`;
|
|
47
|
+
return `${shell} -s -- --container, or ${windows} on Windows`;
|
|
48
|
+
}
|
package/dist/relay.js
CHANGED
|
@@ -2,7 +2,7 @@ let selected;
|
|
|
2
2
|
export function selectRelay(relay) {
|
|
3
3
|
selected = relay === undefined || relay === '' ? null : relay;
|
|
4
4
|
}
|
|
5
|
-
function relayOrigin() {
|
|
5
|
+
export function relayOrigin() {
|
|
6
6
|
if (selected !== undefined)
|
|
7
7
|
return selected ?? undefined;
|
|
8
8
|
const ambient = process.env.HOUSE_KIT_RELAY;
|
package/dist/resident.js
CHANGED
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
7
7
|
import { Agents } from './agents.js';
|
|
8
8
|
import { house } from './api.js';
|
|
9
9
|
import { received } from './bridge.js';
|
|
10
|
-
import { Conversations } from './conversations.js';
|
|
10
|
+
import { CONVERSATION_INPUTS, Conversations } from './conversations.js';
|
|
11
11
|
import { LocalCopies } from './copies.js';
|
|
12
12
|
import { restart } from './end.js';
|
|
13
13
|
import { agentBase, enrolled, forgetRestart, kitHome, restartAsked } from './home.js';
|
|
@@ -84,7 +84,7 @@ export async function resident() {
|
|
|
84
84
|
await placeSkillSet();
|
|
85
85
|
await mkdir(agentBase(), { recursive: true });
|
|
86
86
|
process.env.HOUSE_KIT_RESIDENT = String(process.pid);
|
|
87
|
-
const agents = new Agents(house);
|
|
87
|
+
const agents = new Agents(house, () => changed());
|
|
88
88
|
const copies = new LocalCopies(house);
|
|
89
89
|
await copies.load();
|
|
90
90
|
await installShim(join(kitHome(), 'shim', 'git'), 'git-main');
|
|
@@ -92,7 +92,7 @@ export async function resident() {
|
|
|
92
92
|
await serveOwner(copies);
|
|
93
93
|
let stream = null;
|
|
94
94
|
let reported = false;
|
|
95
|
-
const idle = () => conversations.idle() && signIns.idle();
|
|
95
|
+
const idle = () => conversations.idle() && signIns.idle() && agents.idle();
|
|
96
96
|
const changed = () => {
|
|
97
97
|
if (!idle()) {
|
|
98
98
|
reported = false;
|
|
@@ -140,8 +140,12 @@ export async function resident() {
|
|
|
140
140
|
signIns.start(arrived);
|
|
141
141
|
else if (arrived.kind === 'update')
|
|
142
142
|
updates.start(arrived);
|
|
143
|
-
else
|
|
143
|
+
else if (CONVERSATION_INPUTS.has(arrived.kind))
|
|
144
144
|
conversations.input(arrived);
|
|
145
|
+
else {
|
|
146
|
+
const refused = { refused: `This Kit does not know ${arrived.kind}; update the Kit` };
|
|
147
|
+
logged(house.deliver(`/kit/inputs/${arrived.input_id}/ack`, refused).finally(changed));
|
|
148
|
+
}
|
|
145
149
|
}
|
|
146
150
|
},
|
|
147
151
|
});
|
package/dist/skills.js
CHANGED
|
@@ -49,6 +49,14 @@ async function install() {
|
|
|
49
49
|
await writeHome(RECORD, { version: KIT_VERSION, names });
|
|
50
50
|
}
|
|
51
51
|
export async function placeSkillSet() {
|
|
52
|
+
try {
|
|
53
|
+
await replaceSkillSet();
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
process.stderr.write(`kit: the skills could not be placed: ${error.message}\n`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function replaceSkillSet() {
|
|
52
60
|
const accepted = (await readConfiguration()).skills;
|
|
53
61
|
const installed = await readHome(RECORD);
|
|
54
62
|
if (accepted && installed?.version === KIT_VERSION)
|
|
@@ -63,7 +71,9 @@ export async function placeSkillSet() {
|
|
|
63
71
|
await rm(join(kitHome(), RECORD), { force: true });
|
|
64
72
|
}
|
|
65
73
|
export async function createHowWeWork(bridge) {
|
|
66
|
-
if ((await
|
|
74
|
+
if (!(await readConfiguration()).skills || (await readHome(RECORD))?.version === undefined)
|
|
75
|
+
return;
|
|
76
|
+
if (await present(join(kitHome(), CREATED)))
|
|
67
77
|
return;
|
|
68
78
|
const created = await bridge.tool('edit', { changes: [{ op: 'create', path: HOW_WE_WORK, content: HOW_WE_WORK_TEXT }] });
|
|
69
79
|
if (created.isError === true) {
|
package/dist/stream.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { subscribe } from 'node:diagnostics_channel';
|
|
2
2
|
import { finalRefusal, HouseRefusal, retryDelay } from './api.js';
|
|
3
|
-
import { end, REPLACED, signInAgain } from './end.js';
|
|
3
|
+
import { end, REPLACED, signInAgain, updateRequired } from './end.js';
|
|
4
4
|
import { readEnrolment } from './home.js';
|
|
5
5
|
import { relayed } from './relay.js';
|
|
6
|
+
import { KIT_WIRE, tooOld } from './wire.js';
|
|
6
7
|
const PATH = '/kit/stream';
|
|
7
|
-
const SUBPROTOCOL =
|
|
8
|
+
const SUBPROTOCOL = `house.kit.stream.${KIT_WIRE}`;
|
|
8
9
|
const REFUSAL_HEADER = 'x-house-refusal';
|
|
9
10
|
function streamUrl(house) {
|
|
10
11
|
const url = new URL(PATH, house);
|
|
@@ -56,6 +57,8 @@ export function holdStream(handlers) {
|
|
|
56
57
|
void signInAgain(credential);
|
|
57
58
|
return;
|
|
58
59
|
}
|
|
60
|
+
if (refused !== null && tooOld(refused.text))
|
|
61
|
+
updateRequired(house);
|
|
59
62
|
if (finalRefusal(refused))
|
|
60
63
|
end(refused.message);
|
|
61
64
|
// A closed socket reopens in one to three seconds, spread so Kits one restart closed return apart; an opening House refused as retryable or left unanswered backs off on the jittered curve.
|
package/dist/update.js
CHANGED
|
@@ -4,8 +4,8 @@ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { run } from './clis.js';
|
|
7
|
-
import { kitHome } from './home.js';
|
|
8
|
-
|
|
7
|
+
import { kitHome, workspaceRoot } from './home.js';
|
|
8
|
+
import { service, throughSudo } from './placement.js';
|
|
9
9
|
function logged(error) {
|
|
10
10
|
process.stderr.write(`kit: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
11
11
|
}
|
|
@@ -21,6 +21,21 @@ function outcome() {
|
|
|
21
21
|
function unit(inputId) {
|
|
22
22
|
return `house-kit-update-${inputId}`;
|
|
23
23
|
}
|
|
24
|
+
function manager() {
|
|
25
|
+
return service() === 'user' ? ['--user'] : [];
|
|
26
|
+
}
|
|
27
|
+
function elevated() {
|
|
28
|
+
return throughSudo() ? ['sudo', '-n'] : [];
|
|
29
|
+
}
|
|
30
|
+
function account() {
|
|
31
|
+
return throughSudo() ? [`--uid=${process.getuid()}`, `--gid=${process.getgid()}`] : [];
|
|
32
|
+
}
|
|
33
|
+
function placement() {
|
|
34
|
+
return service() === 'user' ? ['--workspace', workspaceRoot()] : ['--linux'];
|
|
35
|
+
}
|
|
36
|
+
function record() {
|
|
37
|
+
return `outcome=$1; shift; bash "$0" "$@"; printf %s "$?" > "$outcome"; ${[...elevated(), 'systemctl', ...manager(), 'start', 'house-kit.service'].join(' ')}`;
|
|
38
|
+
}
|
|
24
39
|
async function pendingInput() {
|
|
25
40
|
try {
|
|
26
41
|
return JSON.parse(await readFile(pending(), 'utf8')).input_id;
|
|
@@ -41,7 +56,7 @@ async function finished(inputId) {
|
|
|
41
56
|
if (ack !== null)
|
|
42
57
|
return ack;
|
|
43
58
|
// systemctl answers at once, so ten seconds only bounds a stuck service manager.
|
|
44
|
-
if ((await run('systemctl', ['is-active', '--quiet', unit(inputId)], 10_000)).status !== 0) {
|
|
59
|
+
if ((await run('systemctl', [...manager(), 'is-active', '--quiet', unit(inputId)], 10_000)).status !== 0) {
|
|
45
60
|
return (await recorded()) ?? { refused: 'the Kit bootstrap did not finish' };
|
|
46
61
|
}
|
|
47
62
|
// The bootstrap runs for minutes, so checking each second sees its outcome within a second at little cost.
|
|
@@ -50,19 +65,26 @@ async function finished(inputId) {
|
|
|
50
65
|
}
|
|
51
66
|
function rerun(script, inputId, flags) {
|
|
52
67
|
return new Promise((resolve) => {
|
|
53
|
-
const
|
|
68
|
+
const [command, ...args] = [
|
|
69
|
+
...elevated(),
|
|
70
|
+
'systemd-run',
|
|
71
|
+
...manager(),
|
|
72
|
+
...account(),
|
|
54
73
|
'--wait',
|
|
55
74
|
'--collect',
|
|
56
75
|
'--quiet',
|
|
57
76
|
`--unit=${unit(inputId)}`,
|
|
58
77
|
`--setenv=HOME=${homedir()}`,
|
|
78
|
+
`--setenv=HOUSE_KIT_HOME=${kitHome()}`,
|
|
59
79
|
'/bin/sh',
|
|
60
80
|
'-c',
|
|
61
|
-
|
|
81
|
+
record(),
|
|
62
82
|
script,
|
|
63
83
|
outcome(),
|
|
84
|
+
...placement(),
|
|
64
85
|
...flags,
|
|
65
|
-
]
|
|
86
|
+
];
|
|
87
|
+
const child = spawn(command, args, { stdio: ['ignore', 'ignore', 'inherit'] });
|
|
66
88
|
child.on('error', () => resolve({ refused: 'no service manager runs this Kit' }));
|
|
67
89
|
child.on('close', () => resolve(null));
|
|
68
90
|
});
|
package/dist/wire.js
ADDED