forge-cw 0.1.7 → 0.1.8

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 CHANGED
@@ -1526,9 +1526,9 @@ var init_login_manager = __esm({
1526
1526
  cwBin;
1527
1527
  spawn;
1528
1528
  entries = /* @__PURE__ */ new Map();
1529
- constructor(cwBin, spawn7 = spawnInPty) {
1529
+ constructor(cwBin, spawn8 = spawnInPty) {
1530
1530
  this.cwBin = cwBin;
1531
- this.spawn = spawn7;
1531
+ this.spawn = spawn8;
1532
1532
  }
1533
1533
  start(account, harness) {
1534
1534
  const key = `${account}::${harness}`;
@@ -3932,20 +3932,375 @@ var init_liveframe_routes = __esm({
3932
3932
  }
3933
3933
  });
3934
3934
 
3935
- // ../core/dist/server.js
3935
+ // ../core/dist/cw-install.js
3936
+ import { execFile as execFile5 } from "node:child_process";
3937
+ import { createHash as createHash2 } from "node:crypto";
3938
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync11, readFileSync as readFileSync9, statSync as statSync3, writeFileSync as writeFileSync4 } from "node:fs";
3939
+ import { delimiter as delimiter2, join as join12 } from "node:path";
3940
+ function readCwVersion(file) {
3941
+ try {
3942
+ const match = /^CW_VERSION="([^"]+)"/m.exec(readFileSync9(file, "utf-8"));
3943
+ return match ? match[1] : null;
3944
+ } catch {
3945
+ return null;
3946
+ }
3947
+ }
3948
+ function compareVersions(a, b) {
3949
+ const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
3950
+ const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
3951
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
3952
+ const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
3953
+ if (diff !== 0)
3954
+ return diff;
3955
+ }
3956
+ return 0;
3957
+ }
3958
+ function readCwBundle(dir) {
3959
+ try {
3960
+ const { ref } = JSON.parse(readFileSync9(join12(dir, ".bundle.json"), "utf-8"));
3961
+ const version = readCwVersion(join12(dir, "cw"));
3962
+ if (!ref || !version || !existsSync11(join12(dir, "install.sh")))
3963
+ return null;
3964
+ return { dir, version, ref };
3965
+ } catch {
3966
+ return null;
3967
+ }
3968
+ }
3969
+ function decideCwInstall({ bundle, installed, marker, cwOnPath, skip }) {
3970
+ if (skip)
3971
+ return { action: "skip", reason: "FORGE_SKIP_CW_INSTALL is set" };
3972
+ if (!bundle)
3973
+ return { action: "skip", reason: "this Forge carries no CW" };
3974
+ if (!installed) {
3975
+ return cwOnPath ? { action: "skip", reason: "using the cw found on PATH" } : { action: "install", reason: "CW is not installed" };
3976
+ }
3977
+ if (!installed.version)
3978
+ return { action: "skip", reason: "cannot read the installed CW version" };
3979
+ const newer = compareVersions(bundle.version, installed.version);
3980
+ const ownedByForge = marker !== null && marker.sha256 === installed.sha256;
3981
+ if (ownedByForge) {
3982
+ if (marker.ref === bundle.ref)
3983
+ return { action: "skip", reason: `CW ${installed.version} is current` };
3984
+ return newer >= 0 ? { action: "update", reason: `CW ${installed.version} \u2192 ${bundle.version}` } : { action: "skip", reason: `installed CW ${installed.version} is newer than ${bundle.version}` };
3985
+ }
3986
+ return newer > 0 ? { action: "update", reason: `CW ${installed.version} \u2192 ${bundle.version}` } : { action: "skip", reason: `CW ${installed.version} is current` };
3987
+ }
3988
+ function findOnPath(name, pathVar, excludeDir) {
3989
+ for (const dir of (pathVar ?? "").split(delimiter2)) {
3990
+ if (!dir || dir === excludeDir)
3991
+ continue;
3992
+ const candidate = join12(dir, name);
3993
+ try {
3994
+ if (!statSync3(candidate).isFile())
3995
+ continue;
3996
+ accessSync2(candidate, constants2.X_OK);
3997
+ return candidate;
3998
+ } catch {
3999
+ }
4000
+ }
4001
+ return null;
4002
+ }
4003
+ function pathWithCw(cwHome, pathVar) {
4004
+ const bin = join12(cwHome, "bin");
4005
+ const rest = (pathVar ?? "").split(delimiter2).filter((dir) => dir && dir !== bin);
4006
+ return [bin, ...rest].join(delimiter2);
4007
+ }
4008
+ function readMarker(cwHome) {
4009
+ try {
4010
+ return JSON.parse(readFileSync9(join12(cwHome, MARKER_FILE), "utf-8"));
4011
+ } catch {
4012
+ return null;
4013
+ }
4014
+ }
4015
+ function failureMessage2(output) {
4016
+ const lines2 = output.replace(ANSI_RE3, "").split("\n").map((l) => l.trim()).filter(Boolean);
4017
+ const errors = lines2.filter((l) => l.startsWith("[\u2717]")).map((l) => l.slice(3).trim());
4018
+ return (errors.length > 0 ? errors : lines2.slice(-3)).join(" ") || "the CW installer failed";
4019
+ }
4020
+ async function ensureCw(options) {
4021
+ const { cwHome, bundleDir } = options;
4022
+ const env = options.env ?? process.env;
4023
+ const run = options.run ?? runInstall;
4024
+ const bin = join12(cwHome, "bin", "cw");
4025
+ const bundle = readCwBundle(bundleDir);
4026
+ const installed = existsSync11(bin) ? { version: readCwVersion(bin), sha256: sha256Of(bin) } : null;
4027
+ const decision = decideCwInstall({
4028
+ bundle,
4029
+ installed,
4030
+ marker: readMarker(cwHome),
4031
+ cwOnPath: findOnPath("cw", env.PATH, join12(cwHome, "bin")) !== null,
4032
+ skip: env.FORGE_SKIP_CW_INSTALL === "1"
4033
+ });
4034
+ if (decision.action === "skip" || !bundle)
4035
+ return { action: "skipped", message: decision.reason };
4036
+ const { code, output } = await run(join12(bundle.dir, "install.sh"), ["--no-shell"], { ...env, CW_HOME: cwHome });
4037
+ if (code !== 0 || !existsSync11(bin))
4038
+ return { action: "failed", message: failureMessage2(output) };
4039
+ const marker = { version: bundle.version, ref: bundle.ref, sha256: sha256Of(bin) };
4040
+ writeFileSync4(join12(cwHome, MARKER_FILE), JSON.stringify(marker, null, 2) + "\n");
4041
+ return {
4042
+ action: decision.action === "install" ? "installed" : "updated",
4043
+ version: bundle.version,
4044
+ message: decision.reason
4045
+ };
4046
+ }
4047
+ var MARKER_FILE, INSTALL_TIMEOUT_MS2, ANSI_RE3, sha256Of, runInstall;
4048
+ var init_cw_install = __esm({
4049
+ "../core/dist/cw-install.js"() {
4050
+ "use strict";
4051
+ MARKER_FILE = ".forge-cw.json";
4052
+ INSTALL_TIMEOUT_MS2 = 12e4;
4053
+ ANSI_RE3 = /\u001b\[[0-9;?]*[A-Za-z]/g;
4054
+ sha256Of = (file) => createHash2("sha256").update(readFileSync9(file)).digest("hex");
4055
+ runInstall = (script, args, env) => new Promise((resolve5) => {
4056
+ execFile5("bash", [script, ...args], { env, timeout: INSTALL_TIMEOUT_MS2, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
4057
+ const output = `${String(stdout)}${String(stderr)}`;
4058
+ if (!err)
4059
+ return resolve5({ code: 0, output });
4060
+ const code = err.code;
4061
+ resolve5({ code: typeof code === "number" ? code : 1, output: output || err.message });
4062
+ });
4063
+ });
4064
+ }
4065
+ });
4066
+
4067
+ // ../core/dist/kioku.js
4068
+ import { spawn as spawn7 } from "node:child_process";
4069
+ import { existsSync as existsSync12 } from "node:fs";
4070
+ import { homedir as homedir5 } from "node:os";
4071
+ import { join as join13 } from "node:path";
4072
+ import { createInterface } from "node:readline";
4073
+ function resolveKiokuBin(pathVar = process.env.PATH, home = homedir5()) {
4074
+ const fallback = join13(home, ".local", "bin", "kioku");
4075
+ return findOnPath("kioku", pathVar) ?? (existsSync12(fallback) ? fallback : "kioku");
4076
+ }
4077
+ function parseJson(text) {
4078
+ try {
4079
+ return JSON.parse(text);
4080
+ } catch {
4081
+ return null;
4082
+ }
4083
+ }
4084
+ async function mapLimit(items, limit, fn) {
4085
+ const out = new Array(items.length);
4086
+ let next = 0;
4087
+ const worker = async () => {
4088
+ while (next < items.length) {
4089
+ const i = next++;
4090
+ out[i] = await fn(items[i]);
4091
+ }
4092
+ };
4093
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
4094
+ return out;
4095
+ }
4096
+ var KiokuError, NOT_INSTALLED, START_TIMEOUT_MS, CACHE_MS2, ACTIVITY_SINCE, ACTIVITY_LIMIT, PARALLEL, MISSING, brainName, Kioku;
4097
+ var init_kioku = __esm({
4098
+ "../core/dist/kioku.js"() {
4099
+ "use strict";
4100
+ init_task_review();
4101
+ init_cw_install();
4102
+ init_output();
4103
+ KiokuError = class extends Error {
4104
+ status;
4105
+ constructor(message, status = 502) {
4106
+ super(message);
4107
+ this.status = status;
4108
+ }
4109
+ };
4110
+ NOT_INSTALLED = "kioku is not installed on this machine";
4111
+ START_TIMEOUT_MS = 2e4;
4112
+ CACHE_MS2 = 5 * 6e4;
4113
+ ACTIVITY_SINCE = "2w";
4114
+ ACTIVITY_LIMIT = 40;
4115
+ PARALLEL = 6;
4116
+ MISSING = { installed: false, version: null, brains: [], projects: {}, activity: [] };
4117
+ brainName = (b) => b.name ?? b.key;
4118
+ Kioku = class {
4119
+ run;
4120
+ startTimeoutMs;
4121
+ projects;
4122
+ now;
4123
+ binPath;
4124
+ viewer = null;
4125
+ snapshot = null;
4126
+ constructor(options = {}) {
4127
+ this.binPath = options.bin;
4128
+ this.run = options.run ?? createRunner();
4129
+ this.startTimeoutMs = options.startTimeoutMs ?? START_TIMEOUT_MS;
4130
+ this.projects = options.projects ?? (() => ({}));
4131
+ this.now = options.now ?? Date.now;
4132
+ }
4133
+ // resolved on first use, so a Forge that never opens Memory does not search PATH
4134
+ get bin() {
4135
+ return this.binPath ??= resolveKiokuBin();
4136
+ }
4137
+ async status(fresh = false) {
4138
+ const previous = this.snapshot;
4139
+ if (fresh || !previous || this.now() - previous.at > CACHE_MS2) {
4140
+ const value = this.read();
4141
+ this.snapshot = { at: this.now(), value };
4142
+ value.catch(() => {
4143
+ if (this.snapshot?.value === value)
4144
+ this.snapshot = previous;
4145
+ });
4146
+ }
4147
+ const pending = fresh || !previous ? this.snapshot.value : previous.value;
4148
+ const [snapshot, viewer] = await Promise.all([pending, this.viewer?.catch(() => null) ?? null]);
4149
+ return { ...snapshot, viewer: viewer?.address ?? null };
4150
+ }
4151
+ async json(args, cwd) {
4152
+ const result2 = await this.run(this.bin, [...args, "--json"], cwd);
4153
+ return result2.code === 0 ? parseJson(result2.stdout) : null;
4154
+ }
4155
+ async read() {
4156
+ const [version, list] = await Promise.all([
4157
+ this.run(this.bin, ["--version"], homedir5()),
4158
+ this.json(["brain", "list"], homedir5())
4159
+ ]);
4160
+ if (version.code !== 0)
4161
+ return MISSING;
4162
+ const listed = list?.brains ?? [];
4163
+ const entries = Object.entries(this.projects()).filter(([, p]) => existsSync12(p.path));
4164
+ const readBrain = async (b) => {
4165
+ if (!b.onDisk)
4166
+ return { kinds: {}, writes: [] };
4167
+ const [ns, log] = await Promise.all([
4168
+ this.json(["namespace", "list", "--brain", b.key], b.folder),
4169
+ this.json(["brain", "log", "--since", ACTIVITY_SINCE, "--brain", b.key], b.folder)
4170
+ ]);
4171
+ const kinds = {};
4172
+ for (const n of ns?.brains?.find((x) => x.brain === brainName(b))?.namespaces ?? []) {
4173
+ for (const [kind, count] of Object.entries(n.pages))
4174
+ kinds[kind] = (kinds[kind] ?? 0) + count;
4175
+ }
4176
+ return { kinds, writes: log?.writes ?? [] };
4177
+ };
4178
+ const readChain = async (path) => (await this.json(["brain", "list"], path))?.chain ?? [];
4179
+ const [perBrain, chains] = await Promise.all([
4180
+ mapLimit(listed, PARALLEL / 2, readBrain),
4181
+ mapLimit(entries, PARALLEL / 2, ([, p]) => readChain(p.path))
4182
+ ]);
4183
+ const activity = perBrain.flatMap((p) => p.writes).sort((x, y) => y.date.localeCompare(x.date));
4184
+ const brains = listed.map((b, i) => {
4185
+ const { kinds } = perBrain[i];
4186
+ const name = brainName(b);
4187
+ return {
4188
+ ...b,
4189
+ pages: Object.values(kinds).reduce((sum, n) => sum + n, 0),
4190
+ kinds,
4191
+ touched: activity.find((w) => w.brain === name)?.date ?? null
4192
+ };
4193
+ });
4194
+ const projects = Object.fromEntries(entries.map(([name], i) => [name, chains[i].map(({ name: name2, tier }) => ({ name: name2, tier }))]));
4195
+ return {
4196
+ installed: true,
4197
+ version: parseJson(version.stdout)?.version ?? null,
4198
+ brains,
4199
+ projects,
4200
+ activity: activity.slice(0, ACTIVITY_LIMIT).map(({ verb, title, path, date, author, brain }) => ({ verb, title, path, date, author, brain }))
4201
+ };
4202
+ }
4203
+ // one viewer at a time; a second call reuses the running one
4204
+ async openViewer() {
4205
+ this.viewer ??= this.startViewer();
4206
+ return (await this.viewer).address;
4207
+ }
4208
+ startViewer() {
4209
+ const started = new Promise((resolve5, reject) => {
4210
+ const child = spawn7(this.bin, ["web", "--json", "--no-open"], { cwd: homedir5(), stdio: ["ignore", "pipe", "pipe"] });
4211
+ const lines2 = createInterface({ input: child.stdout });
4212
+ let stderr = "";
4213
+ let settled = false;
4214
+ const finish = () => {
4215
+ settled = true;
4216
+ clearTimeout(timer);
4217
+ lines2.close();
4218
+ child.stdout?.resume();
4219
+ };
4220
+ const fail = (err) => {
4221
+ if (settled)
4222
+ return;
4223
+ finish();
4224
+ child.kill();
4225
+ reject(err);
4226
+ };
4227
+ const timer = setTimeout(() => fail(new KiokuError("kioku web did not start in time")), this.startTimeoutMs);
4228
+ child.stderr?.on("data", (chunk) => {
4229
+ stderr = (stderr + chunk.toString()).slice(-4096);
4230
+ });
4231
+ lines2.on("line", (line) => {
4232
+ const event = parseJson(line);
4233
+ if (settled || event?.event !== "listening" || !event.address?.startsWith("http://127.0.0.1:"))
4234
+ return;
4235
+ finish();
4236
+ resolve5({ child, address: event.address });
4237
+ });
4238
+ child.on("error", (err) => {
4239
+ const missing = err.code === "ENOENT";
4240
+ fail(new KiokuError(missing ? NOT_INSTALLED : err.message, missing ? 404 : 502));
4241
+ });
4242
+ child.on("exit", (code) => {
4243
+ if (this.viewer === started)
4244
+ this.viewer = null;
4245
+ fail(new KiokuError(tailOutput(stderr, 1) || `kioku web exited with code ${code}`));
4246
+ });
4247
+ });
4248
+ started.catch(() => {
4249
+ if (this.viewer === started)
4250
+ this.viewer = null;
4251
+ });
4252
+ return started;
4253
+ }
4254
+ stopViewer() {
4255
+ void this.viewer?.then((v) => v.child.kill(), () => {
4256
+ });
4257
+ this.viewer = null;
4258
+ }
4259
+ dispose() {
4260
+ this.stopViewer();
4261
+ }
4262
+ };
4263
+ }
4264
+ });
4265
+
4266
+ // ../core/dist/kioku-routes.js
3936
4267
  import { Hono as Hono4 } from "hono";
4268
+ function kiokuRoutes(kioku = new Kioku()) {
4269
+ const app = new Hono4();
4270
+ app.onError((err, c) => {
4271
+ if (err instanceof KiokuError)
4272
+ return c.json({ error: err.message }, err.status);
4273
+ throw err;
4274
+ });
4275
+ app.get("/status", async (c) => c.json(await kioku.status(c.req.query("fresh") === "1")));
4276
+ app.post("/viewer", async (c) => c.json({ address: await kioku.openViewer() }));
4277
+ app.delete("/viewer", (c) => {
4278
+ kioku.stopViewer();
4279
+ return c.json({ ok: true });
4280
+ });
4281
+ return app;
4282
+ }
4283
+ var init_kioku_routes = __esm({
4284
+ "../core/dist/kioku-routes.js"() {
4285
+ "use strict";
4286
+ init_kioku();
4287
+ }
4288
+ });
4289
+
4290
+ // ../core/dist/server.js
4291
+ import { Hono as Hono5 } from "hono";
3937
4292
  import { cors } from "hono/cors";
3938
- import { join as join12, basename as basename3, resolve as resolve4 } from "node:path";
3939
- import { readdirSync as readdirSync6, existsSync as existsSync11 } from "node:fs";
4293
+ import { join as join14, basename as basename3, resolve as resolve4 } from "node:path";
4294
+ import { readdirSync as readdirSync6, existsSync as existsSync13 } from "node:fs";
3940
4295
  function createForgeServer(options) {
3941
4296
  const { dataDir, db: externalDb, authToken, localOnly = true } = options;
3942
- const dbPath = join12(dataDir, "forge.db");
3943
- const modulesDir = join12(dataDir, "modules");
4297
+ const dbPath = join14(dataDir, "forge.db");
4298
+ const modulesDir = join14(dataDir, "modules");
3944
4299
  const db = externalDb ?? new ForgeDB(dbPath);
3945
4300
  const loader = new ModuleLoader(modulesDir, options.bundledModulesDir);
3946
4301
  const runner = new ActionRunner();
3947
4302
  loader.discover();
3948
- const app = new Hono4();
4303
+ const app = new Hono5();
3949
4304
  app.use("*", localOnly ? localOriginGuard() : cors());
3950
4305
  if (authToken) {
3951
4306
  app.use("/api/*", bearerAuth(authToken));
@@ -3973,9 +4328,11 @@ function createForgeServer(options) {
3973
4328
  }
3974
4329
  });
3975
4330
  const ptyManager = new PTYManager(states);
3976
- const generalSessions = new GeneralSessions(join12(dataDir, "general-sessions.json"));
4331
+ const generalSessions = new GeneralSessions(join14(dataDir, "general-sessions.json"));
3977
4332
  const terminalWss = createTerminalWss(ptyManager, cwReader, { localOnly, general: generalSessions });
3978
4333
  app.route("/api/liveframe", liveframeRoutes());
4334
+ const kioku = new Kioku({ projects: () => cwReader.getProjects() });
4335
+ app.route("/api/kioku", kiokuRoutes(kioku));
3979
4336
  app.get("/api/cw/session-states", (c) => c.json({ classifier: states.remoteName ?? "local", shadow: states.isShadow, states: states.snapshot() }));
3980
4337
  app.post("/api/cw/terminal/kill", async (c) => {
3981
4338
  const { project, sessionDir } = await c.req.json();
@@ -4131,21 +4488,21 @@ data: ${JSON.stringify(data)}
4131
4488
  if (!dir.startsWith(home)) {
4132
4489
  return c.json({ error: "Access denied: path must be within home directory" }, 403);
4133
4490
  }
4134
- if (!existsSync11(dir)) {
4491
+ if (!existsSync13(dir)) {
4135
4492
  return c.json({ error: "Directory not found" }, 404);
4136
4493
  }
4137
4494
  try {
4138
4495
  const entries = readdirSync6(dir, { withFileTypes: true });
4139
4496
  const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({
4140
4497
  name: e.name,
4141
- path: join12(dir, e.name),
4142
- hasPackageJson: existsSync11(join12(dir, e.name, "package.json")),
4143
- hasGit: existsSync11(join12(dir, e.name, ".git"))
4498
+ path: join14(dir, e.name),
4499
+ hasPackageJson: existsSync13(join14(dir, e.name, "package.json")),
4500
+ hasGit: existsSync13(join14(dir, e.name, ".git"))
4144
4501
  })).sort((a, b) => a.name.localeCompare(b.name));
4145
4502
  return c.json({
4146
4503
  current: dir,
4147
4504
  name: basename3(dir),
4148
- parent: join12(dir, ".."),
4505
+ parent: join14(dir, ".."),
4149
4506
  directories: dirs
4150
4507
  });
4151
4508
  } catch {
@@ -4163,6 +4520,7 @@ data: ${JSON.stringify(data)}
4163
4520
  ptyManager.dispose();
4164
4521
  states.dispose();
4165
4522
  loginManager.dispose();
4523
+ kioku.dispose();
4166
4524
  db.close();
4167
4525
  }
4168
4526
  };
@@ -4186,6 +4544,8 @@ var init_server = __esm({
4186
4544
  init_login_manager();
4187
4545
  init_general_sessions();
4188
4546
  init_liveframe_routes();
4547
+ init_kioku_routes();
4548
+ init_kioku();
4189
4549
  }
4190
4550
  });
4191
4551
 
@@ -4308,8 +4668,8 @@ async function createDatabase(opts) {
4308
4668
  await db.migrate();
4309
4669
  return db;
4310
4670
  }
4311
- const { join: join16 } = await import("node:path");
4312
- return new ForgeDB(join16(opts.dataDir, "forge.db"));
4671
+ const { join: join17 } = await import("node:path");
4672
+ return new ForgeDB(join17(opts.dataDir, "forge.db"));
4313
4673
  }
