@cantinasecurity/apex-cli 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/.claude/skills/apex-cli/SKILL.md +38 -0
- package/README.md +259 -0
- package/dist/apex.js +94 -0
- package/dist/api-client.js +125 -0
- package/dist/args.js +56 -0
- package/dist/auth.js +143 -0
- package/dist/browser.js +18 -0
- package/dist/commands.js +629 -0
- package/dist/config.js +54 -0
- package/dist/findings.js +50 -0
- package/dist/help.js +75 -0
- package/dist/local-source-scan.js +304 -0
- package/dist/mcp-main.js +10 -0
- package/dist/mcp.js +487 -0
- package/dist/output.js +93 -0
- package/dist/prompt.js +80 -0
- package/dist/repo-discovery.js +175 -0
- package/dist/repo-url.js +134 -0
- package/dist/scan.js +186 -0
- package/dist/session.js +188 -0
- package/dist/setup.js +275 -0
- package/dist/shell.js +320 -0
- package/dist/types.js +1 -0
- package/dist/update.js +462 -0
- package/dist/version.js +7 -0
- package/dist/workspace-binding.js +30 -0
- package/dist/workspaces.js +50 -0
- package/package.json +43 -0
- package/pkg-bin/apex-mcp.js +2 -0
- package/pkg-bin/apex.js +2 -0
- package/skills/apex-cli/SKILL.md +29 -0
package/dist/setup.js
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { execFile as execFileCallback } from "node:child_process";
|
|
2
|
+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
import { isJsonMode } from "./args.js";
|
|
8
|
+
import { logLine, printJson } from "./output.js";
|
|
9
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
|
+
const CODEX_SKILL_NAME = "apex-cli";
|
|
11
|
+
const MCP_SERVER_NAME = "apex";
|
|
12
|
+
const execFile = promisify(execFileCallback);
|
|
13
|
+
function quoteShellArg(value) {
|
|
14
|
+
return /[^A-Za-z0-9_./:-]/.test(value)
|
|
15
|
+
? `'${value.replace(/'/g, `'\\''`)}'`
|
|
16
|
+
: value;
|
|
17
|
+
}
|
|
18
|
+
async function pathExists(targetPath) {
|
|
19
|
+
try {
|
|
20
|
+
await stat(targetPath);
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function readTextFile(filePath) {
|
|
28
|
+
try {
|
|
29
|
+
return await readFile(filePath, "utf8");
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function execText(command, args, cwd) {
|
|
36
|
+
const result = await execFile(command, args, {
|
|
37
|
+
cwd,
|
|
38
|
+
encoding: "utf8",
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
stdout: result.stdout.trim(),
|
|
42
|
+
stderr: result.stderr.trim(),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function tryExecText(command, args, cwd) {
|
|
46
|
+
try {
|
|
47
|
+
return await execText(command, args, cwd);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function resolveMcpLaunchSpec(packageRoot = PACKAGE_ROOT) {
|
|
54
|
+
const repoLocalBinary = path.join(packageRoot, "bin", "apex-mcp");
|
|
55
|
+
if ((await pathExists(path.join(packageRoot, ".git"))) &&
|
|
56
|
+
(await pathExists(repoLocalBinary))) {
|
|
57
|
+
return {
|
|
58
|
+
command: repoLocalBinary,
|
|
59
|
+
args: [],
|
|
60
|
+
label: quoteShellArg(repoLocalBinary),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
command: "apex-mcp",
|
|
65
|
+
args: [],
|
|
66
|
+
label: "apex-mcp",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function getCodexHome() {
|
|
70
|
+
const configured = process.env.CODEX_HOME?.trim();
|
|
71
|
+
if (configured) {
|
|
72
|
+
return configured;
|
|
73
|
+
}
|
|
74
|
+
return path.join(os.homedir(), ".codex");
|
|
75
|
+
}
|
|
76
|
+
function normalizeArgs(value) {
|
|
77
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
78
|
+
}
|
|
79
|
+
function codexConfigMatches(existing, launch) {
|
|
80
|
+
if (!existing?.transport) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return (existing.transport.type === "stdio" &&
|
|
84
|
+
existing.transport.command === launch.command &&
|
|
85
|
+
JSON.stringify(normalizeArgs(existing.transport.args)) === JSON.stringify(launch.args));
|
|
86
|
+
}
|
|
87
|
+
function claudeConfigMatches(existing, launch) {
|
|
88
|
+
const env = existing?.env;
|
|
89
|
+
const normalizedEnv = env && typeof env === "object" && !Array.isArray(env) ? Object.keys(env).length : 0;
|
|
90
|
+
return (existing?.type === "stdio" &&
|
|
91
|
+
existing.command === launch.command &&
|
|
92
|
+
JSON.stringify(normalizeArgs(existing.args)) === JSON.stringify(launch.args) &&
|
|
93
|
+
normalizedEnv === 0);
|
|
94
|
+
}
|
|
95
|
+
async function writeManagedFile(filePath, content) {
|
|
96
|
+
const current = await readTextFile(filePath);
|
|
97
|
+
if (current === content) {
|
|
98
|
+
return "unchanged";
|
|
99
|
+
}
|
|
100
|
+
await mkdir(path.dirname(filePath), { recursive: true, mode: 0o755 });
|
|
101
|
+
await writeFile(filePath, content, "utf8");
|
|
102
|
+
return current === null ? "installed" : "updated";
|
|
103
|
+
}
|
|
104
|
+
async function readCodexMcpConfig() {
|
|
105
|
+
const response = await tryExecText("codex", ["mcp", "get", MCP_SERVER_NAME, "--json"]);
|
|
106
|
+
if (!response?.stdout) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
return JSON.parse(response.stdout);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function readClaudeUserMcpConfig() {
|
|
117
|
+
const configPath = path.join(os.homedir(), ".claude.json");
|
|
118
|
+
const raw = await readTextFile(configPath);
|
|
119
|
+
if (!raw) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const parsed = JSON.parse(raw);
|
|
124
|
+
return parsed.mcpServers?.[MCP_SERVER_NAME] ?? null;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async function configureCodex(launch) {
|
|
131
|
+
const existing = await readCodexMcpConfig();
|
|
132
|
+
const mcpStatus = existing === null
|
|
133
|
+
? "installed"
|
|
134
|
+
: codexConfigMatches(existing, launch)
|
|
135
|
+
? "unchanged"
|
|
136
|
+
: "updated";
|
|
137
|
+
await execText("codex", ["mcp", "add", MCP_SERVER_NAME, "--", launch.command, ...launch.args]);
|
|
138
|
+
const skillSource = path.join(PACKAGE_ROOT, "skills", CODEX_SKILL_NAME, "SKILL.md");
|
|
139
|
+
const skillTarget = path.join(getCodexHome(), "skills", CODEX_SKILL_NAME, "SKILL.md");
|
|
140
|
+
const skillContent = await readFile(skillSource, "utf8");
|
|
141
|
+
const skillStatus = await writeManagedFile(skillTarget, skillContent);
|
|
142
|
+
return [
|
|
143
|
+
{
|
|
144
|
+
client: "codex",
|
|
145
|
+
kind: "mcp",
|
|
146
|
+
status: mcpStatus,
|
|
147
|
+
path: path.join(getCodexHome(), "config.toml"),
|
|
148
|
+
detail: `Registered ${MCP_SERVER_NAME} -> ${launch.label}`,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
client: "codex",
|
|
152
|
+
kind: "skill",
|
|
153
|
+
status: skillStatus,
|
|
154
|
+
path: skillTarget,
|
|
155
|
+
detail: "Installed the Apex Codex skill",
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
}
|
|
159
|
+
async function configureClaude(cwd, launch) {
|
|
160
|
+
const existing = await readClaudeUserMcpConfig();
|
|
161
|
+
const mcpStatus = existing === null
|
|
162
|
+
? "installed"
|
|
163
|
+
: claudeConfigMatches(existing, launch)
|
|
164
|
+
? "unchanged"
|
|
165
|
+
: "updated";
|
|
166
|
+
if (existing) {
|
|
167
|
+
await execText("claude", ["mcp", "remove", "--scope", "user", MCP_SERVER_NAME]);
|
|
168
|
+
}
|
|
169
|
+
await execText("claude", [
|
|
170
|
+
"mcp",
|
|
171
|
+
"add",
|
|
172
|
+
"--scope",
|
|
173
|
+
"user",
|
|
174
|
+
MCP_SERVER_NAME,
|
|
175
|
+
"--",
|
|
176
|
+
launch.command,
|
|
177
|
+
...launch.args,
|
|
178
|
+
]);
|
|
179
|
+
const skillSource = path.join(PACKAGE_ROOT, ".claude", "skills", CODEX_SKILL_NAME, "SKILL.md");
|
|
180
|
+
const skillTarget = path.join(cwd, ".claude", "skills", CODEX_SKILL_NAME, "SKILL.md");
|
|
181
|
+
const skillContent = await readFile(skillSource, "utf8");
|
|
182
|
+
const skillStatus = await writeManagedFile(skillTarget, skillContent);
|
|
183
|
+
return [
|
|
184
|
+
{
|
|
185
|
+
client: "claude",
|
|
186
|
+
kind: "mcp",
|
|
187
|
+
status: mcpStatus,
|
|
188
|
+
path: path.join(os.homedir(), ".claude.json"),
|
|
189
|
+
detail: `Registered ${MCP_SERVER_NAME} -> ${launch.label} in Claude Code user config`,
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
client: "claude",
|
|
193
|
+
kind: "skill",
|
|
194
|
+
status: skillStatus,
|
|
195
|
+
path: skillTarget,
|
|
196
|
+
detail: "Installed the Apex Claude project skill",
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
}
|
|
200
|
+
function summarizeStep(step) {
|
|
201
|
+
const prefix = `${step.client} ${step.kind}`;
|
|
202
|
+
if (step.path) {
|
|
203
|
+
return `${prefix}: ${step.status} (${step.path})`;
|
|
204
|
+
}
|
|
205
|
+
return `${prefix}: ${step.status}`;
|
|
206
|
+
}
|
|
207
|
+
export async function commandSetup(cwd, flags, requestedTarget, packageRoot = PACKAGE_ROOT) {
|
|
208
|
+
if (requestedTarget !== null &&
|
|
209
|
+
requestedTarget !== "all" &&
|
|
210
|
+
requestedTarget !== "codex" &&
|
|
211
|
+
requestedTarget !== "claude") {
|
|
212
|
+
throw new Error("Usage: apex setup [all|codex|claude]");
|
|
213
|
+
}
|
|
214
|
+
const target = requestedTarget ?? "all";
|
|
215
|
+
const launch = await resolveMcpLaunchSpec(packageRoot);
|
|
216
|
+
const steps = [];
|
|
217
|
+
const requestedClients = target === "all" ? ["codex", "claude"] : [target];
|
|
218
|
+
let configuredClients = 0;
|
|
219
|
+
for (const client of requestedClients) {
|
|
220
|
+
try {
|
|
221
|
+
const clientSteps = client === "codex" ? await configureCodex(launch) : await configureClaude(cwd, launch);
|
|
222
|
+
steps.push(...clientSteps);
|
|
223
|
+
configuredClients += 1;
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
const missingClient = error instanceof Error &&
|
|
227
|
+
/spawn (codex|claude) ENOENT/i.test(error.message);
|
|
228
|
+
if (missingClient && target !== "all") {
|
|
229
|
+
throw new Error(`${client === "codex" ? "Codex" : "Claude Code"} is not installed on this machine. Install it first, then re-run \`apex setup ${client}\`.`);
|
|
230
|
+
}
|
|
231
|
+
if (!missingClient) {
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
steps.push({
|
|
235
|
+
client,
|
|
236
|
+
kind: "mcp",
|
|
237
|
+
status: "skipped",
|
|
238
|
+
path: null,
|
|
239
|
+
detail: `${client} is not installed on this machine`,
|
|
240
|
+
});
|
|
241
|
+
steps.push({
|
|
242
|
+
client,
|
|
243
|
+
kind: "skill",
|
|
244
|
+
status: "skipped",
|
|
245
|
+
path: null,
|
|
246
|
+
detail: `${client} is not installed on this machine`,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (configuredClients === 0) {
|
|
251
|
+
throw new Error("Neither Codex nor Claude Code is installed. Install one of them first, then re-run `apex setup`.");
|
|
252
|
+
}
|
|
253
|
+
const payload = {
|
|
254
|
+
target,
|
|
255
|
+
mcpServerName: MCP_SERVER_NAME,
|
|
256
|
+
mcpCommand: launch.label,
|
|
257
|
+
steps,
|
|
258
|
+
};
|
|
259
|
+
if (isJsonMode(flags)) {
|
|
260
|
+
printJson(payload);
|
|
261
|
+
return payload;
|
|
262
|
+
}
|
|
263
|
+
logLine(`Apex setup target: ${target}`, flags);
|
|
264
|
+
logLine(`MCP launch command: ${launch.label}`, flags);
|
|
265
|
+
for (const step of steps) {
|
|
266
|
+
logLine(summarizeStep(step), flags);
|
|
267
|
+
}
|
|
268
|
+
if (requestedClients.includes("codex")) {
|
|
269
|
+
logLine("Restart Codex to pick up a newly installed or updated skill.", flags);
|
|
270
|
+
}
|
|
271
|
+
if (requestedClients.includes("claude")) {
|
|
272
|
+
logLine("Re-run `apex setup claude` in each repository where you want the Claude project skill.", flags);
|
|
273
|
+
}
|
|
274
|
+
return payload;
|
|
275
|
+
}
|
package/dist/shell.js
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { SHELL_HELP_TEXT } from "./help.js";
|
|
2
|
+
import { commandCancelScan, commandConnect, commandCredits, commandDoctor, commandExportFindings, commandFindings, commandLogout, commandScan, commandScans, commandStatus, commandUpdate, commandWorkspace, commandWorkspaceUse, commandWorkspaces, initializeInteractiveSession, openCurrentApexView, } from "./commands.js";
|
|
3
|
+
import { withFlag } from "./args.js";
|
|
4
|
+
import { printCompanyDetails, printInteractiveSessionSummary, printSourceList, printWorkspaceDetails, } from "./output.js";
|
|
5
|
+
import { readLine } from "./prompt.js";
|
|
6
|
+
import { formatApiError } from "./api-client.js";
|
|
7
|
+
const SHELL_COMPLETIONS = [
|
|
8
|
+
{ command: "credits" },
|
|
9
|
+
{ command: "scan", args: ["standard", "ultra"] },
|
|
10
|
+
{ command: "scans" },
|
|
11
|
+
{ command: "findings" },
|
|
12
|
+
{ command: "export" },
|
|
13
|
+
{ command: "workspaces" },
|
|
14
|
+
{ command: "cancel-scan" },
|
|
15
|
+
{ command: "cancel" },
|
|
16
|
+
{ command: "status" },
|
|
17
|
+
{ command: "doctor" },
|
|
18
|
+
{ command: "update" },
|
|
19
|
+
{ command: "logout" },
|
|
20
|
+
{ command: "repos" },
|
|
21
|
+
{ command: "workspace", args: ["use", "name"] },
|
|
22
|
+
{ command: "company" },
|
|
23
|
+
{ command: "connect", args: ["github", "gitlab"] },
|
|
24
|
+
{ command: "open" },
|
|
25
|
+
{ command: "clear" },
|
|
26
|
+
{ command: "help" },
|
|
27
|
+
{ command: "exit" },
|
|
28
|
+
{ command: "quit" },
|
|
29
|
+
];
|
|
30
|
+
function tokenizeShellInput(input) {
|
|
31
|
+
const tokens = [];
|
|
32
|
+
let current = "";
|
|
33
|
+
let activeQuote = null;
|
|
34
|
+
let escaping = false;
|
|
35
|
+
let tokenStarted = false;
|
|
36
|
+
for (const character of input) {
|
|
37
|
+
if (escaping) {
|
|
38
|
+
current += character;
|
|
39
|
+
escaping = false;
|
|
40
|
+
tokenStarted = true;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (character === "\\") {
|
|
44
|
+
escaping = true;
|
|
45
|
+
tokenStarted = true;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (activeQuote) {
|
|
49
|
+
if (character === activeQuote) {
|
|
50
|
+
activeQuote = null;
|
|
51
|
+
tokenStarted = true;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
current += character;
|
|
55
|
+
tokenStarted = true;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (character === '"' || character === "'") {
|
|
59
|
+
activeQuote = character;
|
|
60
|
+
tokenStarted = true;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (/\s/.test(character)) {
|
|
64
|
+
if (tokenStarted) {
|
|
65
|
+
tokens.push(current);
|
|
66
|
+
current = "";
|
|
67
|
+
tokenStarted = false;
|
|
68
|
+
}
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
current += character;
|
|
72
|
+
tokenStarted = true;
|
|
73
|
+
}
|
|
74
|
+
if (escaping) {
|
|
75
|
+
current += "\\";
|
|
76
|
+
}
|
|
77
|
+
if (tokenStarted) {
|
|
78
|
+
tokens.push(current);
|
|
79
|
+
}
|
|
80
|
+
return tokens;
|
|
81
|
+
}
|
|
82
|
+
export function parseShellInput(input) {
|
|
83
|
+
const trimmed = input.trim();
|
|
84
|
+
if (!trimmed)
|
|
85
|
+
return null;
|
|
86
|
+
const normalized = trimmed.startsWith("/") ? trimmed.slice(1) : trimmed;
|
|
87
|
+
const [command, ...args] = tokenizeShellInput(normalized);
|
|
88
|
+
if (!command)
|
|
89
|
+
return null;
|
|
90
|
+
return {
|
|
91
|
+
command: command.toLowerCase(),
|
|
92
|
+
args,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export function getShellCompletionCandidates(input) {
|
|
96
|
+
const trimmedStart = input.trimStart();
|
|
97
|
+
const leadingWhitespace = input.slice(0, input.length - trimmedStart.length);
|
|
98
|
+
const usesSlash = trimmedStart.startsWith("/");
|
|
99
|
+
const normalizedInput = usesSlash ? trimmedStart.slice(1) : trimmedStart;
|
|
100
|
+
const hasTrailingSpace = /\s$/.test(normalizedInput);
|
|
101
|
+
const tokens = normalizedInput.trim().length > 0
|
|
102
|
+
? tokenizeShellInput(normalizedInput.trim()).map((token) => token.toLowerCase())
|
|
103
|
+
: [];
|
|
104
|
+
const prefix = `${leadingWhitespace}${usesSlash ? "/" : ""}`;
|
|
105
|
+
if (tokens.length === 0) {
|
|
106
|
+
return SHELL_COMPLETIONS.map((entry) => `${prefix}${entry.command}`);
|
|
107
|
+
}
|
|
108
|
+
if (tokens.length === 1 && !hasTrailingSpace) {
|
|
109
|
+
return SHELL_COMPLETIONS
|
|
110
|
+
.map((entry) => entry.command)
|
|
111
|
+
.filter((command) => command.startsWith(tokens[0]))
|
|
112
|
+
.map((command) => `${prefix}${command}`);
|
|
113
|
+
}
|
|
114
|
+
if (tokens.length > 2) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
const command = tokens[0];
|
|
118
|
+
const completion = SHELL_COMPLETIONS.find((entry) => entry.command === command);
|
|
119
|
+
if (!completion?.args) {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
const argPrefix = hasTrailingSpace ? "" : tokens[1] ?? "";
|
|
123
|
+
return completion.args
|
|
124
|
+
.filter((arg) => arg.startsWith(argPrefix))
|
|
125
|
+
.map((arg) => `${prefix}${completion.command} ${arg}`);
|
|
126
|
+
}
|
|
127
|
+
function clearTerminal() {
|
|
128
|
+
process.stdout.write("\x1Bc");
|
|
129
|
+
}
|
|
130
|
+
function printShellHelp() {
|
|
131
|
+
process.stdout.write(`${SHELL_HELP_TEXT}\n`);
|
|
132
|
+
}
|
|
133
|
+
export async function runInteractiveShell(client, cwd, initialFlags) {
|
|
134
|
+
let shellFlags = { ...initialFlags };
|
|
135
|
+
let session = await initializeInteractiveSession(client, cwd, shellFlags);
|
|
136
|
+
printInteractiveSessionSummary(session);
|
|
137
|
+
while (true) {
|
|
138
|
+
let input;
|
|
139
|
+
try {
|
|
140
|
+
input = await readLine("apex> ", {
|
|
141
|
+
completer: getShellCompletionCandidates,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
process.stdout.write("\n");
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const parsed = parseShellInput(input);
|
|
149
|
+
if (!parsed) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
let shouldContinue;
|
|
153
|
+
try {
|
|
154
|
+
shouldContinue = await runShellCommand(client, cwd, parsed, shellFlags, session);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
process.stderr.write(`${formatApiError(error)}\n`);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (shouldContinue === false) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (shouldContinue.flags) {
|
|
164
|
+
shellFlags = shouldContinue.flags;
|
|
165
|
+
}
|
|
166
|
+
if (shouldContinue.session) {
|
|
167
|
+
session = shouldContinue.session;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function runShellCommand(client, cwd, parsed, shellFlags, session) {
|
|
172
|
+
switch (parsed.command) {
|
|
173
|
+
case "help":
|
|
174
|
+
printShellHelp();
|
|
175
|
+
return {};
|
|
176
|
+
case "credits":
|
|
177
|
+
await commandCredits(client, cwd, shellFlags);
|
|
178
|
+
return {};
|
|
179
|
+
case "scan": {
|
|
180
|
+
const mode = parsed.args[0];
|
|
181
|
+
if (mode && !["standard", "ultra"].includes(mode)) {
|
|
182
|
+
process.stderr.write("Usage: /scan [standard|ultra]\n");
|
|
183
|
+
return {};
|
|
184
|
+
}
|
|
185
|
+
const result = await commandScan(client, cwd, mode ? withFlag(shellFlags, "mode", mode) : shellFlags);
|
|
186
|
+
return {
|
|
187
|
+
session: {
|
|
188
|
+
...session,
|
|
189
|
+
company: result.company,
|
|
190
|
+
workspaceName: result.workspaceName,
|
|
191
|
+
sources: result.sources,
|
|
192
|
+
binding: result.binding,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
case "status":
|
|
197
|
+
await commandStatus(client, cwd, shellFlags);
|
|
198
|
+
return {};
|
|
199
|
+
case "scans":
|
|
200
|
+
await commandScans(client, cwd, shellFlags);
|
|
201
|
+
return {};
|
|
202
|
+
case "findings": {
|
|
203
|
+
if (parsed.args.length > 1) {
|
|
204
|
+
process.stderr.write("Usage: /findings [scan-id]\n");
|
|
205
|
+
return {};
|
|
206
|
+
}
|
|
207
|
+
await commandFindings(client, cwd, parsed.args[0]
|
|
208
|
+
? withFlag(shellFlags, "scan", parsed.args[0])
|
|
209
|
+
: shellFlags);
|
|
210
|
+
return {};
|
|
211
|
+
}
|
|
212
|
+
case "export": {
|
|
213
|
+
if (parsed.args.length > 1) {
|
|
214
|
+
process.stderr.write("Usage: /export [scan-id]\n");
|
|
215
|
+
return {};
|
|
216
|
+
}
|
|
217
|
+
await commandExportFindings(client, cwd, parsed.args[0]
|
|
218
|
+
? withFlag(shellFlags, "scan", parsed.args[0])
|
|
219
|
+
: shellFlags);
|
|
220
|
+
return {};
|
|
221
|
+
}
|
|
222
|
+
case "workspaces":
|
|
223
|
+
await commandWorkspaces(client, cwd, shellFlags);
|
|
224
|
+
return {};
|
|
225
|
+
case "cancel-scan":
|
|
226
|
+
case "cancel": {
|
|
227
|
+
if (parsed.args.length > 1) {
|
|
228
|
+
process.stderr.write("Usage: /cancel-scan [scan-id]\n");
|
|
229
|
+
return {};
|
|
230
|
+
}
|
|
231
|
+
await commandCancelScan(client, cwd, shellFlags, parsed.args[0] ?? null);
|
|
232
|
+
return {};
|
|
233
|
+
}
|
|
234
|
+
case "doctor":
|
|
235
|
+
await commandDoctor(client, cwd, shellFlags);
|
|
236
|
+
return {};
|
|
237
|
+
case "update":
|
|
238
|
+
await commandUpdate(shellFlags);
|
|
239
|
+
return false;
|
|
240
|
+
case "logout":
|
|
241
|
+
await commandLogout(client, shellFlags);
|
|
242
|
+
return false;
|
|
243
|
+
case "repos":
|
|
244
|
+
printSourceList(session.sources);
|
|
245
|
+
return {};
|
|
246
|
+
case "workspace": {
|
|
247
|
+
if (parsed.args.length === 0) {
|
|
248
|
+
await commandWorkspace(cwd, shellFlags);
|
|
249
|
+
return {};
|
|
250
|
+
}
|
|
251
|
+
if (parsed.args[0] === "use") {
|
|
252
|
+
const workspaceRef = parsed.args.slice(1).join(" ").trim();
|
|
253
|
+
if (!workspaceRef) {
|
|
254
|
+
process.stderr.write("Usage: /workspace use <workspace-id|prefix|name>\n");
|
|
255
|
+
return {};
|
|
256
|
+
}
|
|
257
|
+
const result = await commandWorkspaceUse(client, cwd, shellFlags, workspaceRef);
|
|
258
|
+
return {
|
|
259
|
+
session: {
|
|
260
|
+
...session,
|
|
261
|
+
company: result.company,
|
|
262
|
+
workspaceName: result.binding.workspaceName,
|
|
263
|
+
binding: result.binding,
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const workspaceName = parsed.args[0] === "name"
|
|
268
|
+
? parsed.args.slice(1).join(" ").trim()
|
|
269
|
+
: parsed.args.join(" ").trim();
|
|
270
|
+
if (!workspaceName) {
|
|
271
|
+
process.stderr.write("Usage: /workspace name <name>\n");
|
|
272
|
+
return {};
|
|
273
|
+
}
|
|
274
|
+
const nextFlags = withFlag(shellFlags, "workspace-name", workspaceName);
|
|
275
|
+
const nextSession = await initializeInteractiveSession(client, cwd, nextFlags);
|
|
276
|
+
printWorkspaceDetails(nextSession);
|
|
277
|
+
return {
|
|
278
|
+
flags: nextFlags,
|
|
279
|
+
session: nextSession,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
case "company": {
|
|
283
|
+
if (parsed.args.length === 0) {
|
|
284
|
+
printCompanyDetails(session);
|
|
285
|
+
return {};
|
|
286
|
+
}
|
|
287
|
+
const companyRef = parsed.args.join(" ").trim();
|
|
288
|
+
const nextFlags = withFlag(shellFlags, "company", companyRef);
|
|
289
|
+
const nextSession = await initializeInteractiveSession(client, cwd, nextFlags);
|
|
290
|
+
printCompanyDetails(nextSession);
|
|
291
|
+
return {
|
|
292
|
+
flags: nextFlags,
|
|
293
|
+
session: nextSession,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
case "connect": {
|
|
297
|
+
const provider = parsed.args[0];
|
|
298
|
+
if (provider !== "github" && provider !== "gitlab") {
|
|
299
|
+
process.stderr.write("Usage: /connect <github|gitlab>\n");
|
|
300
|
+
return {};
|
|
301
|
+
}
|
|
302
|
+
await commandConnect(client, cwd, shellFlags, provider);
|
|
303
|
+
const nextSession = await initializeInteractiveSession(client, cwd, shellFlags);
|
|
304
|
+
return { session: nextSession };
|
|
305
|
+
}
|
|
306
|
+
case "open":
|
|
307
|
+
await openCurrentApexView(client, cwd, shellFlags);
|
|
308
|
+
return {};
|
|
309
|
+
case "clear":
|
|
310
|
+
clearTerminal();
|
|
311
|
+
printInteractiveSessionSummary(session);
|
|
312
|
+
return {};
|
|
313
|
+
case "exit":
|
|
314
|
+
case "quit":
|
|
315
|
+
return false;
|
|
316
|
+
default:
|
|
317
|
+
process.stderr.write(`Unknown command: ${parsed.command}. Type /help for available commands.\n`);
|
|
318
|
+
return {};
|
|
319
|
+
}
|
|
320
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|