forge-cw 0.1.7 → 0.1.9
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
|
@@ -814,6 +814,14 @@ var init_cw_types = __esm({
|
|
|
814
814
|
import { execFile } from "node:child_process";
|
|
815
815
|
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
816
816
|
import { join as join3 } from "node:path";
|
|
817
|
+
function envWithoutHost(env = process.env) {
|
|
818
|
+
const out = {};
|
|
819
|
+
for (const [key, value] of Object.entries(env)) {
|
|
820
|
+
if (value !== void 0 && key !== "CLAUDECODE" && !key.startsWith("CLAUDE_CODE_"))
|
|
821
|
+
out[key] = value;
|
|
822
|
+
}
|
|
823
|
+
return out;
|
|
824
|
+
}
|
|
817
825
|
function envWithoutHarness(env = process.env) {
|
|
818
826
|
const out = {};
|
|
819
827
|
for (const [key, value] of Object.entries(env)) {
|
|
@@ -940,8 +948,8 @@ function closeWarningsFor(input) {
|
|
|
940
948
|
return warnings;
|
|
941
949
|
}
|
|
942
950
|
function createRunner(env, timeoutMs = COMMAND_TIMEOUT_MS) {
|
|
943
|
-
return (bin, args, cwd) => new Promise((resolve5) => {
|
|
944
|
-
execFile2(bin, args, { cwd, env, timeout: timeoutMs, maxBuffer: 5 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
951
|
+
return (bin, args, cwd, input) => new Promise((resolve5) => {
|
|
952
|
+
const child = execFile2(bin, args, { cwd, env, timeout: timeoutMs, maxBuffer: 5 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
945
953
|
if (!err)
|
|
946
954
|
return resolve5({ code: 0, stdout: String(stdout), stderr: String(stderr) });
|
|
947
955
|
const { code } = err;
|
|
@@ -949,6 +957,11 @@ function createRunner(env, timeoutMs = COMMAND_TIMEOUT_MS) {
|
|
|
949
957
|
return resolve5({ code: 127, stdout: "", stderr: "ENOENT" });
|
|
950
958
|
resolve5({ code: typeof code === "number" ? code : 1, stdout: String(stdout), stderr: String(stderr) || err.message });
|
|
951
959
|
});
|
|
960
|
+
if (input !== void 0) {
|
|
961
|
+
child.stdin?.on("error", () => {
|
|
962
|
+
});
|
|
963
|
+
child.stdin?.end(input);
|
|
964
|
+
}
|
|
952
965
|
});
|
|
953
966
|
}
|
|
954
967
|
function isCwLink(worktree, porcelainLine) {
|
|
@@ -1526,9 +1539,9 @@ var init_login_manager = __esm({
|
|
|
1526
1539
|
cwBin;
|
|
1527
1540
|
spawn;
|
|
1528
1541
|
entries = /* @__PURE__ */ new Map();
|
|
1529
|
-
constructor(cwBin,
|
|
1542
|
+
constructor(cwBin, spawn8 = spawnInPty) {
|
|
1530
1543
|
this.cwBin = cwBin;
|
|
1531
|
-
this.spawn =
|
|
1544
|
+
this.spawn = spawn8;
|
|
1532
1545
|
}
|
|
1533
1546
|
start(account, harness) {
|
|
1534
1547
|
const key = `${account}::${harness}`;
|
|
@@ -3144,6 +3157,15 @@ var init_pty_manager = __esm({
|
|
|
3144
3157
|
get(sessionId) {
|
|
3145
3158
|
return this.sessions.get(sessionId);
|
|
3146
3159
|
}
|
|
3160
|
+
// types a line into a live session, then Enter apart, so the agent's prompt takes it as typed and not as a paste
|
|
3161
|
+
typeLine(project, sessionDir, text) {
|
|
3162
|
+
const session = this.sessions.get(this.makeKey(project, sessionDir));
|
|
3163
|
+
if (!session)
|
|
3164
|
+
return false;
|
|
3165
|
+
session.pty.write(text);
|
|
3166
|
+
setTimeout(() => session.pty.write("\r"), 150);
|
|
3167
|
+
return true;
|
|
3168
|
+
}
|
|
3147
3169
|
// the state tracker reads the screen at the size the user sees it
|
|
3148
3170
|
resize(sessionId, cols, rows) {
|
|
3149
3171
|
const session = this.sessions.get(sessionId);
|
|
@@ -3932,20 +3954,458 @@ var init_liveframe_routes = __esm({
|
|
|
3932
3954
|
}
|
|
3933
3955
|
});
|
|
3934
3956
|
|
|
3935
|
-
// ../core/dist/
|
|
3957
|
+
// ../core/dist/cw-install.js
|
|
3958
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
3959
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
3960
|
+
import { accessSync as accessSync2, constants as constants2, existsSync as existsSync11, readFileSync as readFileSync9, statSync as statSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3961
|
+
import { delimiter as delimiter2, join as join12 } from "node:path";
|
|
3962
|
+
function readCwVersion(file) {
|
|
3963
|
+
try {
|
|
3964
|
+
const match = /^CW_VERSION="([^"]+)"/m.exec(readFileSync9(file, "utf-8"));
|
|
3965
|
+
return match ? match[1] : null;
|
|
3966
|
+
} catch {
|
|
3967
|
+
return null;
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
function compareVersions(a, b) {
|
|
3971
|
+
const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
|
|
3972
|
+
const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
|
|
3973
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
3974
|
+
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
3975
|
+
if (diff !== 0)
|
|
3976
|
+
return diff;
|
|
3977
|
+
}
|
|
3978
|
+
return 0;
|
|
3979
|
+
}
|
|
3980
|
+
function readCwBundle(dir) {
|
|
3981
|
+
try {
|
|
3982
|
+
const { ref } = JSON.parse(readFileSync9(join12(dir, ".bundle.json"), "utf-8"));
|
|
3983
|
+
const version = readCwVersion(join12(dir, "cw"));
|
|
3984
|
+
if (!ref || !version || !existsSync11(join12(dir, "install.sh")))
|
|
3985
|
+
return null;
|
|
3986
|
+
return { dir, version, ref };
|
|
3987
|
+
} catch {
|
|
3988
|
+
return null;
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
function decideCwInstall({ bundle, installed, marker, cwOnPath, skip }) {
|
|
3992
|
+
if (skip)
|
|
3993
|
+
return { action: "skip", reason: "FORGE_SKIP_CW_INSTALL is set" };
|
|
3994
|
+
if (!bundle)
|
|
3995
|
+
return { action: "skip", reason: "this Forge carries no CW" };
|
|
3996
|
+
if (!installed) {
|
|
3997
|
+
return cwOnPath ? { action: "skip", reason: "using the cw found on PATH" } : { action: "install", reason: "CW is not installed" };
|
|
3998
|
+
}
|
|
3999
|
+
if (!installed.version)
|
|
4000
|
+
return { action: "skip", reason: "cannot read the installed CW version" };
|
|
4001
|
+
const newer = compareVersions(bundle.version, installed.version);
|
|
4002
|
+
const ownedByForge = marker !== null && marker.sha256 === installed.sha256;
|
|
4003
|
+
if (ownedByForge) {
|
|
4004
|
+
if (marker.ref === bundle.ref)
|
|
4005
|
+
return { action: "skip", reason: `CW ${installed.version} is current` };
|
|
4006
|
+
return newer >= 0 ? { action: "update", reason: `CW ${installed.version} \u2192 ${bundle.version}` } : { action: "skip", reason: `installed CW ${installed.version} is newer than ${bundle.version}` };
|
|
4007
|
+
}
|
|
4008
|
+
return newer > 0 ? { action: "update", reason: `CW ${installed.version} \u2192 ${bundle.version}` } : { action: "skip", reason: `CW ${installed.version} is current` };
|
|
4009
|
+
}
|
|
4010
|
+
function findOnPath(name, pathVar, excludeDir) {
|
|
4011
|
+
for (const dir of (pathVar ?? "").split(delimiter2)) {
|
|
4012
|
+
if (!dir || dir === excludeDir)
|
|
4013
|
+
continue;
|
|
4014
|
+
const candidate = join12(dir, name);
|
|
4015
|
+
try {
|
|
4016
|
+
if (!statSync3(candidate).isFile())
|
|
4017
|
+
continue;
|
|
4018
|
+
accessSync2(candidate, constants2.X_OK);
|
|
4019
|
+
return candidate;
|
|
4020
|
+
} catch {
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
return null;
|
|
4024
|
+
}
|
|
4025
|
+
function pathWithCw(cwHome, pathVar) {
|
|
4026
|
+
const bin = join12(cwHome, "bin");
|
|
4027
|
+
const rest = (pathVar ?? "").split(delimiter2).filter((dir) => dir && dir !== bin);
|
|
4028
|
+
return [bin, ...rest].join(delimiter2);
|
|
4029
|
+
}
|
|
4030
|
+
function readMarker(cwHome) {
|
|
4031
|
+
try {
|
|
4032
|
+
return JSON.parse(readFileSync9(join12(cwHome, MARKER_FILE), "utf-8"));
|
|
4033
|
+
} catch {
|
|
4034
|
+
return null;
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
function failureMessage2(output) {
|
|
4038
|
+
const lines2 = output.replace(ANSI_RE3, "").split("\n").map((l) => l.trim()).filter(Boolean);
|
|
4039
|
+
const errors = lines2.filter((l) => l.startsWith("[\u2717]")).map((l) => l.slice(3).trim());
|
|
4040
|
+
return (errors.length > 0 ? errors : lines2.slice(-3)).join(" ") || "the CW installer failed";
|
|
4041
|
+
}
|
|
4042
|
+
async function ensureCw(options) {
|
|
4043
|
+
const { cwHome, bundleDir } = options;
|
|
4044
|
+
const env = options.env ?? process.env;
|
|
4045
|
+
const run = options.run ?? runInstall;
|
|
4046
|
+
const bin = join12(cwHome, "bin", "cw");
|
|
4047
|
+
const bundle = readCwBundle(bundleDir);
|
|
4048
|
+
const installed = existsSync11(bin) ? { version: readCwVersion(bin), sha256: sha256Of(bin) } : null;
|
|
4049
|
+
const decision = decideCwInstall({
|
|
4050
|
+
bundle,
|
|
4051
|
+
installed,
|
|
4052
|
+
marker: readMarker(cwHome),
|
|
4053
|
+
cwOnPath: findOnPath("cw", env.PATH, join12(cwHome, "bin")) !== null,
|
|
4054
|
+
skip: env.FORGE_SKIP_CW_INSTALL === "1"
|
|
4055
|
+
});
|
|
4056
|
+
if (decision.action === "skip" || !bundle)
|
|
4057
|
+
return { action: "skipped", message: decision.reason };
|
|
4058
|
+
const { code, output } = await run(join12(bundle.dir, "install.sh"), ["--no-shell"], { ...env, CW_HOME: cwHome });
|
|
4059
|
+
if (code !== 0 || !existsSync11(bin))
|
|
4060
|
+
return { action: "failed", message: failureMessage2(output) };
|
|
4061
|
+
const marker = { version: bundle.version, ref: bundle.ref, sha256: sha256Of(bin) };
|
|
4062
|
+
writeFileSync4(join12(cwHome, MARKER_FILE), JSON.stringify(marker, null, 2) + "\n");
|
|
4063
|
+
return {
|
|
4064
|
+
action: decision.action === "install" ? "installed" : "updated",
|
|
4065
|
+
version: bundle.version,
|
|
4066
|
+
message: decision.reason
|
|
4067
|
+
};
|
|
4068
|
+
}
|
|
4069
|
+
var MARKER_FILE, INSTALL_TIMEOUT_MS2, ANSI_RE3, sha256Of, runInstall;
|
|
4070
|
+
var init_cw_install = __esm({
|
|
4071
|
+
"../core/dist/cw-install.js"() {
|
|
4072
|
+
"use strict";
|
|
4073
|
+
MARKER_FILE = ".forge-cw.json";
|
|
4074
|
+
INSTALL_TIMEOUT_MS2 = 12e4;
|
|
4075
|
+
ANSI_RE3 = /\u001b\[[0-9;?]*[A-Za-z]/g;
|
|
4076
|
+
sha256Of = (file) => createHash2("sha256").update(readFileSync9(file)).digest("hex");
|
|
4077
|
+
runInstall = (script, args, env) => new Promise((resolve5) => {
|
|
4078
|
+
execFile5("bash", [script, ...args], { env, timeout: INSTALL_TIMEOUT_MS2, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
|
|
4079
|
+
const output = `${String(stdout)}${String(stderr)}`;
|
|
4080
|
+
if (!err)
|
|
4081
|
+
return resolve5({ code: 0, output });
|
|
4082
|
+
const code = err.code;
|
|
4083
|
+
resolve5({ code: typeof code === "number" ? code : 1, output: output || err.message });
|
|
4084
|
+
});
|
|
4085
|
+
});
|
|
4086
|
+
}
|
|
4087
|
+
});
|
|
4088
|
+
|
|
4089
|
+
// ../core/dist/kioku.js
|
|
4090
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
4091
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
4092
|
+
import { homedir as homedir5 } from "node:os";
|
|
4093
|
+
import { join as join13 } from "node:path";
|
|
4094
|
+
import { createInterface } from "node:readline";
|
|
4095
|
+
function resolveKiokuBin(pathVar = process.env.PATH, home = homedir5()) {
|
|
4096
|
+
const fallback = join13(home, ".local", "bin", "kioku");
|
|
4097
|
+
return findOnPath("kioku", pathVar) ?? (existsSync12(fallback) ? fallback : "kioku");
|
|
4098
|
+
}
|
|
4099
|
+
function rememberPrompt(text) {
|
|
4100
|
+
return `Remember this with kioku: ${text.replace(/\s+/g, " ").trim()}`;
|
|
4101
|
+
}
|
|
4102
|
+
function parseJson(text) {
|
|
4103
|
+
try {
|
|
4104
|
+
return JSON.parse(text);
|
|
4105
|
+
} catch {
|
|
4106
|
+
return null;
|
|
4107
|
+
}
|
|
4108
|
+
}
|
|
4109
|
+
async function mapLimit(items, limit, fn) {
|
|
4110
|
+
const out = new Array(items.length);
|
|
4111
|
+
let next = 0;
|
|
4112
|
+
const worker = async () => {
|
|
4113
|
+
while (next < items.length) {
|
|
4114
|
+
const i = next++;
|
|
4115
|
+
out[i] = await fn(items[i]);
|
|
4116
|
+
}
|
|
4117
|
+
};
|
|
4118
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
4119
|
+
return out;
|
|
4120
|
+
}
|
|
4121
|
+
var PAGE_TYPES, KiokuError, NOT_INSTALLED, START_TIMEOUT_MS, WRITE_TIMEOUT_MS, CACHE_MS2, ACTIVITY_SINCE, ACTIVITY_LIMIT, PARALLEL, MISSING, brainName, Kioku;
|
|
4122
|
+
var init_kioku = __esm({
|
|
4123
|
+
"../core/dist/kioku.js"() {
|
|
4124
|
+
"use strict";
|
|
4125
|
+
init_task_review();
|
|
4126
|
+
init_cw_doctor();
|
|
4127
|
+
init_cw_install();
|
|
4128
|
+
init_output();
|
|
4129
|
+
PAGE_TYPES = {
|
|
4130
|
+
memory: { kind: "memory" },
|
|
4131
|
+
instinct: { kind: "memory", sub: "instinct" },
|
|
4132
|
+
decision: { kind: "knowledge", sub: "decision" },
|
|
4133
|
+
playbook: { kind: "knowledge", sub: "playbook" },
|
|
4134
|
+
context: { kind: "knowledge", sub: "context" },
|
|
4135
|
+
rule: { kind: "rule" }
|
|
4136
|
+
};
|
|
4137
|
+
KiokuError = class extends Error {
|
|
4138
|
+
status;
|
|
4139
|
+
constructor(message, status = 502) {
|
|
4140
|
+
super(message);
|
|
4141
|
+
this.status = status;
|
|
4142
|
+
}
|
|
4143
|
+
};
|
|
4144
|
+
NOT_INSTALLED = "kioku is not installed on this machine";
|
|
4145
|
+
START_TIMEOUT_MS = 2e4;
|
|
4146
|
+
WRITE_TIMEOUT_MS = 6e4;
|
|
4147
|
+
CACHE_MS2 = 5 * 6e4;
|
|
4148
|
+
ACTIVITY_SINCE = "2w";
|
|
4149
|
+
ACTIVITY_LIMIT = 40;
|
|
4150
|
+
PARALLEL = 6;
|
|
4151
|
+
MISSING = { installed: false, version: null, brains: [], projects: {}, activity: [] };
|
|
4152
|
+
brainName = (b) => b.name ?? b.key;
|
|
4153
|
+
Kioku = class {
|
|
4154
|
+
run;
|
|
4155
|
+
write;
|
|
4156
|
+
startTimeoutMs;
|
|
4157
|
+
projects;
|
|
4158
|
+
now;
|
|
4159
|
+
binPath;
|
|
4160
|
+
viewer = null;
|
|
4161
|
+
snapshot = null;
|
|
4162
|
+
constructor(options = {}) {
|
|
4163
|
+
this.binPath = options.bin;
|
|
4164
|
+
this.run = options.run ?? createRunner();
|
|
4165
|
+
this.write = options.write ?? createRunner(envWithoutHost(), WRITE_TIMEOUT_MS);
|
|
4166
|
+
this.startTimeoutMs = options.startTimeoutMs ?? START_TIMEOUT_MS;
|
|
4167
|
+
this.projects = options.projects ?? (() => ({}));
|
|
4168
|
+
this.now = options.now ?? Date.now;
|
|
4169
|
+
}
|
|
4170
|
+
// resolved on first use, so a Forge that never opens Memory does not search PATH
|
|
4171
|
+
get bin() {
|
|
4172
|
+
return this.binPath ??= resolveKiokuBin();
|
|
4173
|
+
}
|
|
4174
|
+
async status(fresh = false) {
|
|
4175
|
+
const previous = this.snapshot;
|
|
4176
|
+
if (fresh || !previous || this.now() - previous.at > CACHE_MS2) {
|
|
4177
|
+
const value = this.read();
|
|
4178
|
+
this.snapshot = { at: this.now(), value };
|
|
4179
|
+
value.catch(() => {
|
|
4180
|
+
if (this.snapshot?.value === value)
|
|
4181
|
+
this.snapshot = previous;
|
|
4182
|
+
});
|
|
4183
|
+
}
|
|
4184
|
+
const pending = fresh || !previous ? this.snapshot.value : previous.value;
|
|
4185
|
+
const [snapshot, viewer] = await Promise.all([pending, this.viewer?.catch(() => null) ?? null]);
|
|
4186
|
+
return { ...snapshot, viewer: viewer?.address ?? null };
|
|
4187
|
+
}
|
|
4188
|
+
async json(args, cwd) {
|
|
4189
|
+
const result2 = await this.run(this.bin, [...args, "--json"], cwd);
|
|
4190
|
+
return result2.code === 0 ? parseJson(result2.stdout) : null;
|
|
4191
|
+
}
|
|
4192
|
+
async read() {
|
|
4193
|
+
const [version, list] = await Promise.all([
|
|
4194
|
+
this.run(this.bin, ["--version"], homedir5()),
|
|
4195
|
+
this.json(["brain", "list"], homedir5())
|
|
4196
|
+
]);
|
|
4197
|
+
if (version.code !== 0)
|
|
4198
|
+
return MISSING;
|
|
4199
|
+
const listed = list?.brains ?? [];
|
|
4200
|
+
const entries = Object.entries(this.projects()).filter(([, p]) => existsSync12(p.path));
|
|
4201
|
+
const readBrain = async (b) => {
|
|
4202
|
+
if (!b.onDisk)
|
|
4203
|
+
return { kinds: {}, namespaces: [], writes: [] };
|
|
4204
|
+
const [ns, log] = await Promise.all([
|
|
4205
|
+
this.json(["namespace", "list", "--brain", b.key], b.folder),
|
|
4206
|
+
this.json(["brain", "log", "--since", ACTIVITY_SINCE, "--brain", b.key], b.folder)
|
|
4207
|
+
]);
|
|
4208
|
+
const kinds = {};
|
|
4209
|
+
const namespaces = ns?.brains?.find((x) => x.brain === brainName(b))?.namespaces ?? [];
|
|
4210
|
+
for (const n of namespaces) {
|
|
4211
|
+
for (const [kind, count] of Object.entries(n.pages))
|
|
4212
|
+
kinds[kind] = (kinds[kind] ?? 0) + count;
|
|
4213
|
+
}
|
|
4214
|
+
return { kinds, namespaces: namespaces.map((n) => n.namespace), writes: log?.writes ?? [] };
|
|
4215
|
+
};
|
|
4216
|
+
const readChain = async (path) => (await this.json(["brain", "list"], path))?.chain ?? [];
|
|
4217
|
+
const [perBrain, chains] = await Promise.all([
|
|
4218
|
+
mapLimit(listed, PARALLEL / 2, readBrain),
|
|
4219
|
+
mapLimit(entries, PARALLEL / 2, ([, p]) => readChain(p.path))
|
|
4220
|
+
]);
|
|
4221
|
+
const activity = perBrain.flatMap((p) => p.writes).sort((x, y) => y.date.localeCompare(x.date));
|
|
4222
|
+
const brains = listed.map((b, i) => {
|
|
4223
|
+
const { kinds, namespaces } = perBrain[i];
|
|
4224
|
+
const name = brainName(b);
|
|
4225
|
+
return {
|
|
4226
|
+
...b,
|
|
4227
|
+
pages: Object.values(kinds).reduce((sum, n) => sum + n, 0),
|
|
4228
|
+
kinds,
|
|
4229
|
+
namespaces,
|
|
4230
|
+
touched: activity.find((w) => w.brain === name)?.date ?? null
|
|
4231
|
+
};
|
|
4232
|
+
});
|
|
4233
|
+
const projects = Object.fromEntries(entries.map(([name], i) => [name, chains[i].map(({ name: name2, tier }) => ({ name: name2, tier }))]));
|
|
4234
|
+
return {
|
|
4235
|
+
installed: true,
|
|
4236
|
+
version: parseJson(version.stdout)?.version ?? null,
|
|
4237
|
+
brains,
|
|
4238
|
+
projects,
|
|
4239
|
+
activity: activity.slice(0, ACTIVITY_LIMIT).map(({ verb, title, path, date, author, brain }) => ({ verb, title, path, date, author, brain }))
|
|
4240
|
+
};
|
|
4241
|
+
}
|
|
4242
|
+
// writes through kioku, which validates, commits and pushes a shared brain; Forge never writes a brain's files
|
|
4243
|
+
async save(input) {
|
|
4244
|
+
const type = PAGE_TYPES[input.type];
|
|
4245
|
+
if (!type)
|
|
4246
|
+
throw new KiokuError(`unknown page type ${input.type}`, 400);
|
|
4247
|
+
const brains = this.snapshot ? (await this.snapshot.value).brains : (await this.json(["brain", "list"], homedir5()))?.brains ?? [];
|
|
4248
|
+
const brain = brains.find((b) => b.key === input.brain);
|
|
4249
|
+
if (!brain?.onDisk)
|
|
4250
|
+
throw new KiokuError(`no brain ${input.brain} on this machine`, 404);
|
|
4251
|
+
const args = [
|
|
4252
|
+
"page",
|
|
4253
|
+
"save",
|
|
4254
|
+
"--brain",
|
|
4255
|
+
brain.key,
|
|
4256
|
+
"--kind",
|
|
4257
|
+
type.kind,
|
|
4258
|
+
...type.sub ? ["--sub", type.sub] : [],
|
|
4259
|
+
"--title",
|
|
4260
|
+
input.title,
|
|
4261
|
+
"--summary",
|
|
4262
|
+
input.summary,
|
|
4263
|
+
"--source",
|
|
4264
|
+
"Forge",
|
|
4265
|
+
...input.namespace ? ["--namespace", input.namespace] : [],
|
|
4266
|
+
...input.create ? ["--create"] : [],
|
|
4267
|
+
"--json"
|
|
4268
|
+
];
|
|
4269
|
+
const result2 = await this.write(this.bin, args, brain.folder, input.body);
|
|
4270
|
+
if (result2.code === 127)
|
|
4271
|
+
throw new KiokuError(NOT_INSTALLED, 404);
|
|
4272
|
+
const out = parseJson(result2.stdout);
|
|
4273
|
+
if (out?.status === "done" && out.path) {
|
|
4274
|
+
this.snapshot = null;
|
|
4275
|
+
return { status: "done", brain: brainName(brain), path: out.path, publication: out.publication ?? null };
|
|
4276
|
+
}
|
|
4277
|
+
if (out?.status === "similar")
|
|
4278
|
+
return { status: "similar", similar: (out.similar ?? []).map(({ brain: brain2, path, title, summary }) => ({ brain: brain2, path, title, summary })) };
|
|
4279
|
+
if (out?.status === "namespace")
|
|
4280
|
+
return { status: "namespace", namespaces: (out.namespaces ?? []).map(({ namespace, summary }) => ({ namespace, summary })) };
|
|
4281
|
+
throw new KiokuError(out?.error?.message ?? (tailOutput(result2.stderr, 1) || "kioku could not save the page"), 400);
|
|
4282
|
+
}
|
|
4283
|
+
// one viewer at a time; a second call reuses the running one
|
|
4284
|
+
async openViewer() {
|
|
4285
|
+
this.viewer ??= this.startViewer();
|
|
4286
|
+
return (await this.viewer).address;
|
|
4287
|
+
}
|
|
4288
|
+
startViewer() {
|
|
4289
|
+
const started = new Promise((resolve5, reject) => {
|
|
4290
|
+
const child = spawn7(this.bin, ["web", "--json", "--no-open"], { cwd: homedir5(), stdio: ["ignore", "pipe", "pipe"] });
|
|
4291
|
+
const lines2 = createInterface({ input: child.stdout });
|
|
4292
|
+
let stderr = "";
|
|
4293
|
+
let settled = false;
|
|
4294
|
+
const finish = () => {
|
|
4295
|
+
settled = true;
|
|
4296
|
+
clearTimeout(timer);
|
|
4297
|
+
lines2.close();
|
|
4298
|
+
child.stdout?.resume();
|
|
4299
|
+
};
|
|
4300
|
+
const fail = (err) => {
|
|
4301
|
+
if (settled)
|
|
4302
|
+
return;
|
|
4303
|
+
finish();
|
|
4304
|
+
child.kill();
|
|
4305
|
+
reject(err);
|
|
4306
|
+
};
|
|
4307
|
+
const timer = setTimeout(() => fail(new KiokuError("kioku web did not start in time")), this.startTimeoutMs);
|
|
4308
|
+
child.stderr?.on("data", (chunk) => {
|
|
4309
|
+
stderr = (stderr + chunk.toString()).slice(-4096);
|
|
4310
|
+
});
|
|
4311
|
+
lines2.on("line", (line) => {
|
|
4312
|
+
const event = parseJson(line);
|
|
4313
|
+
if (settled || event?.event !== "listening" || !event.address?.startsWith("http://127.0.0.1:"))
|
|
4314
|
+
return;
|
|
4315
|
+
finish();
|
|
4316
|
+
resolve5({ child, address: event.address });
|
|
4317
|
+
});
|
|
4318
|
+
child.on("error", (err) => {
|
|
4319
|
+
const missing = err.code === "ENOENT";
|
|
4320
|
+
fail(new KiokuError(missing ? NOT_INSTALLED : err.message, missing ? 404 : 502));
|
|
4321
|
+
});
|
|
4322
|
+
child.on("close", (code) => {
|
|
4323
|
+
if (this.viewer === started)
|
|
4324
|
+
this.viewer = null;
|
|
4325
|
+
fail(new KiokuError(tailOutput(stderr, 1) || `kioku web exited with code ${code}`));
|
|
4326
|
+
});
|
|
4327
|
+
});
|
|
4328
|
+
started.catch(() => {
|
|
4329
|
+
if (this.viewer === started)
|
|
4330
|
+
this.viewer = null;
|
|
4331
|
+
});
|
|
4332
|
+
return started;
|
|
4333
|
+
}
|
|
4334
|
+
stopViewer() {
|
|
4335
|
+
void this.viewer?.then((v) => v.child.kill(), () => {
|
|
4336
|
+
});
|
|
4337
|
+
this.viewer = null;
|
|
4338
|
+
}
|
|
4339
|
+
dispose() {
|
|
4340
|
+
this.stopViewer();
|
|
4341
|
+
}
|
|
4342
|
+
};
|
|
4343
|
+
}
|
|
4344
|
+
});
|
|
4345
|
+
|
|
4346
|
+
// ../core/dist/kioku-routes.js
|
|
3936
4347
|
import { Hono as Hono4 } from "hono";
|
|
4348
|
+
function kiokuRoutes(kioku = new Kioku(), options = {}) {
|
|
4349
|
+
const app = new Hono4();
|
|
4350
|
+
app.onError((err, c) => {
|
|
4351
|
+
if (err instanceof KiokuError)
|
|
4352
|
+
return c.json({ error: err.message }, err.status);
|
|
4353
|
+
throw err;
|
|
4354
|
+
});
|
|
4355
|
+
app.get("/status", async (c) => c.json(await kioku.status(c.req.query("fresh") === "1")));
|
|
4356
|
+
app.post("/pages", async (c) => {
|
|
4357
|
+
const input = await c.req.json().catch(() => ({}));
|
|
4358
|
+
const brain = input.brain?.trim();
|
|
4359
|
+
const title = input.title?.trim();
|
|
4360
|
+
const summary = input.summary?.trim();
|
|
4361
|
+
if (!brain || !input.type || !title || !summary || !input.body?.trim())
|
|
4362
|
+
return c.json({ error: "brain, type, title, summary and body are required" }, 400);
|
|
4363
|
+
const result2 = await kioku.save({ brain, type: input.type, title, summary, body: input.body, namespace: input.namespace?.trim() || void 0, create: input.create === true });
|
|
4364
|
+
return c.json(result2, result2.status === "done" ? 201 : 200);
|
|
4365
|
+
});
|
|
4366
|
+
app.post("/remember", async (c) => {
|
|
4367
|
+
const { project, sessionDir, text } = await c.req.json().catch(() => ({}));
|
|
4368
|
+
const line = text?.trim();
|
|
4369
|
+
if (!project || !sessionDir || !line)
|
|
4370
|
+
return c.json({ error: "project, sessionDir and text are required" }, 400);
|
|
4371
|
+
if (line.length > REMEMBER_MAX)
|
|
4372
|
+
return c.json({ error: `keep it under ${REMEMBER_MAX} characters` }, 400);
|
|
4373
|
+
if (options.canRemember && !options.canRemember(project, sessionDir))
|
|
4374
|
+
return c.json({ error: "only Claude Code sessions have kioku's skill" }, 400);
|
|
4375
|
+
if (!options.type?.(project, sessionDir, rememberPrompt(line)))
|
|
4376
|
+
return c.json({ error: "the session has no live terminal" }, 409);
|
|
4377
|
+
return c.json({ ok: true });
|
|
4378
|
+
});
|
|
4379
|
+
app.post("/viewer", async (c) => c.json({ address: await kioku.openViewer() }));
|
|
4380
|
+
app.delete("/viewer", (c) => {
|
|
4381
|
+
kioku.stopViewer();
|
|
4382
|
+
return c.json({ ok: true });
|
|
4383
|
+
});
|
|
4384
|
+
return app;
|
|
4385
|
+
}
|
|
4386
|
+
var REMEMBER_MAX;
|
|
4387
|
+
var init_kioku_routes = __esm({
|
|
4388
|
+
"../core/dist/kioku-routes.js"() {
|
|
4389
|
+
"use strict";
|
|
4390
|
+
init_kioku();
|
|
4391
|
+
REMEMBER_MAX = 500;
|
|
4392
|
+
}
|
|
4393
|
+
});
|
|
4394
|
+
|
|
4395
|
+
// ../core/dist/server.js
|
|
4396
|
+
import { Hono as Hono5 } from "hono";
|
|
3937
4397
|
import { cors } from "hono/cors";
|
|
3938
|
-
import { join as
|
|
3939
|
-
import { readdirSync as readdirSync6, existsSync as
|
|
4398
|
+
import { join as join14, basename as basename3, resolve as resolve4 } from "node:path";
|
|
4399
|
+
import { readdirSync as readdirSync6, existsSync as existsSync13 } from "node:fs";
|
|
3940
4400
|
function createForgeServer(options) {
|
|
3941
4401
|
const { dataDir, db: externalDb, authToken, localOnly = true } = options;
|
|
3942
|
-
const dbPath =
|
|
3943
|
-
const modulesDir =
|
|
4402
|
+
const dbPath = join14(dataDir, "forge.db");
|
|
4403
|
+
const modulesDir = join14(dataDir, "modules");
|
|
3944
4404
|
const db = externalDb ?? new ForgeDB(dbPath);
|
|
3945
4405
|
const loader = new ModuleLoader(modulesDir, options.bundledModulesDir);
|
|
3946
4406
|
const runner = new ActionRunner();
|
|
3947
4407
|
loader.discover();
|
|
3948
|
-
const app = new
|
|
4408
|
+
const app = new Hono5();
|
|
3949
4409
|
app.use("*", localOnly ? localOriginGuard() : cors());
|
|
3950
4410
|
if (authToken) {
|
|
3951
4411
|
app.use("/api/*", bearerAuth(authToken));
|
|
@@ -3973,9 +4433,15 @@ function createForgeServer(options) {
|
|
|
3973
4433
|
}
|
|
3974
4434
|
});
|
|
3975
4435
|
const ptyManager = new PTYManager(states);
|
|
3976
|
-
const generalSessions = new GeneralSessions(
|
|
4436
|
+
const generalSessions = new GeneralSessions(join14(dataDir, "general-sessions.json"));
|
|
3977
4437
|
const terminalWss = createTerminalWss(ptyManager, cwReader, { localOnly, general: generalSessions });
|
|
3978
4438
|
app.route("/api/liveframe", liveframeRoutes());
|
|
4439
|
+
const kioku = new Kioku({ projects: () => cwReader.getProjects() });
|
|
4440
|
+
app.route("/api/kioku", kiokuRoutes(kioku, {
|
|
4441
|
+
type: (project, sessionDir, text) => ptyManager.typeLine(project, sessionDir, text),
|
|
4442
|
+
// kioku's plugin, and so its skill, exists only for Claude Code
|
|
4443
|
+
canRemember: (project, sessionDir) => cwReader.getSession(project, sessionDir)?.harness === "claude"
|
|
4444
|
+
}));
|
|
3979
4445
|
app.get("/api/cw/session-states", (c) => c.json({ classifier: states.remoteName ?? "local", shadow: states.isShadow, states: states.snapshot() }));
|
|
3980
4446
|
app.post("/api/cw/terminal/kill", async (c) => {
|
|
3981
4447
|
const { project, sessionDir } = await c.req.json();
|
|
@@ -4131,21 +4597,21 @@ data: ${JSON.stringify(data)}
|
|
|
4131
4597
|
if (!dir.startsWith(home)) {
|
|
4132
4598
|
return c.json({ error: "Access denied: path must be within home directory" }, 403);
|
|
4133
4599
|
}
|
|
4134
|
-
if (!
|
|
4600
|
+
if (!existsSync13(dir)) {
|
|
4135
4601
|
return c.json({ error: "Directory not found" }, 404);
|
|
4136
4602
|
}
|
|
4137
4603
|
try {
|
|
4138
4604
|
const entries = readdirSync6(dir, { withFileTypes: true });
|
|
4139
4605
|
const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({
|
|
4140
4606
|
name: e.name,
|
|
4141
|
-
path:
|
|
4142
|
-
hasPackageJson:
|
|
4143
|
-
hasGit:
|
|
4607
|
+
path: join14(dir, e.name),
|
|
4608
|
+
hasPackageJson: existsSync13(join14(dir, e.name, "package.json")),
|
|
4609
|
+
hasGit: existsSync13(join14(dir, e.name, ".git"))
|
|
4144
4610
|
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
4145
4611
|
return c.json({
|
|
4146
4612
|
current: dir,
|
|
4147
4613
|
name: basename3(dir),
|
|
4148
|
-
parent:
|
|
4614
|
+
parent: join14(dir, ".."),
|
|
4149
4615
|
directories: dirs
|
|
4150
4616
|
});
|
|
4151
4617
|
} catch {
|
|
@@ -4163,6 +4629,7 @@ data: ${JSON.stringify(data)}
|
|
|
4163
4629
|
ptyManager.dispose();
|
|
4164
4630
|
states.dispose();
|
|
4165
4631
|
loginManager.dispose();
|
|
4632
|
+
kioku.dispose();
|
|
4166
4633
|
db.close();
|
|
4167
4634
|
}
|
|
4168
4635
|
};
|
|
@@ -4186,6 +4653,8 @@ var init_server = __esm({
|
|
|
4186
4653
|
init_login_manager();
|
|
4187
4654
|
init_general_sessions();
|
|
4188
4655
|
init_liveframe_routes();
|
|
4656
|
+
init_kioku_routes();
|
|
4657
|
+
init_kioku();
|
|
4189
4658
|
}
|
|
4190
4659
|
});
|
|
4191
4660
|
|
|
@@ -4308,8 +4777,8 @@ async function createDatabase(opts) {
|
|
|
4308
4777
|
await db.migrate();
|
|
4309
4778
|
return db;
|
|
4310
4779
|
}
|
|
4311
|
-
const { join:
|
|
4312
|
-
return new ForgeDB(
|
|
4780
|
+
const { join: join17 } = await import("node:path");
|
|
4781
|
+
return new ForgeDB(join17(opts.dataDir, "forge.db"));
|
|
4313
4782
|
}
|
|
4314
4783
|
var init_db_factory = __esm({
|
|
4315
4784
|
"../core/dist/db-factory.js"() {
|
|
@@ -4319,22 +4788,22 @@ var init_db_factory = __esm({
|
|
|
4319
4788
|
});
|
|
4320
4789
|
|
|
4321
4790
|
// ../core/dist/init.js
|
|
4322
|
-
import { existsSync as
|
|
4323
|
-
import { join as
|
|
4324
|
-
import { homedir as
|
|
4791
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4792
|
+
import { join as join15 } from "node:path";
|
|
4793
|
+
import { homedir as homedir6 } from "node:os";
|
|
4325
4794
|
function getForgeDir() {
|
|
4326
|
-
return
|
|
4795
|
+
return join15(homedir6(), ".forge");
|
|
4327
4796
|
}
|
|
4328
4797
|
function ensureForgeDir() {
|
|
4329
4798
|
const forgeDir = getForgeDir();
|
|
4330
|
-
if (
|
|
4799
|
+
if (existsSync14(forgeDir)) {
|
|
4331
4800
|
return { forgeDir, created: false };
|
|
4332
4801
|
}
|
|
4333
4802
|
mkdirSync5(forgeDir, { recursive: true });
|
|
4334
4803
|
for (const d of FORGE_SUBDIRS) {
|
|
4335
|
-
mkdirSync5(
|
|
4804
|
+
mkdirSync5(join15(forgeDir, d), { recursive: true });
|
|
4336
4805
|
}
|
|
4337
|
-
|
|
4806
|
+
writeFileSync5(join15(forgeDir, "config.json"), JSON.stringify({
|
|
4338
4807
|
port: 3e3,
|
|
4339
4808
|
theme: "dark",
|
|
4340
4809
|
openBrowser: true,
|
|
@@ -4350,138 +4819,6 @@ var init_init = __esm({
|
|
|
4350
4819
|
}
|
|
4351
4820
|
});
|
|
4352
4821
|
|
|
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
4822
|
// ../core/dist/port-in-use.js
|
|
4486
4823
|
function waitForListening(server) {
|
|
4487
4824
|
if (server.listening)
|
|
@@ -4537,6 +4874,8 @@ __export(dist_exports, {
|
|
|
4537
4874
|
ForgeDB: () => ForgeDB,
|
|
4538
4875
|
HARNESS_CAPABILITIES: () => HARNESS_CAPABILITIES,
|
|
4539
4876
|
HARNESS_NAME_RE: () => HARNESS_NAME_RE,
|
|
4877
|
+
Kioku: () => Kioku,
|
|
4878
|
+
KiokuError: () => KiokuError,
|
|
4540
4879
|
Liveframe: () => Liveframe,
|
|
4541
4880
|
LiveframeError: () => LiveframeError,
|
|
4542
4881
|
MODEL_NAME_RE: () => MODEL_NAME_RE,
|
|
@@ -4551,11 +4890,14 @@ __export(dist_exports, {
|
|
|
4551
4890
|
ensureCw: () => ensureCw,
|
|
4552
4891
|
ensureForgeDir: () => ensureForgeDir,
|
|
4553
4892
|
getForgeDir: () => getForgeDir,
|
|
4893
|
+
kiokuRoutes: () => kiokuRoutes,
|
|
4554
4894
|
liveframeRoutes: () => liveframeRoutes,
|
|
4555
4895
|
pathWithCw: () => pathWithCw,
|
|
4556
4896
|
portInUseMessage: () => portInUseMessage,
|
|
4557
4897
|
probePort: () => probePort,
|
|
4558
4898
|
quietSqliteWarning: () => quietSqliteWarning,
|
|
4899
|
+
rememberPrompt: () => rememberPrompt,
|
|
4900
|
+
resolveKiokuBin: () => resolveKiokuBin,
|
|
4559
4901
|
resolveListenOptions: () => resolveListenOptions,
|
|
4560
4902
|
skillRoutes: () => skillRoutes,
|
|
4561
4903
|
supports: () => supports,
|
|
@@ -4584,14 +4926,16 @@ var init_dist = __esm({
|
|
|
4584
4926
|
init_pty_routes();
|
|
4585
4927
|
init_liveframe_routes();
|
|
4586
4928
|
init_liveframe();
|
|
4929
|
+
init_kioku_routes();
|
|
4930
|
+
init_kioku();
|
|
4587
4931
|
init_port_in_use();
|
|
4588
4932
|
}
|
|
4589
4933
|
});
|
|
4590
4934
|
|
|
4591
4935
|
// src/index.ts
|
|
4592
|
-
import { existsSync as
|
|
4593
|
-
import { homedir as
|
|
4594
|
-
import { join as
|
|
4936
|
+
import { existsSync as existsSync15, readFileSync as readFileSync10 } from "node:fs";
|
|
4937
|
+
import { homedir as homedir7 } from "node:os";
|
|
4938
|
+
import { join as join16 } from "node:path";
|
|
4595
4939
|
function parseArgs(argv) {
|
|
4596
4940
|
const out = { open: true };
|
|
4597
4941
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -4602,11 +4946,11 @@ function parseArgs(argv) {
|
|
|
4602
4946
|
}
|
|
4603
4947
|
return out;
|
|
4604
4948
|
}
|
|
4605
|
-
var packageRoot =
|
|
4606
|
-
var isCheckout =
|
|
4607
|
-
var consoleDist = isCheckout ?
|
|
4608
|
-
var forgeVersion = JSON.parse(readFileSync10(
|
|
4609
|
-
var bundledModulesDir = isCheckout ?
|
|
4949
|
+
var packageRoot = join16(import.meta.dirname, "..");
|
|
4950
|
+
var isCheckout = existsSync15(join16(packageRoot, "src"));
|
|
4951
|
+
var consoleDist = isCheckout ? join16(packageRoot, "../console/dist") : join16(packageRoot, "console");
|
|
4952
|
+
var forgeVersion = JSON.parse(readFileSync10(join16(packageRoot, "package.json"), "utf-8")).version;
|
|
4953
|
+
var bundledModulesDir = isCheckout ? join16(packageRoot, "../../modules") : join16(packageRoot, "modules");
|
|
4610
4954
|
async function main() {
|
|
4611
4955
|
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
4956
|
quietSqliteWarning2();
|
|
@@ -4615,8 +4959,8 @@ async function main() {
|
|
|
4615
4959
|
if (created) {
|
|
4616
4960
|
console.log("First run \u2014 initialized Forge.");
|
|
4617
4961
|
}
|
|
4618
|
-
const cwHome =
|
|
4619
|
-
const cw = isCheckout ? { action: "skipped", message: "a checkout does not install CW" } : await ensureCw2({ cwHome, bundleDir:
|
|
4962
|
+
const cwHome = join16(process.env.HOME ?? homedir7(), ".cw");
|
|
4963
|
+
const cw = isCheckout ? { action: "skipped", message: "a checkout does not install CW" } : await ensureCw2({ cwHome, bundleDir: join16(packageRoot, "cw") });
|
|
4620
4964
|
if (cw.action === "installed") console.log(` Installed CW ${cw.version} in ${cwHome}`);
|
|
4621
4965
|
else if (cw.action === "updated") console.log(` Updated ${cw.message}`);
|
|
4622
4966
|
else if (cw.action === "failed") console.log(` Could not install CW: ${cw.message}`);
|
|
@@ -4641,9 +4985,9 @@ async function main() {
|
|
|
4641
4985
|
version: forgeVersion
|
|
4642
4986
|
});
|
|
4643
4987
|
const { serveStatic } = await import("@hono/node-server/serve-static");
|
|
4644
|
-
if (
|
|
4988
|
+
if (existsSync15(join16(consoleDist, "index.html"))) {
|
|
4645
4989
|
server.app.use("/*", serveStatic({ root: consoleDist }));
|
|
4646
|
-
const indexHtml = readFileSync10(
|
|
4990
|
+
const indexHtml = readFileSync10(join16(consoleDist, "index.html"), "utf-8");
|
|
4647
4991
|
server.app.get("*", (c) => c.html(indexHtml));
|
|
4648
4992
|
}
|
|
4649
4993
|
const { serve } = await import("@hono/node-server");
|