@akira-tl/forgerelay 1.1.2 → 1.2.0
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 +38 -0
- package/README.md +14 -0
- package/dist/activity/runtime/mcp-query-tools.js +15 -5
- package/dist/cli/config/inspect.js +331 -0
- package/dist/cli/config/migrate.js +351 -0
- package/dist/cli/init/setup-config.js +41 -0
- package/dist/cli/init.js +128 -130
- package/dist/cli/maintenance.js +15 -5
- package/dist/cli/mcp/external-mcp.js +14 -10
- package/dist/cli/mcp/status.js +11 -6
- package/dist/cli.js +40 -7
- package/dist/lsp/language-server-config.js +66 -144
- package/dist/lsp/runtime/manager.js +35 -4
- package/dist/mcp/hooks/config.js +141 -0
- package/dist/mcp/hooks/external-mcp-transform.js +22 -13
- package/dist/mcp/hooks/hook-cli.js +62 -52
- package/dist/mcp/hooks/hooks.js +111 -85
- package/dist/mcp/operations/external-mcp/external-mcp-runtime.js +8 -5
- package/dist/mcp/operations/external-mcp/external-mcp.js +9 -2
- package/dist/mcp/server/core/capability-support.js +7 -0
- package/dist/runtime/config/config.js +142 -64
- package/dist/runtime/config/definition/catalog.js +12 -0
- package/dist/runtime/config/definition/definition.js +115 -0
- package/dist/runtime/config/definition/external-mcp.js +58 -0
- package/dist/runtime/config/definition/general-config.js +258 -0
- package/dist/runtime/config/definition/language-servers.js +116 -0
- package/dist/runtime/config/definition/schema.js +113 -0
- package/dist/runtime/config/definition/types.js +2 -0
- package/dist/runtime/config/external-mcp-auth-store.js +105 -29
- package/dist/runtime/config/external-mcp-config.js +39 -0
- package/dist/runtime/config/external-mcp-registry.js +284 -106
- package/dist/runtime/config/inspection/live.js +77 -0
- package/dist/runtime/config/resolution/general.js +93 -0
- package/dist/runtime/config/resolution/hooks.js +315 -0
- package/dist/runtime/config/resolution/language-servers.js +237 -0
- package/dist/runtime/config/resolution/project-sources.js +159 -0
- package/dist/runtime/config/resolution/resolver.js +485 -0
- package/dist/runtime/config/resolution/types.js +1 -0
- package/dist/runtime/config/runtime/config-runtime.js +74 -0
- package/dist/runtime/config/runtime/source-refresh.js +263 -0
- package/dist/runtime/config/user-config.js +38 -31
- package/dist/runtime/security/project-execution-trust.js +34 -0
- package/dist/runtime/testing/server-fixture.js +9 -8
- package/dist/server.js +10 -6
- package/dist/subagents/profiles.js +336 -53
- package/dist/subagents/sessions/execution.js +6 -35
- package/dist/subagents/sessions/manager.js +14 -2
- package/dist/ui/.vite/manifest.json +275 -275
- package/dist/ui/activity-panel-app.html +1 -1
- package/dist/ui/assets/{activity-panel-app-Bw7ZFX2g.js → activity-panel-app-BeDiwd7D.js} +3 -3
- package/dist/ui/assets/{angular-html-DeUNP12X.js → angular-html-DuoCE7nn.js} +1 -1
- package/dist/ui/assets/{angular-ts-B3v8FKi_.js → angular-ts-D5tGk0hL.js} +1 -1
- package/dist/ui/assets/{apl-Bs3iQm4P.js → apl-lciOTPbw.js} +1 -1
- package/dist/ui/assets/{astro-D6GHecBY.js → astro-vJbqfNnJ.js} +1 -1
- package/dist/ui/assets/{blade-BMNbVOHv.js → blade-C7655Q4K.js} +1 -1
- package/dist/ui/assets/{c-BquuA6bv.js → c-DdHp1Jrw.js} +1 -1
- package/dist/ui/assets/{cobol-BuZ6kfps.js → cobol-DgvzQxZP.js} +1 -1
- package/dist/ui/assets/{coffee-DgC5uBzL.js → coffee-BIjVUOUd.js} +1 -1
- package/dist/ui/assets/{cpp-BdvWqCaB.js → cpp-CU8tGzvI.js} +1 -1
- package/dist/ui/assets/{crystal-0Q5qBvNI.js → crystal-CUQAwsg9.js} +1 -1
- package/dist/ui/assets/{css-Be_T8idh.js → css-DtMTqD3F.js} +1 -1
- package/dist/ui/assets/{edge-DaGLdgh_.js → edge-DoCX9RnL.js} +1 -1
- package/dist/ui/assets/{elixir-CMgiFIzB.js → elixir-DJxV9_U6.js} +1 -1
- package/dist/ui/assets/{elm-B-ufMlMC.js → elm-DIgWqK4r.js} +1 -1
- package/dist/ui/assets/{erb-CaGiOZM-.js → erb-BHxP0c_e.js} +1 -1
- package/dist/ui/assets/{git-rebase-CpwUWCzK.js → git-rebase-HbI_Vo8d.js} +1 -1
- package/dist/ui/assets/{glimmer-js-BRxccvnE.js → glimmer-js-CthY4Ty_.js} +1 -1
- package/dist/ui/assets/{glimmer-ts-CgQbPzlT.js → glimmer-ts-B2-_A1Xz.js} +1 -1
- package/dist/ui/assets/{glsl-D7JmHFr0.js → glsl-D9V9gq98.js} +1 -1
- package/dist/ui/assets/{graphql-C8n0eUBK.js → graphql-CddeAaS3.js} +1 -1
- package/dist/ui/assets/{hack-lVN7KVQv.js → hack-D_loGkOT.js} +1 -1
- package/dist/ui/assets/{haml-h18lG0pS.js → haml-BoWDczd0.js} +1 -1
- package/dist/ui/assets/{handlebars-lRFjRPfj.js → handlebars-ZoSOvw9y.js} +1 -1
- package/dist/ui/assets/{heavy-payload-Cqx9MOkH.js → heavy-payload-DO34LwoZ.js} +3 -3
- package/dist/ui/assets/{html-DtILqTE1.js → html-CE-obXVo.js} +1 -1
- package/dist/ui/assets/{html-derivative-CSTSmPup.js → html-derivative-Cd-smAbW.js} +1 -1
- package/dist/ui/assets/{http-DNI8fsbY.js → http-CZrkbrSl.js} +1 -1
- package/dist/ui/assets/{hurl-DgRi60JG.js → hurl-CxTuxBt1.js} +1 -1
- package/dist/ui/assets/{java-CW9pZ5bh.js → java-BshLjCat.js} +1 -1
- package/dist/ui/assets/{javascript-Cvg2UrWL.js → javascript-D2pxQTiX.js} +1 -1
- package/dist/ui/assets/{jinja-CZw_S1FB.js → jinja-kbfPP7-F.js} +1 -1
- package/dist/ui/assets/{jison-C56u_FZv.js → jison-C2mt9l1M.js} +1 -1
- package/dist/ui/assets/{json-DW2UTv1q.js → json-DU_rg_AU.js} +1 -1
- package/dist/ui/assets/{jsx-Zj5gobdD.js → jsx-D_8aQMlx.js} +1 -1
- package/dist/ui/assets/{julia-Cvcrre-L.js → julia-BmefTaVV.js} +1 -1
- package/dist/ui/assets/{just-8CZ2u6Tj.js → just-DXV8vk1S.js} +1 -1
- package/dist/ui/assets/{latex-D289AXWG.js → latex-Cjm2MvGW.js} +1 -1
- package/dist/ui/assets/{liquid-CFEX3SkZ.js → liquid-DyQDlo0u.js} +1 -1
- package/dist/ui/assets/{lua-BKpJ0ybO.js → lua-B2A1vzzy.js} +1 -1
- package/dist/ui/assets/{marko-D9maZ0a6.js → marko-B9vOkXOU.js} +1 -1
- package/dist/ui/assets/{mdc-rM9l0qWR.js → mdc-D42m6spU.js} +1 -1
- package/dist/ui/assets/{nginx-C9w6p2C9.js → nginx-TpfVxSUY.js} +1 -1
- package/dist/ui/assets/{nim-BRePHk0J.js → nim-CJr5T1FK.js} +1 -1
- package/dist/ui/assets/{perl-CskTmiPQ.js → perl-cJZcsdm9.js} +1 -1
- package/dist/ui/assets/{php-DCU_FvNI.js → php-CSZL5KRT.js} +1 -1
- package/dist/ui/assets/{pug-XLaVxTvv.js → pug-B1eMKzuA.js} +1 -1
- package/dist/ui/assets/{qml-CaONKSl-.js → qml-Dl2_7zc9.js} +1 -1
- package/dist/ui/assets/{r-BCVR0ZE3.js → r-F0aaO9gG.js} +1 -1
- package/dist/ui/assets/{razor-DDnGXxnU.js → razor-BPnkSMCw.js} +1 -1
- package/dist/ui/assets/{regexp-DWGTYYVx.js → regexp-BnGHMFbv.js} +1 -1
- package/dist/ui/assets/{rst-Dz3GWvQm.js → rst-B3CO7WYO.js} +1 -1
- package/dist/ui/assets/{ruby-DC3jGtMY.js → ruby-B6VMzT1T.js} +1 -1
- package/dist/ui/assets/{sas-7mNpvsjZ.js → sas-Df2lHVuU.js} +1 -1
- package/dist/ui/assets/{scss-uBsoCJVy.js → scss-4XxI6sgu.js} +1 -1
- package/dist/ui/assets/{shellscript-BCN9N_Jf.js → shellscript-DYASCN4r.js} +1 -1
- package/dist/ui/assets/{shellsession-Hsw46VXV.js → shellsession-Br6AGJO_.js} +1 -1
- package/dist/ui/assets/{soy-D6KV69HU.js → soy-p3ZNq1We.js} +1 -1
- package/dist/ui/assets/{sql-CVzVQkJh.js → sql-CAdv2OXL.js} +1 -1
- package/dist/ui/assets/{stata-BwUjXNhI.js → stata-Dx7yUbG_.js} +1 -1
- package/dist/ui/assets/{surrealql-CgloWqRa.js → surrealql-6YsE8HQa.js} +1 -1
- package/dist/ui/assets/{svelte-DcC3n6IC.js → svelte-YgzedwpD.js} +1 -1
- package/dist/ui/assets/{templ-DOFfnWTJ.js → templ-Bahqx3uD.js} +1 -1
- package/dist/ui/assets/{tex-ln89VPaX.js → tex-o6fTQjkH.js} +1 -1
- package/dist/ui/assets/{ts-tags-CWMzCstH.js → ts-tags-BO-Co0Ju.js} +1 -1
- package/dist/ui/assets/{tsx-DgzfiGeK.js → tsx-DtQe5AoH.js} +1 -1
- package/dist/ui/assets/{twig-Bi2itZL9.js → twig-BcUs6Kqc.js} +1 -1
- package/dist/ui/assets/{typescript-CQ24f4zW.js → typescript-BPtM8h4u.js} +1 -1
- package/dist/ui/assets/{vue-BS8jE5u6.js → vue-5KkGUzJJ.js} +1 -1
- package/dist/ui/assets/{vue-html-o38xqhyB.js → vue-html-Df_Xfsz9.js} +1 -1
- package/dist/ui/assets/{vue-vine-D2NvKv50.js → vue-vine-hUIe_u_Y.js} +1 -1
- package/dist/ui/assets/{xml-_44rx9_2.js → xml-C3Jy7as5.js} +1 -1
- package/dist/ui/assets/{xsl-B5QWQPoi.js → xsl-BnF9OuFo.js} +1 -1
- package/dist/ui/assets/{yaml-CKL2pEWe.js → yaml-DyrFgCou.js} +1 -1
- package/dist/workspaces/conversation-test-support.js +3 -11
- package/dist/workspaces/sessions.js +2 -0
- package/dist/workspaces/state/project-context.js +221 -0
- package/dist/workspaces.js +3 -1
- package/docs/chatgpt-coding-workflow.md +8 -3
- package/docs/configuration.md +99 -41
- package/docs/gotchas.md +24 -5
- package/docs/security.md +14 -3
- package/docs/setup.md +12 -7
- package/package.json +12 -3
- package/schemas/v1/config.project-local.schema.json +14 -0
- package/schemas/v1/config.project.schema.json +14 -0
- package/schemas/v1/config.user.schema.json +538 -0
- package/schemas/v1/hooks.project-local.schema.json +118 -0
- package/schemas/v1/hooks.project.schema.json +118 -0
- package/schemas/v1/hooks.user.schema.json +118 -0
- package/schemas/v1/language-servers.project-local.schema.json +230 -0
- package/schemas/v1/language-servers.project.schema.json +230 -0
- package/schemas/v1/language-servers.user.schema.json +230 -0
- package/schemas/v1/mcp.project-local.schema.json +143 -0
- package/schemas/v1/mcp.project.schema.json +143 -0
- package/schemas/v1/mcp.user.schema.json +143 -0
- package/scripts/ci/architecture.mjs +54 -4
- package/scripts/ci/cmd-acceptance.mjs +9 -9
- package/scripts/ci/config-v2-product-acceptance.mjs +639 -0
- package/scripts/ci/gates/acceptance-artifact.mjs +66 -0
- package/scripts/ci/gates/core-contract.mjs +27 -0
- package/scripts/ci/gates/platform-tests.mjs +38 -0
- package/scripts/ci/gates/prepare-windows-product.mjs +57 -0
- package/scripts/ci/gates/test-shard.mjs +71 -0
- package/scripts/ci/powershell51-acceptance.mjs +9 -9
- package/scripts/ci/pwsh-acceptance.mjs +9 -9
- package/scripts/ci/verify.mjs +2 -0
- package/scripts/ci/windows-product-acceptance.mjs +9 -10
- package/scripts/config/schema-text.mjs +4 -0
- package/scripts/config/schema-text.test.mjs +18 -0
- package/scripts/config/schema.mjs +74 -0
- package/scripts/lsp/interop.mjs +4 -1
- package/scripts/release/pack.mjs +64 -4
- package/scripts/release/release-gate.test.mjs +93 -27
- package/scripts/release-parity.mjs +19 -2
- package/templates/instructions/cmd.md +12 -0
- package/templates/instructions/fish.md +11 -0
- package/templates/instructions/powershell.md +14 -0
- package/templates/instructions/pwsh.md +14 -0
- package/templates/instructions/zsh.md +11 -0
- package/dist/runtime/instructions/powershell-skill.js +0 -65
package/dist/cli/init.js
CHANGED
|
@@ -4,14 +4,16 @@ import { publicEndpointUrl } from "../mcp/oauth/public-url.js";
|
|
|
4
4
|
import { expandHomePath } from "../mcp/filesystem/roots.js";
|
|
5
5
|
import { detectLauncherCommandShell, } from "../runtime/shell/command-shell-runtime.js";
|
|
6
6
|
import { seedShellInstructionFiles, shellInstructionFamiliesToSeed, } from "../runtime/instructions/shell-instructions.js";
|
|
7
|
-
import { seedPowerShellSkill, } from "../runtime/instructions/powershell-skill.js";
|
|
8
7
|
import { installManagedLanguageServers, installedManagedLanguageServers, managedLanguageServerOptions, } from "../lsp/runtime/managed-language-servers.js";
|
|
9
|
-
import { generateInstanceId, generateOwnerToken, loadForgeRelayFiles,
|
|
10
|
-
import {
|
|
8
|
+
import { generateInstanceId, generateOwnerToken, loadForgeRelayFiles, writeForgeRelayAuth, writeForgeRelayConfig, } from "../runtime/config/user-config.js";
|
|
9
|
+
import { generalConfigDefinition } from "../runtime/config/definition/general-config.js";
|
|
10
|
+
import { configSchemaId } from "../runtime/config/definition/schema.js";
|
|
11
|
+
import { classifyClientFacingBaseUrl, compactPublicBaseUrlConfig, hasInsecureLanBaseUrl, isLoopbackBindAddress, normalizePublicBaseUrlsInput, SetupCancelledError, textPrompt, validateHttpsProxyBaseUrls, validateLanClientFacingBaseUrls, validatePort, } from "./setup-support.js";
|
|
11
12
|
import { commandShellCompatibilityWarning, commandShellSetupOptions, customPinnedPreference, defaultCommandShellSetupChoice, followLauncherPreference, pinnedFamilyPreference, preservePinnedPreference, shellFamiliesForCustomSelection, } from "./shell/setup.js";
|
|
12
|
-
|
|
13
|
+
import { applySetupConfig } from "./init/setup-config.js";
|
|
14
|
+
export async function runInit({ force, advanced, version }) {
|
|
13
15
|
const files = loadForgeRelayFiles();
|
|
14
|
-
if (!force && files.configExists && files.authExists) {
|
|
16
|
+
if (!force && !advanced && files.configExists && files.authExists) {
|
|
15
17
|
prompts.log.info(`ForgeRelay is already configured at ${files.dir}`);
|
|
16
18
|
prompts.log.info("Run `forgerelay init --force` to update it.");
|
|
17
19
|
return;
|
|
@@ -30,13 +32,14 @@ export async function runInit({ force, version }) {
|
|
|
30
32
|
.map((root) => resolve(expandHomePath(root.trim())))
|
|
31
33
|
.filter(Boolean);
|
|
32
34
|
const defaultPort = String(files.config.port ?? 7676);
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
const port = advanced
|
|
36
|
+
? Number(await textPrompt({
|
|
37
|
+
message: `Which local port should ForgeRelay use? Press Enter to use ${defaultPort}`,
|
|
38
|
+
placeholder: defaultPort,
|
|
39
|
+
defaultValue: defaultPort,
|
|
40
|
+
validate: validatePort,
|
|
41
|
+
}))
|
|
42
|
+
: Number(defaultPort);
|
|
40
43
|
const existingPublicBaseUrls = Array.isArray(files.config.publicBaseUrl)
|
|
41
44
|
? files.config.publicBaseUrl
|
|
42
45
|
: files.config.publicBaseUrl ? [files.config.publicBaseUrl] : [];
|
|
@@ -74,10 +77,8 @@ export async function runInit({ force, version }) {
|
|
|
74
77
|
if (prompts.isCancel(selectedMode))
|
|
75
78
|
throw new SetupCancelledError();
|
|
76
79
|
const networkMode = selectedMode;
|
|
77
|
-
|
|
78
|
-
let publicBaseUrl = null;
|
|
80
|
+
let publicBaseUrl;
|
|
79
81
|
let clientFacingBaseUrls = [`http://127.0.0.1:${port}`];
|
|
80
|
-
const trustedProxies = networkMode === "proxy" ? ["loopback"] : undefined;
|
|
81
82
|
if (networkMode === "lan" || networkMode === "proxy") {
|
|
82
83
|
const validateBaseUrls = networkMode === "lan"
|
|
83
84
|
? validateLanClientFacingBaseUrls
|
|
@@ -120,123 +121,118 @@ export async function runInit({ force, version }) {
|
|
|
120
121
|
}
|
|
121
122
|
publicBaseUrl = compactPublicBaseUrlConfig(clientFacingBaseUrls);
|
|
122
123
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
options: commandShellSetupOptions(process.platform, files.config.commandShell, launcherShell),
|
|
128
|
-
});
|
|
129
|
-
if (prompts.isCancel(selectedShellChoice))
|
|
130
|
-
throw new SetupCancelledError();
|
|
131
|
-
let commandShell;
|
|
132
|
-
const shellChoice = selectedShellChoice;
|
|
133
|
-
if (shellChoice === "follow-launcher") {
|
|
134
|
-
commandShell = followLauncherPreference(files.config.commandShell, launcherShell);
|
|
135
|
-
}
|
|
136
|
-
else if (shellChoice === "keep-pinned") {
|
|
137
|
-
commandShell = preservePinnedPreference(files.config.commandShell);
|
|
138
|
-
}
|
|
139
|
-
else if (shellChoice === "custom") {
|
|
140
|
-
const customFamily = await prompts.select({
|
|
141
|
-
message: "Which command language does the custom executable implement?",
|
|
142
|
-
options: shellFamiliesForCustomSelection(),
|
|
143
|
-
});
|
|
144
|
-
if (prompts.isCancel(customFamily))
|
|
145
|
-
throw new SetupCancelledError();
|
|
146
|
-
const executable = await textPrompt({
|
|
147
|
-
message: "What is the command-shell executable path?",
|
|
148
|
-
placeholder: process.platform === "win32" ? "D:\\Portable\\PowerShell\\pwsh.exe" : "/opt/custom/bin/zsh",
|
|
149
|
-
defaultValue: "",
|
|
150
|
-
validate: (value) => value?.trim() ? undefined : "Enter an executable path.",
|
|
151
|
-
});
|
|
152
|
-
commandShell = customPinnedPreference(customFamily, executable);
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
commandShell = pinnedFamilyPreference(shellChoice);
|
|
156
|
-
}
|
|
157
|
-
const shellWarning = commandShellCompatibilityWarning(commandShell.family);
|
|
158
|
-
if (shellWarning)
|
|
159
|
-
prompts.note(shellWarning, "Command shell compatibility");
|
|
160
|
-
const shellInstructionFamilies = shellInstructionFamiliesToSeed(process.platform, commandShell.family);
|
|
161
|
-
let shellInstructions = files.config.shellInstructions;
|
|
124
|
+
let advancedSelection;
|
|
125
|
+
let commandShell = files.config.commandShell;
|
|
126
|
+
let shellInstructionFamilies = [];
|
|
127
|
+
let shellInstructions = files.config.shellInstructions ?? false;
|
|
162
128
|
let shellInstructionSeedResults = [];
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
message: "
|
|
167
|
-
initialValue: files.config.
|
|
129
|
+
if (advanced) {
|
|
130
|
+
const launcherShell = detectLauncherCommandShell();
|
|
131
|
+
const selectedShellChoice = await prompts.select({
|
|
132
|
+
message: "Which command shell should Agent commands and Hooks use?",
|
|
133
|
+
initialValue: defaultCommandShellSetupChoice(files.config.commandShell),
|
|
134
|
+
options: commandShellSetupOptions(process.platform, files.config.commandShell, launcherShell),
|
|
168
135
|
});
|
|
169
|
-
if (prompts.isCancel(
|
|
136
|
+
if (prompts.isCancel(selectedShellChoice))
|
|
170
137
|
throw new SetupCancelledError();
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
version,
|
|
175
|
-
families: shellInstructionFamilies,
|
|
176
|
-
});
|
|
177
|
-
const failedShellInstructions = shellInstructionSeedResults.filter((result) => result.status === "failed");
|
|
178
|
-
if (failedShellInstructions.length > 0) {
|
|
179
|
-
prompts.note(failedShellInstructions.map((result) => `${result.family}: ${result.error ?? "download failed"}\n${result.sourceUrl}`).join("\n\n"), "Shell Instructions unavailable");
|
|
138
|
+
const shellChoice = selectedShellChoice;
|
|
139
|
+
if (shellChoice === "follow-launcher") {
|
|
140
|
+
commandShell = followLauncherPreference(files.config.commandShell, launcherShell);
|
|
180
141
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
powerShellSkillSeedResult = await seedPowerShellSkill({
|
|
184
|
-
configDir: files.dir,
|
|
185
|
-
version,
|
|
186
|
-
});
|
|
187
|
-
if (powerShellSkillSeedResult.status === "failed") {
|
|
188
|
-
prompts.note(`${powerShellSkillSeedResult.error ?? "download failed"}\n${powerShellSkillSeedResult.sourceUrl}`, "PowerShell Skill unavailable");
|
|
142
|
+
else if (shellChoice === "keep-pinned") {
|
|
143
|
+
commandShell = preservePinnedPreference(files.config.commandShell);
|
|
189
144
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
});
|
|
205
|
-
if (prompts.isCancel(allowAgentInstallAnswer))
|
|
206
|
-
throw new SetupCancelledError();
|
|
207
|
-
const allowAgentLanguageServerInstall = allowAgentInstallAnswer === true;
|
|
208
|
-
if (managedIds.length > 0) {
|
|
209
|
-
prompts.note([
|
|
210
|
-
"ForgeRelay installs these optional Language Servers into its private config directory, not global npm.",
|
|
211
|
-
"Rust Analyzer, gopls, and clangd remain external toolchain/system installations and are auto-detected when available.",
|
|
212
|
-
].join("\n"), "Managed Language Servers");
|
|
213
|
-
const spinner = prompts.spinner();
|
|
214
|
-
spinner.start("Installing managed Language Servers with npm");
|
|
215
|
-
try {
|
|
216
|
-
const installed = await installManagedLanguageServers(managedIds, files.dir);
|
|
217
|
-
spinner.stop(`Installed ${installed.installed.join(", ")} under ${installed.root}`);
|
|
145
|
+
else if (shellChoice === "custom") {
|
|
146
|
+
const customFamily = await prompts.select({
|
|
147
|
+
message: "Which command language does the custom executable implement?",
|
|
148
|
+
options: shellFamiliesForCustomSelection(),
|
|
149
|
+
});
|
|
150
|
+
if (prompts.isCancel(customFamily))
|
|
151
|
+
throw new SetupCancelledError();
|
|
152
|
+
const executable = await textPrompt({
|
|
153
|
+
message: "What is the command-shell executable path?",
|
|
154
|
+
placeholder: process.platform === "win32" ? "D:\\Portable\\PowerShell\\pwsh.exe" : "/opt/custom/bin/zsh",
|
|
155
|
+
defaultValue: "",
|
|
156
|
+
validate: (value) => value?.trim() ? undefined : "Enter an executable path.",
|
|
157
|
+
});
|
|
158
|
+
commandShell = customPinnedPreference(customFamily, executable);
|
|
218
159
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
throw error;
|
|
160
|
+
else {
|
|
161
|
+
commandShell = pinnedFamilyPreference(shellChoice);
|
|
222
162
|
}
|
|
163
|
+
const shellWarning = commandShellCompatibilityWarning(commandShell.family);
|
|
164
|
+
if (shellWarning)
|
|
165
|
+
prompts.note(shellWarning, "Command shell compatibility");
|
|
166
|
+
shellInstructionFamilies = shellInstructionFamiliesToSeed(process.platform, commandShell.family);
|
|
167
|
+
if (shellInstructionFamilies.length > 0) {
|
|
168
|
+
const shellInstructionsAnswer = await prompts.confirm({
|
|
169
|
+
message: "Enable ForgeRelay Runtime Shell Instructions for this command shell?",
|
|
170
|
+
initialValue: files.config.shellInstructions === true,
|
|
171
|
+
});
|
|
172
|
+
if (prompts.isCancel(shellInstructionsAnswer))
|
|
173
|
+
throw new SetupCancelledError();
|
|
174
|
+
shellInstructions = shellInstructionsAnswer === true;
|
|
175
|
+
if (shellInstructions) {
|
|
176
|
+
shellInstructionSeedResults = await seedShellInstructionFiles({
|
|
177
|
+
configDir: files.dir,
|
|
178
|
+
version,
|
|
179
|
+
families: shellInstructionFamilies,
|
|
180
|
+
});
|
|
181
|
+
const failedShellInstructions = shellInstructionSeedResults.filter((result) => result.status === "failed");
|
|
182
|
+
if (failedShellInstructions.length > 0) {
|
|
183
|
+
prompts.note(failedShellInstructions.map((result) => `${result.family}: ${result.error ?? "download failed"}\n${result.sourceUrl}`).join("\n\n"), "Shell Instructions unavailable");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const installedManaged = installedManagedLanguageServers(files.dir);
|
|
188
|
+
const selectedManaged = await prompts.multiselect({
|
|
189
|
+
message: "Which Language Servers should ForgeRelay manage with npm?",
|
|
190
|
+
options: managedLanguageServerOptions(),
|
|
191
|
+
initialValues: installedManaged,
|
|
192
|
+
required: false,
|
|
193
|
+
});
|
|
194
|
+
if (prompts.isCancel(selectedManaged))
|
|
195
|
+
throw new SetupCancelledError();
|
|
196
|
+
const managedIds = selectedManaged;
|
|
197
|
+
const allowAgentInstallAnswer = await prompts.confirm({
|
|
198
|
+
message: "Allow Agents to install or update ForgeRelay-managed Language Servers on demand?",
|
|
199
|
+
initialValue: files.config.allowAgentLanguageServerInstall === true,
|
|
200
|
+
});
|
|
201
|
+
if (prompts.isCancel(allowAgentInstallAnswer))
|
|
202
|
+
throw new SetupCancelledError();
|
|
203
|
+
const allowAgentLanguageServerInstall = allowAgentInstallAnswer === true;
|
|
204
|
+
if (managedIds.length > 0) {
|
|
205
|
+
prompts.note([
|
|
206
|
+
"ForgeRelay installs these optional Language Servers into its private config directory, not global npm.",
|
|
207
|
+
"Rust Analyzer, gopls, and clangd remain external toolchain/system installations and are auto-detected when available.",
|
|
208
|
+
].join("\n"), "Managed Language Servers");
|
|
209
|
+
const spinner = prompts.spinner();
|
|
210
|
+
spinner.start("Installing managed Language Servers with npm");
|
|
211
|
+
try {
|
|
212
|
+
const installed = await installManagedLanguageServers(managedIds, files.dir);
|
|
213
|
+
spinner.stop(`Installed ${installed.installed.join(", ")} under ${installed.root}`);
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
spinner.stop("Managed Language Server installation failed");
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
advancedSelection = {
|
|
221
|
+
port,
|
|
222
|
+
commandShell,
|
|
223
|
+
shellInstructions,
|
|
224
|
+
allowAgentLanguageServerInstall,
|
|
225
|
+
};
|
|
223
226
|
}
|
|
224
|
-
const config = {
|
|
225
|
-
|
|
226
|
-
host,
|
|
227
|
-
port,
|
|
227
|
+
const config = applySetupConfig(files.config, {
|
|
228
|
+
schema: configSchemaId(generalConfigDefinition, "user"),
|
|
228
229
|
allowedRoots,
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
shellInstructions,
|
|
236
|
-
subagents: resolveSubagentsFlag(files.config),
|
|
237
|
-
languageServers: files.config.languageServers,
|
|
238
|
-
allowAgentLanguageServerInstall,
|
|
239
|
-
};
|
|
230
|
+
network: {
|
|
231
|
+
mode: networkMode,
|
|
232
|
+
...(publicBaseUrl === undefined ? {} : { publicBaseUrl }),
|
|
233
|
+
},
|
|
234
|
+
...(advancedSelection ? { advanced: advancedSelection } : {}),
|
|
235
|
+
});
|
|
240
236
|
const auth = {
|
|
241
237
|
...files.auth,
|
|
242
238
|
ownerToken: files.auth.ownerToken ?? generateOwnerToken(),
|
|
@@ -244,24 +240,26 @@ export async function runInit({ force, version }) {
|
|
|
244
240
|
};
|
|
245
241
|
const configPath = writeForgeRelayConfig(config);
|
|
246
242
|
const authPath = await writeForgeRelayAuth(auth);
|
|
243
|
+
const bindHost = config.host ?? "127.0.0.1";
|
|
244
|
+
const bindPort = config.port ?? 7676;
|
|
247
245
|
const lines = [
|
|
248
246
|
`Config: ${configPath}`,
|
|
249
247
|
`Auth: ${authPath}`,
|
|
250
|
-
`Bind: http://${
|
|
251
|
-
|
|
252
|
-
...(
|
|
248
|
+
`Bind: http://${bindHost}:${bindPort}`,
|
|
249
|
+
"OAuth mode: Owner-password approval",
|
|
250
|
+
...(advancedSelection && commandShell
|
|
251
|
+
? [`Command shell: ${commandShell.mode} ${commandShell.family} (${commandShell.executable})`]
|
|
252
|
+
: []),
|
|
253
|
+
...(advancedSelection && shellInstructionFamilies.length > 0
|
|
253
254
|
? [
|
|
254
255
|
`Shell Instructions: ${shellInstructions ? "enabled" : "disabled"}`,
|
|
255
256
|
...shellInstructionSeedResults.map((result) => `Shell Instructions ${result.family}: ${result.status} (${result.path})`),
|
|
256
257
|
]
|
|
257
258
|
: []),
|
|
258
|
-
...(powerShellSkillSeedResult
|
|
259
|
-
? [`PowerShell Skill: ${powerShellSkillSeedResult.status} (${powerShellSkillSeedResult.path})`]
|
|
260
|
-
: []),
|
|
261
259
|
...clientFacingBaseUrls.map((baseUrl, index) => `${index === 0 ? "Client-facing MCP URL" : "Client-facing MCP alias"}: ${publicEndpointUrl(baseUrl, "mcp").toString()}`),
|
|
262
260
|
];
|
|
263
261
|
if (networkMode === "ssh") {
|
|
264
|
-
lines.push(`SSH relay: forgerelay auth -J <ssh-host> 127.0.0.1:${
|
|
262
|
+
lines.push(`SSH relay: forgerelay auth -J <ssh-host> 127.0.0.1:${bindPort} --ssh-auth`);
|
|
265
263
|
}
|
|
266
264
|
prompts.note(lines.join("\n"), "ForgeRelay configured");
|
|
267
265
|
prompts.note([
|
package/dist/cli/maintenance.js
CHANGED
|
@@ -8,6 +8,8 @@ import { databasePath } from "../runtime/state/db/client.js";
|
|
|
8
8
|
import { bashBytesExpression, columnExists, eligibleActivityCte, tableExists, } from "./maintenance-retention.js";
|
|
9
9
|
import { printMaintenancePruneReport, pruneMaintenanceState, } from "./maintenance-prune.js";
|
|
10
10
|
import { forgerelayConfigPath, } from "../runtime/config/user-config.js";
|
|
11
|
+
import { resolveGeneralConfig } from "../runtime/config/resolution/general.js";
|
|
12
|
+
import { assertConfigResolutionValid } from "../runtime/config/resolution/resolver.js";
|
|
11
13
|
const DAY_MS = 24 * 60 * 60 * 1_000;
|
|
12
14
|
const MAX_RETENTION_DAYS = 36_500;
|
|
13
15
|
const MAX_CHECKPOINT_STATE_BYTES = 1024 * 1024;
|
|
@@ -44,9 +46,17 @@ export function runMaintenanceCommand(args, env = process.env) {
|
|
|
44
46
|
printMaintenancePruneReport(report);
|
|
45
47
|
}
|
|
46
48
|
export function inspectMaintenanceState(env = process.env, now = new Date()) {
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
49
|
+
const configPath = forgerelayConfigPath(env);
|
|
50
|
+
const persistedConfig = readMaintenanceConfig(env);
|
|
51
|
+
const resolution = resolveGeneralConfig({
|
|
52
|
+
env,
|
|
53
|
+
user: persistedConfig,
|
|
54
|
+
userSourcePath: configPath,
|
|
55
|
+
});
|
|
56
|
+
assertConfigResolutionValid(resolution);
|
|
57
|
+
const config = resolution.values;
|
|
58
|
+
const stateDir = resolveMaintenanceStateDir(config);
|
|
59
|
+
const policy = resolveMaintenanceRetentionPolicy(config.retention, {});
|
|
50
60
|
const cutoff = policy.historyDays === null
|
|
51
61
|
? null
|
|
52
62
|
: new Date(now.getTime() - policy.historyDays * DAY_MS).toISOString();
|
|
@@ -113,8 +123,8 @@ function readMaintenanceConfig(env) {
|
|
|
113
123
|
throw new Error(`Unable to read ForgeRelay maintenance config ${path}: ${errorMessage(error)}`);
|
|
114
124
|
}
|
|
115
125
|
}
|
|
116
|
-
function resolveMaintenanceStateDir(config
|
|
117
|
-
const configured =
|
|
126
|
+
function resolveMaintenanceStateDir(config) {
|
|
127
|
+
const configured = config.stateDir ?? join(homedir(), ".local", "share", "forgerelay");
|
|
118
128
|
return resolve(expandHomePath(String(configured)));
|
|
119
129
|
}
|
|
120
130
|
function openReadOnlyDatabaseSnapshot(stateDir) {
|
|
@@ -77,7 +77,7 @@ async function runExternalMcpList(options, dependencies) {
|
|
|
77
77
|
if (options.rest.length > 0) {
|
|
78
78
|
throw new Error("Usage: forgerelay mcp list [--project <path>|--global]");
|
|
79
79
|
}
|
|
80
|
-
const scope = resolveExternalMcpScope(options, dependencies);
|
|
80
|
+
const scope = await resolveExternalMcpScope(options, dependencies);
|
|
81
81
|
const status = inspectExternalMcpStatus(scope);
|
|
82
82
|
console.log(formatExternalMcpList(status));
|
|
83
83
|
if (status.configIssues > 0) {
|
|
@@ -85,7 +85,7 @@ async function runExternalMcpList(options, dependencies) {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
async function runExternalMcpTest(options, dependencies) {
|
|
88
|
-
const scope = resolveExternalMcpScope(options, dependencies);
|
|
88
|
+
const scope = await resolveExternalMcpScope(options, dependencies);
|
|
89
89
|
const initialStatus = inspectExternalMcpStatus(scope);
|
|
90
90
|
const serverStatus = findExternalMcpServerStatus(initialStatus, options.server);
|
|
91
91
|
if (!serverStatus)
|
|
@@ -99,7 +99,10 @@ async function runExternalMcpTest(options, dependencies) {
|
|
|
99
99
|
printTestTarget(scope, serverStatus);
|
|
100
100
|
const gateway = new ExternalMcpGateway(scope.config.mediaMaxBytes, scope.store);
|
|
101
101
|
try {
|
|
102
|
-
const probe = await gateway.probe(scope.snapshot.servers, options.server, undefined, {
|
|
102
|
+
const probe = await gateway.probe(scope.snapshot.servers, options.server, undefined, {
|
|
103
|
+
...(scope.project ? { project: { id: scope.project.id, projectRoot: scope.project.projectRoot } } : {}),
|
|
104
|
+
origins: scope.snapshot.origins,
|
|
105
|
+
});
|
|
103
106
|
console.log("Connection: ok");
|
|
104
107
|
console.log(`Protocol: ${probe.protocolEra}` +
|
|
105
108
|
(probe.protocolVersion ? ` · ${probe.protocolVersion}` : ""));
|
|
@@ -148,11 +151,11 @@ function cliArgument(value) {
|
|
|
148
151
|
return /^[A-Za-z0-9_./:\\-]+$/.test(value) ? value : JSON.stringify(value);
|
|
149
152
|
}
|
|
150
153
|
function formatCredentialScope(source, projectRoot) {
|
|
151
|
-
return source === "project" ? `project · ${projectRoot}` : "global";
|
|
154
|
+
return source === "project" || source === "project-local" ? `project · ${projectRoot}` : "global";
|
|
152
155
|
}
|
|
153
156
|
async function runExternalMcpAuth(options, dependencies) {
|
|
154
157
|
assertInteractive(dependencies);
|
|
155
|
-
const target = resolveExternalMcpCliTarget(options, dependencies);
|
|
158
|
+
const target = await resolveExternalMcpCliTarget(options, dependencies);
|
|
156
159
|
if (hasStaticAuthorizationHeader(target.serverConfig.headers)) {
|
|
157
160
|
throw new Error(`External MCP ${target.server} uses a static Authorization header; remove that header before using OAuth authentication.`);
|
|
158
161
|
}
|
|
@@ -160,7 +163,7 @@ async function runExternalMcpAuth(options, dependencies) {
|
|
|
160
163
|
console.log(`Source: ${target.source}`);
|
|
161
164
|
console.log(`Transport: ${target.serverConfig.transport}`);
|
|
162
165
|
console.log(`Credential scope: ${formatCredentialScope(target.source, target.projectRoot)}`);
|
|
163
|
-
const identity = externalMcpCredentialIdentity(target.source, target.server, target.
|
|
166
|
+
const identity = externalMcpCredentialIdentity(target.source, target.server, target.project);
|
|
164
167
|
const existing = target.store.read(identity);
|
|
165
168
|
const receiver = await (dependencies.createLoopbackReceiver ?? createLoopbackReceiver)(target.serverConfig.oauth?.callbackPort);
|
|
166
169
|
let authorizationUrl;
|
|
@@ -224,11 +227,11 @@ async function runExternalMcpAuth(options, dependencies) {
|
|
|
224
227
|
}
|
|
225
228
|
}
|
|
226
229
|
async function runExternalMcpLogout(options, dependencies) {
|
|
227
|
-
const target = resolveExternalMcpCliTarget(options, dependencies);
|
|
230
|
+
const target = await resolveExternalMcpCliTarget(options, dependencies);
|
|
228
231
|
console.log(`Logging out External MCP ${target.server}`);
|
|
229
232
|
console.log(`Source: ${target.source}`);
|
|
230
233
|
console.log(`Credential scope: ${formatCredentialScope(target.source, target.projectRoot)}`);
|
|
231
|
-
const identity = externalMcpCredentialIdentity(target.source, target.server, target.
|
|
234
|
+
const identity = externalMcpCredentialIdentity(target.source, target.server, target.project);
|
|
232
235
|
let existing;
|
|
233
236
|
await target.store.withIdentityLock(identity, async () => {
|
|
234
237
|
existing = target.store.read(identity);
|
|
@@ -254,8 +257,8 @@ async function runExternalMcpLogout(options, dependencies) {
|
|
|
254
257
|
else
|
|
255
258
|
console.log("Remote revocation: failed; the local credential was still removed.");
|
|
256
259
|
}
|
|
257
|
-
function resolveExternalMcpCliTarget(options, dependencies) {
|
|
258
|
-
const scope = resolveExternalMcpScope(options, dependencies);
|
|
260
|
+
async function resolveExternalMcpCliTarget(options, dependencies) {
|
|
261
|
+
const scope = await resolveExternalMcpScope(options, dependencies);
|
|
259
262
|
const serverConfig = scope.snapshot.servers[options.server];
|
|
260
263
|
const source = scope.snapshot.origins[options.server];
|
|
261
264
|
if (!serverConfig || !source) {
|
|
@@ -271,6 +274,7 @@ function resolveExternalMcpCliTarget(options, dependencies) {
|
|
|
271
274
|
return {
|
|
272
275
|
store: scope.store,
|
|
273
276
|
projectRoot: scope.projectRoot,
|
|
277
|
+
...(scope.project ? { project: { id: scope.project.id, projectRoot: scope.project.projectRoot } } : {}),
|
|
274
278
|
server: options.server,
|
|
275
279
|
source,
|
|
276
280
|
serverConfig,
|
package/dist/cli/mcp/status.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { ProjectContextResolver } from "../../workspaces/state/project-context.js";
|
|
3
4
|
import { loadConfig } from "../../runtime/config/config.js";
|
|
4
5
|
import { ExternalMcpCredentialStore, externalMcpCredentialIdentity, } from "../../runtime/config/external-mcp-auth-store.js";
|
|
5
6
|
import { ExternalMcpConfigRegistry, } from "../../runtime/config/external-mcp-registry.js";
|
|
6
|
-
export function resolveExternalMcpScope(request, dependencies = {}) {
|
|
7
|
+
export async function resolveExternalMcpScope(request, dependencies = {}) {
|
|
7
8
|
if (request.global && request.projectRoot) {
|
|
8
9
|
throw new Error("--global and --project cannot be used together.");
|
|
9
10
|
}
|
|
@@ -12,7 +13,7 @@ export function resolveExternalMcpScope(request, dependencies = {}) {
|
|
|
12
13
|
const cwd = resolve(dependencies.cwd ?? process.cwd());
|
|
13
14
|
const registry = new ExternalMcpConfigRegistry({
|
|
14
15
|
configDir: config.configDir,
|
|
15
|
-
|
|
16
|
+
sourceRuntime: config.configRuntime.sources,
|
|
16
17
|
});
|
|
17
18
|
const store = new ExternalMcpCredentialStore({ configDir: config.configDir });
|
|
18
19
|
if (request.global) {
|
|
@@ -27,13 +28,15 @@ export function resolveExternalMcpScope(request, dependencies = {}) {
|
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
30
|
const selected = selectProjectRoot(request.projectRoot, cwd, env);
|
|
31
|
+
const project = await new ProjectContextResolver(config.configDir).resolve(selected.root);
|
|
30
32
|
return {
|
|
31
33
|
mode: "project",
|
|
32
|
-
projectRoot:
|
|
34
|
+
projectRoot: project.projectRoot,
|
|
35
|
+
project,
|
|
33
36
|
projectSelection: selected.selection,
|
|
34
37
|
config,
|
|
35
38
|
registry,
|
|
36
|
-
snapshot: registry.resolve(
|
|
39
|
+
snapshot: registry.resolve(project),
|
|
37
40
|
store,
|
|
38
41
|
};
|
|
39
42
|
}
|
|
@@ -79,7 +82,7 @@ export function formatExternalMcpList(status) {
|
|
|
79
82
|
}
|
|
80
83
|
lines.push("", "Config:");
|
|
81
84
|
for (const source of status.scope.snapshot.sources) {
|
|
82
|
-
lines.push(` ${source.source.padEnd(
|
|
85
|
+
lines.push(` ${source.source.padEnd(13)} ${source.path} · ${formatConfigSourceState(source, status)}`);
|
|
83
86
|
}
|
|
84
87
|
if (status.credentialStore === "invalid") {
|
|
85
88
|
lines.push(` auth ${status.scope.store.filePath} · invalid`);
|
|
@@ -111,6 +114,8 @@ export function formatExternalMcpList(status) {
|
|
|
111
114
|
for (const diagnostic of status.scope.snapshot.diagnostics) {
|
|
112
115
|
const source = status.scope.snapshot.sources.find((candidate) => candidate.source === diagnostic.source);
|
|
113
116
|
lines.push(` ${diagnostic.source}: ${diagnostic.message}`);
|
|
117
|
+
if (diagnostic.severity !== "error")
|
|
118
|
+
continue;
|
|
114
119
|
if (source?.usingLastKnownGood) {
|
|
115
120
|
lines.push(" Using this process's last-known-good configuration.");
|
|
116
121
|
}
|
|
@@ -195,7 +200,7 @@ function serverStatus(scope, name, source, config, credentialStoreInvalid) {
|
|
|
195
200
|
authState: "not-detected",
|
|
196
201
|
};
|
|
197
202
|
}
|
|
198
|
-
const identity = externalMcpCredentialIdentity(source, name, scope.projectRoot);
|
|
203
|
+
const identity = externalMcpCredentialIdentity(source, name, scope.project ? { id: scope.project.id, projectRoot: scope.project.projectRoot } : undefined);
|
|
199
204
|
let record;
|
|
200
205
|
try {
|
|
201
206
|
record = scope.store.read(identity);
|
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,8 @@ import * as prompts from "@clack/prompts";
|
|
|
11
11
|
import { loadConfig } from "./runtime/config/config.js";
|
|
12
12
|
import { acquireRuntimeLease } from "./runtime/state/runtime-lease.js";
|
|
13
13
|
import { runInit } from "./cli/init.js";
|
|
14
|
+
import { runConfigMigration } from "./cli/config/migrate.js";
|
|
15
|
+
import { runConfigInspection } from "./cli/config/inspect.js";
|
|
14
16
|
import { runMaintenanceCommand } from "./cli/maintenance.js";
|
|
15
17
|
import { runExternalMcpCommand } from "./cli/mcp/external-mcp.js";
|
|
16
18
|
import { formatExternalMcpDoctor, inspectExternalMcpStatus, resolveExternalMcpScope, } from "./cli/mcp/status.js";
|
|
@@ -47,14 +49,16 @@ async function main(argv) {
|
|
|
47
49
|
await serve(runtimePrivilege);
|
|
48
50
|
return;
|
|
49
51
|
}
|
|
50
|
-
case "init":
|
|
51
|
-
|
|
52
|
+
case "init": {
|
|
53
|
+
const initOptions = parseInitCommandArgs(args);
|
|
54
|
+
await runInit({ ...initOptions, version: installedForgeRelayVersion() });
|
|
52
55
|
return;
|
|
56
|
+
}
|
|
53
57
|
case "doctor":
|
|
54
58
|
await runDoctor();
|
|
55
59
|
return;
|
|
56
60
|
case "config":
|
|
57
|
-
runConfigCommand(args);
|
|
61
|
+
await runConfigCommand(args);
|
|
58
62
|
return;
|
|
59
63
|
case "hooks":
|
|
60
64
|
await runHooksCommand(args);
|
|
@@ -90,6 +94,21 @@ function normalizeCommand(command) {
|
|
|
90
94
|
return "version";
|
|
91
95
|
throw new Error(`Unknown command: ${command}`);
|
|
92
96
|
}
|
|
97
|
+
function parseInitCommandArgs(args) {
|
|
98
|
+
let force = false;
|
|
99
|
+
let advanced = false;
|
|
100
|
+
for (const arg of args) {
|
|
101
|
+
if (arg === "--force" && !force)
|
|
102
|
+
force = true;
|
|
103
|
+
else if (arg === "--advanced" && !advanced)
|
|
104
|
+
advanced = true;
|
|
105
|
+
else if (arg === "--force" || arg === "--advanced")
|
|
106
|
+
throw new Error(`${arg} may only be supplied once.`);
|
|
107
|
+
else
|
|
108
|
+
throw new Error(`Unknown init option: ${arg}`);
|
|
109
|
+
}
|
|
110
|
+
return { force, advanced };
|
|
111
|
+
}
|
|
93
112
|
function parseServeCommandArgs(args) {
|
|
94
113
|
let allowElevated = false;
|
|
95
114
|
for (const arg of args) {
|
|
@@ -123,7 +142,7 @@ async function ensureConfigured() {
|
|
|
123
142
|
"Or provide FORGERELAY_OAUTH_OWNER_TOKEN and FORGERELAY_ALLOWED_ROOTS.",
|
|
124
143
|
].join("\n"));
|
|
125
144
|
}
|
|
126
|
-
await runInit({ force: false, version: installedForgeRelayVersion() });
|
|
145
|
+
await runInit({ force: false, advanced: false, version: installedForgeRelayVersion() });
|
|
127
146
|
}
|
|
128
147
|
async function serve(runtimePrivilege) {
|
|
129
148
|
const sqliteStatus = checkSqliteNative();
|
|
@@ -384,7 +403,7 @@ async function runDoctor() {
|
|
|
384
403
|
console.log(`Agent-managed Language Server install: ${config.allowAgentLanguageServerInstall ? "enabled" : "disabled"}`);
|
|
385
404
|
console.log(`Allowed roots: ${config.allowedRoots.join(", ")}`);
|
|
386
405
|
console.log(`Allowed hosts: ${config.allowedHosts.join(", ")}`);
|
|
387
|
-
console.log(formatExternalMcpDoctor(inspectExternalMcpStatus(resolveExternalMcpScope({}, {
|
|
406
|
+
console.log(formatExternalMcpDoctor(inspectExternalMcpStatus(await resolveExternalMcpScope({}, {
|
|
388
407
|
env: process.env,
|
|
389
408
|
cwd: process.cwd(),
|
|
390
409
|
}))));
|
|
@@ -393,8 +412,16 @@ async function runDoctor() {
|
|
|
393
412
|
console.log(`Config status: ${error instanceof Error ? error.message : String(error)}`);
|
|
394
413
|
}
|
|
395
414
|
}
|
|
396
|
-
function runConfigCommand(args) {
|
|
415
|
+
async function runConfigCommand(args) {
|
|
397
416
|
const [subcommand, key, ...rest] = args;
|
|
417
|
+
if (subcommand === "migrate") {
|
|
418
|
+
await runConfigMigration(args.slice(1));
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (subcommand === "check" || subcommand === "sources" || subcommand === "explain") {
|
|
422
|
+
process.exitCode = await runConfigInspection(args);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
398
425
|
const files = loadForgeRelayFiles();
|
|
399
426
|
if (!subcommand || subcommand === "get") {
|
|
400
427
|
console.log(JSON.stringify(files.config, null, 2));
|
|
@@ -424,10 +451,16 @@ function printHelp() {
|
|
|
424
451
|
" forgerelay serve Start the server",
|
|
425
452
|
" forgerelay serve --allow-elevated",
|
|
426
453
|
" Explicitly allow this invocation to run with elevated/unknown OS privilege",
|
|
427
|
-
" forgerelay init
|
|
454
|
+
" forgerelay init Run minimal first-time setup",
|
|
455
|
+
" forgerelay init --advanced Configure common advanced runtime options",
|
|
456
|
+
" forgerelay init --force Update setup-owned fields without migrating legacy config",
|
|
428
457
|
" forgerelay doctor Show config, runtime, and native dependency status",
|
|
429
458
|
" forgerelay config get Print persisted config",
|
|
430
459
|
" forgerelay config set publicBaseUrl <url[,url...]|null>",
|
|
460
|
+
" forgerelay config check [--global|--project <path>] [--json]",
|
|
461
|
+
" forgerelay config sources [--global|--project <path>] [--json]",
|
|
462
|
+
" forgerelay config explain <logical-path> [--global|--project <path>] [--json]",
|
|
463
|
+
" forgerelay config migrate [--dry-run] [--global|--project <path>]",
|
|
431
464
|
" forgerelay hooks list [--project <path>]",
|
|
432
465
|
" forgerelay hooks check [--project <path>]",
|
|
433
466
|
" forgerelay agents ls List subagent sessions",
|