perimetercli 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/LICENSE +21 -0
- package/README.md +203 -0
- package/bin/perimeter.js +5 -0
- package/package.json +55 -0
- package/src/audit.js +164 -0
- package/src/catalog.js +335 -0
- package/src/cli.js +469 -0
- package/src/discover.js +262 -0
- package/src/guard.js +159 -0
- package/src/index.js +9 -0
- package/src/report.js +333 -0
- package/src/rules.js +357 -0
- package/src/serve.js +64 -0
- package/src/server.js +194 -0
- package/src/sessions.js +161 -0
- package/src/tokens.js +99 -0
- package/src/util.js +137 -0
- package/src/version.js +1 -0
package/src/cli.js
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import {
|
|
4
|
+
log,
|
|
5
|
+
info,
|
|
6
|
+
success,
|
|
7
|
+
warn,
|
|
8
|
+
error,
|
|
9
|
+
green,
|
|
10
|
+
cyan,
|
|
11
|
+
yellow,
|
|
12
|
+
red,
|
|
13
|
+
bold,
|
|
14
|
+
dim,
|
|
15
|
+
} from "./util.js";
|
|
16
|
+
import { VERSION } from "./version.js";
|
|
17
|
+
import { runAudit, writeBaseline, makeBaseline, policyPath, DEFAULT_POLICY } from "./audit.js";
|
|
18
|
+
import { discover } from "./discover.js";
|
|
19
|
+
import { buildSessionReport } from "./sessions.js";
|
|
20
|
+
import { resolveModel } from "./tokens.js";
|
|
21
|
+
import { createStaticServer, listen } from "./serve.js";
|
|
22
|
+
import { runGuardCli, defaultPolicy } from "./guard.js";
|
|
23
|
+
import { createServer as createPerimeterServer, listen as listenPerimeter } from "./server.js";
|
|
24
|
+
import { SEVERITY_RANK } from "./rules.js";
|
|
25
|
+
import {
|
|
26
|
+
formatText,
|
|
27
|
+
formatJson,
|
|
28
|
+
formatMarkdown,
|
|
29
|
+
formatSarif,
|
|
30
|
+
formatHtml,
|
|
31
|
+
formatSessionText,
|
|
32
|
+
formatSessionJson,
|
|
33
|
+
} from "./report.js";
|
|
34
|
+
|
|
35
|
+
function parseArgs(argv) {
|
|
36
|
+
const BOOLEAN_FLAGS = new Set([
|
|
37
|
+
"json",
|
|
38
|
+
"md",
|
|
39
|
+
"sarif",
|
|
40
|
+
"html",
|
|
41
|
+
"enforce",
|
|
42
|
+
"recursive",
|
|
43
|
+
"debug",
|
|
44
|
+
"silent",
|
|
45
|
+
"home",
|
|
46
|
+
"no-home",
|
|
47
|
+
"baseline",
|
|
48
|
+
"no-baseline",
|
|
49
|
+
"help",
|
|
50
|
+
"version",
|
|
51
|
+
]);
|
|
52
|
+
const flags = {};
|
|
53
|
+
const positional = [];
|
|
54
|
+
for (let i = 0; i < argv.length; i++) {
|
|
55
|
+
const arg = argv[i];
|
|
56
|
+
if (arg.startsWith("--")) {
|
|
57
|
+
const eq = arg.indexOf("=");
|
|
58
|
+
let name, value;
|
|
59
|
+
if (eq !== -1) {
|
|
60
|
+
name = arg.slice(2, eq);
|
|
61
|
+
value = arg.slice(eq + 1);
|
|
62
|
+
} else {
|
|
63
|
+
name = arg.slice(2);
|
|
64
|
+
if (BOOLEAN_FLAGS.has(name)) {
|
|
65
|
+
const camel = name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
66
|
+
flags[camel] = true;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const next = argv[i + 1];
|
|
70
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
71
|
+
value = next;
|
|
72
|
+
i++;
|
|
73
|
+
} else {
|
|
74
|
+
const camel = name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
75
|
+
flags[camel] = true;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const camel = name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
80
|
+
flags[camel] = value;
|
|
81
|
+
} else {
|
|
82
|
+
positional.push(arg);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { flags, positional };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function printVersion() {
|
|
89
|
+
log(VERSION);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function printHelp() {
|
|
93
|
+
log(`perimeter ${VERSION}
|
|
94
|
+
|
|
95
|
+
${bold("Usage")}
|
|
96
|
+
perimeter <command> [path] [options]
|
|
97
|
+
|
|
98
|
+
${bold("Commands")}
|
|
99
|
+
audit Scan agent/MCP configs for risk, cost and drift
|
|
100
|
+
baseline Record a signature baseline for drift detection
|
|
101
|
+
list List detected servers, tools and capabilities
|
|
102
|
+
init Write a .perimeter/config.json policy
|
|
103
|
+
cost Show the token/cost footprint only
|
|
104
|
+
session Observe what agents actually ran (session logs)
|
|
105
|
+
serve Serve a generated report locally
|
|
106
|
+
guard Proxy an MCP server with a runtime policy gate
|
|
107
|
+
server Run a self-hosted audit dashboard (Pro)
|
|
108
|
+
push Push an audit JSON to a Perimeter server
|
|
109
|
+
version Print the version
|
|
110
|
+
help Show this help
|
|
111
|
+
|
|
112
|
+
${bold("Audit options")}
|
|
113
|
+
--json Print machine-readable JSON
|
|
114
|
+
--md Print a Markdown report
|
|
115
|
+
--sarif Print SARIF (for GitHub code scanning)
|
|
116
|
+
--html Print an HTML report
|
|
117
|
+
--out <dir> Write all report formats to a directory
|
|
118
|
+
--enforce Exit non-zero if verdict >= fail-on
|
|
119
|
+
--fail-on <sev> Fail threshold (default: critical)
|
|
120
|
+
--model <model> Model for cost estimation
|
|
121
|
+
--budget <amt> Daily spend budget in dollars
|
|
122
|
+
--no-baseline Skip drift comparison
|
|
123
|
+
--no-home Do not scan home-directory configs
|
|
124
|
+
--recursive Also scan nested .mcp.json files
|
|
125
|
+
|
|
126
|
+
${bold("Examples")}
|
|
127
|
+
perimeter audit
|
|
128
|
+
perimeter audit --enforce --fail-on high
|
|
129
|
+
perimeter audit --json | jq '.verdict'
|
|
130
|
+
perimeter baseline
|
|
131
|
+
perimeter init
|
|
132
|
+
`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function resolveDir(flags) {
|
|
136
|
+
const dir = path.resolve(flags._dir || process.cwd());
|
|
137
|
+
if (!fs.existsSync(dir)) {
|
|
138
|
+
throw new Error(`Directory not found: ${dir}`);
|
|
139
|
+
}
|
|
140
|
+
return dir;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function cmdAudit(flags) {
|
|
144
|
+
const cwd = resolveDir(flags);
|
|
145
|
+
const audit = await runAudit({
|
|
146
|
+
cwd,
|
|
147
|
+
model: flags.model,
|
|
148
|
+
budget: flags.budget !== undefined && flags.budget !== true ? Number(flags.budget) : undefined,
|
|
149
|
+
failOn: flags.failOn,
|
|
150
|
+
maxTokensPerServer: flags.maxTokens,
|
|
151
|
+
useBaseline: flags.baseline !== false && flags.noBaseline !== true,
|
|
152
|
+
home: effectiveHome(flags),
|
|
153
|
+
recursive: flags.recursive === true,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const wantOut = flags.out ? path.resolve(flags.out) : null;
|
|
157
|
+
|
|
158
|
+
if (wantOut) {
|
|
159
|
+
fs.mkdirSync(wantOut, { recursive: true });
|
|
160
|
+
const files = {
|
|
161
|
+
"audit.txt": formatText(audit),
|
|
162
|
+
"audit.json": formatJson(audit),
|
|
163
|
+
"audit.md": formatMarkdown(audit),
|
|
164
|
+
"audit.sarif": formatSarif(audit),
|
|
165
|
+
"index.html": formatHtml(audit),
|
|
166
|
+
};
|
|
167
|
+
for (const [name, content] of Object.entries(files)) {
|
|
168
|
+
fs.writeFileSync(path.join(wantOut, name), content);
|
|
169
|
+
}
|
|
170
|
+
success(`Reports written to ${cyan(wantOut)}`);
|
|
171
|
+
log(textHeader(audit));
|
|
172
|
+
} else if (flags.json) {
|
|
173
|
+
log(formatJson(audit));
|
|
174
|
+
} else if (flags.md) {
|
|
175
|
+
log(formatMarkdown(audit));
|
|
176
|
+
} else if (flags.sarif) {
|
|
177
|
+
log(formatSarif(audit));
|
|
178
|
+
} else if (flags.html) {
|
|
179
|
+
log(formatHtml(audit));
|
|
180
|
+
} else {
|
|
181
|
+
log(formatText(audit));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (flags.enforce) {
|
|
185
|
+
const threshold = flags.failOn || audit.config.failOn || DEFAULT_POLICY.failOn;
|
|
186
|
+
const thresholdRank = SEVERITY_RANK[threshold] ?? 4;
|
|
187
|
+
const verdictRank = SEVERITY_RANK[audit.effectiveVerdict] ?? 1;
|
|
188
|
+
const budgetFail = Boolean(audit.costs && audit.costs.overBudget);
|
|
189
|
+
if (verdictRank >= thresholdRank || budgetFail) {
|
|
190
|
+
log(
|
|
191
|
+
`${red("✗")} Failed — verdict ${audit.effectiveVerdict}${
|
|
192
|
+
verdictRank >= thresholdRank ? ` >= threshold ${threshold}` : ""
|
|
193
|
+
}${budgetFail ? ` · single load over daily budget` : ""}`
|
|
194
|
+
);
|
|
195
|
+
process.exitCode = 1;
|
|
196
|
+
} else {
|
|
197
|
+
log(`${green("✓")} Passed — verdict ${audit.effectiveVerdict} < threshold ${threshold}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function cmdBaseline(flags) {
|
|
203
|
+
const cwd = resolveDir(flags);
|
|
204
|
+
const { servers, hooks } = discover(cwd, {
|
|
205
|
+
recursive: flags.recursive === true,
|
|
206
|
+
home: effectiveHome(flags),
|
|
207
|
+
});
|
|
208
|
+
writeBaseline(cwd, [...servers, ...hooks]);
|
|
209
|
+
success(
|
|
210
|
+
`Baseline written to ${cyan(policyBaseline(cwd))} (${servers.length} servers, ${hooks.length} hooks)`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function cmdList(flags) {
|
|
215
|
+
const cwd = resolveDir(flags);
|
|
216
|
+
const { servers, hooks, contextFiles, errors, files } = discover(cwd, {
|
|
217
|
+
recursive: flags.recursive === true,
|
|
218
|
+
home: effectiveHome(flags),
|
|
219
|
+
});
|
|
220
|
+
if (servers.length === 0 && hooks.length === 0) {
|
|
221
|
+
log("No MCP servers or agent tooling found.");
|
|
222
|
+
} else {
|
|
223
|
+
log(
|
|
224
|
+
`${green("Detected")} ${servers.length} server${servers.length === 1 ? "" : "s"}, ${hooks.length} hook${hooks.length === 1 ? "" : "s"}`
|
|
225
|
+
);
|
|
226
|
+
for (const s of servers) {
|
|
227
|
+
const caps = s.catalog ? s.catalog.capabilities.join(", ") : "(unverified)";
|
|
228
|
+
log(
|
|
229
|
+
` ${cyan(s.displayName)} ${dim(s.source)} ${s.transport} ${dim(caps)}`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
for (const h of hooks) {
|
|
233
|
+
log(` ${cyan(h.displayName)} ${dim(h.source)} ${yellow("code-exec")}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (contextFiles.length) {
|
|
237
|
+
log(`\n${dim(`${contextFiles.length} context/instruction file(s):`)}`);
|
|
238
|
+
for (const c of contextFiles) log(` ${dim(c.label)}`);
|
|
239
|
+
}
|
|
240
|
+
if (flags.json) {
|
|
241
|
+
log("\n" + JSON.stringify({ servers, hooks, contextFiles, errors }, null, 2));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function cmdInit(flags) {
|
|
246
|
+
const cwd = resolveDir(flags);
|
|
247
|
+
const target = policyPath(cwd);
|
|
248
|
+
if (fs.existsSync(target)) {
|
|
249
|
+
warn(`${policyPath(cwd)} already exists — leaving it unchanged`);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const config = {
|
|
253
|
+
name: "agent-toolchain",
|
|
254
|
+
...DEFAULT_POLICY,
|
|
255
|
+
model: "claude-sonnet",
|
|
256
|
+
block: ["@modelcontextprotocol/server-everything"],
|
|
257
|
+
ignoredRules: [],
|
|
258
|
+
};
|
|
259
|
+
fs.writeFileSync(target, JSON.stringify(config, null, 2) + "\n");
|
|
260
|
+
success(`Wrote ${cyan(target)}`);
|
|
261
|
+
info(dim("Set failOn, model, budget, and server allow/block lists to match your policy."));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function cmdCost(flags) {
|
|
265
|
+
const cwd = resolveDir(flags);
|
|
266
|
+
const model = resolveModel(flags.model || DEFAULT_POLICY.model);
|
|
267
|
+
const audit = await runAudit({
|
|
268
|
+
cwd,
|
|
269
|
+
model: model.name,
|
|
270
|
+
budget: flags.budget !== undefined && flags.budget !== true ? Number(flags.budget) : undefined,
|
|
271
|
+
useBaseline: false,
|
|
272
|
+
});
|
|
273
|
+
const rows = audit.servers
|
|
274
|
+
.sort((a, b) => b.estimatedTokens - a.estimatedTokens)
|
|
275
|
+
.map(
|
|
276
|
+
(s) =>
|
|
277
|
+
`${s.displayName.padEnd(20)} ${String(s.estimatedTokens).padStart(8)} tokens ${dim(`(${s.capabilities.length} caps)`)}`
|
|
278
|
+
)
|
|
279
|
+
.join("\n");
|
|
280
|
+
log(`Cost footprint — model ${cyan(model.name)}${audit.costs.estimated ? " (estimated)" : ""}`);
|
|
281
|
+
log(`\n${rows}`);
|
|
282
|
+
log("");
|
|
283
|
+
log(`~${audit.costs.totalTokens.toLocaleString()} tokens per load`);
|
|
284
|
+
log(
|
|
285
|
+
`${audit.costs.perLoadCost ? green("~$" + audit.costs.perLoadCost.toFixed(2)) : "—"} per load`
|
|
286
|
+
);
|
|
287
|
+
if (audit.costs.budget) {
|
|
288
|
+
log(
|
|
289
|
+
`budget ${audit.costs.budget}/day → ${audit.costs.loadsPerDayWithinBudget ?? 0} loads/day`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function cmdSession(flags) {
|
|
295
|
+
const cwd = resolveDir(flags);
|
|
296
|
+
const report = buildSessionReport(cwd, {
|
|
297
|
+
model: flags.model || DEFAULT_POLICY.model,
|
|
298
|
+
sessions: flags.sessions,
|
|
299
|
+
home: effectiveHome(flags),
|
|
300
|
+
});
|
|
301
|
+
if (flags.json) log(formatSessionJson(report));
|
|
302
|
+
else log(formatSessionText(report));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function cmdServe(flags) {
|
|
306
|
+
const cwd = resolveDir(flags);
|
|
307
|
+
const dir =
|
|
308
|
+
flags.dir && flags.dir !== true
|
|
309
|
+
? path.resolve(flags.dir)
|
|
310
|
+
: fs.existsSync(path.join(cwd, ".perimeter", "report"))
|
|
311
|
+
? path.join(cwd, ".perimeter", "report")
|
|
312
|
+
: cwd;
|
|
313
|
+
const port = Number(flags.port || 4173);
|
|
314
|
+
const host = flags.host || "127.0.0.1";
|
|
315
|
+
const { server } = createStaticServer(dir, { host, port });
|
|
316
|
+
await listen(server, { host, port });
|
|
317
|
+
const addr = server.address();
|
|
318
|
+
const shown = typeof addr === "object" && addr ? addr.port : port;
|
|
319
|
+
log(`${green("Serving")} ${cyan(dir)} at ${cyan(`http://${host}:${shown}`)}`);
|
|
320
|
+
log(dim("Press Ctrl+C to stop."));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function cmdGuard(flags) {
|
|
324
|
+
if (!flags.server) {
|
|
325
|
+
error("guard requires --server \"<command>\"");
|
|
326
|
+
process.exitCode = 1;
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const policy = { ...defaultPolicy() };
|
|
330
|
+
if (flags.policy && flags.policy !== true) {
|
|
331
|
+
const filePolicy = JSON.parse(fs.readFileSync(flags.policy, "utf8"));
|
|
332
|
+
Object.assign(policy, filePolicy);
|
|
333
|
+
}
|
|
334
|
+
if (flags.allow) policy.allowTools = splitList(flags.allow);
|
|
335
|
+
if (flags.deny) policy.denyTools = splitList(flags.deny);
|
|
336
|
+
if (flags.denyCapabilities) {
|
|
337
|
+
policy.denyCapabilities = splitList(flags.denyCapabilities);
|
|
338
|
+
}
|
|
339
|
+
if (flags.allowCapabilities) {
|
|
340
|
+
policy.allowCapabilities = splitList(flags.allowCapabilities);
|
|
341
|
+
}
|
|
342
|
+
if (flags.log) policy.log = flags.log;
|
|
343
|
+
runGuardCli({ command: flags.server, policy });
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async function cmdServer(flags) {
|
|
347
|
+
const cwd = resolveDir(flags);
|
|
348
|
+
const port = Number(flags.port || 4173);
|
|
349
|
+
const host = flags.host || "127.0.0.1";
|
|
350
|
+
const dataDir = flags.data && flags.data !== true ? path.resolve(flags.data) : path.join(cwd, ".perimeter", "data");
|
|
351
|
+
const { server } = createPerimeterServer({ dataDir, host, port });
|
|
352
|
+
await listenPerimeter(server, { host, port });
|
|
353
|
+
const addr = server.address();
|
|
354
|
+
const shown = typeof addr === "object" && addr ? addr.port : port;
|
|
355
|
+
log(`${green("Perimeter server")} listening at ${cyan(`http://${host}:${shown}`)}`);
|
|
356
|
+
log(dim("Press Ctrl+C to stop."));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function cmdPush(flags) {
|
|
360
|
+
const url = flags._dir || flags._url;
|
|
361
|
+
if (!url) {
|
|
362
|
+
error("push requires <url> e.g. perimeter push http://localhost:4173 --project acme");
|
|
363
|
+
process.exitCode = 1;
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const project = flags.project || "project";
|
|
367
|
+
let body;
|
|
368
|
+
if (flags.file && flags.file !== true) {
|
|
369
|
+
body = fs.readFileSync(flags.file, "utf8");
|
|
370
|
+
} else {
|
|
371
|
+
body = await readStdin();
|
|
372
|
+
}
|
|
373
|
+
try {
|
|
374
|
+
const target = url.replace(/\/$/, "") + "/audit?project=" + encodeURIComponent(project);
|
|
375
|
+
const res = await fetch(target, {
|
|
376
|
+
method: "POST",
|
|
377
|
+
headers: { "content-type": "application/json" },
|
|
378
|
+
body,
|
|
379
|
+
});
|
|
380
|
+
const data = await res.json();
|
|
381
|
+
if (data.ok) success(`Pushed audit for ${cyan(project)} to ${cyan(target)}`);
|
|
382
|
+
else error(data.error || "push failed");
|
|
383
|
+
} catch (err) {
|
|
384
|
+
error(`Could not reach ${url} — ${err.message}`);
|
|
385
|
+
process.exitCode = 1;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function splitList(value) {
|
|
390
|
+
return String(value)
|
|
391
|
+
.split(",")
|
|
392
|
+
.map((s) => s.trim())
|
|
393
|
+
.filter(Boolean);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function effectiveHome(flags) {
|
|
397
|
+
if (flags.noHome === true) return false;
|
|
398
|
+
return flags.home === true;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function readStdin() {
|
|
402
|
+
return new Promise((resolve, reject) => {
|
|
403
|
+
let data = "";
|
|
404
|
+
process.stdin.setEncoding("utf8");
|
|
405
|
+
process.stdin.on("data", (c) => (data += c));
|
|
406
|
+
process.stdin.on("end", () => resolve(data));
|
|
407
|
+
process.stdin.on("error", reject);
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function textHeader(audit) {
|
|
412
|
+
return dim(`${audit.servers.length} servers · verdict ${audit.effectiveVerdict}`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function policyBaseline(cwd) {
|
|
416
|
+
return path.join(cwd, ".perimeter", "baseline.json");
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export async function main(argv) {
|
|
420
|
+
const { flags, positional } = parseArgs(argv);
|
|
421
|
+
if (flags.debug) process.env.PERIMETER_DEBUG = "1";
|
|
422
|
+
const command = positional[0] || "help";
|
|
423
|
+
flags._dir = positional[1];
|
|
424
|
+
try {
|
|
425
|
+
switch (command) {
|
|
426
|
+
case "audit":
|
|
427
|
+
await cmdAudit(flags);
|
|
428
|
+
break;
|
|
429
|
+
case "baseline":
|
|
430
|
+
await cmdBaseline(flags);
|
|
431
|
+
break;
|
|
432
|
+
case "list":
|
|
433
|
+
await cmdList(flags);
|
|
434
|
+
break;
|
|
435
|
+
case "init":
|
|
436
|
+
await cmdInit(flags);
|
|
437
|
+
break;
|
|
438
|
+
case "cost":
|
|
439
|
+
await cmdCost(flags);
|
|
440
|
+
break;
|
|
441
|
+
case "session":
|
|
442
|
+
await cmdSession(flags);
|
|
443
|
+
break;
|
|
444
|
+
case "serve":
|
|
445
|
+
await cmdServe(flags);
|
|
446
|
+
break;
|
|
447
|
+
case "guard":
|
|
448
|
+
await cmdGuard(flags);
|
|
449
|
+
break;
|
|
450
|
+
case "server":
|
|
451
|
+
await cmdServer(flags);
|
|
452
|
+
break;
|
|
453
|
+
case "push":
|
|
454
|
+
await cmdPush(flags);
|
|
455
|
+
break;
|
|
456
|
+
case "version":
|
|
457
|
+
case "--version":
|
|
458
|
+
case "-v":
|
|
459
|
+
printVersion();
|
|
460
|
+
break;
|
|
461
|
+
default:
|
|
462
|
+
printHelp();
|
|
463
|
+
}
|
|
464
|
+
} catch (err) {
|
|
465
|
+
error(err.message || String(err));
|
|
466
|
+
if (process.env.PERIMETER_DEBUG) console.error(err);
|
|
467
|
+
process.exitCode = 1;
|
|
468
|
+
}
|
|
469
|
+
}
|