@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/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 env = existing?.env;
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
- normalizedEnv === 0);
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", ["mcp", "add", MCP_SERVER_NAME, "--", launch.command, ...launch.args]);
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 runShellCommand(client, cwd, parsed, shellFlags, session);
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;