@craftspace/cli 0.9.1 → 0.10.0
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/dist/index.js +245 -132
- package/dist/machine.d.ts +30 -5
- package/dist/machine.js +230 -113
- package/dist/probe.d.ts +2 -1
- package/dist/probe.js +5 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19892,7 +19892,7 @@ var SaveWorkstationBodySchema = external_exports.object({
|
|
|
19892
19892
|
var WorkstationResponseSchema = external_exports.object({ workstation: WorkstationSchema });
|
|
19893
19893
|
|
|
19894
19894
|
// ../../shared/dist/machines/machine.js
|
|
19895
|
-
var CLI_VERSION = "0.
|
|
19895
|
+
var CLI_VERSION = "0.10.0";
|
|
19896
19896
|
var CLI_PACKAGE = "@craftspace/cli";
|
|
19897
19897
|
var CLI_INSTALL = `npm i -g ${CLI_PACKAGE}`;
|
|
19898
19898
|
var MACHINE_BEAT_INTERVAL_MS = 15e3;
|
|
@@ -20384,7 +20384,7 @@ var MemberActivityListResponse = external_exports.object({
|
|
|
20384
20384
|
// dist/machine.js
|
|
20385
20385
|
import { execFile as execFile3, spawn as spawn2 } from "node:child_process";
|
|
20386
20386
|
import { existsSync as existsSync2 } from "node:fs";
|
|
20387
|
-
import { mkdir as mkdir3, readFile as readFile3, readdir as readdir2, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
|
|
20387
|
+
import { mkdir as mkdir3, readFile as readFile3, readdir as readdir2, rename as rename2, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
|
|
20388
20388
|
import os6 from "node:os";
|
|
20389
20389
|
import path5 from "node:path";
|
|
20390
20390
|
import { promisify as promisify2 } from "node:util";
|
|
@@ -22344,16 +22344,17 @@ import { readFile as readFile2 } from "node:fs/promises";
|
|
|
22344
22344
|
import os2 from "node:os";
|
|
22345
22345
|
import path3 from "node:path";
|
|
22346
22346
|
var probe = {
|
|
22347
|
-
async tools({ dirs }) {
|
|
22348
|
-
return Promise.all([brainWired(dirs), sshListening()]);
|
|
22347
|
+
async tools({ dirs, organization }) {
|
|
22348
|
+
return Promise.all([brainWired({ dirs, organization }), sshListening()]);
|
|
22349
22349
|
}
|
|
22350
22350
|
};
|
|
22351
|
-
async function brainWired(dirs) {
|
|
22351
|
+
async function brainWired({ dirs, organization }) {
|
|
22352
22352
|
if (dirs.length === 0) {
|
|
22353
22353
|
return { id: "brain", ok: false, detail: "no repos cloned here yet, so there is nothing to wire it into" };
|
|
22354
22354
|
}
|
|
22355
22355
|
const projects = await claudeProjects();
|
|
22356
|
-
const
|
|
22356
|
+
const wanted = `craftspace-${organization}`;
|
|
22357
|
+
const unwired = dirs.filter((dir) => !(wanted in mcpServersIn(projects[dir])));
|
|
22357
22358
|
return unwired.length === 0 ? { id: "brain", ok: true, detail: `wired into ${dirs.length} repo${dirs.length === 1 ? "" : "s"}` } : { id: "brain", ok: false, detail: `not wired into ${unwired.map(short).join(", ")}`.slice(0, DETAIL_MAX) };
|
|
22358
22359
|
}
|
|
22359
22360
|
async function claudeProjects() {
|
|
@@ -23254,9 +23255,24 @@ var INSTALL_TIMEOUT_MS = 18e4;
|
|
|
23254
23255
|
// dist/machine.js
|
|
23255
23256
|
var run2 = promisify2(execFile3);
|
|
23256
23257
|
var machine = {
|
|
23257
|
-
|
|
23258
|
+
root() {
|
|
23258
23259
|
return process.env.CRAFTSPACE_CLI_HOME ?? path5.join(os6.homedir(), ".craftspace");
|
|
23259
23260
|
},
|
|
23261
|
+
async seats() {
|
|
23262
|
+
await settleLayout();
|
|
23263
|
+
const entries = await readdir2(machine.root(), { withFileTypes: true }).catch(() => []);
|
|
23264
|
+
const seats = [];
|
|
23265
|
+
for (const entry of entries.filter((found) => found.isDirectory())) {
|
|
23266
|
+
const home = path5.join(machine.root(), entry.name);
|
|
23267
|
+
const config2 = await readConfig(home);
|
|
23268
|
+
if (config2 !== null)
|
|
23269
|
+
seats.push({ home, config: config2 });
|
|
23270
|
+
}
|
|
23271
|
+
const legacy = await readConfig(machine.root());
|
|
23272
|
+
if (legacy !== null)
|
|
23273
|
+
seats.push({ home: machine.root(), config: legacy });
|
|
23274
|
+
return seats;
|
|
23275
|
+
},
|
|
23260
23276
|
async login({ token, url: url2, install }) {
|
|
23261
23277
|
const answer = await call({
|
|
23262
23278
|
url: url2,
|
|
@@ -23274,26 +23290,29 @@ var machine = {
|
|
|
23274
23290
|
},
|
|
23275
23291
|
schema: MachineLoginResponseSchema
|
|
23276
23292
|
});
|
|
23277
|
-
|
|
23278
|
-
|
|
23279
|
-
await
|
|
23293
|
+
const organization = toSpaceSlug(answer.organizationName) || "team";
|
|
23294
|
+
const account = answer.machine.ownerAccountId;
|
|
23295
|
+
await refuseSecondAccount({ account, organization });
|
|
23296
|
+
const home = path5.join(machine.root(), organization);
|
|
23297
|
+
await mkdir3(home, { recursive: true, mode: 448 });
|
|
23298
|
+
await writeConfig(home, { url: url2, machineId: answer.machine.id, organization, account });
|
|
23299
|
+
await writeFile2(tokenPath(home), token, { mode: 384 });
|
|
23280
23300
|
await writeAgentConfigs({ writes: answer.write });
|
|
23281
|
-
await wireRepos({ url: url2, token, dirs: clonedDirs(await readEnvironment()) });
|
|
23301
|
+
await wireRepos({ url: url2, token, organization, dirs: clonedDirs(await readEnvironment(home)) });
|
|
23282
23302
|
await installService();
|
|
23283
23303
|
if (install)
|
|
23284
|
-
await machine.beatOnce(ui.say);
|
|
23285
|
-
return answer.machine;
|
|
23304
|
+
await machine.beatOnce({ home, say: ui.say });
|
|
23305
|
+
return { url: url2, token, machine: answer.machine, organization };
|
|
23286
23306
|
},
|
|
23287
|
-
async workDir() {
|
|
23288
|
-
|
|
23289
|
-
return organization === void 0 ? null : setup.workDir(organization);
|
|
23307
|
+
async workDir(organization) {
|
|
23308
|
+
return setup.workDir(organization);
|
|
23290
23309
|
},
|
|
23291
23310
|
async controlPlane(override) {
|
|
23292
|
-
const where = override ?? process.env.CRAFTSPACE_URL ?? (await
|
|
23311
|
+
const where = override ?? process.env.CRAFTSPACE_URL ?? (await anySeat())?.config.url ?? DEFAULT_URL;
|
|
23293
23312
|
return where.replace(/\/$/, "");
|
|
23294
23313
|
},
|
|
23295
23314
|
async signIn({ url: url2, why, install = false }) {
|
|
23296
|
-
const where = url2 ?? (await
|
|
23315
|
+
const where = url2 ?? (await anySeat())?.config.url ?? DEFAULT_URL;
|
|
23297
23316
|
if (!process.stdin.isTTY)
|
|
23298
23317
|
throw new Error(`${why} Run: cs login --token <token>`);
|
|
23299
23318
|
const page = `${where}/workstations`;
|
|
@@ -23315,16 +23334,34 @@ ${ui.attention(why)}
|
|
|
23315
23334
|
throw new Error("That line carries no key.");
|
|
23316
23335
|
const signed = await machine.login({ token, url: where, install });
|
|
23317
23336
|
ui.say(`
|
|
23318
|
-
${ui.ok(`Signed in as ${signed.ownerName}. This machine is ${ui.name(signed.name)}.`)}`);
|
|
23319
|
-
return
|
|
23337
|
+
${ui.ok(`Signed in as ${signed.machine.ownerName}. This machine is ${ui.name(signed.machine.name)}.`)}`);
|
|
23338
|
+
return signed;
|
|
23320
23339
|
},
|
|
23321
23340
|
async status() {
|
|
23322
|
-
const
|
|
23323
|
-
|
|
23341
|
+
const seats = await machine.seats();
|
|
23342
|
+
if (seats.length === 0) {
|
|
23343
|
+
const signed = await machine.signIn({ why: "This machine is not signed in to Craftspace yet." });
|
|
23344
|
+
return [{ machine: signed.machine, url: signed.url, organization: signed.organization }];
|
|
23345
|
+
}
|
|
23346
|
+
const found = [];
|
|
23347
|
+
for (const seat of seats) {
|
|
23348
|
+
const live = await withAuth(await authOf(seat), (auth) => call({ ...auth, method: "GET", path: "/api/machines/me", schema: MachineSchema }));
|
|
23349
|
+
found.push({ machine: live.value, url: live.auth.url, organization: seat.config.organization ?? "?" });
|
|
23350
|
+
}
|
|
23351
|
+
return found;
|
|
23352
|
+
},
|
|
23353
|
+
async beatAll(say) {
|
|
23354
|
+
const seats = await machine.seats();
|
|
23355
|
+
if (seats.length === 0)
|
|
23356
|
+
throw new Error("This machine is not signed in. Run: cs login --token <token>");
|
|
23357
|
+
let busy = false;
|
|
23358
|
+
for (const seat of seats)
|
|
23359
|
+
busy = await machine.beatOnce({ home: seat.home, say }) || busy;
|
|
23360
|
+
return busy;
|
|
23324
23361
|
},
|
|
23325
|
-
async beatOnce(say) {
|
|
23326
|
-
const { url: url2, token, organization } = await requireSignedIn();
|
|
23327
|
-
const built = await readEnvironment();
|
|
23362
|
+
async beatOnce({ home, say }) {
|
|
23363
|
+
const { url: url2, token, organization } = await requireSignedIn(home);
|
|
23364
|
+
const built = await readEnvironment(home);
|
|
23328
23365
|
const answer = await call({
|
|
23329
23366
|
url: url2,
|
|
23330
23367
|
token,
|
|
@@ -23334,8 +23371,8 @@ ${ui.ok(`Signed in as ${signed.ownerName}. This machine is ${ui.name(signed.name
|
|
|
23334
23371
|
cli: CLI_VERSION,
|
|
23335
23372
|
...load2(),
|
|
23336
23373
|
sessions: await readSessions(),
|
|
23337
|
-
runs: drainReports(),
|
|
23338
|
-
tools: await checks(),
|
|
23374
|
+
runs: drainReports(home),
|
|
23375
|
+
tools: await checks(home),
|
|
23339
23376
|
environment: built?.results ?? [],
|
|
23340
23377
|
templateId: built?.templateId ?? null,
|
|
23341
23378
|
...organization === void 0 ? {} : { workDir: setup.workDir(organization) },
|
|
@@ -23348,10 +23385,10 @@ ${ui.ok(`Signed in as ${signed.ownerName}. This machine is ${ui.name(signed.name
|
|
|
23348
23385
|
updateWanted = answer.cli.version;
|
|
23349
23386
|
const workDir = setup.workDir(answer.organization);
|
|
23350
23387
|
if (answer.organization !== organization)
|
|
23351
|
-
await writeConfig({ organization: answer.organization });
|
|
23352
|
-
await migrate.toLatest({ home: machine.
|
|
23353
|
-
await writeAuthorizedKeys(answer.authorizedKeys);
|
|
23354
|
-
const building2 = buildEnvironment({ next: answer.environment, workDir, say });
|
|
23388
|
+
await writeConfig(home, { organization: answer.organization });
|
|
23389
|
+
await migrate.toLatest({ home: machine.root(), workDir }).catch(() => []);
|
|
23390
|
+
await writeAuthorizedKeys({ organization: answer.organization, keys: answer.authorizedKeys });
|
|
23391
|
+
const building2 = buildEnvironment({ next: answer.environment, home, workDir, say });
|
|
23355
23392
|
if (say === void 0)
|
|
23356
23393
|
void building2.catch(() => void 0);
|
|
23357
23394
|
else
|
|
@@ -23360,7 +23397,7 @@ ${ui.ok(`Signed in as ${signed.ownerName}. This machine is ${ui.name(signed.name
|
|
|
23360
23397
|
if (running.has(work.id))
|
|
23361
23398
|
continue;
|
|
23362
23399
|
running.add(work.id);
|
|
23363
|
-
void runner.unattended({ work, cwd: startIn(built), onProgress: report }).then(report).catch((error51) => report({ id: work.id, state: "failed", output: messageOf(error51) })).finally(() => running.delete(work.id));
|
|
23400
|
+
void runner.unattended({ work, cwd: startIn(built), onProgress: (next) => report(home, next) }).then((next) => report(home, next)).catch((error51) => report(home, { id: work.id, state: "failed", output: messageOf(error51) })).finally(() => running.delete(work.id));
|
|
23364
23401
|
}
|
|
23365
23402
|
return running.size > 0 || answer.work.length > 0;
|
|
23366
23403
|
},
|
|
@@ -23381,7 +23418,7 @@ ${ui.ok(`Signed in as ${signed.ownerName}. This machine is ${ui.name(signed.name
|
|
|
23381
23418
|
}
|
|
23382
23419
|
}
|
|
23383
23420
|
try {
|
|
23384
|
-
busy = await machine.
|
|
23421
|
+
busy = await machine.beatAll();
|
|
23385
23422
|
failures = 0;
|
|
23386
23423
|
} catch (error51) {
|
|
23387
23424
|
failures += 1;
|
|
@@ -23392,7 +23429,7 @@ ${ui.ok(`Signed in as ${signed.ownerName}. This machine is ${ui.name(signed.name
|
|
|
23392
23429
|
}
|
|
23393
23430
|
},
|
|
23394
23431
|
async run() {
|
|
23395
|
-
const first = await
|
|
23432
|
+
const first = await authOfAnySeatOrAsk();
|
|
23396
23433
|
const { auth, value: answer } = await withAuth(first, (live) => call({ ...live, method: "GET", path: "/api/machines", schema: MachinesResponseSchema }));
|
|
23397
23434
|
if (answer.machines.length === 0)
|
|
23398
23435
|
return nowhereToGo(auth);
|
|
@@ -23410,23 +23447,55 @@ ${ui.ok(`Signed in as ${signed.ownerName}. This machine is ${ui.name(signed.name
|
|
|
23410
23447
|
return { text: BEATING, ok: true };
|
|
23411
23448
|
},
|
|
23412
23449
|
async logout() {
|
|
23413
|
-
const
|
|
23414
|
-
|
|
23415
|
-
|
|
23416
|
-
|
|
23417
|
-
|
|
23418
|
-
|
|
23450
|
+
for (const seat of await machine.seats()) {
|
|
23451
|
+
const token = await readToken(seat.home);
|
|
23452
|
+
if (token === null)
|
|
23453
|
+
continue;
|
|
23454
|
+
await call({
|
|
23455
|
+
url: seat.config.url,
|
|
23456
|
+
token,
|
|
23457
|
+
method: "POST",
|
|
23458
|
+
path: "/api/machines/logout",
|
|
23459
|
+
schema: external_exports.object({})
|
|
23460
|
+
}).catch(() => void 0);
|
|
23461
|
+
await removeAgentConfigs(seat.home);
|
|
23462
|
+
if (seat.config.organization !== void 0) {
|
|
23463
|
+
await writeAuthorizedKeys({ organization: seat.config.organization, keys: [] });
|
|
23464
|
+
}
|
|
23465
|
+
}
|
|
23466
|
+
await unwireRepos();
|
|
23419
23467
|
await uninstallService();
|
|
23420
|
-
await rm2(machine.
|
|
23468
|
+
await rm2(machine.root(), { recursive: true, force: true });
|
|
23421
23469
|
}
|
|
23422
23470
|
};
|
|
23423
|
-
async function requireSignedIn() {
|
|
23424
|
-
const config2 = await readConfig();
|
|
23425
|
-
const token = await readToken();
|
|
23471
|
+
async function requireSignedIn(home) {
|
|
23472
|
+
const config2 = await readConfig(home);
|
|
23473
|
+
const token = await readToken(home);
|
|
23426
23474
|
if (!config2 || !token)
|
|
23427
23475
|
throw new Error("This machine is not signed in. Run: cs login --token <token>");
|
|
23428
23476
|
return { url: config2.url, token, ...spreadIfDefined({ organization: config2.organization }) };
|
|
23429
23477
|
}
|
|
23478
|
+
async function refuseSecondAccount({ account, organization }) {
|
|
23479
|
+
const taken = (await machine.seats()).find((seat) => seat.config.account !== void 0 && seat.config.account !== account);
|
|
23480
|
+
if (taken === void 0)
|
|
23481
|
+
return;
|
|
23482
|
+
throw new Error(`This box is already signed in as someone else, under ${taken.config.organization ?? "another org"}. One box holds one account, any number of its orgs. Run: cs logout, then sign in for ${organization}.`);
|
|
23483
|
+
}
|
|
23484
|
+
async function anySeat() {
|
|
23485
|
+
return (await machine.seats())[0];
|
|
23486
|
+
}
|
|
23487
|
+
async function authOf(seat) {
|
|
23488
|
+
const token = await readToken(seat.home);
|
|
23489
|
+
if (token === null)
|
|
23490
|
+
throw new Error("This machine is not signed in. Run: cs login --token <token>");
|
|
23491
|
+
return { url: seat.config.url, token };
|
|
23492
|
+
}
|
|
23493
|
+
async function authOfAnySeatOrAsk() {
|
|
23494
|
+
const seat = await anySeat();
|
|
23495
|
+
if (seat === void 0)
|
|
23496
|
+
return machine.signIn({ why: "This machine is not signed in to Craftspace yet." });
|
|
23497
|
+
return authOf(seat);
|
|
23498
|
+
}
|
|
23430
23499
|
async function pick2(machines) {
|
|
23431
23500
|
return dist_default6({
|
|
23432
23501
|
message: "Which workstation?",
|
|
@@ -23485,25 +23554,27 @@ ${ui.attention("No workstations here yet.")}
|
|
|
23485
23554
|
ui.say("\n");
|
|
23486
23555
|
return 1;
|
|
23487
23556
|
}
|
|
23488
|
-
async function checks() {
|
|
23489
|
-
|
|
23490
|
-
|
|
23491
|
-
|
|
23492
|
-
const
|
|
23557
|
+
async function checks(home) {
|
|
23558
|
+
const seen = probed.get(home);
|
|
23559
|
+
if (seen !== void 0 && Date.now() - seen.at < PROBE_EVERY_MS)
|
|
23560
|
+
return seen.tools;
|
|
23561
|
+
const organization = (await readConfig(home))?.organization ?? "team";
|
|
23562
|
+
const dirs = clonedDirs(await readEnvironment(home));
|
|
23563
|
+
const first = await probe.tools({ dirs, organization });
|
|
23493
23564
|
const unwired = first.some((tool) => tool.id === "brain" && !tool.ok);
|
|
23494
|
-
const found = unwired && installs() && dirs.length > 0 ? await rewire(dirs).then(() => probe.tools({ dirs }), () => first) : first;
|
|
23495
|
-
probed
|
|
23565
|
+
const found = unwired && installs() && dirs.length > 0 ? await rewire(home, dirs).then(() => probe.tools({ dirs, organization }), () => first) : first;
|
|
23566
|
+
probed.set(home, { at: Date.now(), tools: found });
|
|
23496
23567
|
return found;
|
|
23497
23568
|
}
|
|
23498
|
-
async function buildEnvironment({ next, workDir, say }) {
|
|
23499
|
-
if (building || !installs() || next.templateId === null)
|
|
23569
|
+
async function buildEnvironment({ next, home, workDir, say }) {
|
|
23570
|
+
if (building.has(home) || !installs() || next.templateId === null)
|
|
23500
23571
|
return;
|
|
23501
|
-
const built = await readEnvironment();
|
|
23572
|
+
const built = await readEnvironment(home);
|
|
23502
23573
|
const buildsSteps = buildIsDue(built, next.templateId);
|
|
23503
23574
|
const clonesRepos = cloneIsDue({ built, wanted: next.repos, workDir });
|
|
23504
23575
|
if (!buildsSteps && !clonesRepos)
|
|
23505
23576
|
return;
|
|
23506
|
-
building
|
|
23577
|
+
building.add(home);
|
|
23507
23578
|
try {
|
|
23508
23579
|
let results = built?.results ?? [];
|
|
23509
23580
|
let repos = built?.repos ?? [];
|
|
@@ -23514,11 +23585,11 @@ async function buildEnvironment({ next, workDir, say }) {
|
|
|
23514
23585
|
if (clonesRepos) {
|
|
23515
23586
|
say?.(ui.heading(`Cloning the ${next.repos.length} repos your team works in, into ${workDir}`));
|
|
23516
23587
|
repos = await setup.cloneRepos({ repos: next.repos, workDir, say });
|
|
23517
|
-
await rewire(repos.filter((repo) => repo.ok).map((repo) => repo.path));
|
|
23588
|
+
await rewire(home, repos.filter((repo) => repo.ok).map((repo) => repo.path));
|
|
23518
23589
|
}
|
|
23519
|
-
await writeEnvironment({ templateId: next.templateId, results, repos, at: Date.now() });
|
|
23590
|
+
await writeEnvironment(home, { templateId: next.templateId, results, repos, at: Date.now() });
|
|
23520
23591
|
} finally {
|
|
23521
|
-
building
|
|
23592
|
+
building.delete(home);
|
|
23522
23593
|
}
|
|
23523
23594
|
}
|
|
23524
23595
|
function cloneIsDue({ built, wanted, workDir }) {
|
|
@@ -23545,35 +23616,40 @@ function buildIsDue(built, templateId) {
|
|
|
23545
23616
|
return false;
|
|
23546
23617
|
return Date.now() - built.at > REBUILD_AFTER_MS;
|
|
23547
23618
|
}
|
|
23548
|
-
async function readEnvironment() {
|
|
23549
|
-
const raw = await readFile3(environmentPath(), "utf8").catch(() => null);
|
|
23619
|
+
async function readEnvironment(home) {
|
|
23620
|
+
const raw = await readFile3(environmentPath(home), "utf8").catch(() => null);
|
|
23550
23621
|
if (raw === null)
|
|
23551
23622
|
return null;
|
|
23552
23623
|
const parsed = BuiltSchema.safeParse(JSON.parse(raw));
|
|
23553
23624
|
return parsed.success ? parsed.data : null;
|
|
23554
23625
|
}
|
|
23555
|
-
async function writeEnvironment(built) {
|
|
23556
|
-
await mkdir3(
|
|
23557
|
-
await writeFile2(environmentPath(), JSON.stringify(built, null, 2), { mode: 384 });
|
|
23626
|
+
async function writeEnvironment(home, built) {
|
|
23627
|
+
await mkdir3(home, { recursive: true, mode: 448 });
|
|
23628
|
+
await writeFile2(environmentPath(home), JSON.stringify(built, null, 2), { mode: 384 });
|
|
23558
23629
|
}
|
|
23559
23630
|
function installs() {
|
|
23560
23631
|
return process.env.CRAFTSPACE_NO_INSTALL !== "1";
|
|
23561
23632
|
}
|
|
23562
|
-
async function rewire(dirs) {
|
|
23563
|
-
const { url: url2, token } = await requireSignedIn();
|
|
23564
|
-
await wireRepos({ url: url2, token, dirs });
|
|
23633
|
+
async function rewire(home, dirs) {
|
|
23634
|
+
const { url: url2, token, organization } = await requireSignedIn(home);
|
|
23635
|
+
await wireRepos({ url: url2, token, organization: organization ?? "team", dirs });
|
|
23565
23636
|
}
|
|
23566
23637
|
function clonedDirs(built) {
|
|
23567
23638
|
return (built?.repos ?? []).filter((repo) => repo.ok).map((repo) => repo.path);
|
|
23568
23639
|
}
|
|
23569
|
-
function report(next) {
|
|
23570
|
-
reports.
|
|
23640
|
+
function report(home, next) {
|
|
23641
|
+
const mine = reports.get(home) ?? /* @__PURE__ */ new Map();
|
|
23642
|
+
mine.set(next.id, next);
|
|
23643
|
+
reports.set(home, mine);
|
|
23571
23644
|
}
|
|
23572
|
-
function drainReports() {
|
|
23573
|
-
const
|
|
23645
|
+
function drainReports(home) {
|
|
23646
|
+
const mine = reports.get(home);
|
|
23647
|
+
if (mine === void 0)
|
|
23648
|
+
return [];
|
|
23649
|
+
const pending = [...mine.values()];
|
|
23574
23650
|
for (const item of pending)
|
|
23575
23651
|
if (item.state !== "running")
|
|
23576
|
-
|
|
23652
|
+
mine.delete(item.id);
|
|
23577
23653
|
return pending;
|
|
23578
23654
|
}
|
|
23579
23655
|
async function call({ url: url2, token, method, path: route, body, schema }) {
|
|
@@ -23597,13 +23673,6 @@ var Unauthorized = class extends Error {
|
|
|
23597
23673
|
super("This machine no longer has a key Craftspace accepts.");
|
|
23598
23674
|
}
|
|
23599
23675
|
};
|
|
23600
|
-
async function signedInOrAsk() {
|
|
23601
|
-
const config2 = await readConfig();
|
|
23602
|
-
const token = await readToken();
|
|
23603
|
-
if (config2 && token)
|
|
23604
|
-
return { url: config2.url, token };
|
|
23605
|
-
return machine.signIn({ why: "This machine is not signed in to Craftspace yet." });
|
|
23606
|
-
}
|
|
23607
23676
|
async function withAuth(auth, work) {
|
|
23608
23677
|
try {
|
|
23609
23678
|
return { auth, value: await work(auth) };
|
|
@@ -23628,33 +23697,46 @@ function tokenIn(line) {
|
|
|
23628
23697
|
return /cst_[A-Za-z0-9_-]+/.exec(line)?.[0];
|
|
23629
23698
|
}
|
|
23630
23699
|
async function ensureKey() {
|
|
23631
|
-
const priv = path5.join(machine.
|
|
23700
|
+
const priv = path5.join(machine.root(), "id_ed25519");
|
|
23632
23701
|
const pub = `${priv}.pub`;
|
|
23633
23702
|
const existing = await readFile3(pub, "utf8").catch(() => null);
|
|
23634
23703
|
if (existing !== null)
|
|
23635
23704
|
return existing.trim();
|
|
23636
|
-
await mkdir3(machine.
|
|
23705
|
+
await mkdir3(machine.root(), { recursive: true, mode: 448 });
|
|
23637
23706
|
await run2("ssh-keygen", ["-t", "ed25519", "-N", "", "-q", "-f", priv, "-C", `craftspace-${os6.hostname()}`]);
|
|
23638
23707
|
return (await readFile3(pub, "utf8")).trim();
|
|
23639
23708
|
}
|
|
23640
|
-
async function writeConfig(next) {
|
|
23641
|
-
const merged = { ...await readConfig(), ...next };
|
|
23642
|
-
await mkdir3(
|
|
23643
|
-
await writeFile2(configPath(), `${JSON.stringify(merged, null, 2)}
|
|
23709
|
+
async function writeConfig(home, next) {
|
|
23710
|
+
const merged = { ...await readConfig(home), ...next };
|
|
23711
|
+
await mkdir3(home, { recursive: true, mode: 448 });
|
|
23712
|
+
await writeFile2(configPath(home), `${JSON.stringify(merged, null, 2)}
|
|
23644
23713
|
`, { mode: 384 });
|
|
23645
23714
|
}
|
|
23646
|
-
async function readConfig() {
|
|
23647
|
-
const raw = await readFile3(configPath(), "utf8").catch(() => null);
|
|
23715
|
+
async function readConfig(home) {
|
|
23716
|
+
const raw = await readFile3(configPath(home), "utf8").catch(() => null);
|
|
23648
23717
|
if (raw === null)
|
|
23649
23718
|
return null;
|
|
23650
|
-
|
|
23719
|
+
const parsed = ConfigSchema.safeParse(JSON.parse(raw));
|
|
23720
|
+
return parsed.success ? parsed.data : null;
|
|
23651
23721
|
}
|
|
23652
|
-
async function readToken() {
|
|
23653
|
-
const raw = await readFile3(tokenPath(), "utf8").catch(() => null);
|
|
23722
|
+
async function readToken(home) {
|
|
23723
|
+
const raw = await readFile3(tokenPath(home), "utf8").catch(() => null);
|
|
23654
23724
|
return raw === null ? null : raw.trim();
|
|
23655
23725
|
}
|
|
23726
|
+
async function settleLayout() {
|
|
23727
|
+
const root = machine.root();
|
|
23728
|
+
const config2 = await readConfig(root);
|
|
23729
|
+
const organization = config2?.organization;
|
|
23730
|
+
if (config2 === null || organization === void 0)
|
|
23731
|
+
return;
|
|
23732
|
+
const home = path5.join(root, organization);
|
|
23733
|
+
await mkdir3(home, { recursive: true, mode: 448 });
|
|
23734
|
+
for (const name of SEAT_FILES) {
|
|
23735
|
+
await rename2(path5.join(root, name), path5.join(home, name)).catch(() => void 0);
|
|
23736
|
+
}
|
|
23737
|
+
}
|
|
23656
23738
|
async function readSessions() {
|
|
23657
|
-
const dir = path5.join(machine.
|
|
23739
|
+
const dir = path5.join(machine.root(), "sessions");
|
|
23658
23740
|
const names = await readdir2(dir).catch(() => []);
|
|
23659
23741
|
const sessions = [];
|
|
23660
23742
|
for (const name of names.filter((entry) => entry.endsWith(".json"))) {
|
|
@@ -23667,25 +23749,37 @@ async function readSessions() {
|
|
|
23667
23749
|
}
|
|
23668
23750
|
return sessions.slice(0, MAX_REPORTED_SESSIONS);
|
|
23669
23751
|
}
|
|
23670
|
-
async function writeAuthorizedKeys(keys) {
|
|
23752
|
+
async function writeAuthorizedKeys({ organization, keys }) {
|
|
23671
23753
|
const target = path5.join(os6.homedir(), ".ssh", "authorized_keys");
|
|
23672
23754
|
const current = await readFile3(target, "utf8").catch(() => "");
|
|
23673
|
-
const
|
|
23674
|
-
const after = current.includes(KEYS_END) ? current.split(KEYS_END)[1] ?? "" : "";
|
|
23675
|
-
const block = keys.length === 0 ? "" : `${KEYS_BEGIN}
|
|
23676
|
-
${keys.join("\n")}
|
|
23677
|
-
${KEYS_END}
|
|
23755
|
+
const block = keys.length === 0 ? "" : `${keys.join("\n")}
|
|
23678
23756
|
`;
|
|
23679
|
-
const next =
|
|
23757
|
+
const next = replaceBlock({
|
|
23758
|
+
text: replaceBlock({ text: current, begin: KEYS_BEGIN, end: KEYS_END, block: "" }),
|
|
23759
|
+
begin: `${KEYS_BEGIN} ${organization}`,
|
|
23760
|
+
end: `${KEYS_END} ${organization}`,
|
|
23761
|
+
block
|
|
23762
|
+
});
|
|
23680
23763
|
if (next === current)
|
|
23681
23764
|
return;
|
|
23682
23765
|
await mkdir3(path5.dirname(target), { recursive: true, mode: 448 });
|
|
23683
23766
|
await writeFile2(target, next, { mode: 384 });
|
|
23684
23767
|
}
|
|
23768
|
+
function replaceBlock({ text, begin, end, block }) {
|
|
23769
|
+
const before = text.split(begin)[0] ?? "";
|
|
23770
|
+
const after = text.includes(end) ? text.split(end)[1] ?? "" : text.includes(begin) ? "" : null;
|
|
23771
|
+
if (after === null && block === "")
|
|
23772
|
+
return text;
|
|
23773
|
+
const kept = after ?? "";
|
|
23774
|
+
const wrapped = block === "" ? "" : `${begin}
|
|
23775
|
+
${block}${end}
|
|
23776
|
+
`;
|
|
23777
|
+
return `${trimEnd(before)}${trimEnd(before) === "" ? "" : "\n"}${wrapped}${kept.replace(/^\n/, "")}`;
|
|
23778
|
+
}
|
|
23685
23779
|
function trimEnd(text) {
|
|
23686
23780
|
return text.replace(/\s+$/, "");
|
|
23687
23781
|
}
|
|
23688
|
-
async function wireRepos({ url: url2, token, dirs }) {
|
|
23782
|
+
async function wireRepos({ url: url2, token, organization, dirs }) {
|
|
23689
23783
|
if (dirs.length === 0)
|
|
23690
23784
|
return;
|
|
23691
23785
|
await migrate.stripGlobalMcp();
|
|
@@ -23694,17 +23788,24 @@ async function wireRepos({ url: url2, token, dirs }) {
|
|
|
23694
23788
|
for (const dir of dirs) {
|
|
23695
23789
|
const here2 = isPlainObject3(projects[dir]) ? { ...projects[dir] } : {};
|
|
23696
23790
|
const servers = isPlainObject3(here2.mcpServers) ? { ...here2.mcpServers } : {};
|
|
23697
|
-
|
|
23791
|
+
delete servers.craftspace;
|
|
23792
|
+
projects[dir] = {
|
|
23793
|
+
...here2,
|
|
23794
|
+
hasTrustDialogAccepted: true,
|
|
23795
|
+
mcpServers: { ...servers, [serverNameFor(organization)]: server }
|
|
23796
|
+
};
|
|
23698
23797
|
}
|
|
23699
23798
|
});
|
|
23700
23799
|
}
|
|
23800
|
+
function serverNameFor(organization) {
|
|
23801
|
+
return `craftspace-${organization}`;
|
|
23802
|
+
}
|
|
23701
23803
|
async function unwireRepos() {
|
|
23702
23804
|
await editClaudeConfig((projects) => {
|
|
23703
23805
|
for (const [dir, found] of Object.entries(projects)) {
|
|
23704
23806
|
if (!isPlainObject3(found) || !isPlainObject3(found.mcpServers))
|
|
23705
23807
|
continue;
|
|
23706
|
-
const servers =
|
|
23707
|
-
delete servers.craftspace;
|
|
23808
|
+
const servers = Object.fromEntries(Object.entries(found.mcpServers).filter(([name]) => name !== "craftspace" && !name.startsWith("craftspace-")));
|
|
23708
23809
|
projects[dir] = { ...found, mcpServers: servers };
|
|
23709
23810
|
}
|
|
23710
23811
|
});
|
|
@@ -23724,7 +23825,7 @@ async function editClaudeConfig(edit) {
|
|
|
23724
23825
|
async function writeAgentConfigs({ writes }) {
|
|
23725
23826
|
if (writes.length === 0)
|
|
23726
23827
|
return;
|
|
23727
|
-
const owned = [
|
|
23828
|
+
const owned = [];
|
|
23728
23829
|
for (const write of writes) {
|
|
23729
23830
|
const target = expand(write.path);
|
|
23730
23831
|
await mkdir3(path5.dirname(target), { recursive: true });
|
|
@@ -23736,10 +23837,10 @@ async function writeAgentConfigs({ writes }) {
|
|
|
23736
23837
|
`, { mode: 384 });
|
|
23737
23838
|
owned.push(target);
|
|
23738
23839
|
}
|
|
23739
|
-
await writeFile2(ownedPath(), JSON.stringify({ paths: [...new Set(owned)] }, null, 2));
|
|
23840
|
+
await writeFile2(ownedPath(machine.root()), JSON.stringify({ paths: [...new Set(owned)] }, null, 2));
|
|
23740
23841
|
}
|
|
23741
|
-
async function readOwned() {
|
|
23742
|
-
const raw = await readFile3(ownedPath(), "utf8").catch(() => null);
|
|
23842
|
+
async function readOwned(home) {
|
|
23843
|
+
const raw = await readFile3(ownedPath(home), "utf8").catch(() => null);
|
|
23743
23844
|
if (raw === null)
|
|
23744
23845
|
return [];
|
|
23745
23846
|
const parsed = OwnedSchema.safeParse(JSON.parse(raw));
|
|
@@ -23756,9 +23857,8 @@ function mergeInto(current, incoming) {
|
|
|
23756
23857
|
function isPlainObject3(value) {
|
|
23757
23858
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23758
23859
|
}
|
|
23759
|
-
async function removeAgentConfigs() {
|
|
23760
|
-
await
|
|
23761
|
-
for (const target of await readOwned()) {
|
|
23860
|
+
async function removeAgentConfigs(home) {
|
|
23861
|
+
for (const target of await readOwned(home)) {
|
|
23762
23862
|
const current = await readFile3(target, "utf8").catch(() => null);
|
|
23763
23863
|
if (current === null)
|
|
23764
23864
|
continue;
|
|
@@ -23856,17 +23956,17 @@ function entryPath() {
|
|
|
23856
23956
|
function expand(target) {
|
|
23857
23957
|
return target.startsWith("~/") ? path5.join(os6.homedir(), target.slice(2)) : target;
|
|
23858
23958
|
}
|
|
23859
|
-
function configPath() {
|
|
23860
|
-
return path5.join(
|
|
23959
|
+
function configPath(home) {
|
|
23960
|
+
return path5.join(home, "config.json");
|
|
23861
23961
|
}
|
|
23862
|
-
function tokenPath() {
|
|
23863
|
-
return path5.join(
|
|
23962
|
+
function tokenPath(home) {
|
|
23963
|
+
return path5.join(home, "token");
|
|
23864
23964
|
}
|
|
23865
|
-
function ownedPath() {
|
|
23866
|
-
return path5.join(
|
|
23965
|
+
function ownedPath(home) {
|
|
23966
|
+
return path5.join(home, "owned.json");
|
|
23867
23967
|
}
|
|
23868
|
-
function environmentPath() {
|
|
23869
|
-
return path5.join(
|
|
23968
|
+
function environmentPath(home) {
|
|
23969
|
+
return path5.join(home, "environment.json");
|
|
23870
23970
|
}
|
|
23871
23971
|
function load2() {
|
|
23872
23972
|
const cores = Math.max(1, os6.cpus().length);
|
|
@@ -23885,7 +23985,12 @@ function nextDelayMs(failures, busy = false) {
|
|
|
23885
23985
|
function messageOf(error51) {
|
|
23886
23986
|
return error51 instanceof Error ? error51.message : String(error51);
|
|
23887
23987
|
}
|
|
23888
|
-
var ConfigSchema = external_exports.object({
|
|
23988
|
+
var ConfigSchema = external_exports.object({
|
|
23989
|
+
url: external_exports.string(),
|
|
23990
|
+
machineId: external_exports.string(),
|
|
23991
|
+
organization: external_exports.string().optional(),
|
|
23992
|
+
account: external_exports.string().optional()
|
|
23993
|
+
});
|
|
23889
23994
|
var BuiltSchema = external_exports.object({
|
|
23890
23995
|
templateId: external_exports.string(),
|
|
23891
23996
|
results: external_exports.array(WorkstationStepResultSchema),
|
|
@@ -23894,6 +23999,7 @@ var BuiltSchema = external_exports.object({
|
|
|
23894
23999
|
});
|
|
23895
24000
|
var OwnedSchema = external_exports.object({ paths: external_exports.array(external_exports.string()) });
|
|
23896
24001
|
var CLAUDE_FILE = ".claude.json";
|
|
24002
|
+
var SEAT_FILES = ["config.json", "token", "environment.json", "owned.json"];
|
|
23897
24003
|
var SERVICE_NAME = "craftspace";
|
|
23898
24004
|
var LAUNCH_LABEL = "app.craftspace.machine";
|
|
23899
24005
|
var REQUEST_TIMEOUT_MS2 = 15e3;
|
|
@@ -23924,8 +24030,8 @@ var DEFAULT_URL = process.env.CRAFTSPACE_URL ?? "https://craftspace.app";
|
|
|
23924
24030
|
var PROBE_EVERY_MS = 3e5;
|
|
23925
24031
|
var REBUILD_AFTER_MS = 216e5;
|
|
23926
24032
|
var updateWanted = null;
|
|
23927
|
-
var probed =
|
|
23928
|
-
var building =
|
|
24033
|
+
var probed = /* @__PURE__ */ new Map();
|
|
24034
|
+
var building = /* @__PURE__ */ new Set();
|
|
23929
24035
|
var reports = /* @__PURE__ */ new Map();
|
|
23930
24036
|
var running = /* @__PURE__ */ new Set();
|
|
23931
24037
|
|
|
@@ -23935,12 +24041,13 @@ var program2 = new Command();
|
|
|
23935
24041
|
program2.enablePositionalOptions().name("cs").description("Sign this machine in to your team, so every agent on it reads the same brain.").version(CLI_VERSION).showHelpAfterError();
|
|
23936
24042
|
program2.command("login").description("sign this machine in").option("--token <token>", "the login token from your team", process.env.CRAFTSPACE_TOKEN).option("--url <url>", "your control plane, remembered after the first login").option("--no-install", "do not install anything this machine is missing").action(async ({ token, url: url2, install }) => {
|
|
23937
24043
|
const where = await machine.controlPlane(url2);
|
|
23938
|
-
const signed = token === void 0 ?
|
|
24044
|
+
const signed = token === void 0 ? await machine.signIn({ url: where, why: "Signing this machine in.", install }) : await machine.login({ token, url: where, install });
|
|
23939
24045
|
const daemon = await machine.daemon();
|
|
23940
|
-
|
|
23941
|
-
ui.say(ui.
|
|
23942
|
-
|
|
23943
|
-
ui.say(ui.field("
|
|
24046
|
+
const seats = await machine.seats();
|
|
24047
|
+
ui.say(ui.heading(`Signed in as ${signed.machine.ownerName}`));
|
|
24048
|
+
ui.say(ui.field("Machine", `${ui.name(signed.machine.name)} ${ui.dim(machineSpecs(signed.machine))}`));
|
|
24049
|
+
ui.say(ui.field("Org", `${ui.name(signed.organization)}${orgTally(seats.length)}`));
|
|
24050
|
+
ui.say(ui.field("Work dir", await machine.workDir(signed.organization)));
|
|
23944
24051
|
ui.say(ui.field("MCP wired", "into every repo cloned there, for Claude Code"));
|
|
23945
24052
|
ui.say(ui.field("Daemon", daemon.ok ? daemon.text : ui.attention(daemon.text)));
|
|
23946
24053
|
ui.say(`
|
|
@@ -23951,14 +24058,17 @@ program2.command("run").description("pick a workstation and open a terminal on i
|
|
|
23951
24058
|
process.exitCode = await machine.run();
|
|
23952
24059
|
});
|
|
23953
24060
|
program2.command("status").description("what this machine is and what it is doing").action(async () => {
|
|
23954
|
-
const { machine: found }
|
|
23955
|
-
|
|
23956
|
-
|
|
23957
|
-
|
|
23958
|
-
|
|
23959
|
-
|
|
23960
|
-
|
|
23961
|
-
|
|
24061
|
+
for (const { machine: found, organization } of await machine.status()) {
|
|
24062
|
+
const since = found.lastBeatAt === null ? "never" : `${secondsSince(found.lastBeatAt)}s ago`;
|
|
24063
|
+
ui.say(ui.heading(`${found.name} ${found.state} ${ui.dim(organization)}`));
|
|
24064
|
+
ui.say(ui.field("Owner", found.ownerName));
|
|
24065
|
+
ui.say(ui.field("Machine", ui.dim(machineSpecs(found))));
|
|
24066
|
+
ui.say(ui.field("CLI", `${found.cli}, beat ${since}`));
|
|
24067
|
+
ui.say(ui.field("Work dir", found.workDir ?? "set on the first beat"));
|
|
24068
|
+
for (const session of found.sessions) {
|
|
24069
|
+
ui.say(ui.field("Session", `${session.state} ${session.title ?? session.id}`));
|
|
24070
|
+
}
|
|
24071
|
+
}
|
|
23962
24072
|
ui.say("\n");
|
|
23963
24073
|
});
|
|
23964
24074
|
program2.command("logout").description("leave the team, and undo what login wrote").action(async () => {
|
|
@@ -23966,6 +24076,9 @@ program2.command("logout").description("leave the team, and undo what login wrot
|
|
|
23966
24076
|
ui.say(ui.ok("Left the team. Nothing of ours is running here."));
|
|
23967
24077
|
});
|
|
23968
24078
|
program2.command("daemon", { hidden: true }).action(async () => machine.beatForever());
|
|
24079
|
+
function orgTally(seats) {
|
|
24080
|
+
return seats < 2 ? "" : ui.dim(` \xB7 ${seats} orgs on this box`);
|
|
24081
|
+
}
|
|
23969
24082
|
try {
|
|
23970
24083
|
await program2.parseAsync(process.argv);
|
|
23971
24084
|
} catch (error51) {
|