@akira-tl/forgerelay 0.8.8 → 0.8.10
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/CHANGELOG.md +32 -0
- package/README.md +10 -18
- package/capabilities/code-intelligence/GUIDE.md +7 -3
- package/capabilities/host-integration/GUIDE.md +2 -2
- package/capabilities/subagents/GUIDE.md +9 -0
- package/capabilities/{workspace-tasks → workspace/workspace-tasks}/GUIDE.md +6 -0
- package/dist/activity/{audit-store.js → history/audit-store.js} +129 -21
- package/dist/activity/history/bash-output-store.js +250 -0
- package/dist/activity/{host-turn-store.js → history/host-turn-store.js} +4 -1
- package/dist/activity/{query-service.js → history/query-service.js} +13 -7
- package/dist/activity/{lifecycle.js → runtime/lifecycle.js} +1 -1
- package/dist/activity/{mcp-query-tools.js → runtime/mcp-query-tools.js} +3 -3
- package/dist/activity/storage/history.js +12 -0
- package/dist/activity/storage/paths.js +56 -0
- package/dist/activity/storage/segmented-log.js +149 -0
- package/dist/cli/init.js +180 -0
- package/dist/cli/setup-support.js +181 -0
- package/dist/cli.js +23 -216
- package/dist/lsp/code-intelligence.js +18 -4
- package/dist/lsp/runtime/diagnostic-snapshots.js +52 -1
- package/dist/lsp/runtime/managed-language-servers.js +172 -0
- package/dist/lsp/runtime/manager.js +37 -2
- package/dist/lsp/runtime/process-launch.js +3 -0
- package/dist/lsp/test-support/server-fixture.js +14 -11
- package/dist/mcp/filesystem/filesystem-tools.js +245 -0
- package/dist/mcp/hooks/command-runner.js +79 -0
- package/dist/{hook-cli.js → mcp/hooks/hook-cli.js} +1 -1
- package/dist/{hooks.js → mcp/hooks/hooks.js} +3 -80
- package/dist/{oauth-provider.js → mcp/oauth/oauth-provider.js} +1 -1
- package/dist/{oauth-store.js → mcp/oauth/oauth-store.js} +5 -4
- package/dist/{operations → mcp/operations}/native-bulk-mutations.js +2 -2
- package/dist/mcp/panel/app.js +143 -0
- package/dist/{mcp-app-template.js → mcp/panel/mcp-app-template.js} +2 -38
- package/dist/{process-platform.js → mcp/process/process-platform.js} +21 -9
- package/dist/{process-sessions.js → mcp/process/process-sessions.js} +3 -3
- package/dist/mcp/process/runtime.js +257 -0
- package/dist/mcp/process/tools.js +346 -0
- package/dist/mcp/server/core/activity-support.js +70 -0
- package/dist/{capabilities.js → mcp/server/core/capabilities.js} +5 -3
- package/dist/{capability-registry.js → mcp/server/core/capability-registry.js} +9 -4
- package/dist/mcp/server/core/capability-support.js +90 -0
- package/dist/mcp/server/core/schemas.js +216 -0
- package/dist/mcp/server/core/tool-support.js +210 -0
- package/dist/mcp/server/core/version.js +9 -0
- package/dist/mcp/server/operations/runtime/filesystem-tools.js +455 -0
- package/dist/mcp/server/operations/runtime/mutation-diagnostics.js +54 -0
- package/dist/mcp/server/operations/runtime/operation-runtime.js +524 -0
- package/dist/mcp/server/transport/http-server.js +301 -0
- package/dist/mcp/server/transport/http-support.js +43 -0
- package/dist/mcp/server/workspace/runtime/workspace-open-presentation.js +242 -0
- package/dist/mcp/server/workspace/runtime/workspace-open-schema.js +187 -0
- package/dist/mcp/server/workspace/runtime/workspace-open.js +555 -0
- package/dist/mcp/server/workspace/runtime/workspace-tools.js +601 -0
- package/dist/mcp/server-instructions.js +1 -1
- package/dist/{config.js → runtime/config/config.js} +4 -3
- package/dist/{user-config.js → runtime/config/user-config.js} +14 -63
- package/dist/{db → runtime/state/db}/client.js +1 -1
- package/dist/{db → runtime/state/db}/migrations.js +34 -3
- package/dist/runtime/state/lock/file-lock.js +130 -0
- package/dist/runtime/testing/server-fixture.js +190 -0
- package/dist/server.js +133 -4189
- package/dist/subagents/providers/adapters/claude.js +86 -52
- package/dist/subagents/providers/adapters/codex.js +103 -39
- package/dist/subagents/providers/adapters/opencode.js +114 -84
- package/dist/subagents/providers/adapters/pi.js +59 -8
- package/dist/subagents/providers/availability.js +15 -30
- package/dist/subagents/providers/runtime/external-command.js +56 -0
- package/dist/subagents/sessions/capability.js +1 -1
- package/dist/subagents/sessions/execution.js +1 -1
- package/dist/subagents/sessions/mcp/activity-audit-test-support.js +30 -0
- package/dist/subagents/sessions/store.js +1 -1
- package/dist/ui/.vite/manifest.json +342 -410
- package/dist/ui/activity-panel-app.html +2 -4
- package/dist/ui/assets/activity-panel-app-7Xe0alhm.css +1 -0
- package/dist/ui/assets/activity-panel-app-Sl1Jro8v.js +115 -0
- package/dist/ui/assets/angular-html-B_yn91wW.js +1 -0
- package/dist/ui/assets/{angular-ts-Bxh4-O1-.js → angular-ts-DSgtloKz.js} +1 -1
- package/dist/ui/assets/{apl-BmYHKQ6m.js → apl-NatWtF8X.js} +1 -1
- package/dist/ui/assets/{astro-C51vqDKq.js → astro-BYFEVBuN.js} +1 -1
- package/dist/ui/assets/{blade-CdhVsmwq.js → blade-DoO7yvMR.js} +1 -1
- package/dist/ui/assets/c-Rl2hMCcg.js +1 -0
- package/dist/ui/assets/{cobol-Cf6azoN_.js → cobol-ByC5mWiB.js} +1 -1
- package/dist/ui/assets/{coffee-vVdGzlmt.js → coffee-Dez77Qov.js} +1 -1
- package/dist/ui/assets/cpp-DtaAQT9h.js +1 -0
- package/dist/ui/assets/{crystal-5UZy6KIX.js → crystal-Bgmbf8ec.js} +1 -1
- package/dist/ui/assets/css-C1tjBtVN.js +1 -0
- package/dist/ui/assets/{edge-CESgo4ia.js → edge-DeH6u8Wg.js} +1 -1
- package/dist/ui/assets/{elixir-9FKfw4cJ.js → elixir-_H3KnmWT.js} +1 -1
- package/dist/ui/assets/{elm-Dv7A0aLa.js → elm-CsCe3U28.js} +1 -1
- package/dist/ui/assets/{erb-DHFmDvM0.js → erb-CbM-L2g8.js} +1 -1
- package/dist/ui/assets/{git-rebase-utbU6CzV.js → git-rebase-D0s5pDh9.js} +1 -1
- package/dist/ui/assets/{glimmer-js-t2kiCkyB.js → glimmer-js-BfOToq_A.js} +1 -1
- package/dist/ui/assets/{glimmer-ts-F0Jv5okV.js → glimmer-ts-CCUOkL2K.js} +1 -1
- package/dist/ui/assets/glsl-DH4ec9dE.js +1 -0
- package/dist/ui/assets/graphql-DilAMBoI.js +1 -0
- package/dist/ui/assets/{hack-8yjdbaSA.js → hack-BDkNDZlF.js} +1 -1
- package/dist/ui/assets/haml-D_QBp6K7.js +1 -0
- package/dist/ui/assets/{handlebars-BBFEg4Lo.js → handlebars-DePUrTpC.js} +1 -1
- package/dist/ui/assets/heavy-payload-DOwZpO6n.js +290 -0
- package/dist/ui/assets/html-By4dNnM4.js +1 -0
- package/dist/ui/assets/{html-derivative-DJAQcZyc.js → html-derivative-DDw9Ef4Q.js} +1 -1
- package/dist/ui/assets/{http-Dg5LSYRK.js → http-B0QxWc9M.js} +1 -1
- package/dist/ui/assets/{hurl-Y5uYzMZR.js → hurl-vMQ4lda1.js} +1 -1
- package/dist/ui/assets/java-DYRe2lWG.js +1 -0
- package/dist/ui/assets/javascript-CxyGqwcL.js +1 -0
- package/dist/ui/assets/{jinja-Bt30h8Ik.js → jinja-JUwV-fZt.js} +1 -1
- package/dist/ui/assets/{jison-D5uZ1Nyo.js → jison-7ZqCEvS8.js} +1 -1
- package/dist/ui/assets/json-dbltgiiA.js +1 -0
- package/dist/ui/assets/jsx-oDopT7YQ.js +1 -0
- package/dist/ui/assets/{julia-76hRaaGz.js → julia-CkKiNSav.js} +1 -1
- package/dist/ui/assets/{just-CDE7W0vW.js → just-DuqCAKJ3.js} +1 -1
- package/dist/ui/assets/{latex-CVO2Byab.js → latex-sudcRDSn.js} +1 -1
- package/dist/ui/assets/{liquid-CA-AS3EX.js → liquid-Di-08Js7.js} +1 -1
- package/dist/ui/assets/lua-YlqdMoV0.js +1 -0
- package/dist/ui/assets/{marko-_BWrPfny.js → marko-CJRNyJDb.js} +1 -1
- package/dist/ui/assets/{mdc-PyJv85eS.js → mdc-t12CzZHs.js} +1 -1
- package/dist/ui/assets/{nginx-DAWVw8yu.js → nginx-57-b4XwT.js} +1 -1
- package/dist/ui/assets/{nim-CKE1dzpU.js → nim-BzrswYQa.js} +1 -1
- package/dist/ui/assets/{perl-BBhWva19.js → perl-D9JR_iIh.js} +1 -1
- package/dist/ui/assets/{php-ChIeaAoF.js → php-Py2gFHil.js} +1 -1
- package/dist/ui/assets/{pug-CMgI5afi.js → pug-PzLLy0h1.js} +1 -1
- package/dist/ui/assets/{qml-CjxTkcFH.js → qml-By8fwgV4.js} +1 -1
- package/dist/ui/assets/r-CcezTbLq.js +1 -0
- package/dist/ui/assets/{razor-i4DbHr8p.js → razor-dG-vdhL_.js} +1 -1
- package/dist/ui/assets/regexp-BlsQ-N_8.js +1 -0
- package/dist/ui/assets/{rst-oy_97Kl4.js → rst-C2awDCat.js} +1 -1
- package/dist/ui/assets/{ruby-C5zYZIyl.js → ruby-zA2W_49d.js} +1 -1
- package/dist/ui/assets/{sas-BnMEh2S3.js → sas-D49bhyaN.js} +1 -1
- package/dist/ui/assets/scss-BQb-cp01.js +1 -0
- package/dist/ui/assets/shellscript-Dga4H_Ni.js +1 -0
- package/dist/ui/assets/{shellsession-BpE5o7_3.js → shellsession-n3-5CqcF.js} +1 -1
- package/dist/ui/assets/{soy-COesmhTY.js → soy-XYBOf_BA.js} +1 -1
- package/dist/ui/assets/sql-ET4u5Cpl.js +1 -0
- package/dist/ui/assets/{stata-CGQEuSZq.js → stata-CLHcJHO_.js} +1 -1
- package/dist/ui/assets/{surrealql-Bz4JbU-9.js → surrealql-BdjQ60T4.js} +1 -1
- package/dist/ui/assets/{svelte-1jM2ejXj.js → svelte-B2RPtKJe.js} +1 -1
- package/dist/ui/assets/{templ-C-VwELcp.js → templ-CrByPW_W.js} +1 -1
- package/dist/ui/assets/{tex-t1rUiQCm.js → tex-DZBkg363.js} +1 -1
- package/dist/ui/assets/{ts-tags-Bewoa6pS.js → ts-tags-DJR3Gphb.js} +1 -1
- package/dist/ui/assets/tsx-CajzROyy.js +1 -0
- package/dist/ui/assets/{twig-DVg-vELl.js → twig-BZjk5Zda.js} +1 -1
- package/dist/ui/assets/typescript-B-EWToLj.js +1 -0
- package/dist/ui/assets/{vue-wPQ7UlMy.js → vue-D_rHCCr8.js} +1 -1
- package/dist/ui/assets/{vue-html-BHtJ8geX.js → vue-html-CC4WlvVt.js} +1 -1
- package/dist/ui/assets/{vue-vine-CNSGAWwc.js → vue-vine-BgNMVgpk.js} +1 -1
- package/dist/ui/assets/xml-BI86-WbH.js +1 -0
- package/dist/ui/assets/{xsl-3_vJ27aE.js → xsl-DXeLZV4J.js} +1 -1
- package/dist/ui/assets/yaml-60_PFlWa.js +1 -0
- package/dist/workspaces/bootstrap.js +72 -0
- package/dist/workspaces/context.js +337 -0
- package/dist/workspaces/conversation-test-support.js +86 -0
- package/dist/{git-worktrees.js → workspaces/git/git-worktrees.js} +1 -1
- package/dist/workspaces/inventory.js +131 -0
- package/dist/workspaces/paths.js +55 -0
- package/dist/{remote-auth.js → workspaces/relay/auth/remote-auth.js} +2 -2
- package/dist/workspaces/relay/result-support.js +111 -0
- package/dist/workspaces/relay/tests/test-support.js +203 -0
- package/dist/{remote-mcp-connection-pool.js → workspaces/relay/transport/remote-mcp-connection-pool.js} +1 -1
- package/dist/{remote-transport.js → workspaces/relay/transport/remote-transport.js} +30 -3
- package/dist/workspaces/relay/types.js +1 -0
- package/dist/{remote-workspace-relay.js → workspaces/relay/workspace-relay.js} +23 -172
- package/dist/workspaces/resources/resource-monitor.js +377 -0
- package/dist/workspaces/resources/skills.js +283 -0
- package/dist/{review-checkpoints.js → workspaces/review/review-checkpoints.js} +2 -2
- package/dist/workspaces/sessions.js +460 -0
- package/dist/{workspace-store.js → workspaces/state/workspace-store.js} +2 -2
- package/dist/workspaces.js +77 -993
- package/docs/chatgpt-coding-workflow.md +7 -14
- package/docs/configuration.md +26 -34
- package/docs/gotchas.md +0 -30
- package/docs/roadmap.md +6 -10
- package/docs/security.md +1 -5
- package/docs/setup.md +0 -11
- package/package.json +6 -9
- package/scripts/ci/architecture.mjs +109 -0
- package/scripts/ci/verify.mjs +5 -0
- package/scripts/debug/accept/bootstrap.mjs +326 -0
- package/scripts/debug/accept/harness.mjs +76 -0
- package/scripts/debug/accept/support.mjs +432 -0
- package/scripts/debug/accept.mjs +43 -806
- package/scripts/debug/relay-accept/support.mjs +135 -0
- package/scripts/debug/relay-accept.mjs +26 -115
- package/scripts/debug/traffic/support.mjs +167 -0
- package/scripts/debug/traffic/traffic-audit.mjs +4 -161
- package/scripts/{lsp-interop-support.test.mjs → lsp/interop-support.test.mjs} +1 -1
- package/scripts/{lsp-interop.mjs → lsp/interop.mjs} +3 -3
- package/scripts/release/publish.mjs +12 -9
- package/scripts/release/release-gate.test.mjs +3 -1
- package/scripts/release/release-version.test.mjs +13 -1
- package/scripts/release-parity.mjs +1 -1
- package/scripts/release-proof.mjs +15 -0
- package/scripts/release-proof.test.mjs +27 -1
- package/scripts/release-version.mjs +16 -1
- package/scripts/wiki/sync.mjs +21 -10
- package/dist/activity/bash-output-store.js +0 -127
- package/dist/pi-tools.js +0 -110
- package/dist/skills.js +0 -127
- package/dist/ui/assets/activity-panel-app-7AckaxIR.js +0 -2
- package/dist/ui/assets/angular-html-BkgDQl-G.js +0 -1
- package/dist/ui/assets/c-IBzBpxAO.js +0 -1
- package/dist/ui/assets/chunk-EyZ2wyi3.js +0 -1
- package/dist/ui/assets/cpp-CVHeO-Jk.js +0 -1
- package/dist/ui/assets/css-DEfus9J5.js +0 -1
- package/dist/ui/assets/glsl-_ooCjaeB.js +0 -1
- package/dist/ui/assets/graphql-B9LqP-cY.js +0 -1
- package/dist/ui/assets/haml-BU5EoZBn.js +0 -1
- package/dist/ui/assets/heavy-payload-Bol1_mcs.js +0 -4
- package/dist/ui/assets/html-D-C6YZk4.js +0 -1
- package/dist/ui/assets/java-ppHvycj5.js +0 -1
- package/dist/ui/assets/javascript-DDTjKLlQ.js +0 -1
- package/dist/ui/assets/json-Cw91r8h1.js +0 -1
- package/dist/ui/assets/jsx-04X2FT_1.js +0 -1
- package/dist/ui/assets/lua-CiyBp52Y.js +0 -1
- package/dist/ui/assets/r-DfFga4si.js +0 -1
- package/dist/ui/assets/regexp-iC9_nbSI.js +0 -1
- package/dist/ui/assets/review-payload-BqJp0c5e.js +0 -1
- package/dist/ui/assets/scrollbar-CvE-I-jG.js +0 -287
- package/dist/ui/assets/scss-BfZXsAWQ.js +0 -1
- package/dist/ui/assets/shellscript-BC2srNaB.js +0 -1
- package/dist/ui/assets/sql-Ccv_yeUm.js +0 -1
- package/dist/ui/assets/tsx-CTXzrBkN.js +0 -1
- package/dist/ui/assets/typescript--ElHoFkw.js +0 -1
- package/dist/ui/assets/workspace-app-BLW7p6IZ.js +0 -114
- package/dist/ui/assets/workspace-app-BjNqR0en.js +0 -1
- package/dist/ui/assets/workspace-app-CmaYU4DW.js +0 -3
- package/dist/ui/assets/workspace-app-D2bJ5fjt.css +0 -1
- package/dist/ui/assets/workspace-lifecycle-app-BVbI2Ilf.js +0 -1
- package/dist/ui/assets/xml-JnmX6vyS.js +0 -1
- package/dist/ui/assets/yaml-OAcZkP-w.js +0 -1
- package/dist/ui/workspace-app.html +0 -18
- package/dist/ui/workspace-lifecycle-app.html +0 -18
- package/docs/agent-profile-schema.md +0 -114
- package/docs/artifact-exchange.md +0 -65
- package/docs/versioning.md +0 -205
- package/skills/subagent-delegation/SKILL.md +0 -132
- /package/capabilities/{managed-worktrees → workspace/managed-worktrees}/GUIDE.md +0 -0
- /package/dist/activity/{process-output-audit.js → runtime/process-output-audit.js} +0 -0
- /package/dist/{artifact-error.js → mcp/artifacts/artifact-error.js} +0 -0
- /package/dist/{artifact-tools.js → mcp/artifacts/artifact-tools.js} +0 -0
- /package/dist/{incoming-artifacts.js → mcp/artifacts/incoming-artifacts.js} +0 -0
- /package/dist/{advertised-files.js → mcp/filesystem/advertised-files.js} +0 -0
- /package/dist/{apply-patch.js → mcp/filesystem/apply-patch.js} +0 -0
- /package/dist/{file-mutations.js → mcp/filesystem/file-mutations.js} +0 -0
- /package/dist/{roots.js → mcp/filesystem/roots.js} +0 -0
- /package/dist/{oauth → mcp/oauth}/public-url.js +0 -0
- /package/dist/{oauth → mcp/oauth}/router.js +0 -0
- /package/dist/{operations → mcp/operations}/batch/executor.js +0 -0
- /package/dist/{operations → mcp/operations}/batch/scheduler.js +0 -0
- /package/dist/{operations → mcp/operations}/batch/types.js +0 -0
- /package/dist/{operations → mcp/operations}/bulk-mutation.js +0 -0
- /package/dist/{operations → mcp/operations}/bulk-read.js +0 -0
- /package/dist/{operations → mcp/operations}/core-operation-executor.js +0 -0
- /package/dist/{request-meta.js → mcp/request-meta.js} +0 -0
- /package/dist/{mcp-sessions.js → mcp/server/transport/mcp-sessions.js} +0 -0
- /package/dist/{server-shutdown.js → mcp/server/transport/server-shutdown.js} +0 -0
- /package/dist/{logger.js → runtime/logging/logger.js} +0 -0
- /package/dist/{db → runtime/state/db}/schema.js +0 -0
- /package/dist/{composite-activity.js → workspaces/composite/composite-activity.js} +0 -0
- /package/dist/{composite-workspaces.js → workspaces/composite/composite-workspaces.js} +0 -0
- /package/dist/{git.js → workspaces/git/git.js} +0 -0
- /package/dist/{workspace-presentation.js → workspaces/presentation/workspace-presentation.js} +0 -0
- /package/dist/{workspace-task-reminders.js → workspaces/tasks/workspace-task-reminders.js} +0 -0
- /package/dist/{workspace-tasks.js → workspaces/tasks/workspace-tasks.js} +0 -0
- /package/scripts/{lsp-interop-support.mjs → lsp/interop-support.mjs} +0 -0
package/dist/cli/init.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import * as prompts from "@clack/prompts";
|
|
3
|
+
import { publicEndpointUrl } from "../mcp/oauth/public-url.js";
|
|
4
|
+
import { expandHomePath } from "../mcp/filesystem/roots.js";
|
|
5
|
+
import { installManagedLanguageServers, installedManagedLanguageServers, managedLanguageServerOptions, } from "../lsp/runtime/managed-language-servers.js";
|
|
6
|
+
import { generateInstanceId, generateOwnerToken, loadForgeRelayFiles, resolveSubagentsFlag, writeForgeRelayAuth, writeForgeRelayConfig, } from "../runtime/config/user-config.js";
|
|
7
|
+
import { classifyClientFacingBaseUrl, compactPublicBaseUrlConfig, hasInsecureLanBaseUrl, isLoopbackBindAddress, normalizePublicBaseUrlsInput, SetupCancelledError, textPrompt, validateBindAddress, validateClientFacingBaseUrls, validatePort, } from "./setup-support.js";
|
|
8
|
+
export async function runInit({ force }) {
|
|
9
|
+
const files = loadForgeRelayFiles();
|
|
10
|
+
if (!force && files.configExists && files.authExists) {
|
|
11
|
+
prompts.log.info(`ForgeRelay is already configured at ${files.dir}`);
|
|
12
|
+
prompts.log.info("Run `forgerelay init --force` to update it.");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
prompts.intro("ForgeRelay setup");
|
|
17
|
+
const defaultRoots = files.config.allowedRoots?.join(", ") || process.cwd();
|
|
18
|
+
const rootsAnswer = await textPrompt({
|
|
19
|
+
message: `Where are your projects located? Press Enter to use ${defaultRoots}`,
|
|
20
|
+
placeholder: defaultRoots,
|
|
21
|
+
defaultValue: defaultRoots,
|
|
22
|
+
validate: (value) => value?.trim() ? undefined : "Enter at least one project root.",
|
|
23
|
+
});
|
|
24
|
+
const allowedRoots = rootsAnswer
|
|
25
|
+
.split(",")
|
|
26
|
+
.map((root) => resolve(expandHomePath(root.trim())))
|
|
27
|
+
.filter(Boolean);
|
|
28
|
+
const defaultPort = String(files.config.port ?? 7676);
|
|
29
|
+
const portAnswer = await textPrompt({
|
|
30
|
+
message: `Which local port should ForgeRelay use? Press Enter to use ${defaultPort}`,
|
|
31
|
+
placeholder: defaultPort,
|
|
32
|
+
defaultValue: defaultPort,
|
|
33
|
+
validate: validatePort,
|
|
34
|
+
});
|
|
35
|
+
const port = Number(portAnswer);
|
|
36
|
+
const existingPublicBaseUrls = Array.isArray(files.config.publicBaseUrl)
|
|
37
|
+
? files.config.publicBaseUrl
|
|
38
|
+
: files.config.publicBaseUrl ? [files.config.publicBaseUrl] : [];
|
|
39
|
+
const defaultNetworkMode = !isLoopbackBindAddress(files.config.host ?? "127.0.0.1") || existingPublicBaseUrls.length > 0
|
|
40
|
+
? "network"
|
|
41
|
+
: "local";
|
|
42
|
+
const selectedMode = await prompts.select({
|
|
43
|
+
message: "How should clients reach this ForgeRelay instance?",
|
|
44
|
+
initialValue: defaultNetworkMode,
|
|
45
|
+
options: [
|
|
46
|
+
{
|
|
47
|
+
value: "local",
|
|
48
|
+
label: "Local only",
|
|
49
|
+
hint: "Bind to loopback; local clients only. Client-facing URL is derived automatically.",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
value: "ssh",
|
|
53
|
+
label: "SSH relay",
|
|
54
|
+
hint: "Bind to loopback; another ForgeRelay reaches it through an SSH tunnel.",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
value: "network",
|
|
58
|
+
label: "LAN / HTTPS proxy",
|
|
59
|
+
hint: "Expose through a LAN address or an HTTPS reverse proxy/tunnel.",
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
});
|
|
63
|
+
if (prompts.isCancel(selectedMode))
|
|
64
|
+
throw new SetupCancelledError();
|
|
65
|
+
const networkMode = selectedMode;
|
|
66
|
+
let host = "127.0.0.1";
|
|
67
|
+
let publicBaseUrl = null;
|
|
68
|
+
let clientFacingBaseUrls = [`http://127.0.0.1:${port}`];
|
|
69
|
+
if (networkMode === "network") {
|
|
70
|
+
const defaultHost = files.config.host && !isLoopbackBindAddress(files.config.host)
|
|
71
|
+
? files.config.host
|
|
72
|
+
: "0.0.0.0";
|
|
73
|
+
host = await textPrompt({
|
|
74
|
+
message: "Which address should ForgeRelay bind to? Use 0.0.0.0 for direct LAN, or 127.0.0.1 behind a local reverse proxy.",
|
|
75
|
+
placeholder: defaultHost,
|
|
76
|
+
defaultValue: defaultHost,
|
|
77
|
+
validate: validateBindAddress,
|
|
78
|
+
});
|
|
79
|
+
const defaultClientFacing = existingPublicBaseUrls.join(", ");
|
|
80
|
+
clientFacingBaseUrls = normalizePublicBaseUrlsInput(await textPrompt({
|
|
81
|
+
message: defaultClientFacing
|
|
82
|
+
? `What client-facing base URLs should clients use? Press Enter to keep ${defaultClientFacing}`
|
|
83
|
+
: "What client-facing base URL should clients use?",
|
|
84
|
+
placeholder: defaultClientFacing || `http://192.168.1.20:${port} or https://forge.example.com`,
|
|
85
|
+
defaultValue: defaultClientFacing,
|
|
86
|
+
validate: validateClientFacingBaseUrls,
|
|
87
|
+
}));
|
|
88
|
+
for (const baseUrl of clientFacingBaseUrls)
|
|
89
|
+
classifyClientFacingBaseUrl(baseUrl);
|
|
90
|
+
if (hasInsecureLanBaseUrl(clientFacingBaseUrls)) {
|
|
91
|
+
prompts.note([
|
|
92
|
+
"Plain HTTP does not encrypt the ForgeRelay Owner approval flow or MCP bearer tokens.",
|
|
93
|
+
"Use this only on a trusted private LAN. Prefer SSH relay or HTTPS when the network is not fully trusted.",
|
|
94
|
+
].join("\n"), "Unencrypted LAN access");
|
|
95
|
+
const approved = await prompts.confirm({
|
|
96
|
+
message: "Allow unencrypted HTTP access on this private network?",
|
|
97
|
+
initialValue: false,
|
|
98
|
+
});
|
|
99
|
+
if (prompts.isCancel(approved) || approved !== true)
|
|
100
|
+
throw new SetupCancelledError();
|
|
101
|
+
}
|
|
102
|
+
publicBaseUrl = compactPublicBaseUrlConfig(clientFacingBaseUrls);
|
|
103
|
+
}
|
|
104
|
+
const installedManaged = installedManagedLanguageServers(files.dir);
|
|
105
|
+
const selectedManaged = await prompts.multiselect({
|
|
106
|
+
message: "Which Language Servers should ForgeRelay manage with npm?",
|
|
107
|
+
options: managedLanguageServerOptions(),
|
|
108
|
+
initialValues: installedManaged,
|
|
109
|
+
required: false,
|
|
110
|
+
});
|
|
111
|
+
if (prompts.isCancel(selectedManaged))
|
|
112
|
+
throw new SetupCancelledError();
|
|
113
|
+
const managedIds = selectedManaged;
|
|
114
|
+
const allowAgentInstallAnswer = await prompts.confirm({
|
|
115
|
+
message: "Allow Agents to install or update ForgeRelay-managed Language Servers on demand?",
|
|
116
|
+
initialValue: files.config.allowAgentLanguageServerInstall === true,
|
|
117
|
+
});
|
|
118
|
+
if (prompts.isCancel(allowAgentInstallAnswer))
|
|
119
|
+
throw new SetupCancelledError();
|
|
120
|
+
const allowAgentLanguageServerInstall = allowAgentInstallAnswer === true;
|
|
121
|
+
if (managedIds.length > 0) {
|
|
122
|
+
prompts.note([
|
|
123
|
+
"ForgeRelay installs these optional Language Servers into its private config directory, not global npm.",
|
|
124
|
+
"Rust Analyzer, gopls, and clangd remain external toolchain/system installations and are auto-detected when available.",
|
|
125
|
+
].join("\n"), "Managed Language Servers");
|
|
126
|
+
const spinner = prompts.spinner();
|
|
127
|
+
spinner.start("Installing managed Language Servers with npm");
|
|
128
|
+
try {
|
|
129
|
+
const installed = await installManagedLanguageServers(managedIds, files.dir);
|
|
130
|
+
spinner.stop(`Installed ${installed.installed.join(", ")} under ${installed.root}`);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
spinner.stop("Managed Language Server installation failed");
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const config = {
|
|
138
|
+
host,
|
|
139
|
+
port,
|
|
140
|
+
allowedRoots,
|
|
141
|
+
publicBaseUrl,
|
|
142
|
+
allowedHosts: files.config.allowedHosts,
|
|
143
|
+
workflowInstructions: files.config.workflowInstructions,
|
|
144
|
+
appendInstructions: files.config.appendInstructions,
|
|
145
|
+
subagents: resolveSubagentsFlag(files.config),
|
|
146
|
+
languageServers: files.config.languageServers,
|
|
147
|
+
allowAgentLanguageServerInstall,
|
|
148
|
+
};
|
|
149
|
+
const auth = {
|
|
150
|
+
...files.auth,
|
|
151
|
+
ownerToken: files.auth.ownerToken ?? generateOwnerToken(),
|
|
152
|
+
instanceId: files.auth.instanceId ?? generateInstanceId(),
|
|
153
|
+
};
|
|
154
|
+
const configPath = writeForgeRelayConfig(config);
|
|
155
|
+
const authPath = await writeForgeRelayAuth(auth);
|
|
156
|
+
const lines = [
|
|
157
|
+
`Config: ${configPath}`,
|
|
158
|
+
`Auth: ${authPath}`,
|
|
159
|
+
`Bind: http://${config.host}:${config.port}`,
|
|
160
|
+
...clientFacingBaseUrls.map((baseUrl, index) => `${index === 0 ? "Client-facing MCP URL" : "Client-facing MCP alias"}: ${publicEndpointUrl(baseUrl, "mcp").toString()}`),
|
|
161
|
+
];
|
|
162
|
+
if (networkMode === "ssh") {
|
|
163
|
+
lines.push(`SSH relay: forgerelay auth -J <ssh-host> 127.0.0.1:${port} --ssh-auth`);
|
|
164
|
+
}
|
|
165
|
+
prompts.note(lines.join("\n"), "ForgeRelay configured");
|
|
166
|
+
prompts.note([
|
|
167
|
+
`Owner password: ${auth.ownerToken}`,
|
|
168
|
+
"Use this when ChatGPT or Claude asks you to approve ForgeRelay access.",
|
|
169
|
+
`Stored at: ${authPath}`,
|
|
170
|
+
].join("\n"), "Owner password");
|
|
171
|
+
prompts.outro("Run `forgerelay serve` to start the MCP server.");
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (error instanceof SetupCancelledError) {
|
|
175
|
+
prompts.cancel("Setup cancelled");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { isIP } from "node:net";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import * as prompts from "@clack/prompts";
|
|
4
|
+
import { satisfies } from "semver";
|
|
5
|
+
const SUPPORTED_NODE_RANGE = ">=20.12 <27";
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
export function isNullConfigValue(value) {
|
|
8
|
+
const normalized = value.trim().toLowerCase();
|
|
9
|
+
return !normalized || normalized === "null" || normalized === "none";
|
|
10
|
+
}
|
|
11
|
+
export function normalizeOptionalPublicBaseUrl(value) {
|
|
12
|
+
const trimmed = value.trim();
|
|
13
|
+
if (isNullConfigValue(trimmed))
|
|
14
|
+
return null;
|
|
15
|
+
return compactPublicBaseUrlConfig(normalizePublicBaseUrlsInput(trimmed));
|
|
16
|
+
}
|
|
17
|
+
export function normalizePublicBaseUrlsInput(value) {
|
|
18
|
+
const entries = value
|
|
19
|
+
.split(",")
|
|
20
|
+
.map((entry) => entry.trim())
|
|
21
|
+
.filter(Boolean);
|
|
22
|
+
if (entries.length === 0)
|
|
23
|
+
throw new Error("Enter at least one public base URL.");
|
|
24
|
+
return Array.from(new Set(entries.map((entry) => normalizePublicBaseUrl(entry))));
|
|
25
|
+
}
|
|
26
|
+
export function compactPublicBaseUrlConfig(baseUrls) {
|
|
27
|
+
return baseUrls.length === 1 ? baseUrls[0] : baseUrls;
|
|
28
|
+
}
|
|
29
|
+
export function normalizePublicBaseUrl(value) {
|
|
30
|
+
const trimmed = value.trim();
|
|
31
|
+
const parsed = new URL(trimmed);
|
|
32
|
+
parsed.hash = "";
|
|
33
|
+
parsed.search = "";
|
|
34
|
+
parsed.pathname = parsed.pathname.replace(/\/+$/, "");
|
|
35
|
+
return parsed.toString().replace(/\/$/, "");
|
|
36
|
+
}
|
|
37
|
+
export function classifyClientFacingBaseUrl(value) {
|
|
38
|
+
const parsed = new URL(normalizePublicBaseUrl(value));
|
|
39
|
+
if (parsed.protocol === "https:")
|
|
40
|
+
return "secure";
|
|
41
|
+
if (parsed.protocol !== "http:") {
|
|
42
|
+
throw new Error("Client-facing base URLs must use http:// or https://.");
|
|
43
|
+
}
|
|
44
|
+
if (!isPrivateNetworkHost(parsed.hostname)) {
|
|
45
|
+
throw new Error("Plain HTTP is allowed only for local/LAN addresses. Use HTTPS for public addresses.");
|
|
46
|
+
}
|
|
47
|
+
return "insecure-lan";
|
|
48
|
+
}
|
|
49
|
+
export function validateClientFacingBaseUrls(value) {
|
|
50
|
+
const trimmed = value?.trim() ?? "";
|
|
51
|
+
if (!trimmed)
|
|
52
|
+
return "Enter at least one client-facing base URL.";
|
|
53
|
+
try {
|
|
54
|
+
for (const baseUrl of normalizePublicBaseUrlsInput(trimmed))
|
|
55
|
+
classifyClientFacingBaseUrl(baseUrl);
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
return error instanceof Error ? error.message : String(error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function hasInsecureLanBaseUrl(baseUrls) {
|
|
63
|
+
return baseUrls.some((baseUrl) => classifyClientFacingBaseUrl(baseUrl) === "insecure-lan");
|
|
64
|
+
}
|
|
65
|
+
function isPrivateNetworkHost(hostname) {
|
|
66
|
+
const host = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
67
|
+
if (host === "localhost" || !host.includes(".") || host.endsWith(".local") || host.endsWith(".lan") || host.endsWith(".home.arpa")) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
const family = isIP(host);
|
|
71
|
+
if (family === 4) {
|
|
72
|
+
const [a, b] = host.split(".").map(Number);
|
|
73
|
+
return a === 10
|
|
74
|
+
|| a === 127
|
|
75
|
+
|| (a === 172 && b >= 16 && b <= 31)
|
|
76
|
+
|| (a === 192 && b === 168)
|
|
77
|
+
|| (a === 169 && b === 254)
|
|
78
|
+
|| (a === 100 && b >= 64 && b <= 127);
|
|
79
|
+
}
|
|
80
|
+
if (family === 6) {
|
|
81
|
+
return host === "::1"
|
|
82
|
+
|| host.startsWith("fc")
|
|
83
|
+
|| host.startsWith("fd")
|
|
84
|
+
|| /^fe[89ab]/.test(host);
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
export async function textPrompt(options) {
|
|
89
|
+
const result = await prompts.text({
|
|
90
|
+
...options,
|
|
91
|
+
validate: (value) => options.validate?.(value?.trim() ? value : options.defaultValue),
|
|
92
|
+
});
|
|
93
|
+
if (prompts.isCancel(result))
|
|
94
|
+
throw new SetupCancelledError();
|
|
95
|
+
const value = String(result).trim();
|
|
96
|
+
return value || options.defaultValue;
|
|
97
|
+
}
|
|
98
|
+
export function validatePort(value) {
|
|
99
|
+
const port = Number(value);
|
|
100
|
+
return Number.isInteger(port) && port >= 1 && port <= 65535
|
|
101
|
+
? undefined
|
|
102
|
+
: "Enter a port between 1 and 65535.";
|
|
103
|
+
}
|
|
104
|
+
export function isLoopbackBindAddress(value) {
|
|
105
|
+
const host = value.trim().toLowerCase();
|
|
106
|
+
return host === "127.0.0.1" || host === "localhost" || host === "::1";
|
|
107
|
+
}
|
|
108
|
+
export function validateBindAddress(value) {
|
|
109
|
+
const host = value?.trim() ?? "";
|
|
110
|
+
if (!host)
|
|
111
|
+
return "Enter a bind address.";
|
|
112
|
+
if (/\s|:\/\//.test(host) || host.includes("/")) {
|
|
113
|
+
return "Enter only a host or IP address, not a URL.";
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
export function validateRequiredPublicBaseUrls(value) {
|
|
118
|
+
const trimmed = value?.trim() ?? "";
|
|
119
|
+
if (!trimmed)
|
|
120
|
+
return "Enter at least one public base URL from your tunnel or reverse proxy.";
|
|
121
|
+
try {
|
|
122
|
+
normalizePublicBaseUrlsInput(trimmed);
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
return error instanceof Error ? error.message : String(error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export function assertSupportedNode() {
|
|
130
|
+
if (satisfies(process.versions.node, SUPPORTED_NODE_RANGE))
|
|
131
|
+
return;
|
|
132
|
+
throw new Error([
|
|
133
|
+
`ForgeRelay requires Node ${SUPPORTED_NODE_RANGE}.`,
|
|
134
|
+
`Current Node: ${process.version}`,
|
|
135
|
+
"",
|
|
136
|
+
"Install Node 22 LTS or use a version manager such as nvm, fnm, or mise.",
|
|
137
|
+
].join("\n"));
|
|
138
|
+
}
|
|
139
|
+
export function nodeVersionStatus() {
|
|
140
|
+
return satisfies(process.versions.node, SUPPORTED_NODE_RANGE)
|
|
141
|
+
? `supported ${SUPPORTED_NODE_RANGE}`
|
|
142
|
+
: `unsupported, requires ${SUPPORTED_NODE_RANGE}`;
|
|
143
|
+
}
|
|
144
|
+
export class SetupCancelledError extends Error {
|
|
145
|
+
}
|
|
146
|
+
export function checkSqliteNative() {
|
|
147
|
+
try {
|
|
148
|
+
const Database = require("better-sqlite3");
|
|
149
|
+
const db = new Database(":memory:");
|
|
150
|
+
db.close();
|
|
151
|
+
return "ok";
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
return error instanceof Error ? error.message : String(error);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
export function checkGitAvailable() {
|
|
158
|
+
try {
|
|
159
|
+
const { execFileSync } = require("node:child_process");
|
|
160
|
+
return execFileSync("git", ["--version"], { encoding: "utf8" }).trim();
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
164
|
+
return `unavailable (${message})`;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export function checkBashShell() {
|
|
168
|
+
try {
|
|
169
|
+
const { execFileSync } = require("node:child_process");
|
|
170
|
+
const command = process.env.BASH?.trim() || "bash";
|
|
171
|
+
const version = execFileSync(command, ["--version"], {
|
|
172
|
+
encoding: "utf8",
|
|
173
|
+
windowsHide: true,
|
|
174
|
+
}).split(/\r?\n/, 1)[0]?.trim();
|
|
175
|
+
return version ? `${command} (${version})` : command;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
179
|
+
return `unavailable (${message})`;
|
|
180
|
+
}
|
|
181
|
+
}
|