@cantinasecurity/apex-cli 0.1.10 → 0.1.11
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 +3 -0
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.mcp.claude.json +6 -2
- package/.mcp.codex.json +6 -2
- package/MARKETPLACE.md +1 -1
- package/README.md +80 -7
- package/dist/apex.js +21 -3
- package/dist/api-client.js +5 -0
- package/dist/commands.js +36 -0
- package/dist/config.js +4 -0
- package/dist/help.js +6 -0
- package/dist/mcp.js +101 -24
- package/dist/setup.js +52 -5
- package/dist/shell.js +27 -2
- package/dist/telemetry.js +755 -0
- package/package.json +1 -1
- package/skills/apex-cli/SKILL.md +3 -0
package/dist/setup.js
CHANGED
|
@@ -9,6 +9,8 @@ import { logLine, printJson } from "./output.js";
|
|
|
9
9
|
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
10
|
const CODEX_SKILL_NAME = "apex-cli";
|
|
11
11
|
const MCP_SERVER_NAME = "apex";
|
|
12
|
+
const MCP_CLIENT_ENV_NAME = "APEX_MCP_CLIENT";
|
|
13
|
+
const MCP_CLIENT_INTEGRATION_ENV_NAME = "APEX_CLIENT_INTEGRATION";
|
|
12
14
|
const execFile = promisify(execFileCallback);
|
|
13
15
|
const SETUP_CLIENTS = ["codex", "claude", "copilot"];
|
|
14
16
|
function quoteShellArg(value) {
|
|
@@ -96,21 +98,49 @@ function normalizeTools(value) {
|
|
|
96
98
|
}
|
|
97
99
|
return [];
|
|
98
100
|
}
|
|
101
|
+
function mcpClientEnv(client) {
|
|
102
|
+
return {
|
|
103
|
+
[MCP_CLIENT_ENV_NAME]: client,
|
|
104
|
+
[MCP_CLIENT_INTEGRATION_ENV_NAME]: client,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function normalizeEnvKeys(value) {
|
|
108
|
+
if (Array.isArray(value)) {
|
|
109
|
+
return value.filter((item) => typeof item === "string");
|
|
110
|
+
}
|
|
111
|
+
if (value && typeof value === "object") {
|
|
112
|
+
return Object.keys(value);
|
|
113
|
+
}
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
function envMatches(value, expected) {
|
|
117
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
const env = value;
|
|
121
|
+
return Object.entries(expected).every(([key, expectedValue]) => env[key] === expectedValue);
|
|
122
|
+
}
|
|
123
|
+
function envKeyListMatches(value, expected) {
|
|
124
|
+
const keys = normalizeEnvKeys(value);
|
|
125
|
+
return Object.keys(expected).every((key) => keys.includes(key));
|
|
126
|
+
}
|
|
99
127
|
function codexConfigMatches(existing, launch) {
|
|
100
128
|
if (!existing?.transport) {
|
|
101
129
|
return false;
|
|
102
130
|
}
|
|
131
|
+
const expectedEnv = mcpClientEnv("codex");
|
|
103
132
|
return (existing.transport.type === "stdio" &&
|
|
104
133
|
existing.transport.command === launch.command &&
|
|
105
|
-
JSON.stringify(normalizeArgs(existing.transport.args)) === JSON.stringify(launch.args)
|
|
134
|
+
JSON.stringify(normalizeArgs(existing.transport.args)) === JSON.stringify(launch.args) &&
|
|
135
|
+
(envMatches(existing.transport.env, expectedEnv) ||
|
|
136
|
+
envKeyListMatches(existing.transport.env_vars, expectedEnv)));
|
|
106
137
|
}
|
|
107
138
|
function claudeConfigMatches(existing, launch) {
|
|
108
|
-
const
|
|
109
|
-
const normalizedEnv = env && typeof env === "object" && !Array.isArray(env) ? Object.keys(env).length : 0;
|
|
139
|
+
const expectedEnv = mcpClientEnv("claude");
|
|
110
140
|
return (existing?.type === "stdio" &&
|
|
111
141
|
existing.command === launch.command &&
|
|
112
142
|
JSON.stringify(normalizeArgs(existing.args)) === JSON.stringify(launch.args) &&
|
|
113
|
-
|
|
143
|
+
envMatches(existing.env, expectedEnv));
|
|
114
144
|
}
|
|
115
145
|
function copilotConfigMatches(existing, launch) {
|
|
116
146
|
const env = existing?.env;
|
|
@@ -174,12 +204,24 @@ async function readCopilotUserMcpConfig() {
|
|
|
174
204
|
}
|
|
175
205
|
async function configureCodex(launch) {
|
|
176
206
|
const existing = await readCodexMcpConfig();
|
|
207
|
+
const env = mcpClientEnv("codex");
|
|
177
208
|
const mcpStatus = existing === null
|
|
178
209
|
? "installed"
|
|
179
210
|
: codexConfigMatches(existing, launch)
|
|
180
211
|
? "unchanged"
|
|
181
212
|
: "updated";
|
|
182
|
-
await execText("codex", [
|
|
213
|
+
await execText("codex", [
|
|
214
|
+
"mcp",
|
|
215
|
+
"add",
|
|
216
|
+
MCP_SERVER_NAME,
|
|
217
|
+
"--env",
|
|
218
|
+
`${MCP_CLIENT_ENV_NAME}=${env[MCP_CLIENT_ENV_NAME]}`,
|
|
219
|
+
"--env",
|
|
220
|
+
`${MCP_CLIENT_INTEGRATION_ENV_NAME}=${env[MCP_CLIENT_INTEGRATION_ENV_NAME]}`,
|
|
221
|
+
"--",
|
|
222
|
+
launch.command,
|
|
223
|
+
...launch.args,
|
|
224
|
+
]);
|
|
183
225
|
const skillSource = path.join(PACKAGE_ROOT, "skills", CODEX_SKILL_NAME, "SKILL.md");
|
|
184
226
|
const skillTarget = path.join(getCodexHome(), "skills", CODEX_SKILL_NAME, "SKILL.md");
|
|
185
227
|
const skillContent = await readFile(skillSource, "utf8");
|
|
@@ -203,6 +245,7 @@ async function configureCodex(launch) {
|
|
|
203
245
|
}
|
|
204
246
|
async function configureClaude(cwd, launch) {
|
|
205
247
|
const existing = await readClaudeUserMcpConfig();
|
|
248
|
+
const env = mcpClientEnv("claude");
|
|
206
249
|
const mcpStatus = existing === null
|
|
207
250
|
? "installed"
|
|
208
251
|
: claudeConfigMatches(existing, launch)
|
|
@@ -216,6 +259,10 @@ async function configureClaude(cwd, launch) {
|
|
|
216
259
|
"add",
|
|
217
260
|
"--scope",
|
|
218
261
|
"user",
|
|
262
|
+
"-e",
|
|
263
|
+
`${MCP_CLIENT_ENV_NAME}=${env[MCP_CLIENT_ENV_NAME]}`,
|
|
264
|
+
"-e",
|
|
265
|
+
`${MCP_CLIENT_INTEGRATION_ENV_NAME}=${env[MCP_CLIENT_INTEGRATION_ENV_NAME]}`,
|
|
219
266
|
MCP_SERVER_NAME,
|
|
220
267
|
"--",
|
|
221
268
|
launch.command,
|
package/dist/shell.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { SHELL_HELP_TEXT } from "./help.js";
|
|
2
|
-
import { commandCancelScan, commandConnect, commandCredits, commandDoctor, commandExportFindings, commandFindingComment, commandFindingFeedback, commandFindingFixReview, commandFindings, commandLogout, commandScan, commandScans, commandStatus, commandUpdate, commandWorkspace, commandWorkspaceUse, commandWorkspaces, initializeInteractiveSession, openCurrentApexView, } from "./commands.js";
|
|
2
|
+
import { commandCancelScan, commandConnect, commandCredits, commandDoctor, commandExportFindings, commandFindingComment, commandFindingFeedback, commandFindingFixReview, commandFindings, commandLogout, commandScan, commandScans, commandStatus, commandTelemetry, commandUpdate, commandWorkspace, commandWorkspaceUse, commandWorkspaces, initializeInteractiveSession, openCurrentApexView, } from "./commands.js";
|
|
3
3
|
import { withFlag } from "./args.js";
|
|
4
4
|
import { printCompanyDetails, printInteractiveSessionSummary, printSourceList, printWorkspaceDetails, } from "./output.js";
|
|
5
5
|
import { readLine } from "./prompt.js";
|
|
6
6
|
import { formatApiError } from "./api-client.js";
|
|
7
|
+
import { createShellTelemetryInvocation, emitInvocationCompleted, emitInvocationStarted, withTelemetryContext, } from "./telemetry.js";
|
|
7
8
|
const SHELL_COMPLETIONS = [
|
|
8
9
|
{ command: "credits" },
|
|
9
10
|
{ command: "scan", args: ["standard", "audit", "ultra", "pr"] },
|
|
@@ -15,6 +16,7 @@ const SHELL_COMPLETIONS = [
|
|
|
15
16
|
{ command: "cancel" },
|
|
16
17
|
{ command: "status" },
|
|
17
18
|
{ command: "doctor" },
|
|
19
|
+
{ command: "telemetry", args: ["status", "enable", "disable"] },
|
|
18
20
|
{ command: "update" },
|
|
19
21
|
{ command: "logout" },
|
|
20
22
|
{ command: "repos" },
|
|
@@ -151,7 +153,7 @@ export async function runInteractiveShell(client, cwd, initialFlags) {
|
|
|
151
153
|
}
|
|
152
154
|
let shouldContinue;
|
|
153
155
|
try {
|
|
154
|
-
shouldContinue = await
|
|
156
|
+
shouldContinue = await runShellCommandWithTelemetry(client, cwd, parsed, shellFlags, session);
|
|
155
157
|
}
|
|
156
158
|
catch (error) {
|
|
157
159
|
process.stderr.write(`${formatApiError(error)}\n`);
|
|
@@ -168,6 +170,21 @@ export async function runInteractiveShell(client, cwd, initialFlags) {
|
|
|
168
170
|
}
|
|
169
171
|
}
|
|
170
172
|
}
|
|
173
|
+
async function runShellCommandWithTelemetry(client, cwd, parsed, shellFlags, session) {
|
|
174
|
+
const invocation = createShellTelemetryInvocation(parsed, shellFlags);
|
|
175
|
+
emitInvocationStarted(invocation);
|
|
176
|
+
return withTelemetryContext(invocation, async () => {
|
|
177
|
+
try {
|
|
178
|
+
const result = await runShellCommand(client, cwd, parsed, shellFlags, session);
|
|
179
|
+
emitInvocationCompleted(invocation);
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
emitInvocationCompleted(invocation, error);
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
171
188
|
async function runShellCommand(client, cwd, parsed, shellFlags, session) {
|
|
172
189
|
switch (parsed.command) {
|
|
173
190
|
case "help":
|
|
@@ -300,6 +317,14 @@ async function runShellCommand(client, cwd, parsed, shellFlags, session) {
|
|
|
300
317
|
case "doctor":
|
|
301
318
|
await commandDoctor(client, cwd, shellFlags);
|
|
302
319
|
return {};
|
|
320
|
+
case "telemetry": {
|
|
321
|
+
if (parsed.args.length > 1) {
|
|
322
|
+
process.stderr.write("Usage: /telemetry [status|enable|disable]\n");
|
|
323
|
+
return {};
|
|
324
|
+
}
|
|
325
|
+
await commandTelemetry(shellFlags, parsed.args[0] ?? null);
|
|
326
|
+
return {};
|
|
327
|
+
}
|
|
303
328
|
case "update":
|
|
304
329
|
await commandUpdate(shellFlags);
|
|
305
330
|
return false;
|