pinelabs-mcp 1.0.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.
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+
3
+ const { spawn } = require("node:child_process");
4
+ const { loadConfig } = require("../config.js");
5
+
6
+ function log(message) {
7
+ process.stderr.write(`[pinelabs-mcp] ${message}\n`);
8
+ }
9
+
10
+ function startProxy() {
11
+ let config;
12
+ try {
13
+ config = loadConfig();
14
+ } catch (err) {
15
+ process.stderr.write(err.message + "\n");
16
+ process.exit(1);
17
+ return;
18
+ }
19
+
20
+ const { clientId, clientSecret, endpoint, debug } = config;
21
+
22
+ const maskedId =
23
+ clientId.length > 4
24
+ ? clientId.substring(0, 4) + "***"
25
+ : clientId.substring(0, 1) + "***";
26
+
27
+ log(`Pine Labs MCP Client v${require("../../package.json").version}`);
28
+ log(`Endpoint: ${endpoint}`);
29
+ log(`Client ID: ${maskedId}`);
30
+ log(`Transport: stdio <-> streamable-http`);
31
+
32
+ const mcpRemoteBin = require.resolve("mcp-remote/dist/proxy.js");
33
+
34
+ const args = [
35
+ mcpRemoteBin,
36
+ endpoint,
37
+ "--header",
38
+ `X-Client-Id:${clientId}`,
39
+ "--header",
40
+ `X-Client-Secret:${clientSecret}`,
41
+ "--transport",
42
+ "http-first",
43
+ ];
44
+
45
+ if (debug) {
46
+ args.push("--debug");
47
+ }
48
+
49
+ const child = spawn(process.execPath, args, {
50
+ stdio: ["pipe", "pipe", "pipe"],
51
+ env: { ...process.env },
52
+ });
53
+
54
+ process.stdin.pipe(child.stdin);
55
+ child.stdout.pipe(process.stdout);
56
+ child.stderr.pipe(process.stderr);
57
+
58
+ child.on("exit", (code, signal) => {
59
+ if (signal) {
60
+ log(`mcp-remote terminated by signal: ${signal}`);
61
+ process.exit(1);
62
+ }
63
+ process.exit(code || 0);
64
+ });
65
+
66
+ child.on("error", (err) => {
67
+ log(`Failed to start mcp-remote: ${err.message}`);
68
+ process.exit(1);
69
+ });
70
+
71
+ const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
72
+ for (const sig of signals) {
73
+ process.on(sig, () => {
74
+ child.kill(sig);
75
+ });
76
+ }
77
+
78
+ process.stdin.on("end", () => {
79
+ child.kill("SIGTERM");
80
+ });
81
+ }
82
+
83
+ module.exports = { startProxy };
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const os = require("node:os");
6
+ const { loadStoredConfig } = require("../config-store.js");
7
+
8
+ const SUPPORTED_CLIENTS = [
9
+ "claude-desktop",
10
+ "cursor",
11
+ "vscode",
12
+ "windsurf",
13
+ "opencode",
14
+ "copilot",
15
+ "codex",
16
+ ];
17
+
18
+ function log(message) {
19
+ process.stderr.write(`${message}\n`);
20
+ }
21
+
22
+ function _getClientConfigPath(client) {
23
+ const home = os.homedir();
24
+ const platform = os.platform();
25
+
26
+ switch (client) {
27
+ case "claude-desktop":
28
+ if (platform === "darwin") {
29
+ return path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
30
+ }
31
+ if (platform === "win32") {
32
+ return path.join(process.env.APPDATA || path.join(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
33
+ }
34
+ return path.join(home, ".config", "claude", "claude_desktop_config.json");
35
+
36
+ case "cursor":
37
+ return path.join(home, ".cursor", "mcp.json");
38
+
39
+ case "vscode":
40
+ return path.join(process.cwd(), ".vscode", "mcp.json");
41
+
42
+ case "windsurf":
43
+ return path.join(home, ".codeium", "windsurf", "mcp_config.json");
44
+
45
+ case "opencode":
46
+ return path.join(process.cwd(), ".opencode", "config.json");
47
+
48
+ case "copilot":
49
+ return path.join(home, ".copilot", "mcp-config.json");
50
+
51
+ case "codex":
52
+ return path.join(home, ".codex", "config.toml");
53
+
54
+ default:
55
+ return "";
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Resolve the absolute path to bin/cli.js using forward slashes.
61
+ */
62
+ function _getLocalCliPath() {
63
+ return path.resolve(__dirname, "..", "..", "bin", "cli.js").replace(/\\/g, "/");
64
+ }
65
+
66
+ function _buildMcpConfig(client, config, options = {}) {
67
+ let serverEntry;
68
+
69
+ if (options.local) {
70
+ serverEntry = {
71
+ command: "node",
72
+ args: [_getLocalCliPath()],
73
+ env: {
74
+ PINELABS_CLIENT_ID: config.client_id,
75
+ PINELABS_CLIENT_SECRET: config.client_secret,
76
+ },
77
+ };
78
+ } else {
79
+ serverEntry = {
80
+ command: "npx",
81
+ args: ["-y", "pinelabs-mcp"],
82
+ env: {
83
+ PINELABS_CLIENT_ID: config.client_id,
84
+ PINELABS_CLIENT_SECRET: config.client_secret,
85
+ },
86
+ };
87
+ }
88
+
89
+ if (config.environment && config.environment !== "prod") {
90
+ serverEntry.env.PINELABS_ENV = config.environment;
91
+ }
92
+
93
+ if (client === "vscode") {
94
+ return { servers: { pinelabs: serverEntry } };
95
+ }
96
+
97
+ return { mcpServers: { pinelabs: serverEntry } };
98
+ }
99
+
100
+ /**
101
+ * Build TOML string for Codex CLI config.
102
+ * Codex uses ~/.codex/config.toml with [mcp_servers.<name>] tables.
103
+ */
104
+ function _buildCodexToml(config, options = {}) {
105
+ const lines = [];
106
+ lines.push("[mcp_servers.pinelabs]");
107
+ if (options.local) {
108
+ lines.push('command = "node"');
109
+ lines.push(`args = ["${_getLocalCliPath()}"]`);
110
+ } else {
111
+ lines.push('command = "npx"');
112
+ lines.push('args = ["-y", "pinelabs-mcp"]');
113
+ }
114
+ lines.push("");
115
+ lines.push("[mcp_servers.pinelabs.env]");
116
+ lines.push(`PINELABS_CLIENT_ID = "${config.client_id}"`);
117
+ lines.push(`PINELABS_CLIENT_SECRET = "${config.client_secret}"`);
118
+ if (config.environment && config.environment !== "prod") {
119
+ lines.push(`PINELABS_ENV = "${config.environment}"`);
120
+ }
121
+ lines.push("");
122
+ return lines.join("\n");
123
+ }
124
+
125
+ /**
126
+ * Merge pinelabs MCP server into existing Codex TOML content.
127
+ * If [mcp_servers.pinelabs] already exists, replace the entire section.
128
+ * Otherwise, append the new section at the end.
129
+ */
130
+ function _mergeCodexToml(existingToml, newToml) {
131
+ // Match the pinelabs section: from [mcp_servers.pinelabs] to the next
132
+ // top-level section header or end of file
133
+ const sectionPattern = /\[mcp_servers\.pinelabs\][\s\S]*?(?=\n\[(?!mcp_servers\.pinelabs\.)|$)/;
134
+ if (sectionPattern.test(existingToml)) {
135
+ // Replace existing pinelabs section
136
+ return existingToml.replace(sectionPattern, newToml.trimEnd()).trimEnd() + "\n";
137
+ }
138
+ // Append new section
139
+ const separator = existingToml.endsWith("\n") ? "\n" : "\n\n";
140
+ return existingToml + separator + newToml;
141
+ }
142
+
143
+ function mergeConfig(existing, newConfig, client) {
144
+ const key = client === "vscode" ? "servers" : "mcpServers";
145
+ const merged = { ...existing };
146
+ merged[key] = { ...(existing[key] || {}), ...(newConfig[key] || {}) };
147
+ return merged;
148
+ }
149
+
150
+ async function setup(client, flags) {
151
+ if (!client || !SUPPORTED_CLIENTS.includes(client)) {
152
+ log(`\n Error: Unknown client '${client || "(none)"}'.`);
153
+ log(` Supported clients: ${SUPPORTED_CLIENTS.join(", ")}\n`);
154
+ process.exit(1);
155
+ return;
156
+ }
157
+
158
+ const storedConfig = loadStoredConfig();
159
+ if (!storedConfig || !storedConfig.client_id || !storedConfig.client_secret) {
160
+ log("\n Error: No credentials configured.");
161
+ log(" Run `pinelabs-mcp configure` first.\n");
162
+ process.exit(1);
163
+ return;
164
+ }
165
+
166
+ // Codex uses TOML format — handle separately
167
+ if (client === "codex") {
168
+ const tomlContent = _buildCodexToml(storedConfig, { local: !!flags.local });
169
+
170
+ if (flags.print) {
171
+ process.stdout.write(tomlContent);
172
+ return;
173
+ }
174
+
175
+ const configPath = _getClientConfigPath(client);
176
+
177
+ let finalContent = tomlContent;
178
+ if (fs.existsSync(configPath)) {
179
+ try {
180
+ const existing = fs.readFileSync(configPath, "utf-8");
181
+ finalContent = _mergeCodexToml(existing, tomlContent);
182
+
183
+ const backupPath = configPath + ".bak";
184
+ fs.copyFileSync(configPath, backupPath);
185
+ log(`\n Backup created: ${backupPath}`);
186
+ } catch {
187
+ // If we can't read existing, just overwrite
188
+ }
189
+ }
190
+
191
+ const dir = path.dirname(configPath);
192
+ fs.mkdirSync(dir, { recursive: true });
193
+
194
+ fs.writeFileSync(configPath, finalContent);
195
+
196
+ log(`\n Pine Labs MCP configured for ${client}`);
197
+ log(` Config written to: ${configPath}`);
198
+ log(`\n Restart ${client} to start using Pine Labs tools.\n`);
199
+ return;
200
+ }
201
+
202
+ // All other clients use JSON format
203
+ const mcpConfig = _buildMcpConfig(client, storedConfig, { local: !!flags.local });
204
+
205
+ if (flags.print) {
206
+ process.stdout.write(JSON.stringify(mcpConfig, null, 2) + "\n");
207
+ return;
208
+ }
209
+
210
+ const configPath = _getClientConfigPath(client);
211
+ if (!configPath) {
212
+ log(`\n Error: Cannot determine config path for '${client}'.\n`);
213
+ process.exit(1);
214
+ return;
215
+ }
216
+
217
+ let finalConfig = mcpConfig;
218
+ if (fs.existsSync(configPath)) {
219
+ try {
220
+ const existing = JSON.parse(fs.readFileSync(configPath, "utf-8"));
221
+ finalConfig = mergeConfig(existing, mcpConfig, client);
222
+
223
+ const backupPath = configPath + ".bak";
224
+ fs.copyFileSync(configPath, backupPath);
225
+ log(`\n Backup created: ${backupPath}`);
226
+ } catch {
227
+ // If we can't read/parse existing, just overwrite
228
+ }
229
+ }
230
+
231
+ const dir = path.dirname(configPath);
232
+ fs.mkdirSync(dir, { recursive: true });
233
+
234
+ fs.writeFileSync(configPath, JSON.stringify(finalConfig, null, 2) + "\n");
235
+
236
+ log(`\n Pine Labs MCP configured for ${client}`);
237
+ log(` Config written to: ${configPath}`);
238
+ log(`\n Restart ${client} to start using Pine Labs tools.\n`);
239
+ }
240
+
241
+ module.exports = { setup, _buildMcpConfig, _buildCodexToml, _mergeCodexToml, _getClientConfigPath, _getLocalCliPath, mergeConfig, SUPPORTED_CLIENTS };
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+
3
+ const { loadStoredConfig, getConfigPath } = require("../config-store.js");
4
+ const { ENDPOINTS } = require("../config.js");
5
+
6
+ function log(message) {
7
+ process.stderr.write(`${message}\n`);
8
+ }
9
+
10
+ function maskId(id) {
11
+ if (!id) return "(not set)";
12
+ if (id.length > 4) return id.substring(0, 4) + "***";
13
+ return id.substring(0, 1) + "***";
14
+ }
15
+
16
+ function maskSecret(secret) {
17
+ if (!secret) return "(not set)";
18
+ return "••••••••••••••••";
19
+ }
20
+
21
+ function resolveEndpoint(config) {
22
+ if (config.endpoint) return config.endpoint;
23
+ const env = config.environment || "prod";
24
+ return ENDPOINTS[env] || ENDPOINTS.prod;
25
+ }
26
+
27
+ function showStatus() {
28
+ const configPath = getConfigPath();
29
+ const config = loadStoredConfig();
30
+
31
+ log("\n Pine Labs MCP Configuration");
32
+ log(" ───────────────────────────");
33
+
34
+ if (!config) {
35
+ log(` Config file: ${configPath} (not found)`);
36
+ log("\n No configuration found. Run `pinelabs-mcp configure` to set up.\n");
37
+ return;
38
+ }
39
+
40
+ const endpoint = resolveEndpoint(config);
41
+
42
+ log(` Config file: ${configPath}`);
43
+ log(` Client ID: ${maskId(config.client_id)}`);
44
+ log(` Client Secret: ${maskSecret(config.client_secret)}`);
45
+ log(` Environment: ${config.environment || "prod"}`);
46
+ log(` Endpoint: ${endpoint}`);
47
+
48
+ try {
49
+ const version = require("../../package.json").version;
50
+ log(` Package version: ${version}`);
51
+ } catch {
52
+ // ignore
53
+ }
54
+
55
+ log("");
56
+ }
57
+
58
+ module.exports = { showStatus };
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+
3
+ const https = require("node:https");
4
+ const http = require("node:http");
5
+ const { loadStoredConfig, getConfigPath } = require("../config-store.js");
6
+ const { ENDPOINTS } = require("../config.js");
7
+
8
+ function log(message) {
9
+ process.stderr.write(`${message}\n`);
10
+ }
11
+
12
+ function maskId(id) {
13
+ if (!id) return "(not set)";
14
+ if (id.length > 4) return id.substring(0, 4) + "***";
15
+ return id.substring(0, 1) + "***";
16
+ }
17
+
18
+ function resolveEndpoint(config) {
19
+ if (config.endpoint) return config.endpoint;
20
+ const env = config.environment || "prod";
21
+ return ENDPOINTS[env] || ENDPOINTS.prod;
22
+ }
23
+
24
+ function probeEndpoint(endpoint, clientId, clientSecret) {
25
+ return new Promise((resolve) => {
26
+ const healthUrl = endpoint.replace(/\/mcp\/?$/, "/health");
27
+ const url = new URL(healthUrl);
28
+ const client = url.protocol === "https:" ? https : http;
29
+
30
+ const req = client.request(
31
+ {
32
+ hostname: url.hostname,
33
+ port: url.port || (url.protocol === "https:" ? 443 : 80),
34
+ path: url.pathname,
35
+ method: "GET",
36
+ headers: {
37
+ "X-Client-Id": clientId,
38
+ "X-Client-Secret": clientSecret,
39
+ },
40
+ timeout: 10000,
41
+ },
42
+ (res) => {
43
+ res.resume();
44
+ resolve({ status: res.statusCode, ok: res.statusCode >= 200 && res.statusCode < 400 });
45
+ }
46
+ );
47
+
48
+ req.on("error", () => resolve({ status: 0, ok: false }));
49
+ req.on("timeout", () => {
50
+ req.destroy();
51
+ resolve({ status: 0, ok: false });
52
+ });
53
+ req.end();
54
+ });
55
+ }
56
+
57
+ async function testConnection() {
58
+ log("\n Pine Labs MCP Connection Test");
59
+ log(" ─────────────────────────────");
60
+
61
+ const configPath = getConfigPath();
62
+ const config = loadStoredConfig();
63
+
64
+ if (!config) {
65
+ log(` Config file: ${configPath} (not found)`);
66
+ log("\n No configuration found. Run `pinelabs-mcp configure` first.\n");
67
+ process.exit(1);
68
+ return;
69
+ }
70
+
71
+ log(` Config file: ${configPath}`);
72
+
73
+ if (!config.client_id) {
74
+ log(" Client ID: (not set)");
75
+ log("\n Client ID is missing. Run `pinelabs-mcp configure` to set it.\n");
76
+ process.exit(1);
77
+ return;
78
+ }
79
+
80
+ log(` Client ID: ${maskId(config.client_id)}`);
81
+
82
+ if (!config.client_secret) {
83
+ log(" Client Secret: (not set)");
84
+ log("\n Client Secret is missing. Run `pinelabs-mcp configure` to set it.\n");
85
+ process.exit(1);
86
+ return;
87
+ }
88
+
89
+ const endpoint = resolveEndpoint(config);
90
+ log(` Environment: ${config.environment || "prod"}`);
91
+ log(` Endpoint: ${endpoint}`);
92
+
93
+ log("\n Testing connectivity...");
94
+
95
+ const result = await probeEndpoint(endpoint, config.client_id, config.client_secret);
96
+
97
+ if (result.ok) {
98
+ log(` Server reachable: (HTTP ${result.status})`);
99
+ log("\n Connection test passed. You're ready to go!\n");
100
+ } else if (result.status === 401 || result.status === 403) {
101
+ log(" Server reachable: yes");
102
+ log(` Authentication: failed (HTTP ${result.status})`);
103
+ log("\n Authentication failed. Verify your Client ID and Secret.\n");
104
+ process.exit(1);
105
+ } else if (result.status > 0) {
106
+ log(" Server reachable: yes");
107
+ log(` Server response: HTTP ${result.status}`);
108
+ log("\n Server is reachable but returned an unexpected status.\n");
109
+ process.exit(1);
110
+ } else {
111
+ log(" Server reachable: no");
112
+ log(`\n Cannot reach ${endpoint}`);
113
+ log(" Check your network connection and try again.\n");
114
+ process.exit(1);
115
+ }
116
+ }
117
+
118
+ module.exports = { testConnection };
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Persistent configuration store for pinelabs-mcp.
5
+ *
6
+ * Stores credentials and settings in ~/.pinelabs-mcp/config.json.
7
+ * Provides read/write with automatic directory creation and
8
+ * file permission hardening on Unix systems.
9
+ */
10
+
11
+ const fs = require("node:fs");
12
+ const path = require("node:path");
13
+ const os = require("node:os");
14
+
15
+ const CONFIG_DIR_NAME = ".pinelabs-mcp";
16
+ const CONFIG_FILE_NAME = "config.json";
17
+
18
+ let _configDirOverride = null;
19
+
20
+ function _setConfigDir(dir) {
21
+ _configDirOverride = dir;
22
+ }
23
+
24
+ function getConfigDir() {
25
+ if (_configDirOverride) return _configDirOverride;
26
+ if (process.env.PINELABS_MCP_CONFIG_DIR) return process.env.PINELABS_MCP_CONFIG_DIR;
27
+ return path.join(os.homedir(), CONFIG_DIR_NAME);
28
+ }
29
+
30
+ function getConfigPath() {
31
+ return path.join(getConfigDir(), CONFIG_FILE_NAME);
32
+ }
33
+
34
+ function loadStoredConfig() {
35
+ const configPath = getConfigPath();
36
+ try {
37
+ const raw = fs.readFileSync(configPath, "utf-8");
38
+ return JSON.parse(raw);
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+
44
+ function saveConfig(data) {
45
+ const dir = getConfigDir();
46
+ fs.mkdirSync(dir, { recursive: true });
47
+
48
+ const configPath = getConfigPath();
49
+ const content = JSON.stringify(data, null, 2) + "\n";
50
+ fs.writeFileSync(configPath, content, { mode: 0o600 });
51
+ }
52
+
53
+ module.exports = { loadStoredConfig, saveConfig, getConfigDir, getConfigPath, _setConfigDir };
package/src/config.js ADDED
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Configuration module for pinelabs-mcp.
5
+ *
6
+ * Reads credentials and endpoint configuration from environment variables.
7
+ * Validates that required credentials are present and resolves the correct
8
+ * MCP server endpoint based on the environment (prod/uat).
9
+ */
10
+
11
+ const ENDPOINTS = {
12
+ prod: "https://mcp.pluralpay.in/mcp",
13
+ uat: "https://pluralai.v2.pinepg.in/mcp",
14
+ };
15
+
16
+ /**
17
+ * Parse and validate configuration from environment variables.
18
+ *
19
+ * @returns {{ clientId: string, clientSecret: string, endpoint: string, debug: boolean }}
20
+ * @throws {Error} If required credentials are missing.
21
+ */
22
+ function loadConfig() {
23
+ let clientId = process.env.PINELABS_CLIENT_ID || "";
24
+ let clientSecret = process.env.PINELABS_CLIENT_SECRET || "";
25
+ let env = (process.env.PINELABS_ENV || "").toLowerCase();
26
+ const endpointOverride = process.env.PINELABS_MCP_ENDPOINT || "";
27
+ const debug =
28
+ process.env.DEBUG === "true" || process.env.DEBUG === "1" || false;
29
+
30
+ // Fall back to stored config file if env vars not set
31
+ if (!clientId || !clientSecret) {
32
+ try {
33
+ const { loadStoredConfig } = require("./config-store.js");
34
+ const stored = loadStoredConfig();
35
+ if (stored) {
36
+ if (!clientId) clientId = stored.client_id || "";
37
+ if (!clientSecret) clientSecret = stored.client_secret || "";
38
+ if (!env) env = (stored.environment || "prod").toLowerCase();
39
+ }
40
+ } catch {
41
+ // config-store not available, skip
42
+ }
43
+ }
44
+
45
+ if (!env) env = "prod";
46
+
47
+ const errors = [];
48
+
49
+ if (!clientId) {
50
+ errors.push("PINELABS_CLIENT_ID environment variable is required.");
51
+ }
52
+
53
+ if (!clientSecret) {
54
+ errors.push("PINELABS_CLIENT_SECRET environment variable is required.");
55
+ }
56
+
57
+ if (errors.length > 0) {
58
+ const message = [
59
+ "Error: Missing required credentials.\n",
60
+ ...errors.map((e) => ` - ${e}`),
61
+ "",
62
+ "Configure your MCP client with:",
63
+ ' "env": {',
64
+ ' "PINELABS_CLIENT_ID": "your_client_id",',
65
+ ' "PINELABS_CLIENT_SECRET": "your_client_secret"',
66
+ " }",
67
+ "",
68
+ "Or run: pinelabs-mcp configure --client-id=X --client-secret=Y",
69
+ "",
70
+ "Get your credentials from the Pine Labs developer portal.",
71
+ ].join("\n");
72
+
73
+ throw new Error(message);
74
+ }
75
+
76
+ let endpoint;
77
+ if (endpointOverride) {
78
+ endpoint = endpointOverride;
79
+ } else if (env === "uat") {
80
+ endpoint = ENDPOINTS.uat;
81
+ } else if (env === "prod") {
82
+ endpoint = ENDPOINTS.prod;
83
+ } else {
84
+ endpoint = ENDPOINTS.prod;
85
+ }
86
+
87
+ return { clientId, clientSecret, endpoint, debug };
88
+ }
89
+
90
+ module.exports = { loadConfig, ENDPOINTS };
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Minimal argument parser. Zero dependencies.
5
+ *
6
+ * Parses process.argv.slice(2) style arrays into:
7
+ * { command, positional, flags }
8
+ */
9
+
10
+ function parseArgs(argv) {
11
+ const result = { command: null, positional: [], flags: {} };
12
+
13
+ let commandParsed = false;
14
+
15
+ for (const arg of argv) {
16
+ if (arg.startsWith("--")) {
17
+ const eqIdx = arg.indexOf("=");
18
+ if (eqIdx !== -1) {
19
+ const key = arg.substring(2, eqIdx);
20
+ const value = arg.substring(eqIdx + 1);
21
+ result.flags[key] = value;
22
+ } else {
23
+ const key = arg.substring(2);
24
+ result.flags[key] = true;
25
+ }
26
+ } else if (!commandParsed) {
27
+ result.command = arg;
28
+ commandParsed = true;
29
+ } else {
30
+ result.positional.push(arg);
31
+ }
32
+ }
33
+
34
+ return result;
35
+ }
36
+
37
+ module.exports = { parseArgs };