@s-gw/s-gw 0.1.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/.codex-plugin/plugin.json +35 -0
- package/.mcp.json +16 -0
- package/LICENSE +201 -0
- package/NOTICE +7 -0
- package/README.md +197 -0
- package/TRADEMARKS.md +9 -0
- package/assets/icons/aws-ec2.png +0 -0
- package/assets/icons/lucide/bot.svg +8 -0
- package/assets/icons/lucide/monitor.svg +5 -0
- package/assets/icons/lucide/server.svg +6 -0
- package/assets/icons/lucide/terminal.svg +4 -0
- package/assets/icons/s-gw-128.png +0 -0
- package/assets/icons/s-gw-16.png +0 -0
- package/assets/icons/s-gw-180.png +0 -0
- package/assets/icons/s-gw-192.png +0 -0
- package/assets/icons/s-gw-32.png +0 -0
- package/assets/icons/s-gw-64.png +0 -0
- package/assets/icons/s-gw-menu-bar-template.png +0 -0
- package/dist/agent-context.d.ts +17 -0
- package/dist/agent-context.js +207 -0
- package/dist/agents.d.ts +64 -0
- package/dist/agents.js +763 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1385 -0
- package/dist/command-suggest.d.ts +3 -0
- package/dist/command-suggest.js +131 -0
- package/dist/console-server.d.ts +16 -0
- package/dist/console-server.js +978 -0
- package/dist/console-ui/assets/codex-DYTPdPxi.png +0 -0
- package/dist/console-ui/assets/cursor-CBrUTJD-.png +0 -0
- package/dist/console-ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dist/console-ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dist/console-ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dist/console-ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dist/console-ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dist/console-ui/assets/hermes-B8hNbJPm.png +0 -0
- package/dist/console-ui/assets/index-BxUf0Sye.js +96 -0
- package/dist/console-ui/assets/index-CmTiBR_w.css +2 -0
- package/dist/console-ui/assets/omnigent-Cxa4p2Mq.png +0 -0
- package/dist/console-ui/assets/openclaw-C5wL4ZVW.png +0 -0
- package/dist/console-ui/assets/opencode-D_wFATSC.png +0 -0
- package/dist/console-ui/assets/openhands-DnrlGgev.svg +9 -0
- package/dist/console-ui/assets/s-gw-64-ByMUGQ3K.png +0 -0
- package/dist/console-ui/assets/vscode-Bdtr9eyf.png +0 -0
- package/dist/console-ui/assets/zeptoclaw-DztQW8Sw.png +0 -0
- package/dist/console-ui/index.html +13 -0
- package/dist/crypto.d.ts +6 -0
- package/dist/crypto.js +53 -0
- package/dist/executor.d.ts +7 -0
- package/dist/executor.js +297 -0
- package/dist/gateway.d.ts +31 -0
- package/dist/gateway.js +114 -0
- package/dist/guard.d.ts +61 -0
- package/dist/guard.js +247 -0
- package/dist/install.d.ts +146 -0
- package/dist/install.js +629 -0
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +119 -0
- package/dist/native/s-gw-core +0 -0
- package/dist/native/s-gw-keychain-helper +0 -0
- package/dist/onepassword.d.ts +48 -0
- package/dist/onepassword.js +412 -0
- package/dist/paths.d.ts +4 -0
- package/dist/paths.js +22 -0
- package/dist/s-gw Menu Bar.app/Contents/Info.plist +28 -0
- package/dist/s-gw Menu Bar.app/Contents/MacOS/s-gw-menu-bar-helper +0 -0
- package/dist/s-gw Menu Bar.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/s-gw Menu Bar.app/Contents/Resources/AwsEc2.png +0 -0
- package/dist/s-gw Menu Bar.app/Contents/Resources/Lucide-bot.svg +8 -0
- package/dist/s-gw Menu Bar.app/Contents/Resources/Lucide-monitor.svg +5 -0
- package/dist/s-gw Menu Bar.app/Contents/Resources/Lucide-server.svg +6 -0
- package/dist/s-gw Menu Bar.app/Contents/Resources/Lucide-terminal.svg +4 -0
- package/dist/s-gw Menu Bar.app/Contents/Resources/MenuBarTemplate.png +0 -0
- package/dist/s-gw Menu Bar.app/Contents/_CodeSignature/CodeResources +194 -0
- package/dist/s-gw.app/Contents/Info.plist +28 -0
- package/dist/s-gw.app/Contents/MacOS/s-gw +0 -0
- package/dist/s-gw.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/s-gw.app/Contents/Resources/MenuBarTemplate.png +0 -0
- package/dist/s-gw.app/Contents/_CodeSignature/CodeResources +139 -0
- package/dist/scanner.d.ts +9 -0
- package/dist/scanner.js +437 -0
- package/dist/ssh.d.ts +31 -0
- package/dist/ssh.js +286 -0
- package/dist/store.d.ts +131 -0
- package/dist/store.js +1611 -0
- package/dist/types.d.ts +196 -0
- package/dist/types.js +2 -0
- package/dist/unlock.d.ts +29 -0
- package/dist/unlock.js +274 -0
- package/dist/windows/VERSION.txt +1 -0
- package/dist/windows/s-gw-client.cmd +4 -0
- package/dist/windows/s-gw-client.ps1 +106 -0
- package/dist/windows/s-gw-credential.cmd +4 -0
- package/dist/windows/s-gw-credential.ps1 +167 -0
- package/dist/windows/s-gw-helper.cmd +4 -0
- package/dist/windows/s-gw-helper.ps1 +180 -0
- package/docs/README.md +23 -0
- package/docs/agents.md +160 -0
- package/docs/architecture.md +72 -0
- package/docs/deployment.md +447 -0
- package/docs/detection.md +44 -0
- package/docs/images/s-gw-overview.png +0 -0
- package/docs/integrations.md +195 -0
- package/docs/keychain.md +39 -0
- package/docs/onepassword.md +84 -0
- package/docs/quickstart.md +104 -0
- package/docs/threat-model.md +100 -0
- package/docs/ui/THIRD_PARTY_NOTICES.md +111 -0
- package/docs/ui/apple-touch-icon.png +0 -0
- package/docs/ui/favicon-32.png +0 -0
- package/docs/ui/local-console.html +4477 -0
- package/docs/ui/vendor/d3-sankey/d3-array.LICENSE.txt +27 -0
- package/docs/ui/vendor/d3-sankey/d3-array.min.js +2 -0
- package/docs/ui/vendor/d3-sankey/d3-path.LICENSE.txt +27 -0
- package/docs/ui/vendor/d3-sankey/d3-path.min.js +2 -0
- package/docs/ui/vendor/d3-sankey/d3-sankey.LICENSE.txt +27 -0
- package/docs/ui/vendor/d3-sankey/d3-sankey.min.js +2 -0
- package/docs/ui/vendor/d3-sankey/d3-shape.LICENSE.txt +27 -0
- package/docs/ui/vendor/d3-sankey/d3-shape.min.js +2 -0
- package/docs/ui/vendor/sankeymatic/LICENSE.txt +17 -0
- package/docs/ui/vendor/sankeymatic/sankey.js +897 -0
- package/package.json +117 -0
- package/skills/s-gw/SKILL.md +19 -0
package/dist/install.js
ADDED
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { getSgwHome, getStorePath } from "./paths.js";
|
|
8
|
+
import { unlockStatus } from "./unlock.js";
|
|
9
|
+
export const consoleLabel = "com.s-gw.sgw.console";
|
|
10
|
+
export const menuBarLabel = "com.s-gw.sgw.menubar";
|
|
11
|
+
export function getPackageLayout() {
|
|
12
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const packageRoot = path.basename(here) === "dist" ? path.dirname(here) : path.dirname(here);
|
|
14
|
+
return {
|
|
15
|
+
packageRoot,
|
|
16
|
+
cliPath: path.join(packageRoot, "dist", "cli.js"),
|
|
17
|
+
mcpPath: path.join(packageRoot, "dist", "mcp-server.js"),
|
|
18
|
+
keychainHelperPath: path.join(packageRoot, "dist", "native", "s-gw-keychain-helper"),
|
|
19
|
+
macAppPath: path.join(packageRoot, "dist", "s-gw.app"),
|
|
20
|
+
macAppBinaryPath: path.join(packageRoot, "dist", "s-gw.app", "Contents", "MacOS", "s-gw"),
|
|
21
|
+
menuBarAppPath: path.join(packageRoot, "dist", "s-gw Menu Bar.app"),
|
|
22
|
+
menuBarBinaryPath: path.join(packageRoot, "dist", "s-gw Menu Bar.app", "Contents", "MacOS", "s-gw-menu-bar-helper"),
|
|
23
|
+
windowsClientScriptPath: path.join(packageRoot, "dist", "windows", "s-gw-client.ps1"),
|
|
24
|
+
windowsClientLauncherPath: path.join(packageRoot, "dist", "windows", "s-gw-client.cmd"),
|
|
25
|
+
windowsHelperScriptPath: path.join(packageRoot, "dist", "windows", "s-gw-helper.ps1"),
|
|
26
|
+
windowsHelperLauncherPath: path.join(packageRoot, "dist", "windows", "s-gw-helper.cmd"),
|
|
27
|
+
windowsCredentialHelperPath: path.join(packageRoot, "dist", "windows", "s-gw-credential.ps1")
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function packageHealth(port = 8718) {
|
|
31
|
+
const layout = getPackageLayout();
|
|
32
|
+
const unlock = unlockStatus();
|
|
33
|
+
const cli = pathStatus(layout.cliPath);
|
|
34
|
+
const mcp = pathStatus(layout.mcpPath);
|
|
35
|
+
// "Ready" means a fresh user could actually store and redeem a secret: the CLI/MCP
|
|
36
|
+
// entry points exist and there is some unlock source. Without this, `status` looked
|
|
37
|
+
// healthy (every path exists) even when the encrypted ledger could not be unlocked.
|
|
38
|
+
const unlockConfigured = unlock.activeSource !== "none";
|
|
39
|
+
const ready = unlockConfigured && cli.exists && mcp.exists;
|
|
40
|
+
return {
|
|
41
|
+
packageRoot: layout.packageRoot,
|
|
42
|
+
ready,
|
|
43
|
+
readiness: buildReadiness({ unlockConfigured, cli: cli.exists, mcp: mcp.exists }),
|
|
44
|
+
cliPath: cli,
|
|
45
|
+
mcpPath: mcp,
|
|
46
|
+
keychainHelperPath: pathStatus(layout.keychainHelperPath),
|
|
47
|
+
macAppPath: pathStatus(layout.macAppPath),
|
|
48
|
+
macAppBinaryPath: pathStatus(layout.macAppBinaryPath),
|
|
49
|
+
menuBarAppPath: pathStatus(layout.menuBarAppPath),
|
|
50
|
+
menuBarBinaryPath: pathStatus(layout.menuBarBinaryPath),
|
|
51
|
+
windowsClientScriptPath: pathStatus(layout.windowsClientScriptPath),
|
|
52
|
+
windowsClientLauncherPath: pathStatus(layout.windowsClientLauncherPath),
|
|
53
|
+
windowsHelperScriptPath: pathStatus(layout.windowsHelperScriptPath),
|
|
54
|
+
windowsHelperLauncherPath: pathStatus(layout.windowsHelperLauncherPath),
|
|
55
|
+
windowsCredentialHelperPath: pathStatus(layout.windowsCredentialHelperPath),
|
|
56
|
+
storePath: getStorePath(),
|
|
57
|
+
consoleUrl: consoleUrl(port),
|
|
58
|
+
unlock,
|
|
59
|
+
launchAgents: {
|
|
60
|
+
console: launchAgentStatus("console"),
|
|
61
|
+
menuBar: launchAgentStatus("menubar")
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// Console/native surfaces share this so the "not ready" wording stays identical
|
|
66
|
+
// everywhere. The console process is, by definition, running from a built package,
|
|
67
|
+
// so it only needs to report unlock readiness.
|
|
68
|
+
export function readinessForUnlock(unlockConfigured) {
|
|
69
|
+
return buildReadiness({ unlockConfigured, cli: true, mcp: true });
|
|
70
|
+
}
|
|
71
|
+
function buildReadiness(checks) {
|
|
72
|
+
const blockers = [];
|
|
73
|
+
if (!checks.cli || !checks.mcp) {
|
|
74
|
+
blockers.push("Build artifacts are missing. Run `npm run build` (or reinstall the package).");
|
|
75
|
+
}
|
|
76
|
+
if (!checks.unlockConfigured) {
|
|
77
|
+
blockers.push("No local unlock material. Run `s-gw setup`, or `s-gw unlock keychain set --value-stdin`, or set SGW_MASTER_PASSPHRASE.");
|
|
78
|
+
}
|
|
79
|
+
const ok = blockers.length === 0;
|
|
80
|
+
return {
|
|
81
|
+
ok,
|
|
82
|
+
summary: ok ? "s-gw is ready to store and redeem secrets." : "s-gw is not ready yet.",
|
|
83
|
+
blockers
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export async function installConsoleLaunchAgent(options = {}) {
|
|
87
|
+
requireMac("launchd service install");
|
|
88
|
+
const port = options.port || 8718;
|
|
89
|
+
const plistPath = launchAgentPath(consoleLabel);
|
|
90
|
+
const logs = await ensureLogDir();
|
|
91
|
+
await writeFile(plistPath, buildConsoleLaunchAgentPlist(port, logs), { mode: 0o644 });
|
|
92
|
+
if (options.start) {
|
|
93
|
+
startLaunchAgent(consoleLabel, plistPath);
|
|
94
|
+
}
|
|
95
|
+
return launchAgentStatus("console");
|
|
96
|
+
}
|
|
97
|
+
export async function uninstallConsoleLaunchAgent() {
|
|
98
|
+
requireMac("launchd service uninstall");
|
|
99
|
+
stopLaunchAgent(consoleLabel);
|
|
100
|
+
await rm(launchAgentPath(consoleLabel), { force: true });
|
|
101
|
+
return launchAgentStatus("console");
|
|
102
|
+
}
|
|
103
|
+
export async function installMenuBarLaunchAgent(options = {}) {
|
|
104
|
+
requireMac("menu-bar install");
|
|
105
|
+
assertMenuBarExists();
|
|
106
|
+
const plistPath = launchAgentPath(menuBarLabel);
|
|
107
|
+
const logs = await ensureLogDir();
|
|
108
|
+
await writeFile(plistPath, buildMenuBarLaunchAgentPlist(options, logs), { mode: 0o644 });
|
|
109
|
+
if (options.start) {
|
|
110
|
+
startLaunchAgent(menuBarLabel, plistPath);
|
|
111
|
+
}
|
|
112
|
+
return launchAgentStatus("menubar");
|
|
113
|
+
}
|
|
114
|
+
export async function uninstallMenuBarLaunchAgent() {
|
|
115
|
+
requireMac("menu-bar uninstall");
|
|
116
|
+
stopLaunchAgent(menuBarLabel);
|
|
117
|
+
await rm(launchAgentPath(menuBarLabel), { force: true });
|
|
118
|
+
return launchAgentStatus("menubar");
|
|
119
|
+
}
|
|
120
|
+
export function startInstalledLaunchAgent(kind) {
|
|
121
|
+
requireMac("launch-agent start");
|
|
122
|
+
const label = kind === "console" ? consoleLabel : menuBarLabel;
|
|
123
|
+
const plistPath = launchAgentPath(label);
|
|
124
|
+
if (!existsSync(plistPath)) {
|
|
125
|
+
throw new Error(`LaunchAgent is not installed: ${plistPath}`);
|
|
126
|
+
}
|
|
127
|
+
startLaunchAgent(label, plistPath);
|
|
128
|
+
return launchAgentStatus(kind);
|
|
129
|
+
}
|
|
130
|
+
export function stopInstalledLaunchAgent(kind) {
|
|
131
|
+
requireMac("launch-agent stop");
|
|
132
|
+
stopLaunchAgent(kind === "console" ? consoleLabel : menuBarLabel);
|
|
133
|
+
return launchAgentStatus(kind);
|
|
134
|
+
}
|
|
135
|
+
export function launchAgentStatus(kind) {
|
|
136
|
+
const label = kind === "console" ? consoleLabel : menuBarLabel;
|
|
137
|
+
const plistPath = launchAgentPath(label);
|
|
138
|
+
return {
|
|
139
|
+
label,
|
|
140
|
+
plistPath,
|
|
141
|
+
installed: existsSync(plistPath),
|
|
142
|
+
loaded: process.platform === "darwin" ? isLaunchAgentLoaded(label) : false
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
export function openMenuBarHelper(options = {}) {
|
|
146
|
+
requireMac("menu-bar open");
|
|
147
|
+
assertMenuBarExists();
|
|
148
|
+
const layout = getPackageLayout();
|
|
149
|
+
const url = options.consoleUrl || consoleUrl(options.port || 8718);
|
|
150
|
+
const env = menuBarEnvironment(url, options.countMode);
|
|
151
|
+
const args = [];
|
|
152
|
+
for (const [key, value] of Object.entries(env)) {
|
|
153
|
+
args.push("--env", `${key}=${value}`);
|
|
154
|
+
}
|
|
155
|
+
args.push(layout.menuBarAppPath, "--args");
|
|
156
|
+
if (options.show) {
|
|
157
|
+
args.push("--show-on-launch");
|
|
158
|
+
}
|
|
159
|
+
if (options.notify !== false) {
|
|
160
|
+
args.push("--notify-on-launch");
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
args.push("--no-notify");
|
|
164
|
+
}
|
|
165
|
+
const result = spawnSync("open", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
166
|
+
if (result.status !== 0) {
|
|
167
|
+
throw new Error(result.stderr.trim() || "Failed to open s-gw menu-bar helper.");
|
|
168
|
+
}
|
|
169
|
+
return { appPath: layout.menuBarAppPath, consoleUrl: url };
|
|
170
|
+
}
|
|
171
|
+
export function openMacApp(options = {}) {
|
|
172
|
+
requireMac("mac app open");
|
|
173
|
+
assertMacAppExists();
|
|
174
|
+
const layout = getPackageLayout();
|
|
175
|
+
const url = options.consoleUrl || consoleUrl(options.port || 8718);
|
|
176
|
+
const existing = existingMacAppProcess(layout);
|
|
177
|
+
if (existing) {
|
|
178
|
+
focusMacAppProcess(existing, layout.macAppPath);
|
|
179
|
+
return {
|
|
180
|
+
appPath: layout.macAppPath,
|
|
181
|
+
consoleUrl: url,
|
|
182
|
+
reusedExisting: true,
|
|
183
|
+
process: existing
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const env = menuBarEnvironment(url);
|
|
187
|
+
const args = [];
|
|
188
|
+
for (const [key, value] of Object.entries(env)) {
|
|
189
|
+
args.push("--env", `${key}=${value}`);
|
|
190
|
+
}
|
|
191
|
+
args.push(layout.macAppPath);
|
|
192
|
+
const result = spawnSync("open", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
193
|
+
if (result.status !== 0) {
|
|
194
|
+
throw new Error(result.stderr.trim() || "Failed to open s-gw macOS app.");
|
|
195
|
+
}
|
|
196
|
+
return { appPath: layout.macAppPath, consoleUrl: url, reusedExisting: false };
|
|
197
|
+
}
|
|
198
|
+
export function openWindowsClient(options = {}) {
|
|
199
|
+
requireWindows("Windows client open");
|
|
200
|
+
assertWindowsClientExists();
|
|
201
|
+
const layout = getPackageLayout();
|
|
202
|
+
const url = options.consoleUrl || consoleUrl(options.port || 8718);
|
|
203
|
+
const result = spawnSync("powershell.exe", [
|
|
204
|
+
"-NoProfile",
|
|
205
|
+
"-ExecutionPolicy",
|
|
206
|
+
"Bypass",
|
|
207
|
+
"-File",
|
|
208
|
+
layout.windowsClientScriptPath,
|
|
209
|
+
"-Port",
|
|
210
|
+
String(options.port || 8718),
|
|
211
|
+
"-ConsoleUrl",
|
|
212
|
+
url
|
|
213
|
+
], {
|
|
214
|
+
encoding: "utf8",
|
|
215
|
+
env: windowsEnvironment(url),
|
|
216
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
217
|
+
});
|
|
218
|
+
if (result.status !== 0) {
|
|
219
|
+
throw new Error(result.stderr.trim() || result.stdout.trim() || "Failed to open s-gw Windows client.");
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
scriptPath: layout.windowsClientScriptPath,
|
|
223
|
+
launcherPath: layout.windowsClientLauncherPath,
|
|
224
|
+
consoleUrl: url
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
export function openWindowsHelper(options = {}) {
|
|
228
|
+
requireWindows("Windows helper open");
|
|
229
|
+
assertWindowsHelperExists();
|
|
230
|
+
const layout = getPackageLayout();
|
|
231
|
+
const url = options.consoleUrl || consoleUrl(options.port || 8718);
|
|
232
|
+
const child = spawn("powershell.exe", [
|
|
233
|
+
"-NoProfile",
|
|
234
|
+
"-ExecutionPolicy",
|
|
235
|
+
"Bypass",
|
|
236
|
+
"-File",
|
|
237
|
+
layout.windowsHelperScriptPath,
|
|
238
|
+
"-Port",
|
|
239
|
+
String(options.port || 8718),
|
|
240
|
+
"-ConsoleUrl",
|
|
241
|
+
url
|
|
242
|
+
], {
|
|
243
|
+
detached: true,
|
|
244
|
+
env: windowsEnvironment(url),
|
|
245
|
+
stdio: "ignore"
|
|
246
|
+
});
|
|
247
|
+
child.unref();
|
|
248
|
+
return {
|
|
249
|
+
scriptPath: layout.windowsHelperScriptPath,
|
|
250
|
+
launcherPath: layout.windowsHelperLauncherPath,
|
|
251
|
+
consoleUrl: url,
|
|
252
|
+
pid: child.pid
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
export function macAppProcessRecordPath() {
|
|
256
|
+
return path.join(os.homedir(), "Library", "Application Support", "s-gw", "s-gw-app.process.json");
|
|
257
|
+
}
|
|
258
|
+
function existingMacAppProcess(layout) {
|
|
259
|
+
const record = readMacAppProcessRecord();
|
|
260
|
+
if (record?.alive) {
|
|
261
|
+
return record;
|
|
262
|
+
}
|
|
263
|
+
return findRunningMacAppProcess(layout);
|
|
264
|
+
}
|
|
265
|
+
function readMacAppProcessRecord() {
|
|
266
|
+
const recordPath = macAppProcessRecordPath();
|
|
267
|
+
if (!existsSync(recordPath)) {
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
const parsed = JSON.parse(readFileSync(recordPath, "utf8"));
|
|
272
|
+
const pid = Number(parsed.pid);
|
|
273
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
pid,
|
|
278
|
+
source: "record",
|
|
279
|
+
alive: isPidAlive(pid),
|
|
280
|
+
recordPath,
|
|
281
|
+
bundleIdentifier: stringValue(parsed.bundleIdentifier),
|
|
282
|
+
bundlePath: stringValue(parsed.bundlePath),
|
|
283
|
+
executablePath: stringValue(parsed.executablePath),
|
|
284
|
+
startedAt: stringValue(parsed.startedAt),
|
|
285
|
+
updatedAt: stringValue(parsed.updatedAt)
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return undefined;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function findRunningMacAppProcess(layout) {
|
|
293
|
+
const result = spawnSync("pgrep", ["-x", "s-gw"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
294
|
+
if (result.status !== 0) {
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
const pids = result.stdout
|
|
298
|
+
.split(/\s+/)
|
|
299
|
+
.map((item) => Number(item.trim()))
|
|
300
|
+
.filter((pid) => Number.isInteger(pid) && pid > 0 && isPidAlive(pid));
|
|
301
|
+
if (pids.length === 0) {
|
|
302
|
+
return undefined;
|
|
303
|
+
}
|
|
304
|
+
const candidates = pids.map((pid) => ({
|
|
305
|
+
pid,
|
|
306
|
+
command: commandForPid(pid)
|
|
307
|
+
}));
|
|
308
|
+
const selected = candidates.find((item) => commandMatchesPath(item.command, layout.macAppBinaryPath)) || candidates[0];
|
|
309
|
+
return {
|
|
310
|
+
pid: selected.pid,
|
|
311
|
+
source: "process-list",
|
|
312
|
+
alive: true,
|
|
313
|
+
bundleIdentifier: "com.s-gw.sgw.app",
|
|
314
|
+
bundlePath: layout.macAppPath,
|
|
315
|
+
executablePath: layout.macAppBinaryPath,
|
|
316
|
+
command: selected.command,
|
|
317
|
+
otherPids: candidates
|
|
318
|
+
.filter((item) => item.pid !== selected.pid)
|
|
319
|
+
.map((item) => item.pid)
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function focusMacAppProcess(app, appPath) {
|
|
323
|
+
if (postOpenMainWindowNotification()) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (app.bundleIdentifier) {
|
|
327
|
+
const byBundle = spawnSync("open", ["-b", app.bundleIdentifier], {
|
|
328
|
+
encoding: "utf8",
|
|
329
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
330
|
+
});
|
|
331
|
+
if (byBundle.status === 0) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const byPath = spawnSync("open", [appPath], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
336
|
+
if (byPath.status !== 0) {
|
|
337
|
+
throw new Error(byPath.stderr.trim() || "Failed to focus running s-gw macOS app.");
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function postOpenMainWindowNotification() {
|
|
341
|
+
const script = [
|
|
342
|
+
"ObjC.import('Foundation')",
|
|
343
|
+
"$.NSDistributedNotificationCenter.defaultCenter.postNotificationNameObjectUserInfoDeliverImmediately(",
|
|
344
|
+
" 'com.s-gw.sgw.openMainWindow', null, null, true",
|
|
345
|
+
")"
|
|
346
|
+
].join("\n");
|
|
347
|
+
const result = spawnSync("osascript", ["-l", "JavaScript", "-e", script], {
|
|
348
|
+
encoding: "utf8",
|
|
349
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
350
|
+
});
|
|
351
|
+
return result.status === 0;
|
|
352
|
+
}
|
|
353
|
+
function commandForPid(pid) {
|
|
354
|
+
const result = spawnSync("ps", ["-p", String(pid), "-o", "command="], {
|
|
355
|
+
encoding: "utf8",
|
|
356
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
357
|
+
});
|
|
358
|
+
if (result.status !== 0) {
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
return result.stdout.trim() || undefined;
|
|
362
|
+
}
|
|
363
|
+
function commandMatchesPath(command, targetPath) {
|
|
364
|
+
return command === targetPath || command?.startsWith(`${targetPath} `) === true;
|
|
365
|
+
}
|
|
366
|
+
function isPidAlive(pid) {
|
|
367
|
+
try {
|
|
368
|
+
process.kill(pid, 0);
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
return error.code === "EPERM";
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function stringValue(value) {
|
|
376
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
377
|
+
}
|
|
378
|
+
export function buildConsoleLaunchAgentPlist(port, logsDir) {
|
|
379
|
+
const layout = getPackageLayout();
|
|
380
|
+
return buildLaunchAgentPlist({
|
|
381
|
+
label: consoleLabel,
|
|
382
|
+
programArguments: [
|
|
383
|
+
process.execPath,
|
|
384
|
+
layout.cliPath,
|
|
385
|
+
"console",
|
|
386
|
+
"--host",
|
|
387
|
+
"127.0.0.1",
|
|
388
|
+
"--port",
|
|
389
|
+
String(port),
|
|
390
|
+
"--no-open"
|
|
391
|
+
],
|
|
392
|
+
environment: launchdBaseEnvironment(),
|
|
393
|
+
runAtLoad: true,
|
|
394
|
+
keepAlive: true,
|
|
395
|
+
stdoutPath: path.join(logsDir, "console.log"),
|
|
396
|
+
stderrPath: path.join(logsDir, "console.err.log")
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
export function buildMenuBarLaunchAgentPlist(options, logsDir) {
|
|
400
|
+
const layout = getPackageLayout();
|
|
401
|
+
const args = [layout.menuBarBinaryPath];
|
|
402
|
+
if (options.notify !== false) {
|
|
403
|
+
args.push("--notify-on-launch");
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
args.push("--no-notify");
|
|
407
|
+
}
|
|
408
|
+
return buildLaunchAgentPlist({
|
|
409
|
+
label: menuBarLabel,
|
|
410
|
+
programArguments: args,
|
|
411
|
+
environment: menuBarEnvironment(options.consoleUrl || consoleUrl(options.port || 8718), options.countMode),
|
|
412
|
+
runAtLoad: true,
|
|
413
|
+
keepAlive: false,
|
|
414
|
+
stdoutPath: path.join(logsDir, "menubar.log"),
|
|
415
|
+
stderrPath: path.join(logsDir, "menubar.err.log"),
|
|
416
|
+
limitToAqua: true
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
function buildLaunchAgentPlist(definition) {
|
|
420
|
+
const envPairs = Object.entries(definition.environment)
|
|
421
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
422
|
+
.map(([key, value]) => ` <key>${xmlEscape(key)}</key>\n <string>${xmlEscape(value)}</string>`)
|
|
423
|
+
.join("\n");
|
|
424
|
+
const programArgs = definition.programArguments
|
|
425
|
+
.map((item) => ` <string>${xmlEscape(item)}</string>`)
|
|
426
|
+
.join("\n");
|
|
427
|
+
const aqua = definition.limitToAqua
|
|
428
|
+
? " <key>LimitLoadToSessionType</key>\n <string>Aqua</string>\n"
|
|
429
|
+
: "";
|
|
430
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
431
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
432
|
+
<plist version="1.0">
|
|
433
|
+
<dict>
|
|
434
|
+
<key>Label</key>
|
|
435
|
+
<string>${xmlEscape(definition.label)}</string>
|
|
436
|
+
<key>ProgramArguments</key>
|
|
437
|
+
<array>
|
|
438
|
+
${programArgs}
|
|
439
|
+
</array>
|
|
440
|
+
<key>EnvironmentVariables</key>
|
|
441
|
+
<dict>
|
|
442
|
+
${envPairs}
|
|
443
|
+
</dict>
|
|
444
|
+
<key>RunAtLoad</key>
|
|
445
|
+
<${definition.runAtLoad ? "true" : "false"}/>
|
|
446
|
+
<key>KeepAlive</key>
|
|
447
|
+
<${definition.keepAlive ? "true" : "false"}/>
|
|
448
|
+
${aqua} <key>StandardOutPath</key>
|
|
449
|
+
<string>${xmlEscape(definition.stdoutPath)}</string>
|
|
450
|
+
<key>StandardErrorPath</key>
|
|
451
|
+
<string>${xmlEscape(definition.stderrPath)}</string>
|
|
452
|
+
</dict>
|
|
453
|
+
</plist>
|
|
454
|
+
`;
|
|
455
|
+
}
|
|
456
|
+
function startLaunchAgent(label, plistPath) {
|
|
457
|
+
stopLaunchAgent(label);
|
|
458
|
+
runLaunchctl(["bootstrap", launchdDomain(), plistPath]);
|
|
459
|
+
runLaunchctl(["kickstart", "-k", `${launchdDomain()}/${label}`]);
|
|
460
|
+
}
|
|
461
|
+
function stopLaunchAgent(label) {
|
|
462
|
+
const plistPath = launchAgentPath(label);
|
|
463
|
+
if (existsSync(plistPath)) {
|
|
464
|
+
spawnSync("launchctl", ["bootout", launchdDomain(), plistPath], {
|
|
465
|
+
encoding: "utf8",
|
|
466
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
spawnSync("launchctl", ["bootout", `${launchdDomain()}/${label}`], {
|
|
470
|
+
encoding: "utf8",
|
|
471
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
472
|
+
});
|
|
473
|
+
waitForLaunchAgentToUnload(label);
|
|
474
|
+
}
|
|
475
|
+
function runLaunchctl(args) {
|
|
476
|
+
const result = spawnSync("launchctl", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
477
|
+
if (result.status !== 0) {
|
|
478
|
+
throw new Error(result.stderr.trim() || `launchctl ${args.join(" ")} failed.`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function isLaunchAgentLoaded(label) {
|
|
482
|
+
const result = spawnSync("launchctl", ["print", `${launchdDomain()}/${label}`], {
|
|
483
|
+
encoding: "utf8",
|
|
484
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
485
|
+
});
|
|
486
|
+
return result.status === 0;
|
|
487
|
+
}
|
|
488
|
+
function waitForLaunchAgentToUnload(label) {
|
|
489
|
+
const flag = new Int32Array(new SharedArrayBuffer(4));
|
|
490
|
+
for (let i = 0; i < 20; i += 1) {
|
|
491
|
+
if (!isLaunchAgentLoaded(label)) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
Atomics.wait(flag, 0, 0, 50);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function launchdDomain() {
|
|
498
|
+
return `gui/${process.getuid?.() ?? os.userInfo().uid}`;
|
|
499
|
+
}
|
|
500
|
+
function launchAgentPath(label) {
|
|
501
|
+
return path.join(os.homedir(), "Library", "LaunchAgents", `${label}.plist`);
|
|
502
|
+
}
|
|
503
|
+
async function ensureLogDir() {
|
|
504
|
+
const logs = path.join(getSgwHome(), "logs");
|
|
505
|
+
await mkdir(logs, { recursive: true, mode: 0o700 });
|
|
506
|
+
await mkdir(path.dirname(launchAgentPath(consoleLabel)), { recursive: true });
|
|
507
|
+
return logs;
|
|
508
|
+
}
|
|
509
|
+
function launchdBaseEnvironment() {
|
|
510
|
+
const env = {
|
|
511
|
+
PATH: process.env.PATH || "/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin",
|
|
512
|
+
SGW_NODE_PATH: process.execPath
|
|
513
|
+
};
|
|
514
|
+
copyEnv(env, "SGW_HOME");
|
|
515
|
+
copyEnv(env, "SGW_KEYCHAIN_SERVICE");
|
|
516
|
+
copyEnv(env, "SGW_KEYCHAIN_ACCOUNT");
|
|
517
|
+
copyEnv(env, "SGW_KEYCHAIN_HELPER");
|
|
518
|
+
return env;
|
|
519
|
+
}
|
|
520
|
+
export function normalizeMenuBarCountMode(value) {
|
|
521
|
+
if (!value) {
|
|
522
|
+
return undefined;
|
|
523
|
+
}
|
|
524
|
+
switch (value.trim().toLowerCase()) {
|
|
525
|
+
case "pending":
|
|
526
|
+
case "approval":
|
|
527
|
+
case "approvals":
|
|
528
|
+
case "authorization":
|
|
529
|
+
case "authorizations":
|
|
530
|
+
case "auth":
|
|
531
|
+
return "pending";
|
|
532
|
+
case "credential":
|
|
533
|
+
case "credentials":
|
|
534
|
+
case "secret":
|
|
535
|
+
case "secrets":
|
|
536
|
+
case "handle":
|
|
537
|
+
case "handles":
|
|
538
|
+
return "credentials";
|
|
539
|
+
case "none":
|
|
540
|
+
case "off":
|
|
541
|
+
case "hide":
|
|
542
|
+
case "hidden":
|
|
543
|
+
return "none";
|
|
544
|
+
default:
|
|
545
|
+
throw new Error("--count must be pending, credentials, or none.");
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
function menuBarEnvironment(url, countMode) {
|
|
549
|
+
const layout = getPackageLayout();
|
|
550
|
+
const env = {
|
|
551
|
+
...launchdBaseEnvironment(),
|
|
552
|
+
SGW_REPO_ROOT: layout.packageRoot,
|
|
553
|
+
SGW_CLI_PATH: layout.cliPath,
|
|
554
|
+
SGW_CONSOLE_URL: url,
|
|
555
|
+
SGW_APP_PATH: layout.macAppPath
|
|
556
|
+
};
|
|
557
|
+
if (countMode) {
|
|
558
|
+
env.SGW_MENU_BAR_COUNT_MODE = countMode;
|
|
559
|
+
}
|
|
560
|
+
return env;
|
|
561
|
+
}
|
|
562
|
+
function windowsEnvironment(url) {
|
|
563
|
+
const layout = getPackageLayout();
|
|
564
|
+
return {
|
|
565
|
+
...process.env,
|
|
566
|
+
SGW_NODE_PATH: process.execPath,
|
|
567
|
+
SGW_CLI_PATH: layout.cliPath,
|
|
568
|
+
SGW_CONSOLE_URL: url,
|
|
569
|
+
SGW_APP_PATH: layout.windowsClientLauncherPath
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
function copyEnv(target, key) {
|
|
573
|
+
const value = process.env[key];
|
|
574
|
+
if (value) {
|
|
575
|
+
target[key] = value;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function consoleUrl(port) {
|
|
579
|
+
return `http://127.0.0.1:${port}/`;
|
|
580
|
+
}
|
|
581
|
+
function pathStatus(filePath) {
|
|
582
|
+
return {
|
|
583
|
+
path: filePath,
|
|
584
|
+
exists: existsSync(filePath)
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
function assertMenuBarExists() {
|
|
588
|
+
const layout = getPackageLayout();
|
|
589
|
+
if (!existsSync(layout.menuBarAppPath) || !existsSync(layout.menuBarBinaryPath)) {
|
|
590
|
+
throw new Error(`Menu-bar helper is missing. Expected app bundle at ${layout.menuBarAppPath}`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
function assertMacAppExists() {
|
|
594
|
+
const layout = getPackageLayout();
|
|
595
|
+
if (!existsSync(layout.macAppPath) || !existsSync(layout.macAppBinaryPath)) {
|
|
596
|
+
throw new Error(`macOS app is missing. Expected app bundle at ${layout.macAppPath}`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
function assertWindowsClientExists() {
|
|
600
|
+
const layout = getPackageLayout();
|
|
601
|
+
if (!existsSync(layout.windowsClientScriptPath)) {
|
|
602
|
+
throw new Error(`Windows client is missing. Expected script at ${layout.windowsClientScriptPath}`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
function assertWindowsHelperExists() {
|
|
606
|
+
const layout = getPackageLayout();
|
|
607
|
+
if (!existsSync(layout.windowsHelperScriptPath)) {
|
|
608
|
+
throw new Error(`Windows helper is missing. Expected script at ${layout.windowsHelperScriptPath}`);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
function requireMac(action) {
|
|
612
|
+
if (process.platform !== "darwin") {
|
|
613
|
+
throw new Error(`${action} is only available on macOS.`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
function requireWindows(action) {
|
|
617
|
+
if (process.platform !== "win32") {
|
|
618
|
+
throw new Error(`${action} is only available on Windows.`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
function xmlEscape(value) {
|
|
622
|
+
return value
|
|
623
|
+
.replaceAll("&", "&")
|
|
624
|
+
.replaceAll("<", "<")
|
|
625
|
+
.replaceAll(">", ">")
|
|
626
|
+
.replaceAll('"', """)
|
|
627
|
+
.replaceAll("'", "'");
|
|
628
|
+
}
|
|
629
|
+
//# sourceMappingURL=install.js.map
|