@solongate/proxy 0.6.7 → 0.7.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/dist/index.js +214 -49
- package/dist/pull-push.js +180 -36
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -88,6 +88,7 @@ function parseArgs(argv) {
|
|
|
88
88
|
let upstreamUrl;
|
|
89
89
|
let upstreamTransport;
|
|
90
90
|
let port;
|
|
91
|
+
let policyId;
|
|
91
92
|
let separatorIndex = args.indexOf("--");
|
|
92
93
|
const flags = separatorIndex >= 0 ? args.slice(0, separatorIndex) : args;
|
|
93
94
|
const upstreamArgs = separatorIndex >= 0 ? args.slice(separatorIndex + 1) : [];
|
|
@@ -126,6 +127,10 @@ function parseArgs(argv) {
|
|
|
126
127
|
case "--port":
|
|
127
128
|
port = parseInt(flags[++i], 10);
|
|
128
129
|
break;
|
|
130
|
+
case "--policy-id":
|
|
131
|
+
case "--id":
|
|
132
|
+
policyId = flags[++i];
|
|
133
|
+
break;
|
|
129
134
|
}
|
|
130
135
|
}
|
|
131
136
|
if (apiKey && /^\$\{.+\}$/.test(apiKey)) {
|
|
@@ -173,7 +178,8 @@ function parseArgs(argv) {
|
|
|
173
178
|
apiKey: apiKey ?? fileConfig.apiKey,
|
|
174
179
|
apiUrl: apiUrl ?? fileConfig.apiUrl,
|
|
175
180
|
port: port ?? fileConfig.port,
|
|
176
|
-
policyPath: resolvePolicyPath(cfgPolicySource) ?? void 0
|
|
181
|
+
policyPath: resolvePolicyPath(cfgPolicySource) ?? void 0,
|
|
182
|
+
policyId: policyId ?? fileConfig.policyId
|
|
177
183
|
};
|
|
178
184
|
}
|
|
179
185
|
if (upstreamUrl) {
|
|
@@ -193,7 +199,8 @@ function parseArgs(argv) {
|
|
|
193
199
|
apiKey,
|
|
194
200
|
apiUrl,
|
|
195
201
|
port,
|
|
196
|
-
policyPath: resolvedPolicyPath ?? void 0
|
|
202
|
+
policyPath: resolvedPolicyPath ?? void 0,
|
|
203
|
+
policyId
|
|
197
204
|
};
|
|
198
205
|
}
|
|
199
206
|
if (upstreamArgs.length === 0) {
|
|
@@ -217,7 +224,8 @@ function parseArgs(argv) {
|
|
|
217
224
|
apiKey,
|
|
218
225
|
apiUrl,
|
|
219
226
|
port,
|
|
220
|
-
policyPath: resolvedPolicyPath ?? void 0
|
|
227
|
+
policyPath: resolvedPolicyPath ?? void 0,
|
|
228
|
+
policyId
|
|
221
229
|
};
|
|
222
230
|
}
|
|
223
231
|
function resolvePolicyPath(source) {
|
|
@@ -2053,7 +2061,7 @@ function parseCliArgs() {
|
|
|
2053
2061
|
}
|
|
2054
2062
|
}
|
|
2055
2063
|
if (!apiKey) {
|
|
2056
|
-
log5("ERROR: API key not found.");
|
|
2064
|
+
log5(red("ERROR: API key not found."));
|
|
2057
2065
|
log5("");
|
|
2058
2066
|
log5("Set it in .env file:");
|
|
2059
2067
|
log5(" SOLONGATE_API_KEY=sg_live_...");
|
|
@@ -2063,53 +2071,169 @@ function parseCliArgs() {
|
|
|
2063
2071
|
process.exit(1);
|
|
2064
2072
|
}
|
|
2065
2073
|
if (!apiKey.startsWith("sg_live_")) {
|
|
2066
|
-
log5("ERROR: Pull/push requires a live API key (sg_live_...).");
|
|
2074
|
+
log5(red("ERROR: Pull/push/list requires a live API key (sg_live_...)."));
|
|
2067
2075
|
process.exit(1);
|
|
2068
2076
|
}
|
|
2069
2077
|
return { command, apiKey, file: resolve5(file), policyId };
|
|
2070
2078
|
}
|
|
2071
2079
|
async function listPolicies(apiKey) {
|
|
2072
|
-
const res = await fetch(
|
|
2080
|
+
const res = await fetch(`${API_URL}/api/v1/policies`, {
|
|
2073
2081
|
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
2074
2082
|
});
|
|
2075
2083
|
if (!res.ok) throw new Error(`Failed to list policies (${res.status})`);
|
|
2076
2084
|
const data = await res.json();
|
|
2077
2085
|
return data.policies ?? [];
|
|
2078
2086
|
}
|
|
2087
|
+
async function list(apiKey, policyId) {
|
|
2088
|
+
const policies = await listPolicies(apiKey);
|
|
2089
|
+
if (policies.length === 0) {
|
|
2090
|
+
log5(yellow("No policies found. Create one in the dashboard first."));
|
|
2091
|
+
log5(dim(" https://dashboard.solongate.com/policies"));
|
|
2092
|
+
return;
|
|
2093
|
+
}
|
|
2094
|
+
if (policyId) {
|
|
2095
|
+
const match = policies.find((p) => p.id === policyId);
|
|
2096
|
+
if (!match) {
|
|
2097
|
+
log5(red(`Policy not found: ${policyId}`));
|
|
2098
|
+
log5("");
|
|
2099
|
+
log5("Available policies:");
|
|
2100
|
+
for (const p of policies) {
|
|
2101
|
+
log5(` ${dim("\u2022")} ${p.id}`);
|
|
2102
|
+
}
|
|
2103
|
+
process.exit(1);
|
|
2104
|
+
}
|
|
2105
|
+
const full = await fetchCloudPolicy(apiKey, API_URL, policyId);
|
|
2106
|
+
printPolicyDetail(full);
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
log5("");
|
|
2110
|
+
log5(bold(` Policies (${policies.length})`));
|
|
2111
|
+
log5(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
2112
|
+
log5("");
|
|
2113
|
+
for (const p of policies) {
|
|
2114
|
+
try {
|
|
2115
|
+
const full = await fetchCloudPolicy(apiKey, API_URL, p.id);
|
|
2116
|
+
printPolicySummary(p, full.rules);
|
|
2117
|
+
} catch {
|
|
2118
|
+
printPolicySummary(p, []);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
log5(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
2122
|
+
log5("");
|
|
2123
|
+
log5(` ${dim("View details:")} solongate-proxy list --policy-id <ID>`);
|
|
2124
|
+
log5(` ${dim("Pull policy:")} solongate-proxy pull --policy-id <ID>`);
|
|
2125
|
+
log5(` ${dim("Push policy:")} solongate-proxy push --policy-id <ID>`);
|
|
2126
|
+
log5("");
|
|
2127
|
+
}
|
|
2128
|
+
function printPolicySummary(p, rules) {
|
|
2129
|
+
const ruleCount = rules.length;
|
|
2130
|
+
const allowCount = rules.filter((r) => r.effect === "ALLOW").length;
|
|
2131
|
+
const denyCount = rules.filter((r) => r.effect === "DENY").length;
|
|
2132
|
+
log5(` ${cyan(p.id)}`);
|
|
2133
|
+
log5(` ${bold(p.name)} ${dim(`v${p.version ?? "?"}`)}`);
|
|
2134
|
+
log5(` ${dim("Rules:")} ${ruleCount} ${green(`${allowCount} ALLOW`)} ${red(`${denyCount} DENY`)}`);
|
|
2135
|
+
if (p.created_at) {
|
|
2136
|
+
log5(` ${dim("Updated:")} ${new Date(p.created_at).toLocaleString()}`);
|
|
2137
|
+
}
|
|
2138
|
+
log5("");
|
|
2139
|
+
}
|
|
2140
|
+
function printPolicyDetail(policy) {
|
|
2141
|
+
log5("");
|
|
2142
|
+
log5(bold(` ${policy.name}`));
|
|
2143
|
+
log5(` ${dim("ID:")} ${cyan(policy.id)} ${dim("Version:")} ${policy.version} ${dim("Rules:")} ${policy.rules.length}`);
|
|
2144
|
+
log5("");
|
|
2145
|
+
if (policy.rules.length === 0) {
|
|
2146
|
+
log5(yellow(" No rules defined."));
|
|
2147
|
+
log5("");
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
2150
|
+
log5(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
2151
|
+
for (const rule of policy.rules) {
|
|
2152
|
+
const effectColor = rule.effect === "ALLOW" ? green : red;
|
|
2153
|
+
log5("");
|
|
2154
|
+
log5(` ${effectColor(rule.effect.padEnd(5))} ${bold(rule.toolPattern)} ${dim(`P:${rule.priority}`)}`);
|
|
2155
|
+
if (rule.description) {
|
|
2156
|
+
log5(` ${dim(rule.description)}`);
|
|
2157
|
+
}
|
|
2158
|
+
log5(` ${dim(`${rule.permission} trust:${rule.minimumTrustLevel || "UNTRUSTED"}`)}`);
|
|
2159
|
+
if (rule.pathConstraints) {
|
|
2160
|
+
const pc = rule.pathConstraints;
|
|
2161
|
+
if (pc.rootDirectory) log5(` ${magenta("ROOT")} ${pc.rootDirectory}`);
|
|
2162
|
+
if (pc.allowed?.length) log5(` ${green("PATHS")} ${pc.allowed.join(", ")}`);
|
|
2163
|
+
if (pc.denied?.length) log5(` ${red("DENY")} ${pc.denied.join(", ")}`);
|
|
2164
|
+
}
|
|
2165
|
+
if (rule.commandConstraints) {
|
|
2166
|
+
const cc = rule.commandConstraints;
|
|
2167
|
+
if (cc.allowed?.length) log5(` ${green("CMDS")} ${cc.allowed.join(", ")}`);
|
|
2168
|
+
if (cc.denied?.length) log5(` ${red("DENY")} ${cc.denied.join(", ")}`);
|
|
2169
|
+
}
|
|
2170
|
+
if (rule.filenameConstraints) {
|
|
2171
|
+
const fc = rule.filenameConstraints;
|
|
2172
|
+
if (fc.allowed?.length) log5(` ${green("FILES")} ${fc.allowed.join(", ")}`);
|
|
2173
|
+
if (fc.denied?.length) log5(` ${red("DENY")} ${fc.denied.join(", ")}`);
|
|
2174
|
+
}
|
|
2175
|
+
if (rule.urlConstraints) {
|
|
2176
|
+
const uc = rule.urlConstraints;
|
|
2177
|
+
if (uc.allowed?.length) log5(` ${green("URLS")} ${uc.allowed.join(", ")}`);
|
|
2178
|
+
if (uc.denied?.length) log5(` ${red("DENY")} ${uc.denied.join(", ")}`);
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
log5("");
|
|
2182
|
+
log5(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
2183
|
+
log5("");
|
|
2184
|
+
}
|
|
2079
2185
|
async function pull(apiKey, file, policyId) {
|
|
2080
|
-
const apiUrl = "https://api.solongate.com";
|
|
2081
2186
|
if (!policyId) {
|
|
2082
2187
|
const policies = await listPolicies(apiKey);
|
|
2083
2188
|
if (policies.length === 0) {
|
|
2084
|
-
log5("No policies found. Create one in the dashboard first.");
|
|
2189
|
+
log5(red("No policies found. Create one in the dashboard first."));
|
|
2085
2190
|
process.exit(1);
|
|
2086
2191
|
}
|
|
2087
|
-
if (policies.length
|
|
2088
|
-
|
|
2089
|
-
`);
|
|
2192
|
+
if (policies.length === 1) {
|
|
2193
|
+
policyId = policies[0].id;
|
|
2194
|
+
log5(dim(`Auto-selecting only policy: ${policyId}`));
|
|
2195
|
+
} else {
|
|
2196
|
+
log5(yellow(`Found ${policies.length} policies:`));
|
|
2197
|
+
log5("");
|
|
2090
2198
|
for (const p of policies) {
|
|
2091
|
-
log5(` ${p.id} ${p.name} (${
|
|
2199
|
+
log5(` ${cyan(p.id)} ${p.name} ${dim(`v${p.version ?? "?"}`)}`);
|
|
2092
2200
|
}
|
|
2093
2201
|
log5("");
|
|
2094
2202
|
log5("Use --policy-id <ID> to specify which one to pull.");
|
|
2095
2203
|
process.exit(1);
|
|
2096
2204
|
}
|
|
2097
2205
|
}
|
|
2098
|
-
log5(`Pulling
|
|
2099
|
-
const policy = await fetchCloudPolicy(apiKey,
|
|
2100
|
-
const
|
|
2206
|
+
log5(`Pulling ${cyan(policyId)} from dashboard...`);
|
|
2207
|
+
const policy = await fetchCloudPolicy(apiKey, API_URL, policyId);
|
|
2208
|
+
const { id: _id, ...policyWithoutId } = policy;
|
|
2209
|
+
const json = JSON.stringify(policyWithoutId, null, 2) + "\n";
|
|
2101
2210
|
writeFileSync5(file, json, "utf-8");
|
|
2102
|
-
log5(`Saved: ${file}`);
|
|
2103
|
-
log5(` Name: ${policy.name}`);
|
|
2104
|
-
log5(` Version: ${policy.version}`);
|
|
2105
|
-
log5(` Rules: ${policy.rules.length}`);
|
|
2106
2211
|
log5("");
|
|
2107
|
-
log5("
|
|
2212
|
+
log5(green(" Saved to: ") + file);
|
|
2213
|
+
log5(` ${dim("Name:")} ${policy.name}`);
|
|
2214
|
+
log5(` ${dim("Version:")} ${policy.version}`);
|
|
2215
|
+
log5(` ${dim("Rules:")} ${policy.rules.length}`);
|
|
2216
|
+
log5("");
|
|
2217
|
+
log5(dim("The policy file does not contain an ID."));
|
|
2218
|
+
log5(dim("Use --policy-id to specify the target when pushing/pulling."));
|
|
2219
|
+
log5("");
|
|
2108
2220
|
}
|
|
2109
|
-
async function push(apiKey, file) {
|
|
2110
|
-
const apiUrl = "https://api.solongate.com";
|
|
2221
|
+
async function push(apiKey, file, policyId) {
|
|
2111
2222
|
if (!existsSync6(file)) {
|
|
2112
|
-
log5(`ERROR: File not found: ${file}`);
|
|
2223
|
+
log5(red(`ERROR: File not found: ${file}`));
|
|
2224
|
+
process.exit(1);
|
|
2225
|
+
}
|
|
2226
|
+
if (!policyId) {
|
|
2227
|
+
log5(red("ERROR: --policy-id is required for push."));
|
|
2228
|
+
log5("");
|
|
2229
|
+
log5("This determines which cloud policy to update.");
|
|
2230
|
+
log5("");
|
|
2231
|
+
log5("Usage:");
|
|
2232
|
+
log5(" solongate-proxy push --policy-id my-policy");
|
|
2233
|
+
log5(" solongate-proxy push --policy-id my-policy --file custom.json");
|
|
2234
|
+
log5("");
|
|
2235
|
+
log5("List your policies:");
|
|
2236
|
+
log5(" solongate-proxy list");
|
|
2113
2237
|
process.exit(1);
|
|
2114
2238
|
}
|
|
2115
2239
|
const content = readFileSync5(file, "utf-8");
|
|
@@ -2117,21 +2241,26 @@ async function push(apiKey, file) {
|
|
|
2117
2241
|
try {
|
|
2118
2242
|
policy = JSON.parse(content);
|
|
2119
2243
|
} catch {
|
|
2120
|
-
log5(`ERROR: Invalid JSON in ${file}`);
|
|
2244
|
+
log5(red(`ERROR: Invalid JSON in ${file}`));
|
|
2121
2245
|
process.exit(1);
|
|
2122
2246
|
}
|
|
2123
|
-
log5(`Pushing
|
|
2124
|
-
log5(` File:
|
|
2125
|
-
log5(` Name:
|
|
2126
|
-
log5(` Rules: ${(policy.rules || []).length}`);
|
|
2127
|
-
const
|
|
2128
|
-
|
|
2247
|
+
log5(`Pushing to ${cyan(policyId)}...`);
|
|
2248
|
+
log5(` ${dim("File:")} ${file}`);
|
|
2249
|
+
log5(` ${dim("Name:")} ${policy.name || "Unnamed"}`);
|
|
2250
|
+
log5(` ${dim("Rules:")} ${(policy.rules || []).length}`);
|
|
2251
|
+
const checkRes = await fetch(`${API_URL}/api/v1/policies/${policyId}`, {
|
|
2252
|
+
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
2253
|
+
});
|
|
2254
|
+
const method = checkRes.ok ? "PUT" : "POST";
|
|
2255
|
+
const url = checkRes.ok ? `${API_URL}/api/v1/policies/${policyId}` : `${API_URL}/api/v1/policies`;
|
|
2256
|
+
const res = await fetch(url, {
|
|
2257
|
+
method,
|
|
2129
2258
|
headers: {
|
|
2130
2259
|
"Authorization": `Bearer ${apiKey}`,
|
|
2131
2260
|
"Content-Type": "application/json"
|
|
2132
2261
|
},
|
|
2133
2262
|
body: JSON.stringify({
|
|
2134
|
-
id:
|
|
2263
|
+
id: policyId,
|
|
2135
2264
|
name: policy.name || "Local Policy",
|
|
2136
2265
|
description: policy.description || "Pushed from local file",
|
|
2137
2266
|
version: policy.version || 1,
|
|
@@ -2140,13 +2269,15 @@ async function push(apiKey, file) {
|
|
|
2140
2269
|
});
|
|
2141
2270
|
if (!res.ok) {
|
|
2142
2271
|
const body = await res.text().catch(() => "");
|
|
2143
|
-
log5(`ERROR: Push failed (${res.status}): ${body}`);
|
|
2272
|
+
log5(red(`ERROR: Push failed (${res.status}): ${body}`));
|
|
2144
2273
|
process.exit(1);
|
|
2145
2274
|
}
|
|
2146
2275
|
const data = await res.json();
|
|
2147
|
-
log5(` Cloud version: ${data._version ?? "created"}`);
|
|
2148
2276
|
log5("");
|
|
2149
|
-
log5(
|
|
2277
|
+
log5(green(` Pushed to cloud: v${data._version ?? "created"}`));
|
|
2278
|
+
log5(` ${dim("Policy ID:")} ${policyId}`);
|
|
2279
|
+
log5(` ${dim("Method:")} ${method === "PUT" ? "Updated existing" : "Created new"}`);
|
|
2280
|
+
log5("");
|
|
2150
2281
|
}
|
|
2151
2282
|
async function main4() {
|
|
2152
2283
|
const { command, apiKey, file, policyId } = parseCliArgs();
|
|
@@ -2154,24 +2285,45 @@ async function main4() {
|
|
|
2154
2285
|
if (command === "pull") {
|
|
2155
2286
|
await pull(apiKey, file, policyId);
|
|
2156
2287
|
} else if (command === "push") {
|
|
2157
|
-
await push(apiKey, file);
|
|
2288
|
+
await push(apiKey, file, policyId);
|
|
2289
|
+
} else if (command === "list" || command === "ls") {
|
|
2290
|
+
await list(apiKey, policyId);
|
|
2158
2291
|
} else {
|
|
2159
|
-
log5(`Unknown command: ${command}`);
|
|
2160
|
-
log5("
|
|
2292
|
+
log5(red(`Unknown command: ${command}`));
|
|
2293
|
+
log5("");
|
|
2294
|
+
log5(bold("Usage:"));
|
|
2295
|
+
log5(" solongate-proxy list List all policies");
|
|
2296
|
+
log5(" solongate-proxy list --policy-id <ID> Show policy details");
|
|
2297
|
+
log5(" solongate-proxy pull --policy-id <ID> Pull policy to local file");
|
|
2298
|
+
log5(" solongate-proxy push --policy-id <ID> Push local file to cloud");
|
|
2299
|
+
log5("");
|
|
2300
|
+
log5(bold("Flags:"));
|
|
2301
|
+
log5(" --policy-id, --id <ID> Cloud policy ID (required for push)");
|
|
2302
|
+
log5(" --file, -f <path> Local file path (default: policy.json)");
|
|
2303
|
+
log5(" --api-key <key> API key (or set SOLONGATE_API_KEY)");
|
|
2304
|
+
log5("");
|
|
2161
2305
|
process.exit(1);
|
|
2162
2306
|
}
|
|
2163
2307
|
} catch (err) {
|
|
2164
|
-
log5(`ERROR: ${err instanceof Error ? err.message : String(err)}`);
|
|
2308
|
+
log5(red(`ERROR: ${err instanceof Error ? err.message : String(err)}`));
|
|
2165
2309
|
process.exit(1);
|
|
2166
2310
|
}
|
|
2167
2311
|
}
|
|
2168
|
-
var log5;
|
|
2312
|
+
var log5, dim, bold, green, red, yellow, cyan, magenta, API_URL;
|
|
2169
2313
|
var init_pull_push = __esm({
|
|
2170
2314
|
"src/pull-push.ts"() {
|
|
2171
2315
|
"use strict";
|
|
2172
2316
|
init_config();
|
|
2173
|
-
log5 = (...args) => process.stderr.write(
|
|
2317
|
+
log5 = (...args) => process.stderr.write(`${args.map(String).join(" ")}
|
|
2174
2318
|
`);
|
|
2319
|
+
dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
2320
|
+
bold = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
2321
|
+
green = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
2322
|
+
red = (s) => `\x1B[31m${s}\x1B[0m`;
|
|
2323
|
+
yellow = (s) => `\x1B[33m${s}\x1B[0m`;
|
|
2324
|
+
cyan = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
2325
|
+
magenta = (s) => `\x1B[35m${s}\x1B[0m`;
|
|
2326
|
+
API_URL = "https://api.solongate.com";
|
|
2175
2327
|
main4();
|
|
2176
2328
|
}
|
|
2177
2329
|
});
|
|
@@ -3736,10 +3888,13 @@ var PolicySyncManager = class {
|
|
|
3736
3888
|
pollTimer = null;
|
|
3737
3889
|
watcher = null;
|
|
3738
3890
|
isLiveKey;
|
|
3891
|
+
/** The cloud policy ID from --policy-id flag. This is the ONLY source of truth for which cloud policy to use. */
|
|
3892
|
+
policyId;
|
|
3739
3893
|
constructor(opts) {
|
|
3740
3894
|
this.localPath = opts.localPath;
|
|
3741
3895
|
this.apiKey = opts.apiKey;
|
|
3742
3896
|
this.apiUrl = opts.apiUrl;
|
|
3897
|
+
this.policyId = opts.policyId;
|
|
3743
3898
|
this.pollIntervalMs = opts.pollIntervalMs ?? 6e4;
|
|
3744
3899
|
this.onPolicyUpdate = opts.onPolicyUpdate;
|
|
3745
3900
|
this.currentPolicy = opts.initialPolicy;
|
|
@@ -3841,7 +3996,7 @@ var PolicySyncManager = class {
|
|
|
3841
3996
|
*/
|
|
3842
3997
|
async onPollTick() {
|
|
3843
3998
|
try {
|
|
3844
|
-
const cloudPolicy = await fetchCloudPolicy(this.apiKey, this.apiUrl);
|
|
3999
|
+
const cloudPolicy = await fetchCloudPolicy(this.apiKey, this.apiUrl, this.policyId);
|
|
3845
4000
|
const cloudVer = cloudPolicy.version ?? 0;
|
|
3846
4001
|
if (cloudVer <= this.localVersion && this.policiesEqual(cloudPolicy, this.currentPolicy)) {
|
|
3847
4002
|
return;
|
|
@@ -3862,17 +4017,24 @@ var PolicySyncManager = class {
|
|
|
3862
4017
|
}
|
|
3863
4018
|
/**
|
|
3864
4019
|
* Push policy to cloud API.
|
|
4020
|
+
* Uses this.policyId (from --policy-id CLI flag) as the cloud policy ID.
|
|
4021
|
+
* Falls back to policy.id from local file only if --policy-id was not set.
|
|
3865
4022
|
*/
|
|
3866
4023
|
async pushToCloud(policy) {
|
|
3867
|
-
const
|
|
4024
|
+
const cloudId = this.policyId || policy.id || "default";
|
|
4025
|
+
const existingRes = await fetch(`${this.apiUrl}/api/v1/policies/${cloudId}`, {
|
|
4026
|
+
headers: { "Authorization": `Bearer ${this.apiKey}` }
|
|
4027
|
+
});
|
|
4028
|
+
const method = existingRes.ok ? "PUT" : "POST";
|
|
4029
|
+
const url = existingRes.ok ? `${this.apiUrl}/api/v1/policies/${cloudId}` : `${this.apiUrl}/api/v1/policies`;
|
|
3868
4030
|
const res = await fetch(url, {
|
|
3869
|
-
method
|
|
4031
|
+
method,
|
|
3870
4032
|
headers: {
|
|
3871
4033
|
"Authorization": `Bearer ${this.apiKey}`,
|
|
3872
4034
|
"Content-Type": "application/json"
|
|
3873
4035
|
},
|
|
3874
4036
|
body: JSON.stringify({
|
|
3875
|
-
id:
|
|
4037
|
+
id: cloudId,
|
|
3876
4038
|
name: policy.name || "Default Policy",
|
|
3877
4039
|
description: policy.description || "Synced from proxy",
|
|
3878
4040
|
version: policy.version || 1,
|
|
@@ -3888,12 +4050,14 @@ var PolicySyncManager = class {
|
|
|
3888
4050
|
}
|
|
3889
4051
|
/**
|
|
3890
4052
|
* Write policy to local file (with loop prevention).
|
|
4053
|
+
* Does NOT write the 'id' field — cloud ID is managed by --policy-id flag.
|
|
3891
4054
|
*/
|
|
3892
4055
|
writeToFile(policy) {
|
|
3893
4056
|
if (!this.localPath) return;
|
|
3894
4057
|
this.skipNextWatch = true;
|
|
3895
4058
|
try {
|
|
3896
|
-
const
|
|
4059
|
+
const { id: _id, ...rest } = policy;
|
|
4060
|
+
const json = JSON.stringify(rest, null, 2) + "\n";
|
|
3897
4061
|
writeFileSync(this.localPath, json, "utf-8");
|
|
3898
4062
|
} catch (err) {
|
|
3899
4063
|
log(`File write error: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3901,10 +4065,10 @@ var PolicySyncManager = class {
|
|
|
3901
4065
|
}
|
|
3902
4066
|
}
|
|
3903
4067
|
/**
|
|
3904
|
-
* Compare two policies by rules content (ignoring timestamps).
|
|
4068
|
+
* Compare two policies by rules content (ignoring timestamps and id).
|
|
3905
4069
|
*/
|
|
3906
4070
|
policiesEqual(a, b) {
|
|
3907
|
-
if (a.
|
|
4071
|
+
if (a.name !== b.name || a.rules.length !== b.rules.length) return false;
|
|
3908
4072
|
return JSON.stringify(a.rules) === JSON.stringify(b.rules);
|
|
3909
4073
|
}
|
|
3910
4074
|
};
|
|
@@ -3995,7 +4159,7 @@ var SolonGateProxy = class {
|
|
|
3995
4159
|
}
|
|
3996
4160
|
if (!this.config.apiKey.startsWith("sg_test_")) {
|
|
3997
4161
|
try {
|
|
3998
|
-
const cloudPolicy = await fetchCloudPolicy(this.config.apiKey, apiUrl);
|
|
4162
|
+
const cloudPolicy = await fetchCloudPolicy(this.config.apiKey, apiUrl, this.config.policyId);
|
|
3999
4163
|
this.config.policy = cloudPolicy;
|
|
4000
4164
|
log2(`Loaded cloud policy: ${cloudPolicy.name} (${cloudPolicy.rules.length} rules)`);
|
|
4001
4165
|
} catch (err) {
|
|
@@ -4310,6 +4474,7 @@ var SolonGateProxy = class {
|
|
|
4310
4474
|
apiUrl,
|
|
4311
4475
|
pollIntervalMs: 6e4,
|
|
4312
4476
|
initialPolicy: this.config.policy,
|
|
4477
|
+
policyId: this.config.policyId,
|
|
4313
4478
|
onPolicyUpdate: (policy) => {
|
|
4314
4479
|
this.config.policy = policy;
|
|
4315
4480
|
this.gate.loadPolicy(policy);
|
|
@@ -4385,7 +4550,7 @@ async function main5() {
|
|
|
4385
4550
|
await Promise.resolve().then(() => (init_create(), create_exports));
|
|
4386
4551
|
return;
|
|
4387
4552
|
}
|
|
4388
|
-
if (subcommand === "pull" || subcommand === "push") {
|
|
4553
|
+
if (subcommand === "pull" || subcommand === "push" || subcommand === "list" || subcommand === "ls") {
|
|
4389
4554
|
await Promise.resolve().then(() => (init_pull_push(), pull_push_exports));
|
|
4390
4555
|
return;
|
|
4391
4556
|
}
|
package/dist/pull-push.js
CHANGED
|
@@ -44,8 +44,15 @@ async function fetchCloudPolicy(apiKey, apiUrl, policyId) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// src/pull-push.ts
|
|
47
|
-
var log = (...args) => process.stderr.write(
|
|
47
|
+
var log = (...args) => process.stderr.write(`${args.map(String).join(" ")}
|
|
48
48
|
`);
|
|
49
|
+
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
50
|
+
var bold = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
51
|
+
var green = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
52
|
+
var red = (s) => `\x1B[31m${s}\x1B[0m`;
|
|
53
|
+
var yellow = (s) => `\x1B[33m${s}\x1B[0m`;
|
|
54
|
+
var cyan = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
55
|
+
var magenta = (s) => `\x1B[35m${s}\x1B[0m`;
|
|
49
56
|
function loadEnv() {
|
|
50
57
|
if (process.env.SOLONGATE_API_KEY) return;
|
|
51
58
|
const envPath = resolve2(".env");
|
|
@@ -93,7 +100,7 @@ function parseCliArgs() {
|
|
|
93
100
|
}
|
|
94
101
|
}
|
|
95
102
|
if (!apiKey) {
|
|
96
|
-
log("ERROR: API key not found.");
|
|
103
|
+
log(red("ERROR: API key not found."));
|
|
97
104
|
log("");
|
|
98
105
|
log("Set it in .env file:");
|
|
99
106
|
log(" SOLONGATE_API_KEY=sg_live_...");
|
|
@@ -103,53 +110,170 @@ function parseCliArgs() {
|
|
|
103
110
|
process.exit(1);
|
|
104
111
|
}
|
|
105
112
|
if (!apiKey.startsWith("sg_live_")) {
|
|
106
|
-
log("ERROR: Pull/push requires a live API key (sg_live_...).");
|
|
113
|
+
log(red("ERROR: Pull/push/list requires a live API key (sg_live_...)."));
|
|
107
114
|
process.exit(1);
|
|
108
115
|
}
|
|
109
116
|
return { command, apiKey, file: resolve2(file), policyId };
|
|
110
117
|
}
|
|
118
|
+
var API_URL = "https://api.solongate.com";
|
|
111
119
|
async function listPolicies(apiKey) {
|
|
112
|
-
const res = await fetch(
|
|
120
|
+
const res = await fetch(`${API_URL}/api/v1/policies`, {
|
|
113
121
|
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
114
122
|
});
|
|
115
123
|
if (!res.ok) throw new Error(`Failed to list policies (${res.status})`);
|
|
116
124
|
const data = await res.json();
|
|
117
125
|
return data.policies ?? [];
|
|
118
126
|
}
|
|
127
|
+
async function list(apiKey, policyId) {
|
|
128
|
+
const policies = await listPolicies(apiKey);
|
|
129
|
+
if (policies.length === 0) {
|
|
130
|
+
log(yellow("No policies found. Create one in the dashboard first."));
|
|
131
|
+
log(dim(" https://dashboard.solongate.com/policies"));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (policyId) {
|
|
135
|
+
const match = policies.find((p) => p.id === policyId);
|
|
136
|
+
if (!match) {
|
|
137
|
+
log(red(`Policy not found: ${policyId}`));
|
|
138
|
+
log("");
|
|
139
|
+
log("Available policies:");
|
|
140
|
+
for (const p of policies) {
|
|
141
|
+
log(` ${dim("\u2022")} ${p.id}`);
|
|
142
|
+
}
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
const full = await fetchCloudPolicy(apiKey, API_URL, policyId);
|
|
146
|
+
printPolicyDetail(full);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
log("");
|
|
150
|
+
log(bold(` Policies (${policies.length})`));
|
|
151
|
+
log(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
152
|
+
log("");
|
|
153
|
+
for (const p of policies) {
|
|
154
|
+
try {
|
|
155
|
+
const full = await fetchCloudPolicy(apiKey, API_URL, p.id);
|
|
156
|
+
printPolicySummary(p, full.rules);
|
|
157
|
+
} catch {
|
|
158
|
+
printPolicySummary(p, []);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
log(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
162
|
+
log("");
|
|
163
|
+
log(` ${dim("View details:")} solongate-proxy list --policy-id <ID>`);
|
|
164
|
+
log(` ${dim("Pull policy:")} solongate-proxy pull --policy-id <ID>`);
|
|
165
|
+
log(` ${dim("Push policy:")} solongate-proxy push --policy-id <ID>`);
|
|
166
|
+
log("");
|
|
167
|
+
}
|
|
168
|
+
function printPolicySummary(p, rules) {
|
|
169
|
+
const ruleCount = rules.length;
|
|
170
|
+
const allowCount = rules.filter((r) => r.effect === "ALLOW").length;
|
|
171
|
+
const denyCount = rules.filter((r) => r.effect === "DENY").length;
|
|
172
|
+
log(` ${cyan(p.id)}`);
|
|
173
|
+
log(` ${bold(p.name)} ${dim(`v${p.version ?? "?"}`)}`);
|
|
174
|
+
log(` ${dim("Rules:")} ${ruleCount} ${green(`${allowCount} ALLOW`)} ${red(`${denyCount} DENY`)}`);
|
|
175
|
+
if (p.created_at) {
|
|
176
|
+
log(` ${dim("Updated:")} ${new Date(p.created_at).toLocaleString()}`);
|
|
177
|
+
}
|
|
178
|
+
log("");
|
|
179
|
+
}
|
|
180
|
+
function printPolicyDetail(policy) {
|
|
181
|
+
log("");
|
|
182
|
+
log(bold(` ${policy.name}`));
|
|
183
|
+
log(` ${dim("ID:")} ${cyan(policy.id)} ${dim("Version:")} ${policy.version} ${dim("Rules:")} ${policy.rules.length}`);
|
|
184
|
+
log("");
|
|
185
|
+
if (policy.rules.length === 0) {
|
|
186
|
+
log(yellow(" No rules defined."));
|
|
187
|
+
log("");
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
log(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
191
|
+
for (const rule of policy.rules) {
|
|
192
|
+
const effectColor = rule.effect === "ALLOW" ? green : red;
|
|
193
|
+
log("");
|
|
194
|
+
log(` ${effectColor(rule.effect.padEnd(5))} ${bold(rule.toolPattern)} ${dim(`P:${rule.priority}`)}`);
|
|
195
|
+
if (rule.description) {
|
|
196
|
+
log(` ${dim(rule.description)}`);
|
|
197
|
+
}
|
|
198
|
+
log(` ${dim(`${rule.permission} trust:${rule.minimumTrustLevel || "UNTRUSTED"}`)}`);
|
|
199
|
+
if (rule.pathConstraints) {
|
|
200
|
+
const pc = rule.pathConstraints;
|
|
201
|
+
if (pc.rootDirectory) log(` ${magenta("ROOT")} ${pc.rootDirectory}`);
|
|
202
|
+
if (pc.allowed?.length) log(` ${green("PATHS")} ${pc.allowed.join(", ")}`);
|
|
203
|
+
if (pc.denied?.length) log(` ${red("DENY")} ${pc.denied.join(", ")}`);
|
|
204
|
+
}
|
|
205
|
+
if (rule.commandConstraints) {
|
|
206
|
+
const cc = rule.commandConstraints;
|
|
207
|
+
if (cc.allowed?.length) log(` ${green("CMDS")} ${cc.allowed.join(", ")}`);
|
|
208
|
+
if (cc.denied?.length) log(` ${red("DENY")} ${cc.denied.join(", ")}`);
|
|
209
|
+
}
|
|
210
|
+
if (rule.filenameConstraints) {
|
|
211
|
+
const fc = rule.filenameConstraints;
|
|
212
|
+
if (fc.allowed?.length) log(` ${green("FILES")} ${fc.allowed.join(", ")}`);
|
|
213
|
+
if (fc.denied?.length) log(` ${red("DENY")} ${fc.denied.join(", ")}`);
|
|
214
|
+
}
|
|
215
|
+
if (rule.urlConstraints) {
|
|
216
|
+
const uc = rule.urlConstraints;
|
|
217
|
+
if (uc.allowed?.length) log(` ${green("URLS")} ${uc.allowed.join(", ")}`);
|
|
218
|
+
if (uc.denied?.length) log(` ${red("DENY")} ${uc.denied.join(", ")}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
log("");
|
|
222
|
+
log(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
223
|
+
log("");
|
|
224
|
+
}
|
|
119
225
|
async function pull(apiKey, file, policyId) {
|
|
120
|
-
const apiUrl = "https://api.solongate.com";
|
|
121
226
|
if (!policyId) {
|
|
122
227
|
const policies = await listPolicies(apiKey);
|
|
123
228
|
if (policies.length === 0) {
|
|
124
|
-
log("No policies found. Create one in the dashboard first.");
|
|
229
|
+
log(red("No policies found. Create one in the dashboard first."));
|
|
125
230
|
process.exit(1);
|
|
126
231
|
}
|
|
127
|
-
if (policies.length
|
|
128
|
-
|
|
129
|
-
`);
|
|
232
|
+
if (policies.length === 1) {
|
|
233
|
+
policyId = policies[0].id;
|
|
234
|
+
log(dim(`Auto-selecting only policy: ${policyId}`));
|
|
235
|
+
} else {
|
|
236
|
+
log(yellow(`Found ${policies.length} policies:`));
|
|
237
|
+
log("");
|
|
130
238
|
for (const p of policies) {
|
|
131
|
-
log(` ${p.id} ${p.name} (${
|
|
239
|
+
log(` ${cyan(p.id)} ${p.name} ${dim(`v${p.version ?? "?"}`)}`);
|
|
132
240
|
}
|
|
133
241
|
log("");
|
|
134
242
|
log("Use --policy-id <ID> to specify which one to pull.");
|
|
135
243
|
process.exit(1);
|
|
136
244
|
}
|
|
137
245
|
}
|
|
138
|
-
log(`Pulling
|
|
139
|
-
const policy = await fetchCloudPolicy(apiKey,
|
|
140
|
-
const
|
|
246
|
+
log(`Pulling ${cyan(policyId)} from dashboard...`);
|
|
247
|
+
const policy = await fetchCloudPolicy(apiKey, API_URL, policyId);
|
|
248
|
+
const { id: _id, ...policyWithoutId } = policy;
|
|
249
|
+
const json = JSON.stringify(policyWithoutId, null, 2) + "\n";
|
|
141
250
|
writeFileSync(file, json, "utf-8");
|
|
142
|
-
log(`Saved: ${file}`);
|
|
143
|
-
log(` Name: ${policy.name}`);
|
|
144
|
-
log(` Version: ${policy.version}`);
|
|
145
|
-
log(` Rules: ${policy.rules.length}`);
|
|
146
251
|
log("");
|
|
147
|
-
log("
|
|
252
|
+
log(green(" Saved to: ") + file);
|
|
253
|
+
log(` ${dim("Name:")} ${policy.name}`);
|
|
254
|
+
log(` ${dim("Version:")} ${policy.version}`);
|
|
255
|
+
log(` ${dim("Rules:")} ${policy.rules.length}`);
|
|
256
|
+
log("");
|
|
257
|
+
log(dim("The policy file does not contain an ID."));
|
|
258
|
+
log(dim("Use --policy-id to specify the target when pushing/pulling."));
|
|
259
|
+
log("");
|
|
148
260
|
}
|
|
149
|
-
async function push(apiKey, file) {
|
|
150
|
-
const apiUrl = "https://api.solongate.com";
|
|
261
|
+
async function push(apiKey, file, policyId) {
|
|
151
262
|
if (!existsSync2(file)) {
|
|
152
|
-
log(`ERROR: File not found: ${file}`);
|
|
263
|
+
log(red(`ERROR: File not found: ${file}`));
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
if (!policyId) {
|
|
267
|
+
log(red("ERROR: --policy-id is required for push."));
|
|
268
|
+
log("");
|
|
269
|
+
log("This determines which cloud policy to update.");
|
|
270
|
+
log("");
|
|
271
|
+
log("Usage:");
|
|
272
|
+
log(" solongate-proxy push --policy-id my-policy");
|
|
273
|
+
log(" solongate-proxy push --policy-id my-policy --file custom.json");
|
|
274
|
+
log("");
|
|
275
|
+
log("List your policies:");
|
|
276
|
+
log(" solongate-proxy list");
|
|
153
277
|
process.exit(1);
|
|
154
278
|
}
|
|
155
279
|
const content = readFileSync2(file, "utf-8");
|
|
@@ -157,21 +281,26 @@ async function push(apiKey, file) {
|
|
|
157
281
|
try {
|
|
158
282
|
policy = JSON.parse(content);
|
|
159
283
|
} catch {
|
|
160
|
-
log(`ERROR: Invalid JSON in ${file}`);
|
|
284
|
+
log(red(`ERROR: Invalid JSON in ${file}`));
|
|
161
285
|
process.exit(1);
|
|
162
286
|
}
|
|
163
|
-
log(`Pushing
|
|
164
|
-
log(` File:
|
|
165
|
-
log(` Name:
|
|
166
|
-
log(` Rules: ${(policy.rules || []).length}`);
|
|
167
|
-
const
|
|
168
|
-
|
|
287
|
+
log(`Pushing to ${cyan(policyId)}...`);
|
|
288
|
+
log(` ${dim("File:")} ${file}`);
|
|
289
|
+
log(` ${dim("Name:")} ${policy.name || "Unnamed"}`);
|
|
290
|
+
log(` ${dim("Rules:")} ${(policy.rules || []).length}`);
|
|
291
|
+
const checkRes = await fetch(`${API_URL}/api/v1/policies/${policyId}`, {
|
|
292
|
+
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
293
|
+
});
|
|
294
|
+
const method = checkRes.ok ? "PUT" : "POST";
|
|
295
|
+
const url = checkRes.ok ? `${API_URL}/api/v1/policies/${policyId}` : `${API_URL}/api/v1/policies`;
|
|
296
|
+
const res = await fetch(url, {
|
|
297
|
+
method,
|
|
169
298
|
headers: {
|
|
170
299
|
"Authorization": `Bearer ${apiKey}`,
|
|
171
300
|
"Content-Type": "application/json"
|
|
172
301
|
},
|
|
173
302
|
body: JSON.stringify({
|
|
174
|
-
id:
|
|
303
|
+
id: policyId,
|
|
175
304
|
name: policy.name || "Local Policy",
|
|
176
305
|
description: policy.description || "Pushed from local file",
|
|
177
306
|
version: policy.version || 1,
|
|
@@ -180,13 +309,15 @@ async function push(apiKey, file) {
|
|
|
180
309
|
});
|
|
181
310
|
if (!res.ok) {
|
|
182
311
|
const body = await res.text().catch(() => "");
|
|
183
|
-
log(`ERROR: Push failed (${res.status}): ${body}`);
|
|
312
|
+
log(red(`ERROR: Push failed (${res.status}): ${body}`));
|
|
184
313
|
process.exit(1);
|
|
185
314
|
}
|
|
186
315
|
const data = await res.json();
|
|
187
|
-
log(` Cloud version: ${data._version ?? "created"}`);
|
|
188
316
|
log("");
|
|
189
|
-
log(
|
|
317
|
+
log(green(` Pushed to cloud: v${data._version ?? "created"}`));
|
|
318
|
+
log(` ${dim("Policy ID:")} ${policyId}`);
|
|
319
|
+
log(` ${dim("Method:")} ${method === "PUT" ? "Updated existing" : "Created new"}`);
|
|
320
|
+
log("");
|
|
190
321
|
}
|
|
191
322
|
async function main() {
|
|
192
323
|
const { command, apiKey, file, policyId } = parseCliArgs();
|
|
@@ -194,14 +325,27 @@ async function main() {
|
|
|
194
325
|
if (command === "pull") {
|
|
195
326
|
await pull(apiKey, file, policyId);
|
|
196
327
|
} else if (command === "push") {
|
|
197
|
-
await push(apiKey, file);
|
|
328
|
+
await push(apiKey, file, policyId);
|
|
329
|
+
} else if (command === "list" || command === "ls") {
|
|
330
|
+
await list(apiKey, policyId);
|
|
198
331
|
} else {
|
|
199
|
-
log(`Unknown command: ${command}`);
|
|
200
|
-
log("
|
|
332
|
+
log(red(`Unknown command: ${command}`));
|
|
333
|
+
log("");
|
|
334
|
+
log(bold("Usage:"));
|
|
335
|
+
log(" solongate-proxy list List all policies");
|
|
336
|
+
log(" solongate-proxy list --policy-id <ID> Show policy details");
|
|
337
|
+
log(" solongate-proxy pull --policy-id <ID> Pull policy to local file");
|
|
338
|
+
log(" solongate-proxy push --policy-id <ID> Push local file to cloud");
|
|
339
|
+
log("");
|
|
340
|
+
log(bold("Flags:"));
|
|
341
|
+
log(" --policy-id, --id <ID> Cloud policy ID (required for push)");
|
|
342
|
+
log(" --file, -f <path> Local file path (default: policy.json)");
|
|
343
|
+
log(" --api-key <key> API key (or set SOLONGATE_API_KEY)");
|
|
344
|
+
log("");
|
|
201
345
|
process.exit(1);
|
|
202
346
|
}
|
|
203
347
|
} catch (err) {
|
|
204
|
-
log(`ERROR: ${err instanceof Error ? err.message : String(err)}`);
|
|
348
|
+
log(red(`ERROR: ${err instanceof Error ? err.message : String(err)}`));
|
|
205
349
|
process.exit(1);
|
|
206
350
|
}
|
|
207
351
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "MCP security proxy — protect any MCP server with customizable policies, path/command constraints, rate limiting, and audit logging. Zero code changes required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|