forge-cw 0.1.8 → 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/console/assets/index-DLM-Ix3c.js +41 -0
- package/console/index.html +1 -1
- package/dist/index.js +120 -10
- package/package.json +1 -1
- package/console/assets/index-BpkdtYRb.js +0 -36
package/console/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Forge Console</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-DLM-Ix3c.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-N7b3C-h8.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
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) {
|
|
@@ -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);
|
|
@@ -4074,6 +4096,9 @@ function resolveKiokuBin(pathVar = process.env.PATH, home = homedir5()) {
|
|
|
4074
4096
|
const fallback = join13(home, ".local", "bin", "kioku");
|
|
4075
4097
|
return findOnPath("kioku", pathVar) ?? (existsSync12(fallback) ? fallback : "kioku");
|
|
4076
4098
|
}
|
|
4099
|
+
function rememberPrompt(text) {
|
|
4100
|
+
return `Remember this with kioku: ${text.replace(/\s+/g, " ").trim()}`;
|
|
4101
|
+
}
|
|
4077
4102
|
function parseJson(text) {
|
|
4078
4103
|
try {
|
|
4079
4104
|
return JSON.parse(text);
|
|
@@ -4093,13 +4118,22 @@ async function mapLimit(items, limit, fn) {
|
|
|
4093
4118
|
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
4094
4119
|
return out;
|
|
4095
4120
|
}
|
|
4096
|
-
var KiokuError, NOT_INSTALLED, START_TIMEOUT_MS, CACHE_MS2, ACTIVITY_SINCE, ACTIVITY_LIMIT, PARALLEL, MISSING, brainName, Kioku;
|
|
4121
|
+
var PAGE_TYPES, KiokuError, NOT_INSTALLED, START_TIMEOUT_MS, WRITE_TIMEOUT_MS, CACHE_MS2, ACTIVITY_SINCE, ACTIVITY_LIMIT, PARALLEL, MISSING, brainName, Kioku;
|
|
4097
4122
|
var init_kioku = __esm({
|
|
4098
4123
|
"../core/dist/kioku.js"() {
|
|
4099
4124
|
"use strict";
|
|
4100
4125
|
init_task_review();
|
|
4126
|
+
init_cw_doctor();
|
|
4101
4127
|
init_cw_install();
|
|
4102
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
|
+
};
|
|
4103
4137
|
KiokuError = class extends Error {
|
|
4104
4138
|
status;
|
|
4105
4139
|
constructor(message, status = 502) {
|
|
@@ -4109,6 +4143,7 @@ var init_kioku = __esm({
|
|
|
4109
4143
|
};
|
|
4110
4144
|
NOT_INSTALLED = "kioku is not installed on this machine";
|
|
4111
4145
|
START_TIMEOUT_MS = 2e4;
|
|
4146
|
+
WRITE_TIMEOUT_MS = 6e4;
|
|
4112
4147
|
CACHE_MS2 = 5 * 6e4;
|
|
4113
4148
|
ACTIVITY_SINCE = "2w";
|
|
4114
4149
|
ACTIVITY_LIMIT = 40;
|
|
@@ -4117,6 +4152,7 @@ var init_kioku = __esm({
|
|
|
4117
4152
|
brainName = (b) => b.name ?? b.key;
|
|
4118
4153
|
Kioku = class {
|
|
4119
4154
|
run;
|
|
4155
|
+
write;
|
|
4120
4156
|
startTimeoutMs;
|
|
4121
4157
|
projects;
|
|
4122
4158
|
now;
|
|
@@ -4126,6 +4162,7 @@ var init_kioku = __esm({
|
|
|
4126
4162
|
constructor(options = {}) {
|
|
4127
4163
|
this.binPath = options.bin;
|
|
4128
4164
|
this.run = options.run ?? createRunner();
|
|
4165
|
+
this.write = options.write ?? createRunner(envWithoutHost(), WRITE_TIMEOUT_MS);
|
|
4129
4166
|
this.startTimeoutMs = options.startTimeoutMs ?? START_TIMEOUT_MS;
|
|
4130
4167
|
this.projects = options.projects ?? (() => ({}));
|
|
4131
4168
|
this.now = options.now ?? Date.now;
|
|
@@ -4163,17 +4200,18 @@ var init_kioku = __esm({
|
|
|
4163
4200
|
const entries = Object.entries(this.projects()).filter(([, p]) => existsSync12(p.path));
|
|
4164
4201
|
const readBrain = async (b) => {
|
|
4165
4202
|
if (!b.onDisk)
|
|
4166
|
-
return { kinds: {}, writes: [] };
|
|
4203
|
+
return { kinds: {}, namespaces: [], writes: [] };
|
|
4167
4204
|
const [ns, log] = await Promise.all([
|
|
4168
4205
|
this.json(["namespace", "list", "--brain", b.key], b.folder),
|
|
4169
4206
|
this.json(["brain", "log", "--since", ACTIVITY_SINCE, "--brain", b.key], b.folder)
|
|
4170
4207
|
]);
|
|
4171
4208
|
const kinds = {};
|
|
4172
|
-
|
|
4209
|
+
const namespaces = ns?.brains?.find((x) => x.brain === brainName(b))?.namespaces ?? [];
|
|
4210
|
+
for (const n of namespaces) {
|
|
4173
4211
|
for (const [kind, count] of Object.entries(n.pages))
|
|
4174
4212
|
kinds[kind] = (kinds[kind] ?? 0) + count;
|
|
4175
4213
|
}
|
|
4176
|
-
return { kinds, writes: log?.writes ?? [] };
|
|
4214
|
+
return { kinds, namespaces: namespaces.map((n) => n.namespace), writes: log?.writes ?? [] };
|
|
4177
4215
|
};
|
|
4178
4216
|
const readChain = async (path) => (await this.json(["brain", "list"], path))?.chain ?? [];
|
|
4179
4217
|
const [perBrain, chains] = await Promise.all([
|
|
@@ -4182,12 +4220,13 @@ var init_kioku = __esm({
|
|
|
4182
4220
|
]);
|
|
4183
4221
|
const activity = perBrain.flatMap((p) => p.writes).sort((x, y) => y.date.localeCompare(x.date));
|
|
4184
4222
|
const brains = listed.map((b, i) => {
|
|
4185
|
-
const { kinds } = perBrain[i];
|
|
4223
|
+
const { kinds, namespaces } = perBrain[i];
|
|
4186
4224
|
const name = brainName(b);
|
|
4187
4225
|
return {
|
|
4188
4226
|
...b,
|
|
4189
4227
|
pages: Object.values(kinds).reduce((sum, n) => sum + n, 0),
|
|
4190
4228
|
kinds,
|
|
4229
|
+
namespaces,
|
|
4191
4230
|
touched: activity.find((w) => w.brain === name)?.date ?? null
|
|
4192
4231
|
};
|
|
4193
4232
|
});
|
|
@@ -4200,6 +4239,47 @@ var init_kioku = __esm({
|
|
|
4200
4239
|
activity: activity.slice(0, ACTIVITY_LIMIT).map(({ verb, title, path, date, author, brain }) => ({ verb, title, path, date, author, brain }))
|
|
4201
4240
|
};
|
|
4202
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
|
+
}
|
|
4203
4283
|
// one viewer at a time; a second call reuses the running one
|
|
4204
4284
|
async openViewer() {
|
|
4205
4285
|
this.viewer ??= this.startViewer();
|
|
@@ -4239,7 +4319,7 @@ var init_kioku = __esm({
|
|
|
4239
4319
|
const missing = err.code === "ENOENT";
|
|
4240
4320
|
fail(new KiokuError(missing ? NOT_INSTALLED : err.message, missing ? 404 : 502));
|
|
4241
4321
|
});
|
|
4242
|
-
child.on("
|
|
4322
|
+
child.on("close", (code) => {
|
|
4243
4323
|
if (this.viewer === started)
|
|
4244
4324
|
this.viewer = null;
|
|
4245
4325
|
fail(new KiokuError(tailOutput(stderr, 1) || `kioku web exited with code ${code}`));
|
|
@@ -4265,7 +4345,7 @@ var init_kioku = __esm({
|
|
|
4265
4345
|
|
|
4266
4346
|
// ../core/dist/kioku-routes.js
|
|
4267
4347
|
import { Hono as Hono4 } from "hono";
|
|
4268
|
-
function kiokuRoutes(kioku = new Kioku()) {
|
|
4348
|
+
function kiokuRoutes(kioku = new Kioku(), options = {}) {
|
|
4269
4349
|
const app = new Hono4();
|
|
4270
4350
|
app.onError((err, c) => {
|
|
4271
4351
|
if (err instanceof KiokuError)
|
|
@@ -4273,6 +4353,29 @@ function kiokuRoutes(kioku = new Kioku()) {
|
|
|
4273
4353
|
throw err;
|
|
4274
4354
|
});
|
|
4275
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
|
+
});
|
|
4276
4379
|
app.post("/viewer", async (c) => c.json({ address: await kioku.openViewer() }));
|
|
4277
4380
|
app.delete("/viewer", (c) => {
|
|
4278
4381
|
kioku.stopViewer();
|
|
@@ -4280,10 +4383,12 @@ function kiokuRoutes(kioku = new Kioku()) {
|
|
|
4280
4383
|
});
|
|
4281
4384
|
return app;
|
|
4282
4385
|
}
|
|
4386
|
+
var REMEMBER_MAX;
|
|
4283
4387
|
var init_kioku_routes = __esm({
|
|
4284
4388
|
"../core/dist/kioku-routes.js"() {
|
|
4285
4389
|
"use strict";
|
|
4286
4390
|
init_kioku();
|
|
4391
|
+
REMEMBER_MAX = 500;
|
|
4287
4392
|
}
|
|
4288
4393
|
});
|
|
4289
4394
|
|
|
@@ -4332,7 +4437,11 @@ function createForgeServer(options) {
|
|
|
4332
4437
|
const terminalWss = createTerminalWss(ptyManager, cwReader, { localOnly, general: generalSessions });
|
|
4333
4438
|
app.route("/api/liveframe", liveframeRoutes());
|
|
4334
4439
|
const kioku = new Kioku({ projects: () => cwReader.getProjects() });
|
|
4335
|
-
app.route("/api/kioku", kiokuRoutes(kioku
|
|
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
|
+
}));
|
|
4336
4445
|
app.get("/api/cw/session-states", (c) => c.json({ classifier: states.remoteName ?? "local", shadow: states.isShadow, states: states.snapshot() }));
|
|
4337
4446
|
app.post("/api/cw/terminal/kill", async (c) => {
|
|
4338
4447
|
const { project, sessionDir } = await c.req.json();
|
|
@@ -4787,6 +4896,7 @@ __export(dist_exports, {
|
|
|
4787
4896
|
portInUseMessage: () => portInUseMessage,
|
|
4788
4897
|
probePort: () => probePort,
|
|
4789
4898
|
quietSqliteWarning: () => quietSqliteWarning,
|
|
4899
|
+
rememberPrompt: () => rememberPrompt,
|
|
4790
4900
|
resolveKiokuBin: () => resolveKiokuBin,
|
|
4791
4901
|
resolveListenOptions: () => resolveListenOptions,
|
|
4792
4902
|
skillRoutes: () => skillRoutes,
|