forge-cw 0.1.13 → 0.1.14
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-GXv21xGs.css → index-DPf7M8cb.css} +1 -1
- package/console/assets/index-DvMplpgv.js +31 -0
- package/console/index.html +2 -2
- package/console/shared/forge-dev-ui.js +2 -2
- package/dist/index.js +262 -94
- package/package.json +1 -1
- package/console/assets/index-CC7_QwVk.js +0 -31
package/dist/index.js
CHANGED
|
@@ -7173,7 +7173,7 @@ var init_module_context = __esm({
|
|
|
7173
7173
|
|
|
7174
7174
|
// ../core/dist/module-host.js
|
|
7175
7175
|
import { existsSync as existsSync13, readdirSync as readdirSync7, readFileSync as readFileSync11, statSync as statSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
7176
|
-
import { basename as basename4, dirname as dirname7, join as join16, resolve as resolve6 } from "node:path";
|
|
7176
|
+
import { basename as basename4, dirname as dirname7, join as join16, resolve as resolve6, sep as sep4 } from "node:path";
|
|
7177
7177
|
import { pathToFileURL } from "node:url";
|
|
7178
7178
|
function disabled(id, c, reason, m) {
|
|
7179
7179
|
const text = (v, fallback) => typeof v === "string" && v ? v : fallback;
|
|
@@ -7268,6 +7268,7 @@ var init_module_host = __esm({
|
|
|
7268
7268
|
opts;
|
|
7269
7269
|
statuses = [];
|
|
7270
7270
|
loaded = /* @__PURE__ */ new Map();
|
|
7271
|
+
origins = /* @__PURE__ */ new Map();
|
|
7271
7272
|
constructor(opts) {
|
|
7272
7273
|
this.opts = opts;
|
|
7273
7274
|
}
|
|
@@ -7277,6 +7278,16 @@ var init_module_host = __esm({
|
|
|
7277
7278
|
get(id) {
|
|
7278
7279
|
return this.loaded.get(id);
|
|
7279
7280
|
}
|
|
7281
|
+
// where a listed module was found, loaded or not; the first one found wins
|
|
7282
|
+
locate(id) {
|
|
7283
|
+
const c = this.origins.get(id);
|
|
7284
|
+
return c && { dir: c.dir, removable: this.removable(c) };
|
|
7285
|
+
}
|
|
7286
|
+
// only what was installed into the modules folder; bundled, dev and Forge's own packages stay
|
|
7287
|
+
removable(c) {
|
|
7288
|
+
const { modulesDir } = this.opts;
|
|
7289
|
+
return c.source === "installed" && dirname7(c.dir) === modulesDir || c.dir.startsWith(join16(modulesDir, "node_modules") + sep4);
|
|
7290
|
+
}
|
|
7280
7291
|
// scans, reloads and dev changes run one at a time: each one awaits imports midway
|
|
7281
7292
|
queue = Promise.resolve();
|
|
7282
7293
|
serial(fn) {
|
|
@@ -7293,12 +7304,17 @@ var init_module_host = __esm({
|
|
|
7293
7304
|
this.unload(id);
|
|
7294
7305
|
const statuses = [];
|
|
7295
7306
|
const seen = /* @__PURE__ */ new Map();
|
|
7307
|
+
const origins = /* @__PURE__ */ new Map();
|
|
7296
7308
|
for (const candidate of this.candidates()) {
|
|
7297
|
-
const status = await this.
|
|
7298
|
-
if (status)
|
|
7299
|
-
|
|
7309
|
+
const status = await this.load(candidate, seen);
|
|
7310
|
+
if (!status)
|
|
7311
|
+
continue;
|
|
7312
|
+
statuses.push(status);
|
|
7313
|
+
if (!origins.has(status.id))
|
|
7314
|
+
origins.set(status.id, candidate);
|
|
7300
7315
|
}
|
|
7301
7316
|
this.statuses = statuses;
|
|
7317
|
+
this.origins = origins;
|
|
7302
7318
|
return statuses;
|
|
7303
7319
|
}
|
|
7304
7320
|
// only this module is unloaded and loaded again; the others keep running
|
|
@@ -7312,7 +7328,7 @@ var init_module_host = __esm({
|
|
|
7312
7328
|
} else {
|
|
7313
7329
|
this.unload(id);
|
|
7314
7330
|
const seen = new Map([...this.loaded.values()].map((m) => [m.status.id, m.dir]));
|
|
7315
|
-
const status = await this.
|
|
7331
|
+
const status = await this.load(mod.candidate, seen);
|
|
7316
7332
|
this.statuses = this.statuses.flatMap((s) => s === mod.status ? status ? [status] : [] : [s]);
|
|
7317
7333
|
}
|
|
7318
7334
|
}
|
|
@@ -7394,6 +7410,12 @@ var init_module_host = __esm({
|
|
|
7394
7410
|
out.push(...packages(this.opts.forgeNodeModules, "node_modules", false));
|
|
7395
7411
|
return out;
|
|
7396
7412
|
}
|
|
7413
|
+
async load(c, seen) {
|
|
7414
|
+
const status = await this.loadCandidate(c, seen);
|
|
7415
|
+
if (status && this.removable(c))
|
|
7416
|
+
status.removable = true;
|
|
7417
|
+
return status;
|
|
7418
|
+
}
|
|
7397
7419
|
async loadCandidate(c, seen) {
|
|
7398
7420
|
const fallbackId = basename4(c.dir);
|
|
7399
7421
|
if (!existsSync13(c.manifestFile)) {
|
|
@@ -7534,9 +7556,186 @@ var init_module_confirmations = __esm({
|
|
|
7534
7556
|
}
|
|
7535
7557
|
});
|
|
7536
7558
|
|
|
7559
|
+
// ../core/dist/module-install.js
|
|
7560
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
7561
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5, mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync3 } from "node:fs";
|
|
7562
|
+
import { homedir as homedir7, tmpdir } from "node:os";
|
|
7563
|
+
import { isAbsolute as isAbsolute2, join as join17, resolve as resolve7, sep as sep5 } from "node:path";
|
|
7564
|
+
async function previewManifest(spec, opts) {
|
|
7565
|
+
if (isPathSpec(spec, opts.cwd))
|
|
7566
|
+
return readFolder(expand(spec, opts.cwd));
|
|
7567
|
+
const tmp = mkdtempSync(join17(tmpdir(), "forge-module-pack-"));
|
|
7568
|
+
try {
|
|
7569
|
+
const packed = await opts.run("npm", ["pack", spec, "--pack-destination", tmp, "--ignore-scripts", "--json"], opts.cwd);
|
|
7570
|
+
if (packed.code !== 0)
|
|
7571
|
+
throw new Error(npmPackError(spec, packed.stderr || packed.stdout));
|
|
7572
|
+
const info = JSON.parse(packed.stdout)[0];
|
|
7573
|
+
const filename = info?.filename;
|
|
7574
|
+
if (!filename)
|
|
7575
|
+
throw new Error(`npm pack ${spec} produced no tarball`);
|
|
7576
|
+
const untar = await opts.run("tar", ["-xzf", join17(tmp, filename), "-C", tmp], opts.cwd);
|
|
7577
|
+
if (untar.code !== 0)
|
|
7578
|
+
throw new Error(`could not unpack ${filename}: ${untar.stderr.trim()}`);
|
|
7579
|
+
const preview = readFolder(join17(tmp, "package"));
|
|
7580
|
+
return info.name && info.version ? { ...preview, packed: { name: info.name, version: info.version } } : preview;
|
|
7581
|
+
} finally {
|
|
7582
|
+
rmSync3(tmp, { recursive: true, force: true });
|
|
7583
|
+
}
|
|
7584
|
+
}
|
|
7585
|
+
function npmPackError(spec, output) {
|
|
7586
|
+
if (/\bE404\b/.test(output))
|
|
7587
|
+
return `${spec} is not on npm`;
|
|
7588
|
+
const lines2 = output.trim().split("\n").filter((l) => !l.includes("A complete log of this run"));
|
|
7589
|
+
return `npm pack ${spec} failed: ${lines2.join("\n")}`;
|
|
7590
|
+
}
|
|
7591
|
+
function readFolder(dir) {
|
|
7592
|
+
const manifestFile = manifestFileFor(dir).file;
|
|
7593
|
+
if (!existsSync14(manifestFile))
|
|
7594
|
+
throw new Error(`${dir} has no forge-module.json`);
|
|
7595
|
+
const raw = JSON.parse(readFileSync12(manifestFile, "utf-8"));
|
|
7596
|
+
const check = checkManifest(raw, dir);
|
|
7597
|
+
if (check.kind === "invalid")
|
|
7598
|
+
throw new Error(`invalid manifest: ${check.reason}`);
|
|
7599
|
+
return check;
|
|
7600
|
+
}
|
|
7601
|
+
function permissionLines(manifest) {
|
|
7602
|
+
const p = "permissions" in manifest ? manifest.permissions ?? {} : {};
|
|
7603
|
+
const lines2 = [];
|
|
7604
|
+
for (const kind of ["network", "fs:read", "fs:write", "shell"]) {
|
|
7605
|
+
for (const value of p[kind] ?? [])
|
|
7606
|
+
lines2.push({ kind, value });
|
|
7607
|
+
}
|
|
7608
|
+
if (p.pty)
|
|
7609
|
+
lines2.push({ kind: "pty", value: "yes" });
|
|
7610
|
+
const legacy = "actions" in manifest && (manifest.actions ?? []).some((a) => "command" in a);
|
|
7611
|
+
if (legacy)
|
|
7612
|
+
lines2.push({ kind: "shell", value: "runs the shell commands in its manifest" });
|
|
7613
|
+
return lines2;
|
|
7614
|
+
}
|
|
7615
|
+
function describePermissions(manifest) {
|
|
7616
|
+
const lines2 = permissionLines(manifest).map((l) => ` ${l.kind.padEnd(9)} ${l.value}`);
|
|
7617
|
+
return [lines2.length ? "It asks for:" : "It asks for no permissions.", ...lines2, "", TRUST_NOTE].join("\n");
|
|
7618
|
+
}
|
|
7619
|
+
function installArgs(spec, prefix) {
|
|
7620
|
+
return ["install", "--ignore-scripts", "--prefix", prefix, spec];
|
|
7621
|
+
}
|
|
7622
|
+
function packageName(dir) {
|
|
7623
|
+
try {
|
|
7624
|
+
const name = JSON.parse(readFileSync12(join17(dir, "package.json"), "utf-8")).name;
|
|
7625
|
+
return typeof name === "string" ? name : void 0;
|
|
7626
|
+
} catch {
|
|
7627
|
+
return void 0;
|
|
7628
|
+
}
|
|
7629
|
+
}
|
|
7630
|
+
var TRUST_NOTE, isPathSpec, expand, InstallError, PREVIEW_TTL_MS, REGISTRY_SPEC, ModuleInstaller;
|
|
7631
|
+
var init_module_install = __esm({
|
|
7632
|
+
"../core/dist/module-install.js"() {
|
|
7633
|
+
"use strict";
|
|
7634
|
+
init_module_manifest();
|
|
7635
|
+
init_output();
|
|
7636
|
+
init_state_log();
|
|
7637
|
+
TRUST_NOTE = "Modules run inside Forge with your access. Forge checks every call against these permissions, but it is not a sandbox: install only modules you trust.";
|
|
7638
|
+
isPathSpec = (spec, cwd) => spec.startsWith(".") || spec.startsWith("~") || isAbsolute2(spec) || existsSync14(resolve7(cwd, spec)) && !spec.startsWith("@");
|
|
7639
|
+
expand = (spec, cwd) => resolve7(cwd, expandHome(spec));
|
|
7640
|
+
InstallError = class extends Error {
|
|
7641
|
+
code;
|
|
7642
|
+
constructor(code, message) {
|
|
7643
|
+
super(message);
|
|
7644
|
+
this.code = code;
|
|
7645
|
+
}
|
|
7646
|
+
};
|
|
7647
|
+
PREVIEW_TTL_MS = 10 * 6e4;
|
|
7648
|
+
REGISTRY_SPEC = /^(@[^/@\s]+\/)?[^/@\s:]+(@[^\s/:]*)?$/;
|
|
7649
|
+
ModuleInstaller = class {
|
|
7650
|
+
opts;
|
|
7651
|
+
previews = /* @__PURE__ */ new Map();
|
|
7652
|
+
busy = false;
|
|
7653
|
+
constructor(opts) {
|
|
7654
|
+
this.opts = opts;
|
|
7655
|
+
}
|
|
7656
|
+
get now() {
|
|
7657
|
+
return (this.opts.now ?? Date.now)();
|
|
7658
|
+
}
|
|
7659
|
+
async preview(spec) {
|
|
7660
|
+
const cwd = this.opts.cwd ?? homedir7();
|
|
7661
|
+
const trimmed = spec.trim();
|
|
7662
|
+
if (!trimmed)
|
|
7663
|
+
throw new InstallError("bad_request", "Give an npm package, a folder or a git url");
|
|
7664
|
+
let preview;
|
|
7665
|
+
try {
|
|
7666
|
+
preview = await previewManifest(trimmed, { cwd, run: this.opts.run });
|
|
7667
|
+
} catch (err) {
|
|
7668
|
+
throw new InstallError("bad_request", err.message);
|
|
7669
|
+
}
|
|
7670
|
+
const m = preview.manifest;
|
|
7671
|
+
const pinned = isPathSpec(trimmed, cwd) ? expand(trimmed, cwd) : preview.packed && REGISTRY_SPEC.test(trimmed) ? `${preview.packed.name}@${preview.packed.version}` : trimmed;
|
|
7672
|
+
for (const [t, p] of this.previews)
|
|
7673
|
+
if (p.expires < this.now)
|
|
7674
|
+
this.previews.delete(t);
|
|
7675
|
+
const token = randomBytes2(24).toString("base64url");
|
|
7676
|
+
this.previews.set(token, { spec: pinned, name: m.name, expires: this.now + PREVIEW_TTL_MS });
|
|
7677
|
+
return {
|
|
7678
|
+
token,
|
|
7679
|
+
kind: preview.kind,
|
|
7680
|
+
name: m.name,
|
|
7681
|
+
version: m.version,
|
|
7682
|
+
displayName: m.displayName,
|
|
7683
|
+
description: "description" in m && typeof m.description === "string" ? m.description : void 0,
|
|
7684
|
+
spec: pinned,
|
|
7685
|
+
permissions: permissionLines(m),
|
|
7686
|
+
trustNote: TRUST_NOTE
|
|
7687
|
+
};
|
|
7688
|
+
}
|
|
7689
|
+
async install(token) {
|
|
7690
|
+
const p = this.previews.get(token);
|
|
7691
|
+
this.previews.delete(token);
|
|
7692
|
+
if (!p || p.expires < this.now)
|
|
7693
|
+
throw new InstallError("confirmation_required", "Preview the module again before installing it");
|
|
7694
|
+
return this.exclusive(async () => {
|
|
7695
|
+
mkdirSync5(this.opts.modulesDir, { recursive: true });
|
|
7696
|
+
const res = await this.opts.run("npm", installArgs(p.spec, this.opts.modulesDir), this.opts.modulesDir);
|
|
7697
|
+
if (res.code !== 0)
|
|
7698
|
+
throw new InstallError("install_failed", `npm install failed: ${tailOutput(res.stderr || res.stdout, 6)}`);
|
|
7699
|
+
const list = await this.opts.host.scan();
|
|
7700
|
+
return list.find((s) => s.name === p.name);
|
|
7701
|
+
});
|
|
7702
|
+
}
|
|
7703
|
+
remove(id) {
|
|
7704
|
+
return this.exclusive(async () => {
|
|
7705
|
+
const found = this.opts.host.locate(id);
|
|
7706
|
+
if (!found)
|
|
7707
|
+
throw new InstallError("not_found", `No module ${id}`);
|
|
7708
|
+
if (!found.removable)
|
|
7709
|
+
throw new InstallError("bad_request", `${id} was not installed into Forge's modules folder; a dev folder unloads when forge module dev stops`);
|
|
7710
|
+
const packages2 = join17(this.opts.modulesDir, "node_modules") + sep5;
|
|
7711
|
+
if (found.dir.startsWith(packages2)) {
|
|
7712
|
+
const name = packageName(found.dir) ?? found.dir.slice(packages2.length);
|
|
7713
|
+
const res = await this.opts.run("npm", ["uninstall", "--prefix", this.opts.modulesDir, name], this.opts.modulesDir);
|
|
7714
|
+
if (res.code !== 0)
|
|
7715
|
+
throw new InstallError("install_failed", `npm uninstall failed: ${tailOutput(res.stderr || res.stdout, 6)}`);
|
|
7716
|
+
} else {
|
|
7717
|
+
rmSync3(found.dir, { recursive: true, force: true });
|
|
7718
|
+
}
|
|
7719
|
+
await this.opts.host.scan();
|
|
7720
|
+
});
|
|
7721
|
+
}
|
|
7722
|
+
async exclusive(fn) {
|
|
7723
|
+
if (this.busy)
|
|
7724
|
+
throw new InstallError("busy", "Another module is being installed or removed");
|
|
7725
|
+
this.busy = true;
|
|
7726
|
+
try {
|
|
7727
|
+
return await fn();
|
|
7728
|
+
} finally {
|
|
7729
|
+
this.busy = false;
|
|
7730
|
+
}
|
|
7731
|
+
}
|
|
7732
|
+
};
|
|
7733
|
+
}
|
|
7734
|
+
});
|
|
7735
|
+
|
|
7537
7736
|
// ../core/dist/module-routes.js
|
|
7538
7737
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
7539
|
-
import { resolve as
|
|
7738
|
+
import { resolve as resolve8 } from "node:path";
|
|
7540
7739
|
import { Hono as Hono5 } from "hono";
|
|
7541
7740
|
function moduleRoutes(host, opts) {
|
|
7542
7741
|
const { db } = opts;
|
|
@@ -7560,6 +7759,26 @@ function moduleRoutes(host, opts) {
|
|
|
7560
7759
|
await host.removeDev(path);
|
|
7561
7760
|
return c.json(host.list());
|
|
7562
7761
|
});
|
|
7762
|
+
const installing = async (c, fn) => {
|
|
7763
|
+
if (!opts.installer)
|
|
7764
|
+
return c.json({ code: "local_only", message: "Modules are installed only in local mode" }, 403);
|
|
7765
|
+
try {
|
|
7766
|
+
return await fn(opts.installer);
|
|
7767
|
+
} catch (err) {
|
|
7768
|
+
if (err instanceof InstallError)
|
|
7769
|
+
return c.json({ code: err.code, message: err.message }, STATUS_BY_CODE[err.code]);
|
|
7770
|
+
throw err;
|
|
7771
|
+
}
|
|
7772
|
+
};
|
|
7773
|
+
app.post("/install/preview", (c) => installing(c, async (installer) => {
|
|
7774
|
+
const { spec } = await c.req.json();
|
|
7775
|
+
return c.json(await installer.preview(typeof spec === "string" ? spec : ""));
|
|
7776
|
+
}));
|
|
7777
|
+
app.post("/install", (c) => installing(c, async (installer) => {
|
|
7778
|
+
const { token } = await c.req.json();
|
|
7779
|
+
const status = await installer.install(typeof token === "string" ? token : "");
|
|
7780
|
+
return c.json({ status, modules: host.list() });
|
|
7781
|
+
}));
|
|
7563
7782
|
app.get("/notifications", (c) => c.json({
|
|
7564
7783
|
items: db.listNotifications().map((n) => ({
|
|
7565
7784
|
moduleId: n.moduleId,
|
|
@@ -7581,6 +7800,10 @@ function moduleRoutes(host, opts) {
|
|
|
7581
7800
|
await host.reload(c.req.param("id"));
|
|
7582
7801
|
return c.json(host.list().find((s) => s.id === c.req.param("id")) ?? removed(c.req.param("id")));
|
|
7583
7802
|
});
|
|
7803
|
+
app.delete("/:id", (c) => installing(c, async (installer) => {
|
|
7804
|
+
await installer.remove(c.req.param("id"));
|
|
7805
|
+
return c.json(host.list());
|
|
7806
|
+
}));
|
|
7584
7807
|
app.get("/:id/status", (c) => {
|
|
7585
7808
|
const status = host.list().find((s) => s.id === c.req.param("id"));
|
|
7586
7809
|
return status ? c.json(status) : c.json(removed(c.req.param("id")), 404);
|
|
@@ -7605,7 +7828,7 @@ function moduleRoutes(host, opts) {
|
|
|
7605
7828
|
if (!mod?.manifest.view)
|
|
7606
7829
|
return c.json(removed(c.req.param("id")), 404);
|
|
7607
7830
|
try {
|
|
7608
|
-
const code = await readFile2(
|
|
7831
|
+
const code = await readFile2(resolve8(mod.dir, mod.manifest.view.entry), "utf-8");
|
|
7609
7832
|
return c.body(code, 200, { "Content-Type": "text/javascript; charset=utf-8", "Cache-Control": "no-cache" });
|
|
7610
7833
|
} catch (err) {
|
|
7611
7834
|
return c.json({ code: "not_found", message: `view bundle missing: ${err.message}` }, 404);
|
|
@@ -7806,12 +8029,16 @@ var init_module_routes = __esm({
|
|
|
7806
8029
|
init_dist();
|
|
7807
8030
|
init_module_manifest();
|
|
7808
8031
|
init_module_confirmations();
|
|
8032
|
+
init_module_install();
|
|
7809
8033
|
STATUS_BY_CODE = {
|
|
7810
8034
|
bad_request: 400,
|
|
7811
8035
|
unauthorized: 401,
|
|
7812
8036
|
not_found: 404,
|
|
7813
8037
|
unreachable: 502,
|
|
7814
|
-
upstream: 502
|
|
8038
|
+
upstream: 502,
|
|
8039
|
+
busy: 409,
|
|
8040
|
+
confirmation_required: 409,
|
|
8041
|
+
install_failed: 502
|
|
7815
8042
|
};
|
|
7816
8043
|
}
|
|
7817
8044
|
});
|
|
@@ -7949,12 +8176,12 @@ var init_module_events = __esm({
|
|
|
7949
8176
|
// ../core/dist/server.js
|
|
7950
8177
|
import { Hono as Hono6 } from "hono";
|
|
7951
8178
|
import { cors } from "hono/cors";
|
|
7952
|
-
import { join as
|
|
7953
|
-
import { readdirSync as readdirSync8, existsSync as
|
|
8179
|
+
import { join as join18, basename as basename5, resolve as resolve9 } from "node:path";
|
|
8180
|
+
import { readdirSync as readdirSync8, existsSync as existsSync15 } from "node:fs";
|
|
7954
8181
|
function createForgeServer(options) {
|
|
7955
8182
|
const { dataDir, db: externalDb, authToken, localOnly = true } = options;
|
|
7956
|
-
const dbPath =
|
|
7957
|
-
const modulesDir =
|
|
8183
|
+
const dbPath = join18(dataDir, "forge.db");
|
|
8184
|
+
const modulesDir = join18(dataDir, "modules");
|
|
7958
8185
|
const db = externalDb ?? new ForgeDB(dbPath);
|
|
7959
8186
|
const loader = new ModuleLoader(modulesDir, options.bundledModulesDir);
|
|
7960
8187
|
const runner = new ActionRunner();
|
|
@@ -7967,7 +8194,7 @@ function createForgeServer(options) {
|
|
|
7967
8194
|
const cwReader = new CWReader(options.cwDir);
|
|
7968
8195
|
const loginManager = new LoginManager(resolveCwBin(cwReader.cwHome));
|
|
7969
8196
|
app.route("/api/cw", cwRoutes(cwReader, { loginManager, localOnly, events: { emit: (name, payload) => moduleEvents.emit(name, payload) } }));
|
|
7970
|
-
app.route("/api/skills", skillRoutes(cwReader, { backupDir:
|
|
8197
|
+
app.route("/api/skills", skillRoutes(cwReader, { backupDir: join18(dataDir, "skill-backups") }));
|
|
7971
8198
|
const { remote, shadow, warning } = remoteClassifierFromEnv();
|
|
7972
8199
|
if (warning)
|
|
7973
8200
|
console.warn(`[state] ${warning}`);
|
|
@@ -7987,7 +8214,7 @@ function createForgeServer(options) {
|
|
|
7987
8214
|
}
|
|
7988
8215
|
});
|
|
7989
8216
|
const ptyManager = new PTYManager(states);
|
|
7990
|
-
const generalSessions = new GeneralSessions(
|
|
8217
|
+
const generalSessions = new GeneralSessions(join18(dataDir, "general-sessions.json"));
|
|
7991
8218
|
const terminalWss = createTerminalWss(ptyManager, cwReader, { localOnly, general: generalSessions });
|
|
7992
8219
|
app.route("/api/liveframe", liveframeRoutes());
|
|
7993
8220
|
const kioku = new Kioku({ projects: () => cwReader.getProjects() });
|
|
@@ -8021,7 +8248,7 @@ function createForgeServer(options) {
|
|
|
8021
8248
|
modulesDir,
|
|
8022
8249
|
bundledDir: options.bundledModulesDir,
|
|
8023
8250
|
forgeNodeModules: options.forgeNodeModules,
|
|
8024
|
-
devFile:
|
|
8251
|
+
devFile: join18(dataDir, "dev-modules.json"),
|
|
8025
8252
|
localOnly,
|
|
8026
8253
|
deps: { db, reader: cwReader, runner },
|
|
8027
8254
|
hooks: {
|
|
@@ -8052,7 +8279,8 @@ function createForgeServer(options) {
|
|
|
8052
8279
|
});
|
|
8053
8280
|
ptyManager.onLifecycle = (event, session) => moduleEvents.emit(event === "started" ? "session.started" : "session.ended", sessionEvent(session));
|
|
8054
8281
|
const modulesReady = moduleHost.scan().catch((err) => console.error(`[modules] scan failed: ${err.message}`));
|
|
8055
|
-
|
|
8282
|
+
const installer = localOnly ? new ModuleInstaller({ modulesDir, host: moduleHost, run: createRunner(void 0, 5 * 6e4) }) : void 0;
|
|
8283
|
+
app.route("/api/modules", moduleRoutes(moduleHost, { db, ready: modulesReady, installer }));
|
|
8056
8284
|
app.get("/api/cw/session-states", (c) => c.json({ classifier: states.remoteName ?? "local", shadow: states.isShadow, states: states.snapshot() }));
|
|
8057
8285
|
app.post("/api/cw/terminal/kill", async (c) => {
|
|
8058
8286
|
const { project, sessionDir } = await c.req.json();
|
|
@@ -8184,25 +8412,25 @@ data: ${JSON.stringify(data)}
|
|
|
8184
8412
|
});
|
|
8185
8413
|
app.get("/api/filesystem/browse", (c) => {
|
|
8186
8414
|
const home = process.env.HOME ?? "/tmp";
|
|
8187
|
-
const dir =
|
|
8415
|
+
const dir = resolve9(c.req.query("path") ?? process.cwd());
|
|
8188
8416
|
if (!dir.startsWith(home)) {
|
|
8189
8417
|
return c.json({ error: "Access denied: path must be within home directory" }, 403);
|
|
8190
8418
|
}
|
|
8191
|
-
if (!
|
|
8419
|
+
if (!existsSync15(dir)) {
|
|
8192
8420
|
return c.json({ error: "Directory not found" }, 404);
|
|
8193
8421
|
}
|
|
8194
8422
|
try {
|
|
8195
8423
|
const entries = readdirSync8(dir, { withFileTypes: true });
|
|
8196
8424
|
const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({
|
|
8197
8425
|
name: e.name,
|
|
8198
|
-
path:
|
|
8199
|
-
hasPackageJson:
|
|
8200
|
-
hasGit:
|
|
8426
|
+
path: join18(dir, e.name),
|
|
8427
|
+
hasPackageJson: existsSync15(join18(dir, e.name, "package.json")),
|
|
8428
|
+
hasGit: existsSync15(join18(dir, e.name, ".git"))
|
|
8201
8429
|
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
8202
8430
|
return c.json({
|
|
8203
8431
|
current: dir,
|
|
8204
8432
|
name: basename5(dir),
|
|
8205
|
-
parent:
|
|
8433
|
+
parent: join18(dir, ".."),
|
|
8206
8434
|
directories: dirs
|
|
8207
8435
|
});
|
|
8208
8436
|
} catch {
|
|
@@ -8249,6 +8477,8 @@ var init_server = __esm({
|
|
|
8249
8477
|
init_kioku_routes();
|
|
8250
8478
|
init_module_host();
|
|
8251
8479
|
init_module_routes();
|
|
8480
|
+
init_module_install();
|
|
8481
|
+
init_task_review();
|
|
8252
8482
|
init_module_jobs();
|
|
8253
8483
|
init_module_events();
|
|
8254
8484
|
init_kioku();
|
|
@@ -8415,22 +8645,22 @@ var init_db_factory = __esm({
|
|
|
8415
8645
|
});
|
|
8416
8646
|
|
|
8417
8647
|
// ../core/dist/init.js
|
|
8418
|
-
import { existsSync as
|
|
8419
|
-
import { join as
|
|
8420
|
-
import { homedir as
|
|
8648
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "node:fs";
|
|
8649
|
+
import { join as join19 } from "node:path";
|
|
8650
|
+
import { homedir as homedir8 } from "node:os";
|
|
8421
8651
|
function getForgeDir() {
|
|
8422
|
-
return
|
|
8652
|
+
return join19(homedir8(), ".forge");
|
|
8423
8653
|
}
|
|
8424
8654
|
function ensureForgeDir() {
|
|
8425
8655
|
const forgeDir = getForgeDir();
|
|
8426
|
-
if (
|
|
8656
|
+
if (existsSync16(forgeDir)) {
|
|
8427
8657
|
return { forgeDir, created: false };
|
|
8428
8658
|
}
|
|
8429
|
-
|
|
8659
|
+
mkdirSync6(forgeDir, { recursive: true });
|
|
8430
8660
|
for (const d of FORGE_SUBDIRS) {
|
|
8431
|
-
|
|
8661
|
+
mkdirSync6(join19(forgeDir, d), { recursive: true });
|
|
8432
8662
|
}
|
|
8433
|
-
writeFileSync6(
|
|
8663
|
+
writeFileSync6(join19(forgeDir, "config.json"), JSON.stringify({
|
|
8434
8664
|
port: 3e3,
|
|
8435
8665
|
theme: "dark",
|
|
8436
8666
|
openBrowser: true,
|
|
@@ -8492,68 +8722,6 @@ var init_port_in_use = __esm({
|
|
|
8492
8722
|
}
|
|
8493
8723
|
});
|
|
8494
8724
|
|
|
8495
|
-
// ../core/dist/module-install.js
|
|
8496
|
-
import { existsSync as existsSync16, mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync3 } from "node:fs";
|
|
8497
|
-
import { tmpdir } from "node:os";
|
|
8498
|
-
import { isAbsolute as isAbsolute2, join as join19, resolve as resolve9 } from "node:path";
|
|
8499
|
-
async function previewManifest(spec, opts) {
|
|
8500
|
-
if (isPathSpec(spec, opts.cwd))
|
|
8501
|
-
return readFolder(expand(spec, opts.cwd));
|
|
8502
|
-
const tmp = mkdtempSync(join19(tmpdir(), "forge-module-pack-"));
|
|
8503
|
-
try {
|
|
8504
|
-
const packed = await opts.run("npm", ["pack", spec, "--pack-destination", tmp, "--ignore-scripts", "--json"], opts.cwd);
|
|
8505
|
-
if (packed.code !== 0)
|
|
8506
|
-
throw new Error(`npm pack ${spec} failed: ${(packed.stderr || packed.stdout).trim()}`);
|
|
8507
|
-
const filename = JSON.parse(packed.stdout)[0]?.filename;
|
|
8508
|
-
if (!filename)
|
|
8509
|
-
throw new Error(`npm pack ${spec} produced no tarball`);
|
|
8510
|
-
const untar = await opts.run("tar", ["-xzf", join19(tmp, filename), "-C", tmp], opts.cwd);
|
|
8511
|
-
if (untar.code !== 0)
|
|
8512
|
-
throw new Error(`could not unpack ${filename}: ${untar.stderr.trim()}`);
|
|
8513
|
-
return readFolder(join19(tmp, "package"));
|
|
8514
|
-
} finally {
|
|
8515
|
-
rmSync3(tmp, { recursive: true, force: true });
|
|
8516
|
-
}
|
|
8517
|
-
}
|
|
8518
|
-
function readFolder(dir) {
|
|
8519
|
-
const manifestFile = manifestFileFor(dir).file;
|
|
8520
|
-
if (!existsSync16(manifestFile))
|
|
8521
|
-
throw new Error(`${dir} has no forge-module.json`);
|
|
8522
|
-
const raw = JSON.parse(readFileSync12(manifestFile, "utf-8"));
|
|
8523
|
-
const check = checkManifest(raw, dir);
|
|
8524
|
-
if (check.kind === "invalid")
|
|
8525
|
-
throw new Error(`invalid manifest: ${check.reason}`);
|
|
8526
|
-
return check;
|
|
8527
|
-
}
|
|
8528
|
-
function describePermissions(manifest) {
|
|
8529
|
-
const p = "permissions" in manifest ? manifest.permissions ?? {} : {};
|
|
8530
|
-
const lines2 = [];
|
|
8531
|
-
for (const key of ["network", "fs:read", "fs:write", "shell"]) {
|
|
8532
|
-
for (const value of p[key] ?? [])
|
|
8533
|
-
lines2.push(` ${key.padEnd(9)} ${value}`);
|
|
8534
|
-
}
|
|
8535
|
-
if (p.pty)
|
|
8536
|
-
lines2.push(` ${"pty".padEnd(9)} yes`);
|
|
8537
|
-
const legacy = "actions" in manifest && (manifest.actions ?? []).some((a) => "command" in a);
|
|
8538
|
-
if (legacy)
|
|
8539
|
-
lines2.push(" shell runs the shell commands in its manifest");
|
|
8540
|
-
return [lines2.length ? "It asks for:" : "It asks for no permissions.", ...lines2, "", TRUST_NOTE].join("\n");
|
|
8541
|
-
}
|
|
8542
|
-
function installArgs(spec, prefix) {
|
|
8543
|
-
return ["install", "--ignore-scripts", "--prefix", prefix, spec];
|
|
8544
|
-
}
|
|
8545
|
-
var TRUST_NOTE, isPathSpec, expand;
|
|
8546
|
-
var init_module_install = __esm({
|
|
8547
|
-
"../core/dist/module-install.js"() {
|
|
8548
|
-
"use strict";
|
|
8549
|
-
init_module_manifest();
|
|
8550
|
-
init_state_log();
|
|
8551
|
-
TRUST_NOTE = "Modules run inside Forge with your access. Forge checks every call against these permissions, but it is not a sandbox: install only modules you trust.";
|
|
8552
|
-
isPathSpec = (spec, cwd) => spec.startsWith(".") || spec.startsWith("~") || isAbsolute2(spec) || existsSync16(resolve9(cwd, spec)) && !spec.startsWith("@");
|
|
8553
|
-
expand = (spec, cwd) => resolve9(cwd, expandHome(spec));
|
|
8554
|
-
}
|
|
8555
|
-
});
|
|
8556
|
-
|
|
8557
8725
|
// ../core/dist/index.js
|
|
8558
8726
|
var dist_exports = {};
|
|
8559
8727
|
__export(dist_exports, {
|
|
@@ -8630,7 +8798,7 @@ var init_dist2 = __esm({
|
|
|
8630
8798
|
|
|
8631
8799
|
// src/index.ts
|
|
8632
8800
|
import { existsSync as existsSync17, readFileSync as readFileSync13 } from "node:fs";
|
|
8633
|
-
import { homedir as
|
|
8801
|
+
import { homedir as homedir9 } from "node:os";
|
|
8634
8802
|
import { join as join20 } from "node:path";
|
|
8635
8803
|
function parseArgs(argv) {
|
|
8636
8804
|
const out = { open: true };
|
|
@@ -8655,7 +8823,7 @@ async function main() {
|
|
|
8655
8823
|
if (created) {
|
|
8656
8824
|
console.log("First run \u2014 initialized Forge.");
|
|
8657
8825
|
}
|
|
8658
|
-
const cwHome = join20(process.env.HOME ??
|
|
8826
|
+
const cwHome = join20(process.env.HOME ?? homedir9(), ".cw");
|
|
8659
8827
|
const cw = isCheckout ? { action: "skipped", message: "a checkout does not install CW" } : await ensureCw2({ cwHome, bundleDir: join20(packageRoot, "cw") });
|
|
8660
8828
|
if (cw.action === "installed") console.log(` Installed CW ${cw.version} in ${cwHome}`);
|
|
8661
8829
|
else if (cw.action === "updated") console.log(` Updated ${cw.message}`);
|