@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.
Files changed (123) hide show
  1. package/.codex-plugin/plugin.json +35 -0
  2. package/.mcp.json +16 -0
  3. package/LICENSE +201 -0
  4. package/NOTICE +7 -0
  5. package/README.md +197 -0
  6. package/TRADEMARKS.md +9 -0
  7. package/assets/icons/aws-ec2.png +0 -0
  8. package/assets/icons/lucide/bot.svg +8 -0
  9. package/assets/icons/lucide/monitor.svg +5 -0
  10. package/assets/icons/lucide/server.svg +6 -0
  11. package/assets/icons/lucide/terminal.svg +4 -0
  12. package/assets/icons/s-gw-128.png +0 -0
  13. package/assets/icons/s-gw-16.png +0 -0
  14. package/assets/icons/s-gw-180.png +0 -0
  15. package/assets/icons/s-gw-192.png +0 -0
  16. package/assets/icons/s-gw-32.png +0 -0
  17. package/assets/icons/s-gw-64.png +0 -0
  18. package/assets/icons/s-gw-menu-bar-template.png +0 -0
  19. package/dist/agent-context.d.ts +17 -0
  20. package/dist/agent-context.js +207 -0
  21. package/dist/agents.d.ts +64 -0
  22. package/dist/agents.js +763 -0
  23. package/dist/cli.d.ts +2 -0
  24. package/dist/cli.js +1385 -0
  25. package/dist/command-suggest.d.ts +3 -0
  26. package/dist/command-suggest.js +131 -0
  27. package/dist/console-server.d.ts +16 -0
  28. package/dist/console-server.js +978 -0
  29. package/dist/console-ui/assets/codex-DYTPdPxi.png +0 -0
  30. package/dist/console-ui/assets/cursor-CBrUTJD-.png +0 -0
  31. package/dist/console-ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  32. package/dist/console-ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  33. package/dist/console-ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  34. package/dist/console-ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  35. package/dist/console-ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  36. package/dist/console-ui/assets/hermes-B8hNbJPm.png +0 -0
  37. package/dist/console-ui/assets/index-BxUf0Sye.js +96 -0
  38. package/dist/console-ui/assets/index-CmTiBR_w.css +2 -0
  39. package/dist/console-ui/assets/omnigent-Cxa4p2Mq.png +0 -0
  40. package/dist/console-ui/assets/openclaw-C5wL4ZVW.png +0 -0
  41. package/dist/console-ui/assets/opencode-D_wFATSC.png +0 -0
  42. package/dist/console-ui/assets/openhands-DnrlGgev.svg +9 -0
  43. package/dist/console-ui/assets/s-gw-64-ByMUGQ3K.png +0 -0
  44. package/dist/console-ui/assets/vscode-Bdtr9eyf.png +0 -0
  45. package/dist/console-ui/assets/zeptoclaw-DztQW8Sw.png +0 -0
  46. package/dist/console-ui/index.html +13 -0
  47. package/dist/crypto.d.ts +6 -0
  48. package/dist/crypto.js +53 -0
  49. package/dist/executor.d.ts +7 -0
  50. package/dist/executor.js +297 -0
  51. package/dist/gateway.d.ts +31 -0
  52. package/dist/gateway.js +114 -0
  53. package/dist/guard.d.ts +61 -0
  54. package/dist/guard.js +247 -0
  55. package/dist/install.d.ts +146 -0
  56. package/dist/install.js +629 -0
  57. package/dist/mcp-server.d.ts +2 -0
  58. package/dist/mcp-server.js +119 -0
  59. package/dist/native/s-gw-core +0 -0
  60. package/dist/native/s-gw-keychain-helper +0 -0
  61. package/dist/onepassword.d.ts +48 -0
  62. package/dist/onepassword.js +412 -0
  63. package/dist/paths.d.ts +4 -0
  64. package/dist/paths.js +22 -0
  65. package/dist/s-gw Menu Bar.app/Contents/Info.plist +28 -0
  66. package/dist/s-gw Menu Bar.app/Contents/MacOS/s-gw-menu-bar-helper +0 -0
  67. package/dist/s-gw Menu Bar.app/Contents/Resources/AppIcon.icns +0 -0
  68. package/dist/s-gw Menu Bar.app/Contents/Resources/AwsEc2.png +0 -0
  69. package/dist/s-gw Menu Bar.app/Contents/Resources/Lucide-bot.svg +8 -0
  70. package/dist/s-gw Menu Bar.app/Contents/Resources/Lucide-monitor.svg +5 -0
  71. package/dist/s-gw Menu Bar.app/Contents/Resources/Lucide-server.svg +6 -0
  72. package/dist/s-gw Menu Bar.app/Contents/Resources/Lucide-terminal.svg +4 -0
  73. package/dist/s-gw Menu Bar.app/Contents/Resources/MenuBarTemplate.png +0 -0
  74. package/dist/s-gw Menu Bar.app/Contents/_CodeSignature/CodeResources +194 -0
  75. package/dist/s-gw.app/Contents/Info.plist +28 -0
  76. package/dist/s-gw.app/Contents/MacOS/s-gw +0 -0
  77. package/dist/s-gw.app/Contents/Resources/AppIcon.icns +0 -0
  78. package/dist/s-gw.app/Contents/Resources/MenuBarTemplate.png +0 -0
  79. package/dist/s-gw.app/Contents/_CodeSignature/CodeResources +139 -0
  80. package/dist/scanner.d.ts +9 -0
  81. package/dist/scanner.js +437 -0
  82. package/dist/ssh.d.ts +31 -0
  83. package/dist/ssh.js +286 -0
  84. package/dist/store.d.ts +131 -0
  85. package/dist/store.js +1611 -0
  86. package/dist/types.d.ts +196 -0
  87. package/dist/types.js +2 -0
  88. package/dist/unlock.d.ts +29 -0
  89. package/dist/unlock.js +274 -0
  90. package/dist/windows/VERSION.txt +1 -0
  91. package/dist/windows/s-gw-client.cmd +4 -0
  92. package/dist/windows/s-gw-client.ps1 +106 -0
  93. package/dist/windows/s-gw-credential.cmd +4 -0
  94. package/dist/windows/s-gw-credential.ps1 +167 -0
  95. package/dist/windows/s-gw-helper.cmd +4 -0
  96. package/dist/windows/s-gw-helper.ps1 +180 -0
  97. package/docs/README.md +23 -0
  98. package/docs/agents.md +160 -0
  99. package/docs/architecture.md +72 -0
  100. package/docs/deployment.md +447 -0
  101. package/docs/detection.md +44 -0
  102. package/docs/images/s-gw-overview.png +0 -0
  103. package/docs/integrations.md +195 -0
  104. package/docs/keychain.md +39 -0
  105. package/docs/onepassword.md +84 -0
  106. package/docs/quickstart.md +104 -0
  107. package/docs/threat-model.md +100 -0
  108. package/docs/ui/THIRD_PARTY_NOTICES.md +111 -0
  109. package/docs/ui/apple-touch-icon.png +0 -0
  110. package/docs/ui/favicon-32.png +0 -0
  111. package/docs/ui/local-console.html +4477 -0
  112. package/docs/ui/vendor/d3-sankey/d3-array.LICENSE.txt +27 -0
  113. package/docs/ui/vendor/d3-sankey/d3-array.min.js +2 -0
  114. package/docs/ui/vendor/d3-sankey/d3-path.LICENSE.txt +27 -0
  115. package/docs/ui/vendor/d3-sankey/d3-path.min.js +2 -0
  116. package/docs/ui/vendor/d3-sankey/d3-sankey.LICENSE.txt +27 -0
  117. package/docs/ui/vendor/d3-sankey/d3-sankey.min.js +2 -0
  118. package/docs/ui/vendor/d3-sankey/d3-shape.LICENSE.txt +27 -0
  119. package/docs/ui/vendor/d3-sankey/d3-shape.min.js +2 -0
  120. package/docs/ui/vendor/sankeymatic/LICENSE.txt +17 -0
  121. package/docs/ui/vendor/sankeymatic/sankey.js +897 -0
  122. package/package.json +117 -0
  123. package/skills/s-gw/SKILL.md +19 -0
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ import { executeApprovedRequest } from "./executor.js";
6
+ import { buildEnvCommandAction, buildSshSessionAction, scanLocalFile, scanLocalText } from "./gateway.js";
7
+ import { SecretStore } from "./store.js";
8
+ import { defaultSshInjectEnv } from "./ssh.js";
9
+ const store = new SecretStore();
10
+ const server = new McpServer({
11
+ name: "s-gw",
12
+ version: "0.1.0"
13
+ });
14
+ function mcpAgentContext() {
15
+ return { mcpClientName: server.server.getClientVersion()?.name };
16
+ }
17
+ server.registerTool("sgw_scan_file", {
18
+ title: "Scan Local File",
19
+ description: "Scan a local file for secrets and return tokenized text plus local handles. Raw values are not returned.",
20
+ inputSchema: {
21
+ path: z.string().min(1),
22
+ persist: z.boolean().optional()
23
+ }
24
+ }, async ({ path, persist }) => asText(await scanLocalFile(store, path, { persist: persist ?? true })));
25
+ server.registerTool("sgw_scan_text", {
26
+ title: "Scan Text",
27
+ description: "Scan supplied text and return tokenized text. Persist=false previews handles without enrolling secrets.",
28
+ inputSchema: {
29
+ text: z.string(),
30
+ persist: z.boolean().optional(),
31
+ source: z.string().optional()
32
+ }
33
+ }, async ({ text, persist, source }) => asText(await scanLocalText(store, text, { persist: persist === true, source })));
34
+ server.registerTool("sgw_list_handles", {
35
+ title: "List Handles",
36
+ description: "List known local secret handles and non-secret metadata.",
37
+ inputSchema: {}
38
+ }, async () => asText(await store.listHandles()));
39
+ server.registerTool("sgw_describe_handle", {
40
+ title: "Describe Handle",
41
+ description: "Show non-secret metadata for one local secret handle.",
42
+ inputSchema: {
43
+ handle: z.string().min(1)
44
+ }
45
+ }, async ({ handle }) => asText(await store.getHandle(handle) ?? { error: "unknown_handle", handle }));
46
+ server.registerTool("sgw_request_execution", {
47
+ title: "Request Secret-Backed Execution",
48
+ description: "Create a pending local manifest for a command that needs one secret injected as an environment variable.",
49
+ inputSchema: {
50
+ handle: z.string().min(1),
51
+ command: z.string().min(1),
52
+ args: z.array(z.string()).optional(),
53
+ injectEnv: z.string().min(1),
54
+ env: z.array(z.object({
55
+ handle: z.string().min(1),
56
+ injectEnv: z.string().min(1)
57
+ })).optional(),
58
+ workingDir: z.string().optional(),
59
+ timeoutMs: z.number().int().nonnegative().optional(),
60
+ reason: z.string().optional()
61
+ }
62
+ }, async ({ handle, command, args, injectEnv, env, workingDir, timeoutMs, reason }) => {
63
+ const action = buildEnvCommandAction({ command, args, injectEnv, env, workingDir, timeoutMs });
64
+ const request = await store.createRequest(handle, action, reason || "Agent requested local secret-backed execution.", mcpAgentContext());
65
+ return asText({
66
+ approvalRequired: request.state !== "approved",
67
+ localApprovalCommand: request.state === "approved" ? undefined : `s-gw approve ${request.id}`,
68
+ request
69
+ });
70
+ });
71
+ server.registerTool("sgw_request_ssh_session", {
72
+ title: "Request s-gw-Owned SSH Session",
73
+ description: "Create a local approval request for an SSH command that s-gw will run over its own persistent ControlMaster session.",
74
+ inputSchema: {
75
+ handle: z.string().min(1),
76
+ target: z.string().min(1),
77
+ port: z.number().int().positive().optional(),
78
+ args: z.array(z.string()).optional(),
79
+ injectEnv: z.string().optional(),
80
+ workingDir: z.string().optional(),
81
+ timeoutMs: z.number().int().nonnegative().optional(),
82
+ reason: z.string().optional()
83
+ }
84
+ }, async ({ handle, target, port, args, injectEnv, workingDir, timeoutMs, reason }) => {
85
+ const secret = await store.getSecretRecord(handle);
86
+ const action = buildSshSessionAction({
87
+ target,
88
+ port,
89
+ args,
90
+ injectEnv: injectEnv || defaultSshInjectEnv(secret),
91
+ workingDir,
92
+ timeoutMs
93
+ });
94
+ const request = await store.createRequest(handle, action, reason || "Agent requested s-gw-owned SSH access.", mcpAgentContext());
95
+ return asText({
96
+ approvalRequired: request.state !== "approved",
97
+ localApprovalCommand: request.state === "approved" ? undefined : `s-gw approve ${request.id}`,
98
+ request
99
+ });
100
+ });
101
+ server.registerTool("sgw_execute_request", {
102
+ title: "Execute Approved Request",
103
+ description: "Execute a previously approved local request and return sanitized output.",
104
+ inputSchema: {
105
+ requestId: z.string().min(1)
106
+ }
107
+ }, async ({ requestId }) => asText(await executeApprovedRequest(store, requestId)));
108
+ function asText(value) {
109
+ return {
110
+ content: [
111
+ {
112
+ type: "text",
113
+ text: JSON.stringify(value, null, 2)
114
+ }
115
+ ]
116
+ };
117
+ }
118
+ await server.connect(new StdioServerTransport());
119
+ //# sourceMappingURL=mcp-server.js.map
Binary file
Binary file
@@ -0,0 +1,48 @@
1
+ import type { SecretType } from "./types.js";
2
+ export interface OnePasswordStatus {
3
+ available: boolean;
4
+ command: string;
5
+ version?: string;
6
+ serviceAccountConfigured: boolean;
7
+ connectConfigured: boolean;
8
+ error?: string;
9
+ }
10
+ export interface OnePasswordSecretReference {
11
+ vault: string;
12
+ itemId: string;
13
+ itemTitle: string;
14
+ itemCategory?: string;
15
+ fieldId: string;
16
+ fieldLabel: string;
17
+ fieldType?: string;
18
+ fieldPurpose?: string;
19
+ reference: string;
20
+ secretType: SecretType;
21
+ suggestedEnv?: string;
22
+ companionFields?: OnePasswordCompanionField[];
23
+ }
24
+ export interface OnePasswordCompanionField {
25
+ vault: string;
26
+ itemId: string;
27
+ itemTitle: string;
28
+ itemCategory?: string;
29
+ fieldId: string;
30
+ fieldLabel: string;
31
+ fieldType?: string;
32
+ fieldPurpose?: string;
33
+ reference: string;
34
+ secretType: SecretType;
35
+ suggestedEnv?: string;
36
+ }
37
+ export interface CreateOnePasswordSecretInput {
38
+ vault?: string;
39
+ title: string;
40
+ type: SecretType;
41
+ value: string;
42
+ notes?: string;
43
+ }
44
+ export declare function normalizeOnePasswordReference(reference: string): string;
45
+ export declare function onePasswordStatus(): OnePasswordStatus;
46
+ export declare function listOnePasswordSecretReferences(vault?: string): Promise<OnePasswordSecretReference[]>;
47
+ export declare function createOnePasswordSecretReference(input: CreateOnePasswordSecretInput): Promise<OnePasswordSecretReference>;
48
+ export declare function readOnePasswordReference(reference: string): Promise<string>;
@@ -0,0 +1,412 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { accessSync, constants } from "node:fs";
3
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ export function normalizeOnePasswordReference(reference) {
7
+ const trimmed = reference.trim();
8
+ if (!trimmed) {
9
+ throw new Error("1Password reference is required.");
10
+ }
11
+ if (trimmed.includes("\0") || /[\r\n]/.test(trimmed)) {
12
+ throw new Error("1Password reference cannot contain control characters.");
13
+ }
14
+ if (!/^op:\/\/[^/]+\/[^/]+\/.+/.test(trimmed)) {
15
+ throw new Error("1Password reference must look like op://vault/item/field.");
16
+ }
17
+ return trimmed;
18
+ }
19
+ export function onePasswordStatus() {
20
+ const command = resolveOpCommand();
21
+ const result = spawnSync(command, ["--version"], {
22
+ encoding: "utf8",
23
+ stdio: ["ignore", "pipe", "pipe"]
24
+ });
25
+ return {
26
+ available: result.status === 0,
27
+ command,
28
+ version: result.status === 0 ? result.stdout.trim() : undefined,
29
+ serviceAccountConfigured: Boolean(process.env.OP_SERVICE_ACCOUNT_TOKEN),
30
+ connectConfigured: Boolean(process.env.OP_CONNECT_HOST && process.env.OP_CONNECT_TOKEN),
31
+ error: result.status === 0 ? undefined : (result.stderr.trim() || result.stdout.trim() || "op CLI is not available")
32
+ };
33
+ }
34
+ export async function listOnePasswordSecretReferences(vault = "Dev") {
35
+ const normalizedVault = normalizeVaultName(vault);
36
+ const items = await runOpJson(["item", "list", "--vault", normalizedVault, "--format", "json"]);
37
+ const refs = [];
38
+ for (const item of items) {
39
+ if (!item.id) {
40
+ continue;
41
+ }
42
+ const detail = await runOpJson(["item", "get", item.id, "--vault", normalizedVault, "--format", "json"]);
43
+ const fields = detail.fields || [];
44
+ const companionFields = fields
45
+ .map((field) => companionForField(normalizedVault, detail, field))
46
+ .filter((field) => Boolean(field));
47
+ for (const field of fields) {
48
+ const candidate = referenceForField(normalizedVault, detail, field, companionFields);
49
+ if (candidate) {
50
+ refs.push(candidate);
51
+ }
52
+ }
53
+ }
54
+ refs.sort((a, b) => {
55
+ const title = a.itemTitle.localeCompare(b.itemTitle);
56
+ if (title !== 0) {
57
+ return title;
58
+ }
59
+ return a.fieldLabel.localeCompare(b.fieldLabel);
60
+ });
61
+ return refs;
62
+ }
63
+ export async function createOnePasswordSecretReference(input) {
64
+ const vault = normalizeVaultName(input.vault || "Dev");
65
+ const title = normalizeItemTitle(input.title);
66
+ if (!input.value) {
67
+ throw new Error("Cannot create an empty 1Password secret.");
68
+ }
69
+ const tmpDir = await mkdtemp(path.join(os.tmpdir(), "sgw-op-create-"));
70
+ const templatePath = path.join(tmpDir, "item.json");
71
+ try {
72
+ await writeFile(templatePath, JSON.stringify(onePasswordTemplate(title, input.value, input.notes), null, 2), {
73
+ mode: 0o600
74
+ });
75
+ const created = await runOpJson([
76
+ "item",
77
+ "create",
78
+ "--vault",
79
+ vault,
80
+ "--template",
81
+ templatePath,
82
+ "--format",
83
+ "json"
84
+ ]);
85
+ const field = (created.fields || []).find((item) => item.id === "credential" || item.label === "credential");
86
+ if (!field) {
87
+ throw new Error("1Password did not return the created credential field.");
88
+ }
89
+ const fieldId = field.id?.trim() || "credential";
90
+ return {
91
+ vault,
92
+ itemId: created.id,
93
+ itemTitle: created.title || title,
94
+ itemCategory: created.category,
95
+ fieldId,
96
+ fieldLabel: field.label?.trim() || fieldId,
97
+ fieldType: field.type,
98
+ fieldPurpose: field.purpose,
99
+ reference: normalizeOnePasswordReference(field.reference || buildReference(vault, created.id, fieldId)),
100
+ secretType: input.type
101
+ };
102
+ }
103
+ finally {
104
+ await rm(tmpDir, { recursive: true, force: true });
105
+ }
106
+ }
107
+ export async function readOnePasswordReference(reference) {
108
+ const normalized = normalizeOnePasswordReference(reference);
109
+ const command = resolveOpCommand();
110
+ const timeoutMs = timeoutFromEnv();
111
+ const child = spawn(command, ["read", normalized], {
112
+ env: process.env,
113
+ shell: false,
114
+ stdio: ["ignore", "pipe", "pipe"]
115
+ });
116
+ let stdout = "";
117
+ let stderr = "";
118
+ let timedOut = false;
119
+ let killTimer;
120
+ const timeout = setTimeout(() => {
121
+ timedOut = true;
122
+ child.kill("SIGTERM");
123
+ killTimer = setTimeout(() => child.kill("SIGKILL"), 1_500);
124
+ }, timeoutMs);
125
+ child.stdout?.on("data", (chunk) => {
126
+ stdout += chunk.toString("utf8");
127
+ });
128
+ child.stderr?.on("data", (chunk) => {
129
+ stderr += chunk.toString("utf8");
130
+ });
131
+ const status = await new Promise((resolve) => {
132
+ child.on("error", (error) => resolve({ code: null, error }));
133
+ child.on("close", (code) => resolve({ code }));
134
+ });
135
+ clearTimeout(timeout);
136
+ if (killTimer) {
137
+ clearTimeout(killTimer);
138
+ }
139
+ if (timedOut) {
140
+ throw new Error(`Timed out reading 1Password reference after ${timeoutMs}ms.`);
141
+ }
142
+ if (status.error) {
143
+ throw new Error(`Failed to run ${command}: ${status.error.message}`);
144
+ }
145
+ if (status.code !== 0) {
146
+ const message = stderr.trim() || stdout.trim() || `${command} read failed.`;
147
+ throw new Error(`1Password read failed: ${message}`);
148
+ }
149
+ if (!stdout) {
150
+ throw new Error("1Password returned an empty secret.");
151
+ }
152
+ return stdout.replace(/\r?\n$/, "");
153
+ }
154
+ async function runOpJson(args, input) {
155
+ const output = await runOp(args, input);
156
+ try {
157
+ return JSON.parse(output);
158
+ }
159
+ catch (error) {
160
+ throw new Error(`1Password returned invalid JSON for op ${args.join(" ")}.`);
161
+ }
162
+ }
163
+ async function runOp(args, input) {
164
+ const command = resolveOpCommand();
165
+ const timeoutMs = timeoutFromEnv();
166
+ const child = spawn(command, args, {
167
+ env: process.env,
168
+ shell: false,
169
+ stdio: [input === undefined ? "ignore" : "pipe", "pipe", "pipe"]
170
+ });
171
+ let stdout = "";
172
+ let stderr = "";
173
+ let timedOut = false;
174
+ let killTimer;
175
+ const timeout = setTimeout(() => {
176
+ timedOut = true;
177
+ child.kill("SIGTERM");
178
+ killTimer = setTimeout(() => child.kill("SIGKILL"), 1_500);
179
+ }, timeoutMs);
180
+ if (input !== undefined && child.stdin) {
181
+ child.stdin.end(input);
182
+ }
183
+ child.stdout?.on("data", (chunk) => {
184
+ stdout += chunk.toString("utf8");
185
+ });
186
+ child.stderr?.on("data", (chunk) => {
187
+ stderr += chunk.toString("utf8");
188
+ });
189
+ const status = await new Promise((resolve) => {
190
+ child.on("error", (error) => resolve({ code: null, error }));
191
+ child.on("close", (code) => resolve({ code }));
192
+ });
193
+ clearTimeout(timeout);
194
+ if (killTimer) {
195
+ clearTimeout(killTimer);
196
+ }
197
+ if (timedOut) {
198
+ throw new Error(`Timed out running 1Password CLI after ${timeoutMs}ms.`);
199
+ }
200
+ if (status.error) {
201
+ throw new Error(`Failed to run ${command}: ${status.error.message}`);
202
+ }
203
+ if (status.code !== 0) {
204
+ const message = stderr.trim() || stdout.trim() || `${command} ${args.join(" ")} failed.`;
205
+ throw new Error(`1Password command failed: ${message}`);
206
+ }
207
+ return stdout;
208
+ }
209
+ function resolveOpCommand() {
210
+ if (process.env.SGW_OP_CLI) {
211
+ return process.env.SGW_OP_CLI;
212
+ }
213
+ const realOp = process.env.SGW_REAL_OP_PATH || "/opt/homebrew/Caskroom/1password-cli/2.32.0/op.sgw-real";
214
+ try {
215
+ accessSync(realOp, constants.X_OK);
216
+ return realOp;
217
+ }
218
+ catch {
219
+ return "op";
220
+ }
221
+ }
222
+ function onePasswordTemplate(title, value, notes) {
223
+ return {
224
+ title,
225
+ category: "API_CREDENTIAL",
226
+ fields: [
227
+ {
228
+ id: "notesPlain",
229
+ type: "STRING",
230
+ purpose: "NOTES",
231
+ label: "notesPlain",
232
+ value: notes || "Created by s-gw."
233
+ },
234
+ {
235
+ id: "username",
236
+ type: "STRING",
237
+ label: "username",
238
+ value: ""
239
+ },
240
+ {
241
+ id: "credential",
242
+ type: "CONCEALED",
243
+ label: "credential",
244
+ value
245
+ },
246
+ {
247
+ id: "type",
248
+ type: "MENU",
249
+ label: "type",
250
+ value: "API Key"
251
+ },
252
+ {
253
+ id: "filename",
254
+ type: "STRING",
255
+ label: "filename",
256
+ value: ""
257
+ },
258
+ {
259
+ id: "validFrom",
260
+ type: "DATE",
261
+ label: "valid from",
262
+ value: ""
263
+ },
264
+ {
265
+ id: "expires",
266
+ type: "DATE",
267
+ label: "expires",
268
+ value: ""
269
+ },
270
+ {
271
+ id: "hostname",
272
+ type: "STRING",
273
+ label: "hostname",
274
+ value: ""
275
+ }
276
+ ]
277
+ };
278
+ }
279
+ function referenceForField(vault, item, field, companionFields = []) {
280
+ const label = field.label?.trim() || field.id?.trim() || "";
281
+ const id = field.id?.trim() || label;
282
+ if (!id || !isSecretLikeField(field, label)) {
283
+ return undefined;
284
+ }
285
+ const reference = normalizeOnePasswordReference(field.reference || buildReference(vault, item.id, id));
286
+ return {
287
+ vault,
288
+ itemId: item.id,
289
+ itemTitle: item.title,
290
+ itemCategory: item.category,
291
+ fieldId: id,
292
+ fieldLabel: label || id,
293
+ fieldType: field.type,
294
+ fieldPurpose: field.purpose,
295
+ reference,
296
+ secretType: secretTypeFor(item, field, label),
297
+ suggestedEnv: suggestedEnvForField(item, field, label, true),
298
+ companionFields: companionFields.filter((companion) => companion.fieldId !== id)
299
+ };
300
+ }
301
+ function companionForField(vault, item, field) {
302
+ const label = field.label?.trim() || field.id?.trim() || "";
303
+ const id = field.id?.trim() || label;
304
+ if (!id || !isCompanionField(item, field, label)) {
305
+ return undefined;
306
+ }
307
+ const reference = normalizeOnePasswordReference(field.reference || buildReference(vault, item.id, id));
308
+ return {
309
+ vault,
310
+ itemId: item.id,
311
+ itemTitle: item.title,
312
+ itemCategory: item.category,
313
+ fieldId: id,
314
+ fieldLabel: label || id,
315
+ fieldType: field.type,
316
+ fieldPurpose: field.purpose,
317
+ reference,
318
+ secretType: companionSecretTypeFor(item, field, label),
319
+ suggestedEnv: suggestedEnvForField(item, field, label, false)
320
+ };
321
+ }
322
+ function normalizeItemTitle(title) {
323
+ const trimmed = title.trim();
324
+ if (!trimmed || trimmed.includes("\0") || /[\r\n]/.test(trimmed)) {
325
+ throw new Error("1Password item title is required.");
326
+ }
327
+ return trimmed.slice(0, 180);
328
+ }
329
+ function isSecretLikeField(field, label) {
330
+ const type = field.type?.toUpperCase() || "";
331
+ const purpose = field.purpose?.toUpperCase() || "";
332
+ const visibleLabel = label.toLowerCase();
333
+ if (type === "OTP") {
334
+ return false;
335
+ }
336
+ if (type === "CONCEALED" || purpose === "PASSWORD") {
337
+ return true;
338
+ }
339
+ return /(password|passphrase|token|secret|credential|api.?key|access.?key|private.?key|client.?secret)/i.test(visibleLabel);
340
+ }
341
+ function isCompanionField(item, field, label) {
342
+ const type = field.type?.toUpperCase() || "";
343
+ if (type === "OTP" || type === "CONCEALED" || isSecretLikeField(field, label)) {
344
+ return false;
345
+ }
346
+ const visibleLabel = `${item.title} ${item.category || ""} ${label} ${field.id || ""}`.toLowerCase();
347
+ return /(username|user name|access.?key.?id|key.?id|account.?id|client.?id|tenant.?id)/i.test(visibleLabel);
348
+ }
349
+ function secretTypeFor(item, field, label) {
350
+ const text = `${item.title} ${item.category || ""} ${label} ${field.id || ""}`.toLowerCase();
351
+ if (text.includes("private key") || text.includes("ssh")) {
352
+ return "private-key";
353
+ }
354
+ if (text.includes("password") || text.includes("passphrase")) {
355
+ return "password";
356
+ }
357
+ if (text.includes("access key")) {
358
+ return "access-key";
359
+ }
360
+ if (text.includes("token") || text.includes("api key") || text.includes("apikey")) {
361
+ return "api-token";
362
+ }
363
+ if (text.includes("credential") || text.includes("secret")) {
364
+ return "credential";
365
+ }
366
+ return "unknown";
367
+ }
368
+ function companionSecretTypeFor(item, field, label) {
369
+ const text = `${item.title} ${item.category || ""} ${label} ${field.id || ""}`.toLowerCase();
370
+ if (text.includes("access key") || text.includes("aws")) {
371
+ return "access-key";
372
+ }
373
+ if (text.includes("client id") || text.includes("username") || text.includes("user name")) {
374
+ return "credential";
375
+ }
376
+ return "unknown";
377
+ }
378
+ function suggestedEnvForField(item, field, label, secretField) {
379
+ const text = `${item.title} ${item.category || ""} ${label} ${field.id || ""}`.toLowerCase();
380
+ if (text.includes("aws")) {
381
+ return secretField ? "AWS_SECRET_ACCESS_KEY" : "AWS_ACCESS_KEY_ID";
382
+ }
383
+ if (secretField && /(token|api.?key)/i.test(text)) {
384
+ return "API_TOKEN";
385
+ }
386
+ return undefined;
387
+ }
388
+ function buildReference(vault, itemId, fieldId) {
389
+ return `op://${encodeRefSegment(vault)}/${encodeRefSegment(itemId)}/${encodeRefSegment(fieldId)}`;
390
+ }
391
+ function encodeRefSegment(value) {
392
+ return value.replaceAll("/", "%2F");
393
+ }
394
+ function normalizeVaultName(vault) {
395
+ const trimmed = vault.trim();
396
+ if (!trimmed || trimmed.includes("\0") || /[\r\n]/.test(trimmed)) {
397
+ throw new Error("1Password vault name is required.");
398
+ }
399
+ return trimmed;
400
+ }
401
+ function timeoutFromEnv() {
402
+ const raw = process.env.SGW_ONEPASSWORD_TIMEOUT_MS;
403
+ if (!raw) {
404
+ return 30_000;
405
+ }
406
+ const parsed = Number(raw);
407
+ if (!Number.isFinite(parsed) || parsed <= 0) {
408
+ return 30_000;
409
+ }
410
+ return Math.min(Math.floor(parsed), 300_000);
411
+ }
412
+ //# sourceMappingURL=onepassword.js.map
@@ -0,0 +1,4 @@
1
+ export declare function expandHome(inputPath: string): string;
2
+ export declare function getSgwHome(): string;
3
+ export declare function getStorePath(home?: string): string;
4
+ export declare function ensureSgwHome(home?: string): Promise<void>;
package/dist/paths.js ADDED
@@ -0,0 +1,22 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import { mkdir } from "node:fs/promises";
4
+ export function expandHome(inputPath) {
5
+ if (inputPath === "~") {
6
+ return os.homedir();
7
+ }
8
+ if (inputPath.startsWith("~/")) {
9
+ return path.join(os.homedir(), inputPath.slice(2));
10
+ }
11
+ return inputPath;
12
+ }
13
+ export function getSgwHome() {
14
+ return path.resolve(expandHome(process.env.SGW_HOME || "~/.s-gw"));
15
+ }
16
+ export function getStorePath(home = getSgwHome()) {
17
+ return path.join(home, "store.json");
18
+ }
19
+ export async function ensureSgwHome(home = getSgwHome()) {
20
+ await mkdir(home, { recursive: true, mode: 0o700 });
21
+ }
22
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1,28 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>CFBundleExecutable</key>
6
+ <string>s-gw-menu-bar-helper</string>
7
+ <key>CFBundleIdentifier</key>
8
+ <string>com.s-gw.sgw.menu-bar</string>
9
+ <key>CFBundleIconFile</key>
10
+ <string>AppIcon</string>
11
+ <key>CFBundleName</key>
12
+ <string>s-gw Menu Bar</string>
13
+ <key>CFBundleDisplayName</key>
14
+ <string>s-gw</string>
15
+ <key>CFBundlePackageType</key>
16
+ <string>APPL</string>
17
+ <key>CFBundleShortVersionString</key>
18
+ <string>0.1.0</string>
19
+ <key>CFBundleVersion</key>
20
+ <string>1</string>
21
+ <key>LSMinimumSystemVersion</key>
22
+ <string>13.0</string>
23
+ <key>LSUIElement</key>
24
+ <true/>
25
+ <key>NSPrincipalClass</key>
26
+ <string>NSApplication</string>
27
+ </dict>
28
+ </plist>
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <path d="M12 8V4H8"/>
3
+ <rect width="16" height="12" x="4" y="8" rx="2"/>
4
+ <path d="M2 14h2"/>
5
+ <path d="M20 14h2"/>
6
+ <path d="M15 13v2"/>
7
+ <path d="M9 13v2"/>
8
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <rect width="20" height="14" x="2" y="3" rx="2"/>
3
+ <line x1="8" x2="16" y1="21" y2="21"/>
4
+ <line x1="12" x2="12" y1="17" y2="21"/>
5
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <rect width="20" height="8" x="2" y="2" rx="2"/>
3
+ <rect width="20" height="8" x="2" y="14" rx="2"/>
4
+ <line x1="6" x2="6.01" y1="6" y2="6"/>
5
+ <line x1="6" x2="6.01" y1="18" y2="18"/>
6
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <path d="M12 19h8"/>
3
+ <path d="m4 17 6-6-6-6"/>
4
+ </svg>