4314
4674
  var init_db_factory = __esm({
4315
4675
  "../core/dist/db-factory.js"() {
@@ -4319,22 +4679,22 @@ var init_db_factory = __esm({
4319
4679
  });
4320
4680
 
4321
4681
  // ../core/dist/init.js
4322
- import { existsSync as existsSync12, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
4323
- import { join as join13 } from "node:path";
4324
- import { homedir as homedir5 } from "node:os";
4682
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
4683
+ import { join as join15 } from "node:path";
4684
+ import { homedir as homedir6 } from "node:os";
4325
4685
  function getForgeDir() {
4326
- return join13(homedir5(), ".forge");
4686
+ return join15(homedir6(), ".forge");
4327
4687
  }
4328
4688
  function ensureForgeDir() {
4329
4689
  const forgeDir = getForgeDir();
4330
- if (existsSync12(forgeDir)) {
4690
+ if (existsSync14(forgeDir)) {
4331
4691
  return { forgeDir, created: false };
4332
4692
  }
4333
4693
  mkdirSync5(forgeDir, { recursive: true });
4334
4694
  for (const d of FORGE_SUBDIRS) {
4335
- mkdirSync5(join13(forgeDir, d), { recursive: true });
4695
+ mkdirSync5(join15(forgeDir, d), { recursive: true });
4336
4696
  }
4337
- writeFileSync4(join13(forgeDir, "config.json"), JSON.stringify({
4697
+ writeFileSync5(join15(forgeDir, "config.json"), JSON.stringify({
4338
4698
  port: 3e3,
4339
4699
  theme: "dark",
4340
4700
  openBrowser: true,
@@ -4350,138 +4710,6 @@ var init_init = __esm({
4350
4710
  }
4351
4711
  });
4352
4712
 
4353
- // ../core/dist/cw-install.js
4354
- import { execFile as execFile5 } from "node:child_process";
4355
- import { createHash as createHash2 } from "node:crypto";
4356
- import { accessSync as accessSync2, constants as constants2, existsSync as existsSync13, readFileSync as readFileSync9, statSync as statSync3, writeFileSync as writeFileSync5 } from "node:fs";
4357
- import { delimiter as delimiter2, join as join14 } from "node:path";
4358
- function readCwVersion(file) {
4359
- try {
4360
- const match = /^CW_VERSION="([^"]+)"/m.exec(readFileSync9(file, "utf-8"));
4361
- return match ? match[1] : null;
4362
- } catch {
4363
- return null;
4364
- }
4365
- }
4366
- function compareVersions(a, b) {
4367
- const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
4368
- const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
4369
- for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
4370
- const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
4371
- if (diff !== 0)
4372
- return diff;
4373
- }
4374
- return 0;
4375
- }
4376
- function readCwBundle(dir) {
4377
- try {
4378
- const { ref } = JSON.parse(readFileSync9(join14(dir, ".bundle.json"), "utf-8"));
4379
- const version = readCwVersion(join14(dir, "cw"));
4380
- if (!ref || !version || !existsSync13(join14(dir, "install.sh")))
4381
- return null;
4382
- return { dir, version, ref };
4383
- } catch {
4384
- return null;
4385
- }
4386
- }
4387
- function decideCwInstall({ bundle, installed, marker, cwOnPath, skip }) {
4388
- if (skip)
4389
- return { action: "skip", reason: "FORGE_SKIP_CW_INSTALL is set" };
4390
- if (!bundle)
4391
- return { action: "skip", reason: "this Forge carries no CW" };
4392
- if (!installed) {
4393
- return cwOnPath ? { action: "skip", reason: "using the cw found on PATH" } : { action: "install", reason: "CW is not installed" };
4394
- }
4395
- if (!installed.version)
4396
- return { action: "skip", reason: "cannot read the installed CW version" };
4397
- const newer = compareVersions(bundle.version, installed.version);
4398
- const ownedByForge = marker !== null && marker.sha256 === installed.sha256;
4399
- if (ownedByForge) {
4400
- if (marker.ref === bundle.ref)
4401
- return { action: "skip", reason: `CW ${installed.version} is current` };
4402
- return newer >= 0 ? { action: "update", reason: `CW ${installed.version} \u2192 ${bundle.version}` } : { action: "skip", reason: `installed CW ${installed.version} is newer than ${bundle.version}` };
4403
- }
4404
- return newer > 0 ? { action: "update", reason: `CW ${installed.version} \u2192 ${bundle.version}` } : { action: "skip", reason: `CW ${installed.version} is current` };
4405
- }
4406
- function findOnPath(name, pathVar, excludeDir) {
4407
- for (const dir of (pathVar ?? "").split(delimiter2)) {
4408
- if (!dir || dir === excludeDir)
4409
- continue;
4410
- const candidate = join14(dir, name);
4411
- try {
4412
- if (!statSync3(candidate).isFile())
4413
- continue;
4414
- accessSync2(candidate, constants2.X_OK);
4415
- return candidate;
4416
- } catch {
4417
- }
4418
- }
4419
- return null;
4420
- }
4421
- function pathWithCw(cwHome, pathVar) {
4422
- const bin = join14(cwHome, "bin");
4423
- const rest = (pathVar ?? "").split(delimiter2).filter((dir) => dir && dir !== bin);
4424
- return [bin, ...rest].join(delimiter2);
4425
- }
4426
- function readMarker(cwHome) {
4427
- try {
4428
- return JSON.parse(readFileSync9(join14(cwHome, MARKER_FILE), "utf-8"));
4429
- } catch {
4430
- return null;
4431
- }
4432
- }
4433
- function failureMessage2(output) {
4434
- const lines2 = output.replace(ANSI_RE3, "").split("\n").map((l) => l.trim()).filter(Boolean);
4435
- const errors = lines2.filter((l) => l.startsWith("[\u2717]")).map((l) => l.slice(3).trim());
4436
- return (errors.length > 0 ? errors : lines2.slice(-3)).join(" ") || "the CW installer failed";
4437
- }
4438
- async function ensureCw(options) {
4439
- const { cwHome, bundleDir } = options;
4440
- const env = options.env ?? process.env;
4441
- const run = options.run ?? runInstall;
4442
- const bin = join14(cwHome, "bin", "cw");
4443
- const bundle = readCwBundle(bundleDir);
4444
- const installed = existsSync13(bin) ? { version: readCwVersion(bin), sha256: sha256Of(bin) } : null;
4445
- const decision = decideCwInstall({
4446
- bundle,
4447
- installed,
4448
- marker: readMarker(cwHome),
4449
- cwOnPath: findOnPath("cw", env.PATH, join14(cwHome, "bin")) !== null,
4450
- skip: env.FORGE_SKIP_CW_INSTALL === "1"
4451
- });
4452
- if (decision.action === "skip" || !bundle)
4453
- return { action: "skipped", message: decision.reason };
4454
- const { code, output } = await run(join14(bundle.dir, "install.sh"), ["--no-shell"], { ...env, CW_HOME: cwHome });
4455
- if (code !== 0 || !existsSync13(bin))
4456
- return { action: "failed", message: failureMessage2(output) };
4457
- const marker = { version: bundle.version, ref: bundle.ref, sha256: sha256Of(bin) };
4458
- writeFileSync5(join14(cwHome, MARKER_FILE), JSON.stringify(marker, null, 2) + "\n");
4459
- return {
4460
- action: decision.action === "install" ? "installed" : "updated",
4461
- version: bundle.version,
4462
- message: decision.reason
4463
- };
4464
- }
4465
- var MARKER_FILE, INSTALL_TIMEOUT_MS2, ANSI_RE3, sha256Of, runInstall;
4466
- var init_cw_install = __esm({
4467
- "../core/dist/cw-install.js"() {
4468
- "use strict";
4469
- MARKER_FILE = ".forge-cw.json";
4470
- INSTALL_TIMEOUT_MS2 = 12e4;
4471
- ANSI_RE3 = /\u001b\[[0-9;?]*[A-Za-z]/g;
4472
- sha256Of = (file) => createHash2("sha256").update(readFileSync9(file)).digest("hex");
4473
- runInstall = (script, args, env) => new Promise((resolve5) => {
4474
- execFile5("bash", [script, ...args], { env, timeout: INSTALL_TIMEOUT_MS2, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
4475
- const output = `${String(stdout)}${String(stderr)}`;
4476
- if (!err)
4477
- return resolve5({ code: 0, output });
4478
- const code = err.code;
4479
- resolve5({ code: typeof code === "number" ? code : 1, output: output || err.message });
4480
- });
4481
- });
4482
- }
4483
- });
4484
-
4485
4713
  // ../core/dist/port-in-use.js
4486
4714
  function waitForListening(server) {
4487
4715
  if (server.listening)
@@ -4537,6 +4765,8 @@ __export(dist_exports, {
4537
4765
  ForgeDB: () => ForgeDB,
4538
4766
  HARNESS_CAPABILITIES: () => HARNESS_CAPABILITIES,
4539
4767
  HARNESS_NAME_RE: () => HARNESS_NAME_RE,
4768
+ Kioku: () => Kioku,
4769
+ KiokuError: () => KiokuError,
4540
4770
  Liveframe: () => Liveframe,
4541
4771
  LiveframeError: () => LiveframeError,
4542
4772
  MODEL_NAME_RE: () => MODEL_NAME_RE,
@@ -4551,11 +4781,13 @@ __export(dist_exports, {
4551
4781
  ensureCw: () => ensureCw,
4552
4782
  ensureForgeDir: () => ensureForgeDir,
4553
4783
  getForgeDir: () => getForgeDir,
4784
+ kiokuRoutes: () => kiokuRoutes,
4554
4785
  liveframeRoutes: () => liveframeRoutes,
4555
4786
  pathWithCw: () => pathWithCw,
4556
4787
  portInUseMessage: () => portInUseMessage,
4557
4788
  probePort: () => probePort,
4558
4789
  quietSqliteWarning: () => quietSqliteWarning,
4790
+ resolveKiokuBin: () => resolveKiokuBin,
4559
4791
  resolveListenOptions: () => resolveListenOptions,
4560
4792
  skillRoutes: () => skillRoutes,
4561
4793
  supports: () => supports,
@@ -4584,14 +4816,16 @@ var init_dist = __esm({
4584
4816
  init_pty_routes();
4585
4817
  init_liveframe_routes();
4586
4818
  init_liveframe();
4819
+ init_kioku_routes();
4820
+ init_kioku();
4587
4821
  init_port_in_use();
4588
4822
  }
4589
4823
  });
4590
4824
 
4591
4825
  // src/index.ts
4592
- import { existsSync as existsSync14, readFileSync as readFileSync10 } from "node:fs";
4593
- import { homedir as homedir6 } from "node:os";
4594
- import { join as join15 } from "node:path";
4826
+ import { existsSync as existsSync15, readFileSync as readFileSync10 } from "node:fs";
4827
+ import { homedir as homedir7 } from "node:os";
4828
+ import { join as join16 } from "node:path";
4595
4829
  function parseArgs(argv) {
4596
4830
  const out = { open: true };
4597
4831
  for (let i = 0; i < argv.length; i++) {
@@ -4602,11 +4836,11 @@ function parseArgs(argv) {
4602
4836
  }
4603
4837
  return out;
4604
4838
  }
4605
- var packageRoot = join15(import.meta.dirname, "..");
4606
- var isCheckout = existsSync14(join15(packageRoot, "src"));
4607
- var consoleDist = isCheckout ? join15(packageRoot, "../console/dist") : join15(packageRoot, "console");
4608
- var forgeVersion = JSON.parse(readFileSync10(join15(packageRoot, "package.json"), "utf-8")).version;
4609
- var bundledModulesDir = isCheckout ? join15(packageRoot, "../../modules") : join15(packageRoot, "modules");
4839
+ var packageRoot = join16(import.meta.dirname, "..");
4840
+ var isCheckout = existsSync15(join16(packageRoot, "src"));
4841
+ var consoleDist = isCheckout ? join16(packageRoot, "../console/dist") : join16(packageRoot, "console");
4842
+ var forgeVersion = JSON.parse(readFileSync10(join16(packageRoot, "package.json"), "utf-8")).version;
4843
+ var bundledModulesDir = isCheckout ? join16(packageRoot, "../../modules") : join16(packageRoot, "modules");
4610
4844
  async function main() {
4611
4845
  const { ensureForgeDir: ensureForgeDir2, createForgeServer: createForgeServer2, createDatabase: createDatabase2, resolveListenOptions: resolveListenOptions2, ensureCw: ensureCw2, pathWithCw: pathWithCw2, quietSqliteWarning: quietSqliteWarning2, waitForListening: waitForListening2, probePort: probePort2, portInUseMessage: portInUseMessage2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
4612
4846
  quietSqliteWarning2();
@@ -4615,8 +4849,8 @@ async function main() {
4615
4849
  if (created) {
4616
4850
  console.log("First run \u2014 initialized Forge.");
4617
4851
  }
4618
- const cwHome = join15(process.env.HOME ?? homedir6(), ".cw");
4619
- const cw = isCheckout ? { action: "skipped", message: "a checkout does not install CW" } : await ensureCw2({ cwHome, bundleDir: join15(packageRoot, "cw") });
4852
+ const cwHome = join16(process.env.HOME ?? homedir7(), ".cw");
4853
+ const cw = isCheckout ? { action: "skipped", message: "a checkout does not install CW" } : await ensureCw2({ cwHome, bundleDir: join16(packageRoot, "cw") });
4620
4854
  if (cw.action === "installed") console.log(` Installed CW ${cw.version} in ${cwHome}`);
4621
4855
  else if (cw.action === "updated") console.log(` Updated ${cw.message}`);
4622
4856
  else if (cw.action === "failed") console.log(` Could not install CW: ${cw.message}`);
@@ -4641,9 +4875,9 @@ async function main() {
4641
4875
  version: forgeVersion
4642
4876
  });
4643
4877
  const { serveStatic } = await import("@hono/node-server/serve-static");
4644
- if (existsSync14(join15(consoleDist, "index.html"))) {
4878
+ if (existsSync15(join16(consoleDist, "index.html"))) {
4645
4879
  server.app.use("/*", serveStatic({ root: consoleDist }));
4646
- const indexHtml = readFileSync10(join15(consoleDist, "index.html"), "utf-8");
4880
+ const indexHtml = readFileSync10(join16(consoleDist, "index.html"), "utf-8");
4647
4881
  server.app.get("*", (c) => c.html(indexHtml));
4648
4882
  }
4649
4883
  const { serve } = await import("@hono/node-server");