forge-cw 0.1.9 → 0.1.11
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-5ifgSOpU.js +41 -0
- package/console/index.html +1 -1
- package/cw/.bundle.json +1 -1
- package/cw/cw +15 -11
- package/dist/index.js +176 -38
- package/package.json +1 -1
- package/console/assets/index-DLM-Ix3c.js +0 -41
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-5ifgSOpU.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-N7b3C-h8.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
package/cw/.bundle.json
CHANGED
package/cw/cw
CHANGED
|
@@ -2991,19 +2991,23 @@ $(cat "$wf_file")"
|
|
|
2991
2991
|
fi
|
|
2992
2992
|
fi
|
|
2993
2993
|
|
|
2994
|
-
# ──
|
|
2995
|
-
local
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
2994
|
+
# ── Work context (init): global first, then the account's ────────
|
|
2995
|
+
local init_tpl init_ctx
|
|
2996
|
+
for init_tpl in "$CW_HOME/templates/work_init.md" "$(_account_root "$account")/templates/work_init.md"; do
|
|
2997
|
+
[[ -f "$init_tpl" ]] || continue
|
|
2998
|
+
init_ctx=$(cat "$init_tpl")
|
|
2999
|
+
init_ctx="${init_ctx//\$\{task\}/$task}"
|
|
3000
|
+
init_ctx="${init_ctx//\$\{project\}/$name}"
|
|
3001
|
+
init_ctx="${init_ctx//\$\{branch\}/$task}"
|
|
3002
3002
|
init_prompt="$init_prompt
|
|
3003
3003
|
|
|
3004
|
-
$
|
|
3005
|
-
|
|
3006
|
-
|
|
3004
|
+
$init_ctx"
|
|
3005
|
+
if [[ "$init_tpl" == "$CW_HOME/templates/work_init.md" ]]; then
|
|
3006
|
+
_dim " Work context: global"
|
|
3007
|
+
else
|
|
3008
|
+
_dim " Account context: $account"
|
|
3009
|
+
fi
|
|
3010
|
+
done
|
|
3007
3011
|
|
|
3008
3012
|
# Exclude .tasks from git (but DON'T pre-create the task dir — worktree needs it empty)
|
|
3009
3013
|
mkdir -p "$path/.tasks"
|
package/dist/index.js
CHANGED
|
@@ -656,7 +656,7 @@ var init_cw_reader = __esm({
|
|
|
656
656
|
if (!match)
|
|
657
657
|
return { frontmatter: {}, body: content };
|
|
658
658
|
const rawBlock = match[1];
|
|
659
|
-
const
|
|
659
|
+
const body2 = match[2];
|
|
660
660
|
const frontmatter = {};
|
|
661
661
|
let inMetadata = false;
|
|
662
662
|
const metadataObj = {};
|
|
@@ -680,7 +680,7 @@ var init_cw_reader = __esm({
|
|
|
680
680
|
}
|
|
681
681
|
if (Object.keys(metadataObj).length > 0)
|
|
682
682
|
frontmatter["metadata"] = metadataObj;
|
|
683
|
-
return { frontmatter, body };
|
|
683
|
+
return { frontmatter, body: body2 };
|
|
684
684
|
}
|
|
685
685
|
// directory whose skills/ holds a scope's skills; an unregistered project ref is used as a path
|
|
686
686
|
getSkillConfigDir(scope, scopeRef) {
|
|
@@ -742,7 +742,7 @@ var init_cw_reader = __esm({
|
|
|
742
742
|
return null;
|
|
743
743
|
try {
|
|
744
744
|
const content = readFileSync2(skillMd, "utf-8");
|
|
745
|
-
const { frontmatter, body } = this.parseFrontmatter(content);
|
|
745
|
+
const { frontmatter, body: body2 } = this.parseFrontmatter(content);
|
|
746
746
|
const references = [];
|
|
747
747
|
const refsDir = join2(dir, "references");
|
|
748
748
|
if (existsSync2(refsDir)) {
|
|
@@ -755,7 +755,7 @@ var init_cw_reader = __esm({
|
|
|
755
755
|
}
|
|
756
756
|
}
|
|
757
757
|
}
|
|
758
|
-
return { name: String(frontmatter["name"] ?? name), scope, scopeRef, frontmatter, body, references };
|
|
758
|
+
return { name: String(frontmatter["name"] ?? name), scope, scopeRef, frontmatter, body: body2, references };
|
|
759
759
|
} catch {
|
|
760
760
|
return null;
|
|
761
761
|
}
|
|
@@ -1311,10 +1311,10 @@ async function probeClaudeUsage(configDir, deps = {}) {
|
|
|
1311
1311
|
});
|
|
1312
1312
|
if (response.status === 401 || response.status === 403)
|
|
1313
1313
|
return { state: "expired", windows: [], detail: "Claude rejected the stored token" };
|
|
1314
|
-
const
|
|
1314
|
+
const body2 = await response.text();
|
|
1315
1315
|
if (!response.ok)
|
|
1316
1316
|
return { state: "error", windows: [], detail: `Claude answered ${response.status}` };
|
|
1317
|
-
return { state: "ok", windows: mapClaudeUsage(JSON.parse(
|
|
1317
|
+
return { state: "ok", windows: mapClaudeUsage(JSON.parse(body2)), detail: null };
|
|
1318
1318
|
} catch (err) {
|
|
1319
1319
|
return { state: "error", windows: [], detail: err instanceof Error ? err.message : String(err) };
|
|
1320
1320
|
}
|
|
@@ -1539,9 +1539,9 @@ var init_login_manager = __esm({
|
|
|
1539
1539
|
cwBin;
|
|
1540
1540
|
spawn;
|
|
1541
1541
|
entries = /* @__PURE__ */ new Map();
|
|
1542
|
-
constructor(cwBin,
|
|
1542
|
+
constructor(cwBin, spawn9 = spawnInPty) {
|
|
1543
1543
|
this.cwBin = cwBin;
|
|
1544
|
-
this.spawn =
|
|
1544
|
+
this.spawn = spawn9;
|
|
1545
1545
|
}
|
|
1546
1546
|
start(account, harness) {
|
|
1547
1547
|
const key = `${account}::${harness}`;
|
|
@@ -1825,14 +1825,14 @@ function cwRoutes(reader, options = {}) {
|
|
|
1825
1825
|
return c.json(reader.getAccounts());
|
|
1826
1826
|
});
|
|
1827
1827
|
app.post("/accounts", async (c) => {
|
|
1828
|
-
const
|
|
1829
|
-
const harness =
|
|
1830
|
-
const provider =
|
|
1831
|
-
const model =
|
|
1832
|
-
if (!
|
|
1828
|
+
const body2 = await c.req.json();
|
|
1829
|
+
const harness = body2.harness || void 0;
|
|
1830
|
+
const provider = body2.provider || void 0;
|
|
1831
|
+
const model = body2.model || void 0;
|
|
1832
|
+
if (!body2.name || !body2.name.trim()) {
|
|
1833
1833
|
return c.json({ ok: false, error: "Account name is required" }, 400);
|
|
1834
1834
|
}
|
|
1835
|
-
const trimmed =
|
|
1835
|
+
const trimmed = body2.name.trim();
|
|
1836
1836
|
if (!ACCOUNT_NAME_RE.test(trimmed)) {
|
|
1837
1837
|
return c.json({ ok: false, error: "Name must start with a letter or number and contain only letters, numbers, hyphens, and underscores (max 64 chars)" }, 400);
|
|
1838
1838
|
}
|
|
@@ -2896,11 +2896,11 @@ function skillRoutes(reader, options = {}) {
|
|
|
2896
2896
|
});
|
|
2897
2897
|
app.get("/marketplaces", (c) => c.json(listMarketplaces(reader)));
|
|
2898
2898
|
app.post("/marketplaces", async (c) => {
|
|
2899
|
-
const
|
|
2900
|
-
const source =
|
|
2899
|
+
const body2 = await c.req.json().catch(() => ({}));
|
|
2900
|
+
const source = body2.source?.trim() ?? "";
|
|
2901
2901
|
if (!source || source.startsWith("-"))
|
|
2902
2902
|
return c.json({ error: "A GitHub repo, URL or path is required" }, 400);
|
|
2903
|
-
const account = knownAccount(
|
|
2903
|
+
const account = knownAccount(body2.account);
|
|
2904
2904
|
if (!account)
|
|
2905
2905
|
return c.json({ error: "Unknown account" }, 404);
|
|
2906
2906
|
const failed = await runClaude("account", account, [["plugin", "marketplace", "add", source]], `Failed to add ${source}`);
|
|
@@ -2909,11 +2909,11 @@ function skillRoutes(reader, options = {}) {
|
|
|
2909
2909
|
return c.json({ ok: true });
|
|
2910
2910
|
});
|
|
2911
2911
|
app.post("/plugins/install", async (c) => {
|
|
2912
|
-
const
|
|
2913
|
-
const id =
|
|
2912
|
+
const body2 = await c.req.json().catch(() => ({}));
|
|
2913
|
+
const id = body2.id ?? "";
|
|
2914
2914
|
if (!PLUGIN_ID_RE.test(id))
|
|
2915
2915
|
return c.json({ error: "Invalid plugin id" }, 400);
|
|
2916
|
-
const account = knownAccount(
|
|
2916
|
+
const account = knownAccount(body2.account);
|
|
2917
2917
|
if (!account)
|
|
2918
2918
|
return c.json({ error: "Unknown account" }, 404);
|
|
2919
2919
|
const { name, marketplace } = splitPluginId(id);
|
|
@@ -3259,7 +3259,7 @@ function createJevClassifier({ apiKey, model = "jev-latest", timeoutMs = 3e3, fe
|
|
|
3259
3259
|
async classify(input) {
|
|
3260
3260
|
if (input.exitCode !== null)
|
|
3261
3261
|
return { state: "exited", confidence: 1, source: "jev" };
|
|
3262
|
-
const
|
|
3262
|
+
const body2 = {
|
|
3263
3263
|
model,
|
|
3264
3264
|
state: { harness: input.harness, seconds_since_last_output: Math.round(input.quietMs / 1e3), screen: jevScreen(input.screen ?? input.text) },
|
|
3265
3265
|
questions: {
|
|
@@ -3273,7 +3273,7 @@ function createJevClassifier({ apiKey, model = "jev-latest", timeoutMs = 3e3, fe
|
|
|
3273
3273
|
const res = await fetch2(JEV_URL, {
|
|
3274
3274
|
method: "POST",
|
|
3275
3275
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
3276
|
-
body: JSON.stringify(
|
|
3276
|
+
body: JSON.stringify(body2),
|
|
3277
3277
|
signal: AbortSignal.timeout(timeoutMs)
|
|
3278
3278
|
});
|
|
3279
3279
|
if (!res.ok)
|
|
@@ -4092,6 +4092,11 @@ import { existsSync as existsSync12 } from "node:fs";
|
|
|
4092
4092
|
import { homedir as homedir5 } from "node:os";
|
|
4093
4093
|
import { join as join13 } from "node:path";
|
|
4094
4094
|
import { createInterface } from "node:readline";
|
|
4095
|
+
import * as pty3 from "node-pty";
|
|
4096
|
+
function assertRepo(repo, what) {
|
|
4097
|
+
if (!REPO.test(repo))
|
|
4098
|
+
throw new KiokuError(`${what} is named as owner/repo, not "${repo}"`, 400);
|
|
4099
|
+
}
|
|
4095
4100
|
function resolveKiokuBin(pathVar = process.env.PATH, home = homedir5()) {
|
|
4096
4101
|
const fallback = join13(home, ".local", "bin", "kioku");
|
|
4097
4102
|
return findOnPath("kioku", pathVar) ?? (existsSync12(fallback) ? fallback : "kioku");
|
|
@@ -4099,6 +4104,26 @@ function resolveKiokuBin(pathVar = process.env.PATH, home = homedir5()) {
|
|
|
4099
4104
|
function rememberPrompt(text) {
|
|
4100
4105
|
return `Remember this with kioku: ${text.replace(/\s+/g, " ").trim()}`;
|
|
4101
4106
|
}
|
|
4107
|
+
function createTerminalRunner(env, timeoutMs) {
|
|
4108
|
+
return (bin, args, cwd) => new Promise((resolve5) => {
|
|
4109
|
+
let proc;
|
|
4110
|
+
try {
|
|
4111
|
+
proc = pty3.spawn(bin, args, { name: "xterm-256color", cols: 400, rows: 40, cwd, env });
|
|
4112
|
+
} catch {
|
|
4113
|
+
return resolve5({ code: 127, stdout: "", stderr: "ENOENT" });
|
|
4114
|
+
}
|
|
4115
|
+
let output = "";
|
|
4116
|
+
const timer = setTimeout(() => proc.kill(), timeoutMs);
|
|
4117
|
+
proc.onData((data) => {
|
|
4118
|
+
output = (output + data).slice(-64 * 1024);
|
|
4119
|
+
});
|
|
4120
|
+
proc.onExit(({ exitCode }) => {
|
|
4121
|
+
clearTimeout(timer);
|
|
4122
|
+
const json = stripAnsi(output).split(/\r?\n/).reverse().map((line) => line.slice(line.indexOf("{")).trim()).find((line) => parseJson(line) !== null);
|
|
4123
|
+
resolve5({ code: exitCode, stdout: json ?? "", stderr: output });
|
|
4124
|
+
});
|
|
4125
|
+
});
|
|
4126
|
+
}
|
|
4102
4127
|
function parseJson(text) {
|
|
4103
4128
|
try {
|
|
4104
4129
|
return JSON.parse(text);
|
|
@@ -4118,7 +4143,7 @@ async function mapLimit(items, limit, fn) {
|
|
|
4118
4143
|
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
4119
4144
|
return out;
|
|
4120
4145
|
}
|
|
4121
|
-
var PAGE_TYPES, KiokuError, NOT_INSTALLED, START_TIMEOUT_MS, WRITE_TIMEOUT_MS, CACHE_MS2, ACTIVITY_SINCE, ACTIVITY_LIMIT, PARALLEL, MISSING, brainName, Kioku;
|
|
4146
|
+
var PAGE_TYPES, KiokuError, NOT_INSTALLED, START_TIMEOUT_MS, WRITE_TIMEOUT_MS, PUBLISH_TIMEOUT_MS, CACHE_MS2, ACTIVITY_SINCE, ACTIVITY_LIMIT, PARALLEL, TIERS, REPO, AREA, LANGUAGE, MISSING, brainName, Kioku;
|
|
4122
4147
|
var init_kioku = __esm({
|
|
4123
4148
|
"../core/dist/kioku.js"() {
|
|
4124
4149
|
"use strict";
|
|
@@ -4144,15 +4169,21 @@ var init_kioku = __esm({
|
|
|
4144
4169
|
NOT_INSTALLED = "kioku is not installed on this machine";
|
|
4145
4170
|
START_TIMEOUT_MS = 2e4;
|
|
4146
4171
|
WRITE_TIMEOUT_MS = 6e4;
|
|
4172
|
+
PUBLISH_TIMEOUT_MS = 3 * 6e4;
|
|
4147
4173
|
CACHE_MS2 = 5 * 6e4;
|
|
4148
4174
|
ACTIVITY_SINCE = "2w";
|
|
4149
4175
|
ACTIVITY_LIMIT = 40;
|
|
4150
4176
|
PARALLEL = 6;
|
|
4177
|
+
TIERS = ["personal", "project", "team"];
|
|
4178
|
+
REPO = /^[\w.-]+\/[\w.-]+(\/[\w.-]+)?$/;
|
|
4179
|
+
AREA = /^[A-Za-z0-9][\w.-]*(\/[A-Za-z0-9][\w.-]*)*$/;
|
|
4180
|
+
LANGUAGE = /^[A-Za-z]{2,3}(-[A-Za-z0-9]+)*$/;
|
|
4151
4181
|
MISSING = { installed: false, version: null, brains: [], projects: {}, activity: [] };
|
|
4152
4182
|
brainName = (b) => b.name ?? b.key;
|
|
4153
4183
|
Kioku = class {
|
|
4154
4184
|
run;
|
|
4155
4185
|
write;
|
|
4186
|
+
terminal;
|
|
4156
4187
|
startTimeoutMs;
|
|
4157
4188
|
projects;
|
|
4158
4189
|
now;
|
|
@@ -4163,6 +4194,7 @@ var init_kioku = __esm({
|
|
|
4163
4194
|
this.binPath = options.bin;
|
|
4164
4195
|
this.run = options.run ?? createRunner();
|
|
4165
4196
|
this.write = options.write ?? createRunner(envWithoutHost(), WRITE_TIMEOUT_MS);
|
|
4197
|
+
this.terminal = options.terminal;
|
|
4166
4198
|
this.startTimeoutMs = options.startTimeoutMs ?? START_TIMEOUT_MS;
|
|
4167
4199
|
this.projects = options.projects ?? (() => ({}));
|
|
4168
4200
|
this.now = options.now ?? Date.now;
|
|
@@ -4244,10 +4276,7 @@ var init_kioku = __esm({
|
|
|
4244
4276
|
const type = PAGE_TYPES[input.type];
|
|
4245
4277
|
if (!type)
|
|
4246
4278
|
throw new KiokuError(`unknown page type ${input.type}`, 400);
|
|
4247
|
-
const
|
|
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);
|
|
4279
|
+
const brain = await this.findBrain(input.brain);
|
|
4251
4280
|
const args = [
|
|
4252
4281
|
"page",
|
|
4253
4282
|
"save",
|
|
@@ -4266,19 +4295,108 @@ var init_kioku = __esm({
|
|
|
4266
4295
|
...input.create ? ["--create"] : [],
|
|
4267
4296
|
"--json"
|
|
4268
4297
|
];
|
|
4269
|
-
const
|
|
4270
|
-
if (result2.code === 127)
|
|
4271
|
-
throw new KiokuError(NOT_INSTALLED, 404);
|
|
4272
|
-
const out = parseJson(result2.stdout);
|
|
4298
|
+
const { out, fail } = await this.invoke(args, brain.folder, this.write, input.body);
|
|
4273
4299
|
if (out?.status === "done" && out.path) {
|
|
4274
|
-
this.
|
|
4300
|
+
this.expire();
|
|
4275
4301
|
return { status: "done", brain: brainName(brain), path: out.path, publication: out.publication ?? null };
|
|
4276
4302
|
}
|
|
4277
4303
|
if (out?.status === "similar")
|
|
4278
4304
|
return { status: "similar", similar: (out.similar ?? []).map(({ brain: brain2, path, title, summary }) => ({ brain: brain2, path, title, summary })) };
|
|
4279
4305
|
if (out?.status === "namespace")
|
|
4280
4306
|
return { status: "namespace", namespaces: (out.namespaces ?? []).map(({ namespace, summary }) => ({ namespace, summary })) };
|
|
4281
|
-
throw
|
|
4307
|
+
throw fail("kioku could not save the page");
|
|
4308
|
+
}
|
|
4309
|
+
// the cached reading when there is one, else only the list of brains
|
|
4310
|
+
async listBrains() {
|
|
4311
|
+
return this.snapshot ? (await this.snapshot.value).brains : (await this.json(["brain", "list"], homedir5()))?.brains ?? [];
|
|
4312
|
+
}
|
|
4313
|
+
async findBrain(key) {
|
|
4314
|
+
const brain = (await this.listBrains()).find((b) => b.key === key);
|
|
4315
|
+
if (!brain?.onDisk)
|
|
4316
|
+
throw new KiokuError(`no brain ${key} on this machine`, 404);
|
|
4317
|
+
return brain;
|
|
4318
|
+
}
|
|
4319
|
+
// the next status reads again, while the old reading still answers a poll that is not fresh
|
|
4320
|
+
expire() {
|
|
4321
|
+
if (this.snapshot)
|
|
4322
|
+
this.snapshot.at = -Infinity;
|
|
4323
|
+
}
|
|
4324
|
+
// runs a verb that writes; fail turns kioku's refusal into a 400 with its message
|
|
4325
|
+
async invoke(args, cwd, run, input) {
|
|
4326
|
+
const result2 = await run(this.bin, args, cwd, input);
|
|
4327
|
+
if (result2.code === 127)
|
|
4328
|
+
throw new KiokuError(NOT_INSTALLED, 404);
|
|
4329
|
+
const out = parseJson(result2.stdout);
|
|
4330
|
+
return { out, ok: result2.code === 0 && out !== null && !out.error, fail: (fallback) => new KiokuError(out?.error?.message ?? (tailOutput(result2.stderr, 1) || fallback), 400) };
|
|
4331
|
+
}
|
|
4332
|
+
async change(args, cwd, run = this.write) {
|
|
4333
|
+
const { out, ok, fail } = await this.invoke([...args, "--json"], cwd, run);
|
|
4334
|
+
if (!ok || !out)
|
|
4335
|
+
throw fail(`kioku ${args.slice(0, 2).join(" ")} failed`);
|
|
4336
|
+
this.expire();
|
|
4337
|
+
return out;
|
|
4338
|
+
}
|
|
4339
|
+
// makes a brain on this machine only; kioku never makes a repository here
|
|
4340
|
+
async createBrain(input) {
|
|
4341
|
+
assertRepo(input.repo, "the brain");
|
|
4342
|
+
if (!TIERS.includes(input.tier))
|
|
4343
|
+
throw new KiokuError(`the tier is one of ${TIERS.join(", ")}`, 400);
|
|
4344
|
+
if (input.team)
|
|
4345
|
+
assertRepo(input.team, "the team brain");
|
|
4346
|
+
if (input.language && !LANGUAGE.test(input.language))
|
|
4347
|
+
throw new KiokuError("the language is a tag such as en or es", 400);
|
|
4348
|
+
const args = [
|
|
4349
|
+
"brain",
|
|
4350
|
+
"create",
|
|
4351
|
+
input.repo,
|
|
4352
|
+
"--tier",
|
|
4353
|
+
input.tier,
|
|
4354
|
+
...input.team && input.tier === "project" ? ["--team", input.team] : [],
|
|
4355
|
+
...input.language ? ["--language", input.language] : []
|
|
4356
|
+
];
|
|
4357
|
+
const { brain, name, path } = await this.change(args, homedir5());
|
|
4358
|
+
return { brain, name, path };
|
|
4359
|
+
}
|
|
4360
|
+
async addArea(key, area, summary) {
|
|
4361
|
+
if (!AREA.test(area))
|
|
4362
|
+
throw new KiokuError("an area is letters, digits, dots, dashes and underscores, with / between parts", 400);
|
|
4363
|
+
if (!summary)
|
|
4364
|
+
throw new KiokuError("say what the area is for", 400);
|
|
4365
|
+
const brain = await this.findBrain(key);
|
|
4366
|
+
const out = await this.change(["namespace", "add", area, `--summary=${summary}`, "--brain", brain.key], brain.folder);
|
|
4367
|
+
return { brain: out.brain, namespace: out.namespace };
|
|
4368
|
+
}
|
|
4369
|
+
// writes the project's kioku.yaml; a brain the machine does not know is made first, since kioku links only known brains without a terminal
|
|
4370
|
+
async linkProject(input) {
|
|
4371
|
+
if (!input.brain && !input.team)
|
|
4372
|
+
throw new KiokuError("name a project brain, a team brain, or both", 400);
|
|
4373
|
+
const path = this.projects()[input.project]?.path;
|
|
4374
|
+
if (!path || !existsSync12(path))
|
|
4375
|
+
throw new KiokuError(`no CW project ${input.project} on this machine`, 404);
|
|
4376
|
+
if (input.brain)
|
|
4377
|
+
assertRepo(input.brain, "the project brain");
|
|
4378
|
+
if (input.team)
|
|
4379
|
+
assertRepo(input.team, "the team brain");
|
|
4380
|
+
const known = await this.listBrains();
|
|
4381
|
+
const isKnown = (repo) => known.some((b) => b.key === repo || b.key.endsWith(`/${repo}`));
|
|
4382
|
+
const created = [];
|
|
4383
|
+
if (input.team && !isKnown(input.team))
|
|
4384
|
+
created.push((await this.createBrain({ repo: input.team, tier: "team" })).brain);
|
|
4385
|
+
if (input.brain && !isKnown(input.brain))
|
|
4386
|
+
created.push((await this.createBrain({ repo: input.brain, tier: "project", team: input.team })).brain);
|
|
4387
|
+
const args = ["init", ...input.brain ? [input.brain] : [], ...input.team ? ["--team", input.team] : []];
|
|
4388
|
+
const { file } = await this.change(args, path);
|
|
4389
|
+
return { file, created };
|
|
4390
|
+
}
|
|
4391
|
+
// the only verb that reaches a git host: without `to`, kioku makes a private GitHub repository with gh
|
|
4392
|
+
async publish(key, to) {
|
|
4393
|
+
if (to?.startsWith("-"))
|
|
4394
|
+
throw new KiokuError("the address cannot start with -", 400);
|
|
4395
|
+
const brain = await this.findBrain(key);
|
|
4396
|
+
if (brain.remote)
|
|
4397
|
+
throw new KiokuError(`${brainName(brain)} already has a repository: ${brain.remote}`, 400);
|
|
4398
|
+
const out = await this.change(["brain", "publish", brain.key, ...to ? ["--to", to] : []], brain.folder, this.terminal ??= createTerminalRunner(envWithoutHost(), PUBLISH_TIMEOUT_MS));
|
|
4399
|
+
return { brain: out.brain, status: out.status, remote: out.remote ?? null };
|
|
4282
4400
|
}
|
|
4283
4401
|
// one viewer at a time; a second call reuses the running one
|
|
4284
4402
|
async openViewer() {
|
|
@@ -4363,6 +4481,24 @@ function kiokuRoutes(kioku = new Kioku(), options = {}) {
|
|
|
4363
4481
|
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
4482
|
return c.json(result2, result2.status === "done" ? 201 : 200);
|
|
4365
4483
|
});
|
|
4484
|
+
app.post("/brains", async (c) => {
|
|
4485
|
+
const { repo, tier, team, language } = await body(c);
|
|
4486
|
+
return c.json(await kioku.createBrain({ repo: trim(repo) ?? "", tier: tier ?? "", team: trim(team), language: trim(language) }), 201);
|
|
4487
|
+
});
|
|
4488
|
+
app.post("/areas", async (c) => {
|
|
4489
|
+
const { brain, area, summary } = await body(c);
|
|
4490
|
+
return c.json(await kioku.addArea(brain ?? "", trim(area) ?? "", trim(summary) ?? ""), 201);
|
|
4491
|
+
});
|
|
4492
|
+
app.post("/link", async (c) => {
|
|
4493
|
+
const { project, brain, team } = await body(c);
|
|
4494
|
+
return c.json(await kioku.linkProject({ project: project ?? "", brain: trim(brain), team: trim(team) }));
|
|
4495
|
+
});
|
|
4496
|
+
app.post("/publish", async (c) => {
|
|
4497
|
+
const { brain, to, understood } = await body(c);
|
|
4498
|
+
if (understood !== true)
|
|
4499
|
+
return c.json({ error: "publishing needs the warning accepted" }, 400);
|
|
4500
|
+
return c.json(await kioku.publish(brain ?? "", trim(to)));
|
|
4501
|
+
});
|
|
4366
4502
|
app.post("/remember", async (c) => {
|
|
4367
4503
|
const { project, sessionDir, text } = await c.req.json().catch(() => ({}));
|
|
4368
4504
|
const line = text?.trim();
|
|
@@ -4383,12 +4519,14 @@ function kiokuRoutes(kioku = new Kioku(), options = {}) {
|
|
|
4383
4519
|
});
|
|
4384
4520
|
return app;
|
|
4385
4521
|
}
|
|
4386
|
-
var REMEMBER_MAX;
|
|
4522
|
+
var trim, REMEMBER_MAX, body;
|
|
4387
4523
|
var init_kioku_routes = __esm({
|
|
4388
4524
|
"../core/dist/kioku-routes.js"() {
|
|
4389
4525
|
"use strict";
|
|
4390
4526
|
init_kioku();
|
|
4527
|
+
trim = (value) => value?.trim() || void 0;
|
|
4391
4528
|
REMEMBER_MAX = 500;
|
|
4529
|
+
body = (c) => c.req.json().catch(() => ({}));
|
|
4392
4530
|
}
|
|
4393
4531
|
});
|
|
4394
4532
|
|
|
@@ -4570,8 +4708,8 @@ data: ${JSON.stringify(data)}
|
|
|
4570
4708
|
});
|
|
4571
4709
|
app.put("/api/modules/:module/settings", async (c) => {
|
|
4572
4710
|
const moduleId = c.req.param("module");
|
|
4573
|
-
const
|
|
4574
|
-
for (const [key, value] of Object.entries(
|
|
4711
|
+
const body2 = await c.req.json();
|
|
4712
|
+
for (const [key, value] of Object.entries(body2)) {
|
|
4575
4713
|
db.setModuleSetting(moduleId, key, String(value));
|
|
4576
4714
|
}
|
|
4577
4715
|
return c.json({ ok: true });
|
|
@@ -4839,8 +4977,8 @@ function waitForListening(server) {
|
|
|
4839
4977
|
async function probePort(port, timeoutMs = 1500) {
|
|
4840
4978
|
try {
|
|
4841
4979
|
const res = await fetch(`http://127.0.0.1:${port}/api/health`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
4842
|
-
const
|
|
4843
|
-
return
|
|
4980
|
+
const body2 = await res.json().catch(() => null);
|
|
4981
|
+
return body2?.status === "ok" && typeof body2.modules === "number" ? "forge" : "other";
|
|
4844
4982
|
} catch (err) {
|
|
4845
4983
|
return err.name === "TimeoutError" ? "unresponsive" : "other";
|
|
4846
4984
|
}
|