@wingman-ai/gateway 0.3.0 → 0.3.2
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/README.md +8 -0
- package/dist/agent/config/agentConfig.cjs +12 -0
- package/dist/agent/config/agentConfig.d.ts +22 -0
- package/dist/agent/config/agentConfig.js +10 -1
- package/dist/agent/config/agentLoader.cjs +9 -0
- package/dist/agent/config/agentLoader.js +9 -0
- package/dist/agent/config/toolRegistry.cjs +17 -0
- package/dist/agent/config/toolRegistry.d.ts +15 -0
- package/dist/agent/config/toolRegistry.js +17 -0
- package/dist/agent/tests/agentConfig.test.cjs +6 -1
- package/dist/agent/tests/agentConfig.test.js +6 -1
- package/dist/agent/tests/browserControlHelpers.test.cjs +35 -0
- package/dist/agent/tests/browserControlHelpers.test.d.ts +1 -0
- package/dist/agent/tests/browserControlHelpers.test.js +29 -0
- package/dist/agent/tests/browserControlTool.test.cjs +2117 -0
- package/dist/agent/tests/browserControlTool.test.d.ts +1 -0
- package/dist/agent/tests/browserControlTool.test.js +2111 -0
- package/dist/agent/tests/internet_search.test.cjs +22 -28
- package/dist/agent/tests/internet_search.test.js +22 -28
- package/dist/agent/tests/toolRegistry.test.cjs +6 -0
- package/dist/agent/tests/toolRegistry.test.js +6 -0
- package/dist/agent/tools/browser_control.cjs +1282 -0
- package/dist/agent/tools/browser_control.d.ts +478 -0
- package/dist/agent/tools/browser_control.js +1242 -0
- package/dist/agent/tools/internet_search.cjs +9 -5
- package/dist/agent/tools/internet_search.js +9 -5
- package/dist/cli/commands/agent.cjs +16 -2
- package/dist/cli/commands/agent.js +16 -2
- package/dist/cli/commands/browser.cjs +603 -0
- package/dist/cli/commands/browser.d.ts +13 -0
- package/dist/cli/commands/browser.js +566 -0
- package/dist/cli/commands/gateway.cjs +18 -7
- package/dist/cli/commands/gateway.d.ts +5 -1
- package/dist/cli/commands/gateway.js +18 -7
- package/dist/cli/commands/init.cjs +134 -45
- package/dist/cli/commands/init.js +134 -45
- package/dist/cli/commands/skill.cjs +3 -2
- package/dist/cli/commands/skill.js +3 -2
- package/dist/cli/config/loader.cjs +15 -0
- package/dist/cli/config/loader.js +15 -0
- package/dist/cli/config/schema.cjs +51 -2
- package/dist/cli/config/schema.d.ts +49 -0
- package/dist/cli/config/schema.js +44 -1
- package/dist/cli/core/workspace.cjs +89 -0
- package/dist/cli/core/workspace.d.ts +1 -0
- package/dist/cli/core/workspace.js +55 -0
- package/dist/cli/index.cjs +53 -5
- package/dist/cli/index.js +53 -5
- package/dist/cli/types/browser.cjs +18 -0
- package/dist/cli/types/browser.d.ts +9 -0
- package/dist/cli/types/browser.js +0 -0
- package/dist/gateway/browserRelayServer.cjs +338 -0
- package/dist/gateway/browserRelayServer.d.ts +38 -0
- package/dist/gateway/browserRelayServer.js +301 -0
- package/dist/gateway/http/agents.cjs +22 -0
- package/dist/gateway/http/agents.js +22 -0
- package/dist/gateway/http/fs.cjs +57 -0
- package/dist/gateway/http/fs.js +58 -1
- package/dist/gateway/server.cjs +43 -6
- package/dist/gateway/server.d.ts +4 -1
- package/dist/gateway/server.js +36 -5
- package/dist/gateway/transport/websocket.cjs +45 -10
- package/dist/gateway/transport/websocket.d.ts +1 -0
- package/dist/gateway/transport/websocket.js +41 -9
- package/dist/gateway/types.d.ts +4 -0
- package/dist/tests/agents-api.test.cjs +52 -0
- package/dist/tests/agents-api.test.js +53 -1
- package/dist/tests/browser-command.test.cjs +264 -0
- package/dist/tests/browser-command.test.d.ts +1 -0
- package/dist/tests/browser-command.test.js +258 -0
- package/dist/tests/browser-relay-server.test.cjs +20 -0
- package/dist/tests/browser-relay-server.test.d.ts +1 -0
- package/dist/tests/browser-relay-server.test.js +14 -0
- package/dist/tests/cli-config-loader.test.cjs +43 -0
- package/dist/tests/cli-config-loader.test.js +43 -0
- package/dist/tests/cli-init.test.cjs +25 -2
- package/dist/tests/cli-init.test.js +25 -2
- package/dist/tests/cli-workspace-root.test.cjs +114 -0
- package/dist/tests/cli-workspace-root.test.d.ts +1 -0
- package/dist/tests/cli-workspace-root.test.js +108 -0
- package/dist/tests/fs-api.test.cjs +138 -0
- package/dist/tests/fs-api.test.d.ts +1 -0
- package/dist/tests/fs-api.test.js +132 -0
- package/dist/tests/gateway-command-workspace.test.cjs +150 -0
- package/dist/tests/gateway-command-workspace.test.d.ts +1 -0
- package/dist/tests/gateway-command-workspace.test.js +144 -0
- package/dist/tests/gateway-request-execution-overrides.test.cjs +42 -0
- package/dist/tests/gateway-request-execution-overrides.test.d.ts +1 -0
- package/dist/tests/gateway-request-execution-overrides.test.js +36 -0
- package/dist/tests/gateway.test.cjs +31 -0
- package/dist/tests/gateway.test.js +31 -0
- package/dist/tests/websocket-transport.test.cjs +31 -0
- package/dist/tests/websocket-transport.test.d.ts +1 -0
- package/dist/tests/websocket-transport.test.js +25 -0
- package/dist/webui/assets/index-Cwkg4DKj.css +11 -0
- package/dist/webui/assets/{index-0nUBsUUq.js → index-DHbfLOUR.js} +109 -107
- package/dist/webui/index.html +2 -2
- package/extensions/wingman-browser-extension/README.md +27 -0
- package/extensions/wingman-browser-extension/background.js +416 -0
- package/extensions/wingman-browser-extension/manifest.json +19 -0
- package/extensions/wingman-browser-extension/options.html +156 -0
- package/extensions/wingman-browser-extension/options.js +106 -0
- package/package.json +8 -8
- package/{.wingman → templates}/agents/README.md +2 -1
- package/{.wingman → templates}/agents/coding/agent.md +0 -1
- package/{.wingman → templates}/agents/coding-v2/agent.md +0 -1
- package/{.wingman → templates}/agents/game-dev/agent.md +8 -1
- package/{.wingman → templates}/agents/game-dev/art-generation.md +1 -0
- package/{.wingman → templates}/agents/main/agent.md +5 -0
- package/{.wingman → templates}/agents/researcher/agent.md +9 -0
- package/{.wingman → templates}/agents/stock-trader/agent.md +1 -0
- package/dist/webui/assets/index-kk7OrD-G.css +0 -11
- /package/{.wingman → templates}/agents/coding-v2/implementor.md +0 -0
- /package/{.wingman → templates}/agents/game-dev/asset-refinement.md +0 -0
- /package/{.wingman → templates}/agents/game-dev/planning-idea.md +0 -0
- /package/{.wingman → templates}/agents/game-dev/ui-specialist.md +0 -0
- /package/{.wingman → templates}/agents/stock-trader/chain-curator.md +0 -0
- /package/{.wingman → templates}/agents/stock-trader/goal-translator.md +0 -0
- /package/{.wingman → templates}/agents/stock-trader/guardrails-veto.md +0 -0
- /package/{.wingman → templates}/agents/stock-trader/path-planner.md +0 -0
- /package/{.wingman → templates}/agents/stock-trader/regime-analyst.md +0 -0
- /package/{.wingman → templates}/agents/stock-trader/risk.md +0 -0
- /package/{.wingman → templates}/agents/stock-trader/selection.md +0 -0
- /package/{.wingman → templates}/agents/stock-trader/strategy-composer.md +0 -0
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { createLogger, getLogFilePath } from "../../logger.js";
|
|
7
|
+
import { OutputManager } from "../core/outputManager.js";
|
|
8
|
+
const DEFAULT_CONFIG_DIR = ".wingman";
|
|
9
|
+
const DEFAULT_PROFILES_DIR = ".wingman/browser-profiles";
|
|
10
|
+
const DEFAULT_EXTENSIONS_DIR = ".wingman/browser-extensions";
|
|
11
|
+
const DEFAULT_BUNDLED_EXTENSION_ID = "wingman";
|
|
12
|
+
const BUNDLED_EXTENSION_RELATIVE_PATH = "../../../extensions/wingman-browser-extension";
|
|
13
|
+
const PROFILE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
14
|
+
async function executeBrowserCommand(args, options = {}) {
|
|
15
|
+
const outputManager = new OutputManager(args.outputMode);
|
|
16
|
+
try {
|
|
17
|
+
switch(args.subcommand){
|
|
18
|
+
case "profile":
|
|
19
|
+
await executeBrowserProfileCommand(args, outputManager, options);
|
|
20
|
+
break;
|
|
21
|
+
case "extension":
|
|
22
|
+
await executeBrowserExtensionCommand(args, outputManager, options);
|
|
23
|
+
break;
|
|
24
|
+
case "":
|
|
25
|
+
case "help":
|
|
26
|
+
case "--help":
|
|
27
|
+
case "-h":
|
|
28
|
+
showBrowserHelp(outputManager);
|
|
29
|
+
break;
|
|
30
|
+
default:
|
|
31
|
+
throw new Error(`Unknown subcommand: ${args.subcommand}. Run 'wingman browser help' for usage.`);
|
|
32
|
+
}
|
|
33
|
+
} catch (error) {
|
|
34
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
35
|
+
const logFile = getLogFilePath();
|
|
36
|
+
createLogger().error("Browser command failed", {
|
|
37
|
+
error: message
|
|
38
|
+
});
|
|
39
|
+
if ("interactive" === outputManager.getMode()) {
|
|
40
|
+
console.error(`\nError: ${message}`);
|
|
41
|
+
console.error(`Logs: ${logFile}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
} else {
|
|
44
|
+
outputManager.emitAgentError(error);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function executeBrowserProfileCommand(args, outputManager, options) {
|
|
50
|
+
const action = args.args[0] || "";
|
|
51
|
+
switch(action){
|
|
52
|
+
case "init":
|
|
53
|
+
await handleProfileInit(args, outputManager, options);
|
|
54
|
+
break;
|
|
55
|
+
case "open":
|
|
56
|
+
await handleProfileOpen(args, outputManager, options);
|
|
57
|
+
break;
|
|
58
|
+
case "":
|
|
59
|
+
case "help":
|
|
60
|
+
case "--help":
|
|
61
|
+
case "-h":
|
|
62
|
+
showBrowserProfileHelp(outputManager);
|
|
63
|
+
break;
|
|
64
|
+
default:
|
|
65
|
+
throw new Error(`Unknown browser profile subcommand: ${action}. Run 'wingman browser profile help' for usage.`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function executeBrowserExtensionCommand(args, outputManager, options) {
|
|
69
|
+
const action = args.args[0] || "";
|
|
70
|
+
switch(action){
|
|
71
|
+
case "install":
|
|
72
|
+
await handleExtensionInstall(args, outputManager, options);
|
|
73
|
+
break;
|
|
74
|
+
case "path":
|
|
75
|
+
await handleExtensionPath(args, outputManager, options);
|
|
76
|
+
break;
|
|
77
|
+
case "list":
|
|
78
|
+
await handleExtensionList(args, outputManager, options);
|
|
79
|
+
break;
|
|
80
|
+
case "pair":
|
|
81
|
+
await handleExtensionPair(args, outputManager, options);
|
|
82
|
+
break;
|
|
83
|
+
case "":
|
|
84
|
+
case "help":
|
|
85
|
+
case "--help":
|
|
86
|
+
case "-h":
|
|
87
|
+
showBrowserExtensionHelp(outputManager);
|
|
88
|
+
break;
|
|
89
|
+
default:
|
|
90
|
+
throw new Error(`Unknown browser extension subcommand: ${action}. Run 'wingman browser extension help' for usage.`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function handleProfileInit(args, outputManager, options) {
|
|
94
|
+
const profileArg = getPositionalArg(args.args, 1);
|
|
95
|
+
if (!profileArg) throw new Error("Profile ID required. Usage: wingman browser profile init <profile-id>");
|
|
96
|
+
const profileId = validateProfileId(profileArg);
|
|
97
|
+
const workspace = options.workspace || process.cwd();
|
|
98
|
+
const configDir = options.configDir || DEFAULT_CONFIG_DIR;
|
|
99
|
+
const configRoot = join(workspace, configDir);
|
|
100
|
+
const configPath = join(configRoot, "wingman.config.json");
|
|
101
|
+
const config = readConfigObject(configPath);
|
|
102
|
+
const browserConfig = readObject(config.browser);
|
|
103
|
+
const profiles = readStringRecord(browserConfig.profiles);
|
|
104
|
+
const force = getBooleanOption(args.options, "force");
|
|
105
|
+
const profilesDirOption = getStringOption(args.options, "profiles-dir") || getStringOption(args.options, "profilesDir");
|
|
106
|
+
if (profilesDirOption) browserConfig.profilesDir = profilesDirOption;
|
|
107
|
+
const baseProfilesDir = resolveProfilesDir(browserConfig.profilesDir);
|
|
108
|
+
const profilePathOption = getStringOption(args.options, "path");
|
|
109
|
+
const profilePath = profilePathOption ? profilePathOption : normalizePathForConfig(join(baseProfilesDir, profileId));
|
|
110
|
+
const existingProfilePath = profiles[profileId];
|
|
111
|
+
if (existingProfilePath && existingProfilePath !== profilePath && !force) throw new Error(`Browser profile "${profileId}" already exists at ${existingProfilePath}. Use --force to overwrite.`);
|
|
112
|
+
profiles[profileId] = profilePath;
|
|
113
|
+
browserConfig.profiles = profiles;
|
|
114
|
+
browserConfig.profilesDir = baseProfilesDir;
|
|
115
|
+
const requestedDefault = getBooleanOption(args.options, "default");
|
|
116
|
+
const shouldSetDefault = requestedDefault || "string" != typeof browserConfig.defaultProfile;
|
|
117
|
+
if (shouldSetDefault) browserConfig.defaultProfile = profileId;
|
|
118
|
+
config.browser = browserConfig;
|
|
119
|
+
const absoluteProfilePath = resolveProfilePath(workspace, profilePath);
|
|
120
|
+
mkdirSync(absoluteProfilePath, {
|
|
121
|
+
recursive: true
|
|
122
|
+
});
|
|
123
|
+
mkdirSync(configRoot, {
|
|
124
|
+
recursive: true
|
|
125
|
+
});
|
|
126
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
127
|
+
writeLine(outputManager, `Initialized browser profile "${profileId}".`);
|
|
128
|
+
writeLine(outputManager, `Profile directory: ${absoluteProfilePath}`);
|
|
129
|
+
if (shouldSetDefault) writeLine(outputManager, `Default browser profile: ${profileId}`);
|
|
130
|
+
writeLine(outputManager, `Saved config: ${configPath}`);
|
|
131
|
+
}
|
|
132
|
+
async function handleProfileOpen(args, outputManager, options) {
|
|
133
|
+
const workspace = options.workspace || process.cwd();
|
|
134
|
+
const configDir = options.configDir || DEFAULT_CONFIG_DIR;
|
|
135
|
+
const configRoot = join(workspace, configDir);
|
|
136
|
+
const configPath = join(configRoot, "wingman.config.json");
|
|
137
|
+
const config = readConfigObject(configPath);
|
|
138
|
+
const browserConfig = readObject(config.browser);
|
|
139
|
+
const requestedProfile = getPositionalArg(args.args, 1);
|
|
140
|
+
const defaultProfile = "string" == typeof browserConfig.defaultProfile ? browserConfig.defaultProfile.trim() : "";
|
|
141
|
+
const resolvedProfileId = validateProfileId(requestedProfile || defaultProfile || "");
|
|
142
|
+
const profiles = readStringRecord(browserConfig.profiles);
|
|
143
|
+
const profilePath = profiles[resolvedProfileId] || normalizePathForConfig(join(resolveProfilesDir(browserConfig.profilesDir), resolvedProfileId));
|
|
144
|
+
const absoluteProfilePath = resolveProfilePath(workspace, profilePath);
|
|
145
|
+
mkdirSync(absoluteProfilePath, {
|
|
146
|
+
recursive: true
|
|
147
|
+
});
|
|
148
|
+
const url = getStringOption(args.options, "url") || getStringOption(args.options, "target-url") || "https://example.com";
|
|
149
|
+
const headless = getBooleanOption(args.options, "headless");
|
|
150
|
+
const explicitExecutablePath = getStringOption(args.options, "executable-path") || getStringOption(args.options, "executablePath");
|
|
151
|
+
const resolveExecutable = options.resolveExecutablePath || resolveChromeExecutablePath;
|
|
152
|
+
const executablePath = resolveExecutable(explicitExecutablePath);
|
|
153
|
+
const defaultExtensionIds = readStringArray(browserConfig.defaultExtensions);
|
|
154
|
+
const extensionArgs = resolveExtensionArgs(workspace, browserConfig, defaultExtensionIds);
|
|
155
|
+
const chromeArgs = [
|
|
156
|
+
`--user-data-dir=${absoluteProfilePath}`,
|
|
157
|
+
"--no-default-browser-check",
|
|
158
|
+
"--no-first-run",
|
|
159
|
+
"--disable-background-networking",
|
|
160
|
+
"--disable-sync",
|
|
161
|
+
"--mute-audio",
|
|
162
|
+
...extensionArgs
|
|
163
|
+
];
|
|
164
|
+
if (headless) chromeArgs.push("--headless=new");
|
|
165
|
+
chromeArgs.push(url);
|
|
166
|
+
const spawnProcess = options.spawnProcess || spawn;
|
|
167
|
+
const chromeProcess = spawnProcess(executablePath, chromeArgs, {
|
|
168
|
+
detached: true,
|
|
169
|
+
stdio: "ignore"
|
|
170
|
+
});
|
|
171
|
+
chromeProcess.unref();
|
|
172
|
+
writeLine(outputManager, `Opened profile "${resolvedProfileId}" at ${url} using ${absoluteProfilePath}`);
|
|
173
|
+
if (defaultExtensionIds.length > 0) writeLine(outputManager, `Loaded default extension(s): ${defaultExtensionIds.join(", ")}`);
|
|
174
|
+
}
|
|
175
|
+
async function handleExtensionInstall(args, outputManager, options) {
|
|
176
|
+
const extensionArg = getPositionalArg(args.args, 1);
|
|
177
|
+
const extensionIdOption = getStringOption(args.options, "id");
|
|
178
|
+
const extensionPathOption = getStringOption(args.options, "path");
|
|
179
|
+
const sourcePathOption = getStringOption(args.options, "source") || getStringOption(args.options, "from");
|
|
180
|
+
const selectedId = extensionArg || extensionIdOption;
|
|
181
|
+
const shouldInstallBundled = !selectedId && !extensionPathOption && !sourcePathOption;
|
|
182
|
+
if (!selectedId && !shouldInstallBundled) throw new Error("Extension ID required when using --source/--path. Usage: wingman browser extension install [extension-id] [options]");
|
|
183
|
+
const extensionId = validateProfileId(selectedId || DEFAULT_BUNDLED_EXTENSION_ID);
|
|
184
|
+
const workspace = options.workspace || process.cwd();
|
|
185
|
+
const configDir = options.configDir || DEFAULT_CONFIG_DIR;
|
|
186
|
+
const configRoot = join(workspace, configDir);
|
|
187
|
+
const configPath = join(configRoot, "wingman.config.json");
|
|
188
|
+
const config = readConfigObject(configPath);
|
|
189
|
+
const browserConfig = readObject(config.browser);
|
|
190
|
+
const force = getBooleanOption(args.options, "force");
|
|
191
|
+
const extensions = readStringRecord(browserConfig.extensions);
|
|
192
|
+
const extensionsDirOption = getStringOption(args.options, "extensions-dir") || getStringOption(args.options, "extensionsDir");
|
|
193
|
+
if (extensionsDirOption) browserConfig.extensionsDir = extensionsDirOption;
|
|
194
|
+
const extensionsDir = resolveExtensionsDir(browserConfig.extensionsDir);
|
|
195
|
+
const extensionPath = extensionPathOption ? extensionPathOption : normalizePathForConfig(join(extensionsDir, extensionId));
|
|
196
|
+
const absoluteExtensionPath = resolveProfilePath(workspace, extensionPath);
|
|
197
|
+
const sourcePath = sourcePathOption || (shouldInstallBundled ? resolveBundledExtensionSourcePath() : void 0);
|
|
198
|
+
if (sourcePath) {
|
|
199
|
+
const absoluteSourcePath = resolveProfilePath(workspace, sourcePath);
|
|
200
|
+
if (!existsSync(absoluteSourcePath)) throw new Error(`Extension source does not exist: ${absoluteSourcePath}`);
|
|
201
|
+
if (!statSync(absoluteSourcePath).isDirectory()) throw new Error(`Extension source must be a directory: ${absoluteSourcePath}`);
|
|
202
|
+
if (existsSync(absoluteExtensionPath) && !force) throw new Error(`Extension target already exists at ${absoluteExtensionPath}. Use --force to overwrite.`);
|
|
203
|
+
cpSync(absoluteSourcePath, absoluteExtensionPath, {
|
|
204
|
+
recursive: true,
|
|
205
|
+
force
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
if (!existsSync(absoluteExtensionPath)) throw new Error(`Extension path does not exist: ${absoluteExtensionPath}. Provide --source or --path to an unpacked extension directory.`);
|
|
209
|
+
if (!statSync(absoluteExtensionPath).isDirectory()) throw new Error(`Extension path must be a directory: ${absoluteExtensionPath}.`);
|
|
210
|
+
const manifestPath = join(absoluteExtensionPath, "manifest.json");
|
|
211
|
+
if (!existsSync(manifestPath)) throw new Error(`manifest.json not found in extension directory: ${absoluteExtensionPath}.`);
|
|
212
|
+
const existingExtensionPath = extensions[extensionId];
|
|
213
|
+
if (existingExtensionPath && existingExtensionPath !== extensionPath && !force) throw new Error(`Extension "${extensionId}" already exists at ${existingExtensionPath}. Use --force to overwrite.`);
|
|
214
|
+
extensions[extensionId] = extensionPath;
|
|
215
|
+
browserConfig.extensions = extensions;
|
|
216
|
+
browserConfig.extensionsDir = extensionsDir;
|
|
217
|
+
const setAsDefault = getBooleanOption(args.options, "default");
|
|
218
|
+
if (setAsDefault) {
|
|
219
|
+
const defaults = new Set(readStringArray(browserConfig.defaultExtensions));
|
|
220
|
+
defaults.add(extensionId);
|
|
221
|
+
browserConfig.defaultExtensions = Array.from(defaults);
|
|
222
|
+
}
|
|
223
|
+
config.browser = browserConfig;
|
|
224
|
+
mkdirSync(configRoot, {
|
|
225
|
+
recursive: true
|
|
226
|
+
});
|
|
227
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
228
|
+
writeLine(outputManager, `Registered extension "${extensionId}".`);
|
|
229
|
+
writeLine(outputManager, `Extension directory: ${absoluteExtensionPath}`);
|
|
230
|
+
if (shouldInstallBundled) writeLine(outputManager, `Installed bundled Wingman extension as "${extensionId}".`);
|
|
231
|
+
if (setAsDefault) writeLine(outputManager, `Added "${extensionId}" to browser.defaultExtensions`);
|
|
232
|
+
writeLine(outputManager, `Saved config: ${configPath}`);
|
|
233
|
+
}
|
|
234
|
+
async function handleExtensionPath(args, outputManager, options) {
|
|
235
|
+
const extensionArg = getPositionalArg(args.args, 1);
|
|
236
|
+
if (!extensionArg) throw new Error("Extension ID required. Usage: wingman browser extension path <extension-id>");
|
|
237
|
+
const extensionId = validateProfileId(extensionArg);
|
|
238
|
+
const workspace = options.workspace || process.cwd();
|
|
239
|
+
const configDir = options.configDir || DEFAULT_CONFIG_DIR;
|
|
240
|
+
const configRoot = join(workspace, configDir);
|
|
241
|
+
const configPath = join(configRoot, "wingman.config.json");
|
|
242
|
+
const config = readConfigObject(configPath);
|
|
243
|
+
const browserConfig = readObject(config.browser);
|
|
244
|
+
const extensions = readStringRecord(browserConfig.extensions);
|
|
245
|
+
const configuredPath = extensions[extensionId];
|
|
246
|
+
if (!configuredPath) throw new Error(`Extension "${extensionId}" is not configured. Run wingman browser extension install ${extensionId} --path <dir> first.`);
|
|
247
|
+
const absoluteExtensionPath = resolveProfilePath(workspace, configuredPath);
|
|
248
|
+
writeLine(outputManager, absoluteExtensionPath);
|
|
249
|
+
}
|
|
250
|
+
async function handleExtensionList(args, outputManager, options) {
|
|
251
|
+
const workspace = options.workspace || process.cwd();
|
|
252
|
+
const configDir = options.configDir || DEFAULT_CONFIG_DIR;
|
|
253
|
+
const configRoot = join(workspace, configDir);
|
|
254
|
+
const configPath = join(configRoot, "wingman.config.json");
|
|
255
|
+
const config = readConfigObject(configPath);
|
|
256
|
+
const browserConfig = readObject(config.browser);
|
|
257
|
+
const extensions = readStringRecord(browserConfig.extensions);
|
|
258
|
+
const defaults = new Set(readStringArray(browserConfig.defaultExtensions));
|
|
259
|
+
const entries = Object.entries(extensions);
|
|
260
|
+
if (0 === entries.length) return void writeLine(outputManager, "No browser extensions configured.");
|
|
261
|
+
for (const [extensionId, extensionPath] of entries){
|
|
262
|
+
const marker = defaults.has(extensionId) ? " (default)" : "";
|
|
263
|
+
writeLine(outputManager, `${extensionId}${marker}: ${extensionPath}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
async function handleExtensionPair(args, outputManager, options) {
|
|
267
|
+
const workspace = options.workspace || process.cwd();
|
|
268
|
+
const configDir = options.configDir || DEFAULT_CONFIG_DIR;
|
|
269
|
+
const configRoot = join(workspace, configDir);
|
|
270
|
+
const configPath = join(configRoot, "wingman.config.json");
|
|
271
|
+
const config = readConfigObject(configPath);
|
|
272
|
+
const browserConfig = readObject(config.browser);
|
|
273
|
+
const relayConfig = readObject(browserConfig.relay);
|
|
274
|
+
const relayHost = getStringOption(args.options, "host") || "127.0.0.1";
|
|
275
|
+
if (![
|
|
276
|
+
"127.0.0.1",
|
|
277
|
+
"localhost",
|
|
278
|
+
"::1"
|
|
279
|
+
].includes(relayHost)) throw new Error(`Relay host must be loopback (127.0.0.1, localhost, ::1). Received "${relayHost}".`);
|
|
280
|
+
const relayPort = getNumberOption(args.options, "port") || ("number" == typeof relayConfig.port ? relayConfig.port : 18792);
|
|
281
|
+
if (!Number.isInteger(relayPort) || relayPort < 1 || relayPort > 65535) throw new Error("Relay port must be an integer between 1 and 65535.");
|
|
282
|
+
const configuredToken = getStringOption(args.options, "token");
|
|
283
|
+
const token = configuredToken || createRelayToken();
|
|
284
|
+
if (token.length < 16) throw new Error("Relay token must be at least 16 characters.");
|
|
285
|
+
relayConfig.enabled = true;
|
|
286
|
+
relayConfig.host = relayHost;
|
|
287
|
+
relayConfig.port = relayPort;
|
|
288
|
+
relayConfig.requireAuth = true;
|
|
289
|
+
relayConfig.authToken = token;
|
|
290
|
+
if ("number" != typeof relayConfig.maxMessageBytes) relayConfig.maxMessageBytes = 262144;
|
|
291
|
+
browserConfig.relay = relayConfig;
|
|
292
|
+
const extensionsDir = resolveExtensionsDir(browserConfig.extensionsDir);
|
|
293
|
+
const extensions = readStringRecord(browserConfig.extensions);
|
|
294
|
+
if (!extensions[DEFAULT_BUNDLED_EXTENSION_ID]) {
|
|
295
|
+
const extensionPath = normalizePathForConfig(join(extensionsDir, DEFAULT_BUNDLED_EXTENSION_ID));
|
|
296
|
+
const absoluteExtensionPath = resolveProfilePath(workspace, extensionPath);
|
|
297
|
+
const bundledSourcePath = resolveBundledExtensionSourcePath();
|
|
298
|
+
mkdirSync(absoluteExtensionPath, {
|
|
299
|
+
recursive: true
|
|
300
|
+
});
|
|
301
|
+
cpSync(bundledSourcePath, absoluteExtensionPath, {
|
|
302
|
+
recursive: true,
|
|
303
|
+
force: true
|
|
304
|
+
});
|
|
305
|
+
extensions[DEFAULT_BUNDLED_EXTENSION_ID] = extensionPath;
|
|
306
|
+
}
|
|
307
|
+
browserConfig.extensions = extensions;
|
|
308
|
+
browserConfig.extensionsDir = extensionsDir;
|
|
309
|
+
const defaults = new Set(readStringArray(browserConfig.defaultExtensions));
|
|
310
|
+
defaults.add(DEFAULT_BUNDLED_EXTENSION_ID);
|
|
311
|
+
browserConfig.defaultExtensions = Array.from(defaults);
|
|
312
|
+
config.browser = browserConfig;
|
|
313
|
+
mkdirSync(configRoot, {
|
|
314
|
+
recursive: true
|
|
315
|
+
});
|
|
316
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
317
|
+
writeLine(outputManager, "Configured secure browser relay pairing.");
|
|
318
|
+
writeLine(outputManager, `Relay host: ${relayHost}`);
|
|
319
|
+
writeLine(outputManager, `Relay port: ${relayPort}`);
|
|
320
|
+
writeLine(outputManager, `Relay token: ${token}`);
|
|
321
|
+
writeLine(outputManager, `Saved config: ${configPath}`);
|
|
322
|
+
writeLine(outputManager, "Next: open the extension options page and set the same relay token.");
|
|
323
|
+
}
|
|
324
|
+
function showBrowserHelp(outputManager) {
|
|
325
|
+
if ("interactive" === outputManager.getMode()) return void console.log(`
|
|
326
|
+
Wingman Browser Tools
|
|
327
|
+
|
|
328
|
+
Usage:
|
|
329
|
+
wingman browser profile init <profile-id> [options]
|
|
330
|
+
wingman browser profile open [profile-id] [options]
|
|
331
|
+
wingman browser extension install [extension-id] [options]
|
|
332
|
+
wingman browser extension pair [options]
|
|
333
|
+
wingman browser extension path <extension-id>
|
|
334
|
+
wingman browser extension list
|
|
335
|
+
wingman browser extension help
|
|
336
|
+
wingman browser profile help
|
|
337
|
+
wingman browser help
|
|
338
|
+
|
|
339
|
+
Examples:
|
|
340
|
+
wingman browser profile init trading
|
|
341
|
+
wingman browser profile open trading --url https://robinhood.com/login
|
|
342
|
+
wingman browser extension install --default
|
|
343
|
+
wingman browser extension pair
|
|
344
|
+
wingman browser extension install relay --source ./my-extension --default
|
|
345
|
+
wingman browser extension path relay
|
|
346
|
+
wingman browser profile init shopping --default
|
|
347
|
+
wingman browser profile init work --path .wingman/profiles/work
|
|
348
|
+
|
|
349
|
+
Options:
|
|
350
|
+
--workspace <dir> Workspace root (defaults to nearest ancestor with .wingman/)
|
|
351
|
+
--path <dir> Path for profile/extension (relative to workspace or absolute)
|
|
352
|
+
--profiles-dir <dir> Base profile directory used when --path is omitted
|
|
353
|
+
--extensions-dir <dir> Base extension directory used when --path is omitted
|
|
354
|
+
--source <dir> Source directory to copy from during extension install
|
|
355
|
+
--url <url> Target URL for profile open
|
|
356
|
+
--headless Open profile in headless mode
|
|
357
|
+
--default Set as browser.defaultProfile or add to browser.defaultExtensions
|
|
358
|
+
--force Overwrite existing profile/extension mapping
|
|
359
|
+
--host <host> Relay host for extension pairing (must be loopback)
|
|
360
|
+
--port <port> Relay port for extension pairing
|
|
361
|
+
--token <token> Explicit relay token for extension pairing
|
|
362
|
+
`);
|
|
363
|
+
outputManager.emitLog("info", "Browser help requested");
|
|
364
|
+
}
|
|
365
|
+
function showBrowserProfileHelp(outputManager) {
|
|
366
|
+
if ("interactive" === outputManager.getMode()) return void console.log(`
|
|
367
|
+
Wingman Browser Profile Manager
|
|
368
|
+
|
|
369
|
+
Usage:
|
|
370
|
+
wingman browser profile init <profile-id> [options]
|
|
371
|
+
wingman browser profile open [profile-id] [options]
|
|
372
|
+
wingman browser profile help
|
|
373
|
+
|
|
374
|
+
Examples:
|
|
375
|
+
wingman browser profile init trading
|
|
376
|
+
wingman browser profile init work --path .wingman/profiles/work --default
|
|
377
|
+
wingman browser profile open trading --url https://robinhood.com/login
|
|
378
|
+
`);
|
|
379
|
+
outputManager.emitLog("info", "Browser profile help requested");
|
|
380
|
+
}
|
|
381
|
+
function showBrowserExtensionHelp(outputManager) {
|
|
382
|
+
if ("interactive" === outputManager.getMode()) return void console.log(`
|
|
383
|
+
Wingman Browser Extension Manager
|
|
384
|
+
|
|
385
|
+
Usage:
|
|
386
|
+
wingman browser extension install [extension-id] [options]
|
|
387
|
+
wingman browser extension pair [options]
|
|
388
|
+
wingman browser extension path <extension-id>
|
|
389
|
+
wingman browser extension list
|
|
390
|
+
wingman browser extension help
|
|
391
|
+
|
|
392
|
+
Examples:
|
|
393
|
+
wingman browser extension install --default
|
|
394
|
+
wingman browser extension pair
|
|
395
|
+
wingman browser extension install relay --path .wingman/browser-extensions/relay
|
|
396
|
+
wingman browser extension install relay --source ./relay-extension --default
|
|
397
|
+
wingman browser extension path relay
|
|
398
|
+
wingman browser extension list
|
|
399
|
+
`);
|
|
400
|
+
outputManager.emitLog("info", "Browser extension help requested");
|
|
401
|
+
}
|
|
402
|
+
function readConfigObject(configPath) {
|
|
403
|
+
if (!existsSync(configPath)) return {};
|
|
404
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
405
|
+
let parsed;
|
|
406
|
+
try {
|
|
407
|
+
parsed = JSON.parse(raw);
|
|
408
|
+
} catch {
|
|
409
|
+
throw new Error("Existing wingman.config.json is invalid JSON. Fix the file or run wingman init --force.");
|
|
410
|
+
}
|
|
411
|
+
if (!isObject(parsed)) throw new Error("Existing wingman.config.json must be a JSON object.");
|
|
412
|
+
return {
|
|
413
|
+
...parsed
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function resolveProfilesDir(rawProfilesDir) {
|
|
417
|
+
if ("string" == typeof rawProfilesDir && rawProfilesDir.trim()) return rawProfilesDir.trim();
|
|
418
|
+
return DEFAULT_PROFILES_DIR;
|
|
419
|
+
}
|
|
420
|
+
function resolveExtensionsDir(rawExtensionsDir) {
|
|
421
|
+
if ("string" == typeof rawExtensionsDir && rawExtensionsDir.trim()) return rawExtensionsDir.trim();
|
|
422
|
+
return DEFAULT_EXTENSIONS_DIR;
|
|
423
|
+
}
|
|
424
|
+
function validateProfileId(value) {
|
|
425
|
+
const profileId = value.trim();
|
|
426
|
+
if (!profileId) throw new Error("Profile ID cannot be empty.");
|
|
427
|
+
if (!PROFILE_ID_PATTERN.test(profileId)) throw new Error(`Invalid profile ID "${value}". Use letters, numbers, dot, underscore, or dash.`);
|
|
428
|
+
return profileId;
|
|
429
|
+
}
|
|
430
|
+
function isObject(value) {
|
|
431
|
+
return Boolean(value) && "object" == typeof value && !Array.isArray(value);
|
|
432
|
+
}
|
|
433
|
+
function readObject(value) {
|
|
434
|
+
if (!isObject(value)) return {};
|
|
435
|
+
return {
|
|
436
|
+
...value
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
function readStringRecord(value) {
|
|
440
|
+
if (!isObject(value)) return {};
|
|
441
|
+
const result = {};
|
|
442
|
+
for (const [key, innerValue] of Object.entries(value))if (key.trim() && "string" == typeof innerValue) result[key] = innerValue;
|
|
443
|
+
return result;
|
|
444
|
+
}
|
|
445
|
+
function readStringArray(value) {
|
|
446
|
+
if (!Array.isArray(value)) return [];
|
|
447
|
+
return value.filter((item)=>"string" == typeof item).map((item)=>item.trim()).filter(Boolean);
|
|
448
|
+
}
|
|
449
|
+
function getStringOption(options, key) {
|
|
450
|
+
const value = options[key];
|
|
451
|
+
if ("string" == typeof value && value.trim()) return value.trim();
|
|
452
|
+
}
|
|
453
|
+
function getNumberOption(options, key) {
|
|
454
|
+
const value = options[key];
|
|
455
|
+
if ("number" == typeof value && Number.isFinite(value)) return value;
|
|
456
|
+
if ("string" == typeof value) {
|
|
457
|
+
const parsed = Number.parseInt(value.trim(), 10);
|
|
458
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function getBooleanOption(options, key) {
|
|
462
|
+
const value = options[key];
|
|
463
|
+
if ("boolean" == typeof value) return value;
|
|
464
|
+
if ("string" == typeof value) {
|
|
465
|
+
const normalized = value.trim().toLowerCase();
|
|
466
|
+
if ([
|
|
467
|
+
"true",
|
|
468
|
+
"1",
|
|
469
|
+
"yes",
|
|
470
|
+
"y",
|
|
471
|
+
"on"
|
|
472
|
+
].includes(normalized)) return true;
|
|
473
|
+
[
|
|
474
|
+
"false",
|
|
475
|
+
"0",
|
|
476
|
+
"no",
|
|
477
|
+
"n",
|
|
478
|
+
"off"
|
|
479
|
+
].includes(normalized);
|
|
480
|
+
}
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
function getPositionalArg(args, index) {
|
|
484
|
+
const value = args[index];
|
|
485
|
+
if ("string" != typeof value) return;
|
|
486
|
+
const trimmed = value.trim();
|
|
487
|
+
if (!trimmed || trimmed.startsWith("--")) return;
|
|
488
|
+
return trimmed;
|
|
489
|
+
}
|
|
490
|
+
function normalizePathForConfig(pathValue) {
|
|
491
|
+
if (isAbsolute(pathValue)) return pathValue;
|
|
492
|
+
return pathValue.split("\\").join("/");
|
|
493
|
+
}
|
|
494
|
+
function resolveProfilePath(workspace, profilePath) {
|
|
495
|
+
if (isAbsolute(profilePath)) return profilePath;
|
|
496
|
+
return resolve(workspace, profilePath);
|
|
497
|
+
}
|
|
498
|
+
function resolveBundledExtensionSourcePath() {
|
|
499
|
+
const bundledPath = resolve(fileURLToPath(new URL(BUNDLED_EXTENSION_RELATIVE_PATH, import.meta.url)));
|
|
500
|
+
const bundledManifest = join(bundledPath, "manifest.json");
|
|
501
|
+
if (existsSync(bundledManifest)) return bundledPath;
|
|
502
|
+
throw new Error("Bundled Wingman extension assets were not found. Reinstall Wingman or provide --source <dir>.");
|
|
503
|
+
}
|
|
504
|
+
function createRelayToken() {
|
|
505
|
+
return randomBytes(24).toString("base64url");
|
|
506
|
+
}
|
|
507
|
+
function resolveExtensionArgs(workspace, browserConfig, extensionIds) {
|
|
508
|
+
if (0 === extensionIds.length) return [
|
|
509
|
+
"--disable-extensions"
|
|
510
|
+
];
|
|
511
|
+
const extensions = readStringRecord(browserConfig.extensions);
|
|
512
|
+
const dirs = extensionIds.map((extensionId)=>{
|
|
513
|
+
const configuredPath = extensions[extensionId];
|
|
514
|
+
if (!configuredPath) throw new Error(`Extension "${extensionId}" is not configured in browser.extensions.`);
|
|
515
|
+
const absolutePath = resolveProfilePath(workspace, configuredPath);
|
|
516
|
+
if (!existsSync(absolutePath)) throw new Error(`Configured extension path does not exist: ${absolutePath}.`);
|
|
517
|
+
return absolutePath;
|
|
518
|
+
});
|
|
519
|
+
const joined = dirs.join(",");
|
|
520
|
+
return [
|
|
521
|
+
`--disable-extensions-except=${joined}`,
|
|
522
|
+
`--load-extension=${joined}`
|
|
523
|
+
];
|
|
524
|
+
}
|
|
525
|
+
function getChromeCandidates() {
|
|
526
|
+
const candidates = [
|
|
527
|
+
process.env.WINGMAN_CHROME_EXECUTABLE,
|
|
528
|
+
"google-chrome",
|
|
529
|
+
"chromium-browser",
|
|
530
|
+
"chromium"
|
|
531
|
+
].filter((candidate)=>Boolean(candidate?.trim()));
|
|
532
|
+
if ("darwin" === process.platform) candidates.push("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/Applications/Chromium.app/Contents/MacOS/Chromium");
|
|
533
|
+
if ("linux" === process.platform) candidates.push("/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/usr/bin/chromium-browser", "/usr/bin/chromium");
|
|
534
|
+
if ("win32" === process.platform) candidates.push("C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe");
|
|
535
|
+
return candidates;
|
|
536
|
+
}
|
|
537
|
+
function resolveBinaryFromPath(binaryName) {
|
|
538
|
+
const locator = "win32" === process.platform ? "where" : "which";
|
|
539
|
+
const result = spawnSync(locator, [
|
|
540
|
+
binaryName
|
|
541
|
+
], {
|
|
542
|
+
encoding: "utf-8"
|
|
543
|
+
});
|
|
544
|
+
if (0 !== result.status || !result.stdout) return null;
|
|
545
|
+
const firstLine = result.stdout.split(/\r?\n/).map((line)=>line.trim()).find(Boolean);
|
|
546
|
+
return firstLine || null;
|
|
547
|
+
}
|
|
548
|
+
function resolveChromeExecutablePath(explicitPath) {
|
|
549
|
+
const candidatePool = explicitPath?.trim() ? [
|
|
550
|
+
explicitPath.trim()
|
|
551
|
+
] : getChromeCandidates();
|
|
552
|
+
for (const candidate of candidatePool)if (candidate) {
|
|
553
|
+
if (isAbsolute(candidate) && existsSync(candidate)) return candidate;
|
|
554
|
+
if (!isAbsolute(candidate)) {
|
|
555
|
+
const fromPath = resolveBinaryFromPath(candidate);
|
|
556
|
+
if (fromPath) return fromPath;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (explicitPath?.trim()) throw new Error(`Chrome executable not found at "${explicitPath}". Provide a valid executable path.`);
|
|
560
|
+
throw new Error("No Chrome/Chromium executable found. Install Chrome/Chromium or set WINGMAN_CHROME_EXECUTABLE.");
|
|
561
|
+
}
|
|
562
|
+
function writeLine(outputManager, message) {
|
|
563
|
+
if ("interactive" === outputManager.getMode()) console.log(message);
|
|
564
|
+
else outputManager.emitLog("info", message);
|
|
565
|
+
}
|
|
566
|
+
export { executeBrowserCommand };
|
|
@@ -39,11 +39,11 @@ function reportGatewayError(context, error) {
|
|
|
39
39
|
console.error(`✗ ${context}: ${errorMsg}`);
|
|
40
40
|
console.error(`Logs: ${logFile}`);
|
|
41
41
|
}
|
|
42
|
-
async function executeGatewayCommand(args) {
|
|
42
|
+
async function executeGatewayCommand(args, commandOptions = {}) {
|
|
43
43
|
const { subcommand, options } = args;
|
|
44
44
|
switch(subcommand){
|
|
45
45
|
case "start":
|
|
46
|
-
await handleStart(options);
|
|
46
|
+
await handleStart(options, commandOptions);
|
|
47
47
|
break;
|
|
48
48
|
case "stop":
|
|
49
49
|
await handleStop();
|
|
@@ -55,7 +55,7 @@ async function executeGatewayCommand(args) {
|
|
|
55
55
|
await handleStatus();
|
|
56
56
|
break;
|
|
57
57
|
case "run":
|
|
58
|
-
await handleRun(options);
|
|
58
|
+
await handleRun(options, commandOptions);
|
|
59
59
|
break;
|
|
60
60
|
case "join":
|
|
61
61
|
await handleJoin(args.args, options);
|
|
@@ -77,8 +77,8 @@ async function executeGatewayCommand(args) {
|
|
|
77
77
|
process.exit(1);
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
-
async function handleStart(options) {
|
|
81
|
-
const configLoader =
|
|
80
|
+
async function handleStart(options, commandOptions) {
|
|
81
|
+
const configLoader = createConfigLoader(commandOptions);
|
|
82
82
|
const wingmanConfig = configLoader.loadConfig();
|
|
83
83
|
const gatewayDefaults = wingmanConfig.gateway;
|
|
84
84
|
const envToken = (0, env_cjs_namespaceObject.getGatewayTokenFromEnv)();
|
|
@@ -93,6 +93,8 @@ async function handleStart(options) {
|
|
|
93
93
|
const config = {
|
|
94
94
|
port: options.port || gatewayDefaults.port || 18789,
|
|
95
95
|
host: options.host || gatewayDefaults.host || "127.0.0.1",
|
|
96
|
+
workspace: commandOptions.workspace,
|
|
97
|
+
configDir: commandOptions.configDir,
|
|
96
98
|
requireAuth: auth?.mode !== "none",
|
|
97
99
|
authToken: auth?.token,
|
|
98
100
|
auth,
|
|
@@ -169,13 +171,15 @@ async function handleStatus() {
|
|
|
169
171
|
}
|
|
170
172
|
console.log(` Log File: ${daemon.getLogFile()}`);
|
|
171
173
|
}
|
|
172
|
-
async function handleRun(options) {
|
|
174
|
+
async function handleRun(options, commandOptions) {
|
|
173
175
|
let config;
|
|
174
176
|
if (options.daemon && process.env.WINGMAN_GATEWAY_CONFIG) {
|
|
175
177
|
const configStr = (0, external_fs_namespaceObject.readFileSync)(process.env.WINGMAN_GATEWAY_CONFIG, "utf-8");
|
|
176
178
|
config = JSON.parse(configStr);
|
|
179
|
+
config.workspace = config.workspace || commandOptions.workspace || process.cwd();
|
|
180
|
+
config.configDir = config.configDir || commandOptions.configDir || ".wingman";
|
|
177
181
|
} else {
|
|
178
|
-
const configLoader =
|
|
182
|
+
const configLoader = createConfigLoader(commandOptions);
|
|
179
183
|
const wingmanConfig = configLoader.loadConfig();
|
|
180
184
|
const gatewayDefaults = wingmanConfig.gateway;
|
|
181
185
|
const envToken = (0, env_cjs_namespaceObject.getGatewayTokenFromEnv)();
|
|
@@ -190,6 +194,8 @@ async function handleRun(options) {
|
|
|
190
194
|
config = {
|
|
191
195
|
port: options.port || gatewayDefaults.port || 18789,
|
|
192
196
|
host: options.host || gatewayDefaults.host || "127.0.0.1",
|
|
197
|
+
workspace: commandOptions.workspace,
|
|
198
|
+
configDir: commandOptions.configDir,
|
|
193
199
|
requireAuth: auth?.mode !== "none",
|
|
194
200
|
authToken: auth?.token,
|
|
195
201
|
auth,
|
|
@@ -223,6 +229,11 @@ async function handleRun(options) {
|
|
|
223
229
|
process.exit(1);
|
|
224
230
|
}
|
|
225
231
|
}
|
|
232
|
+
function createConfigLoader(commandOptions) {
|
|
233
|
+
const workspace = commandOptions.workspace || process.cwd();
|
|
234
|
+
const configDir = commandOptions.configDir || ".wingman";
|
|
235
|
+
return new loader_cjs_namespaceObject.WingmanConfigLoader(configDir, workspace);
|
|
236
|
+
}
|
|
226
237
|
async function handleJoin(args, options) {
|
|
227
238
|
const url = args[0] || "ws://localhost:18789/ws";
|
|
228
239
|
const name = options.name || `node-${Date.now()}`;
|
|
@@ -6,7 +6,11 @@ export interface GatewayCommandArgs {
|
|
|
6
6
|
args: string[];
|
|
7
7
|
options: Record<string, unknown>;
|
|
8
8
|
}
|
|
9
|
+
export interface GatewayCommandOptions {
|
|
10
|
+
workspace?: string;
|
|
11
|
+
configDir?: string;
|
|
12
|
+
}
|
|
9
13
|
/**
|
|
10
14
|
* Execute gateway command
|
|
11
15
|
*/
|
|
12
|
-
export declare function executeGatewayCommand(args: GatewayCommandArgs): Promise<void>;
|
|
16
|
+
export declare function executeGatewayCommand(args: GatewayCommandArgs, commandOptions?: GatewayCommandOptions): Promise<void>;
|