@zhuixin/cli 0.2.2 → 0.2.4

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 CHANGED
@@ -31,7 +31,7 @@ var import_metadata = require("@zhuixin/metadata");
31
31
  var import_shared = require("@zhuixin/shared");
32
32
 
33
33
  // package.json
34
- var version = "0.2.2";
34
+ var version = "0.2.4";
35
35
 
36
36
  // src/mcp-config.ts
37
37
  var import_node_fs = __toESM(require("fs"));
@@ -48,7 +48,7 @@ function object(value) {
48
48
  function writeMcpConfig(filename, server) {
49
49
  const extension = import_node_path.default.extname(filename).toLowerCase();
50
50
  if (![".json", ".toml"].includes(extension)) {
51
- throw new Error("--write-config \u53EA\u652F\u6301 .json \u6216 .toml \u6587\u4EF6\u3002");
51
+ throw new Error("--write-config accepts only .json or .toml files.");
52
52
  }
53
53
  const original = import_node_fs.default.existsSync(filename) ? import_node_fs.default.readFileSync(filename, "utf8") : void 0;
54
54
  const config = object(original?.trim() ? extension === ".json" ? JSON.parse(original) : (0, import_smol_toml.parse)(original) : {});
@@ -134,7 +134,7 @@ function parseGoals(value) {
134
134
  if (invalid.length > 0) {
135
135
  throw new import_metadata.CoreFailure(
136
136
  "INVALID_INPUT",
137
- `\u672A\u77E5\u5206\u6790\u76EE\u6807\uFF1A${invalid.join(", ")}\u3002\u53EF\u9009\uFF1A${GOALS.join(", ")}`
137
+ `Unknown analysis goal: ${invalid.join(", ")}. Available: ${GOALS.join(", ")}`
138
138
  );
139
139
  }
140
140
  return goals;
@@ -149,7 +149,7 @@ function printError(error) {
149
149
  if (error.code === "AUTH_REQUIRED" || error.code === "KEYCHAIN_LOCKED") {
150
150
  console.error("Next: apptrace auth login");
151
151
  } else if (error.code === "LICENSE_REQUIRED") {
152
- console.error("Next: \u5728 iPhone App Store \u83B7\u53D6\u6216\u8D2D\u4E70\u8BE5\u5E94\u7528\u540E\u91CD\u8BD5\u3002");
152
+ console.error("Next: get or purchase the app in the App Store, then retry.");
153
153
  }
154
154
  return;
155
155
  }
@@ -265,7 +265,7 @@ function createEngineLauncher(workspaceRoot) {
265
265
  return launcher;
266
266
  }
267
267
  async function installTools(config, json = false) {
268
- if ((0, import_metadata.missingSetupTools)(config).length === 0) {
268
+ if (await (0, import_metadata.setupToolsReady)(config)) {
269
269
  const managedLauncher = import_node_path2.default.join(config.workspaceRoot, "tools", "bin", process.platform === "win32" ? "rea.cmd" : "rea");
270
270
  if (import_node_path2.default.resolve(config.analysisEnginePath) === managedLauncher) {
271
271
  createEngineLauncher(config.workspaceRoot);
@@ -276,7 +276,7 @@ async function installTools(config, json = false) {
276
276
  if (process.platform === "win32" && process.arch === "x64") {
277
277
  const script2 = import_node_path2.default.resolve(__dirname, "scripts/bootstrap-tools.ps1");
278
278
  if (!import_node_fs2.default.existsSync(script2)) {
279
- throw new import_metadata.CoreFailure("TOOL_INSTALLER_MISSING", `\u627E\u4E0D\u5230\u5B89\u88C5\u811A\u672C\uFF1A${script2}`);
279
+ throw new import_metadata.CoreFailure("TOOL_INSTALLER_MISSING", `Installer script not found: ${script2}`);
280
280
  }
281
281
  process.stderr.write(
282
282
  "Installing JDK, Ghidra and ipatool for Windows x64. This can take several minutes.\n"
@@ -293,13 +293,12 @@ async function installTools(config, json = false) {
293
293
  // PowerShell 7's inherited module paths can hide Windows PowerShell 5.1 modules.
294
294
  PSModulePath: import_node_path2.default.join(process.env.SystemRoot || "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "Modules"),
295
295
  APPTRACE_WORKSPACE_ROOT: config.workspaceRoot,
296
- APPTRACE_TOOLS_DIR: import_node_path2.default.join(config.workspaceRoot, "tools"),
297
- ...process.env.APPTRACE_IPATOOL_SHA256 ? { APPTRACE_IPATOOL_SHA256: process.env.APPTRACE_IPATOOL_SHA256 } : {}
296
+ APPTRACE_TOOLS_DIR: import_node_path2.default.join(config.workspaceRoot, "tools")
298
297
  }, (pid) => (0, import_metadata.trackInstallationChild)(config, pid));
299
298
  if (code2 !== 0) {
300
299
  throw new import_metadata.CoreFailure(
301
300
  "TOOL_INSTALL_FAILED",
302
- `\u5DE5\u5177\u5B89\u88C5\u5931\u8D25\uFF0C\u9000\u51FA\u7801\uFF1A${code2 ?? "unknown"}`
301
+ `Tool installation failed with exit code ${code2 ?? "unknown"}`
303
302
  );
304
303
  }
305
304
  const launcher2 = createEngineLauncher(config.workspaceRoot);
@@ -310,12 +309,12 @@ async function installTools(config, json = false) {
310
309
  if (process.platform !== "darwin" || process.arch !== "arm64") {
311
310
  throw new import_metadata.CoreFailure(
312
311
  "TOOL_INSTALL_UNSUPPORTED",
313
- "\u81EA\u52A8\u5B89\u88C5\u5668\u5F53\u524D\u652F\u6301 macOS arm64 \u548C Windows x64\uFF1B\u5176\u4ED6\u5E73\u53F0\u8BF7\u4F7F\u7528 APPTRACE_ENGINE_PATH \u548C APPTRACE_IPATOOL_PATH\u3002"
312
+ "The automatic installer supports macOS arm64 and Windows x64. On other platforms set APPTRACE_ENGINE_PATH and APPTRACE_IPATOOL_PATH."
314
313
  );
315
314
  }
316
315
  const script = import_node_path2.default.resolve(__dirname, "scripts/bootstrap-tools.sh");
317
316
  if (!import_node_fs2.default.existsSync(script)) {
318
- throw new import_metadata.CoreFailure("TOOL_INSTALLER_MISSING", `\u627E\u4E0D\u5230\u5B89\u88C5\u811A\u672C\uFF1A${script}`);
317
+ throw new import_metadata.CoreFailure("TOOL_INSTALLER_MISSING", `Installer script not found: ${script}`);
319
318
  }
320
319
  process.stderr.write(
321
320
  "Installing JDK, Ghidra and ipatool. This can take several minutes.\n"
@@ -327,7 +326,7 @@ async function installTools(config, json = false) {
327
326
  if (code !== 0) {
328
327
  throw new import_metadata.CoreFailure(
329
328
  "TOOL_INSTALL_FAILED",
330
- `\u5DE5\u5177\u5B89\u88C5\u5931\u8D25\uFF0C\u9000\u51FA\u7801\uFF1A${code ?? "unknown"}`
329
+ `Tool installation failed with exit code ${code ?? "unknown"}`
331
330
  );
332
331
  }
333
332
  const launcher = createEngineLauncher(config.workspaceRoot);
@@ -336,7 +335,7 @@ async function installTools(config, json = false) {
336
335
  }
337
336
  async function setup(json, args) {
338
337
  const configPath = option(args, "--write-config");
339
- if (hasFlag(args, "--install-skill") && !configPath) throw new import_metadata.CoreFailure("CONFIG_REQUIRED", "\u5B89\u88C5 Skill \u65F6\u8BF7\u540C\u65F6\u6307\u5B9A --write-config\u3002");
338
+ if (hasFlag(args, "--install-skill") && !configPath) throw new import_metadata.CoreFailure("CONFIG_REQUIRED", "--install-skill requires --write-config.");
340
339
  let config = (0, import_metadata.loadConfig)();
341
340
  if (hasFlag(args, "--install")) {
342
341
  await (0, import_metadata.runInstallation)(config, () => installTools(config, json));
@@ -371,30 +370,30 @@ async function setup(json, args) {
371
370
  `);
372
371
  return;
373
372
  }
374
- console.log(`\u914D\u7F6E\u5DF2\u5199\u5165\uFF1A${absolute}`);
373
+ console.log(`MCP config written: ${absolute}`);
375
374
  }
376
375
  const escape = (value) => value.replace(/\\/g, "\\\\");
377
376
  const envDisplay = Object.entries(env).map(([key, value]) => `${key} = "${escape(value)}"`).join(", ");
378
377
  console.log(`
379
- MCP \u914D\u7F6E\uFF08\u5BA2\u6237\u7AEF\u8BFB\u53D6\u8FD9\u4E00\u6BB5\uFF0C\u952E\u540D\u4FDD\u6301\u82F1\u6587\uFF09\uFF1A
378
+ MCP configuration:
380
379
 
381
380
  [mcp_servers.apptrace]
382
381
  command = "${escape(process.execPath)}"
383
382
  args = ["${escape(serverEntry)}"]
384
383
  env = { ${envDisplay} }
385
384
 
386
- \u91CD\u542F Agent \u5BA2\u6237\u7AEF\u540E\u751F\u6548\u3002\u52A0 --restart-client \u53EF\u4EE5\u8BA9 AppTrace \u76F4\u63A5\u91CD\u542F\u5B83\u3002
385
+ Restart the agent client to apply. Add --restart-client to let AppTrace restart it.
387
386
  `);
388
387
  if (configPath && hasFlag(args, "--restart-client")) {
389
388
  const client = clientForConfig(import_node_path2.default.resolve(configPath));
390
389
  if (!client.app) {
391
390
  throw new import_metadata.CoreFailure(
392
391
  "RESTART_UNSUPPORTED",
393
- `${client.name} \u9700\u8981\u4F60\u81EA\u5DF1\u91CD\u542F\uFF08\u9000\u51FA\u540E\u91CD\u65B0\u6253\u5F00\uFF09\u3002`
392
+ `${client.name} has no desktop process to restart; quit and reopen it yourself.`
394
393
  );
395
394
  }
396
395
  await restartClient(client.app);
397
- console.log(`\u5DF2\u91CD\u542F ${client.name}\u3002`);
396
+ console.log(`Restarted ${client.name}.`);
398
397
  }
399
398
  }
400
399
  function clientForConfig(configPath) {
@@ -405,13 +404,13 @@ function clientForConfig(configPath) {
405
404
  if (normalized.includes("/.codex/")) return { name: "Codex", app: "Codex" };
406
405
  if (normalized.includes("/.cursor/")) return { name: "Cursor", app: "Cursor" };
407
406
  if (normalized.endsWith(".mcp.json")) return { name: "Claude Code" };
408
- return { name: "Agent \u5BA2\u6237\u7AEF" };
407
+ return { name: "Agent client" };
409
408
  }
410
409
  async function restartClient(app) {
411
410
  if (process.platform !== "darwin") {
412
411
  throw new import_metadata.CoreFailure(
413
412
  "RESTART_UNSUPPORTED",
414
- "\u5F53\u524D\u7CFB\u7EDF\u4E0D\u652F\u6301\u81EA\u52A8\u91CD\u542F\u5BA2\u6237\u7AEF\uFF0C\u8BF7\u624B\u52A8\u9000\u51FA\u540E\u91CD\u65B0\u6253\u5F00\u3002"
413
+ "Automatic client restart is only supported on macOS; quit and reopen the client yourself."
415
414
  );
416
415
  }
417
416
  await interactive("/usr/bin/osascript", ["-e", `quit app "${app}"`]);
@@ -431,22 +430,25 @@ async function authLogin() {
431
430
  if (!ipatool) {
432
431
  throw new import_metadata.CoreFailure(
433
432
  "TOOL_NOT_INSTALLED",
434
- `\u627E\u4E0D\u5230 ipatool\uFF08${config.ipatoolPath}\uFF09\u3002\u8BF7\u5148\u8FD0\u884C apptrace setup --install\uFF0C\u6210\u529F\u540E\u518D\u8FD0\u884C apptrace auth login\u3002`
433
+ `ipatool not found (${config.ipatoolPath}). Run apptrace setup --install first, then apptrace auth login.`
435
434
  );
436
435
  }
436
+ await (0, import_metadata.requireBackgroundCredentials)(config);
437
437
  const code = await interactive(ipatool, ["auth", "login"]);
438
438
  if (code !== 0) {
439
439
  throw new import_metadata.CoreFailure(
440
440
  "AUTH_FAILED",
441
- `ipatool \u767B\u5F55\u6CA1\u6709\u6210\u529F\uFF0C\u9000\u51FA\u7801\uFF1A${code ?? "unknown"}`
441
+ `ipatool login failed with exit code ${code ?? "unknown"}`
442
442
  );
443
443
  }
444
- await authStatus();
444
+ await authStatus(true);
445
+ await (0, import_metadata.recoverAnalysisTasks)(config, __filename);
445
446
  }
446
- async function authStatus() {
447
+ async function authStatus(requireAuthenticated = false) {
447
448
  await withCore(async (core) => {
448
449
  const status = await core.handle(import_shared.CHANNELS.accountStatus, { force: true });
449
450
  printJson(status);
451
+ if (requireAuthenticated && !status.authenticated) throw new import_metadata.CoreFailure(status.errorCode ?? "BACKGROUND_AUTH_FAILED", status.message ?? "Background credential verification failed after sign-in.");
450
452
  });
451
453
  }
452
454
  async function addTarget(source) {
@@ -471,7 +473,7 @@ async function analyze(source, args) {
471
473
  const force = hasFlag(args, "--force");
472
474
  const targetId = option(args, "--target");
473
475
  if (!source && !targetId) {
474
- throw new import_metadata.CoreFailure("SOURCE_REQUIRED", "\u7F3A\u5C11\u5E94\u7528\u94FE\u63A5\u3001ID \u6216 --target\u3002");
476
+ throw new import_metadata.CoreFailure("SOURCE_REQUIRED", "Missing app link, ID, or --target.");
475
477
  }
476
478
  await withCore(async (core) => {
477
479
  const service = new import_metadata.AppAnalysisService(core);
@@ -556,7 +558,7 @@ async function nativeCommand(action, args) {
556
558
  if (!targetId || !value) {
557
559
  throw new import_metadata.CoreFailure(
558
560
  "INVALID_INPUT",
559
- "\u7528\u6CD5\uFF1Aapptrace native <search|decompile> <target-id> <query-or-function>"
561
+ "Usage: apptrace native <search|decompile> <target-id> <query-or-function>"
560
562
  );
561
563
  }
562
564
  await withCore(async (core) => {
@@ -581,7 +583,7 @@ async function nativeCommand(action, args) {
581
583
  }
582
584
  throw new import_metadata.CoreFailure(
583
585
  "INVALID_INPUT",
584
- "\u539F\u751F\u547D\u4EE4\u53EA\u652F\u6301 search \u6216 decompile\u3002"
586
+ "Native commands support only search or decompile."
585
587
  );
586
588
  });
587
589
  }
@@ -604,7 +606,7 @@ async function compare(targetId, leftArtifactId, rightArtifactId, args) {
604
606
  if (!targetId || !leftArtifactId || !rightArtifactId) {
605
607
  throw new import_metadata.CoreFailure(
606
608
  "INVALID_INPUT",
607
- "\u7528\u6CD5\uFF1Aapptrace compare <target-id> <left-artifact-id> <right-artifact-id>"
609
+ "Usage: apptrace compare <target-id> <left-artifact-id> <right-artifact-id>"
608
610
  );
609
611
  }
610
612
  await withCore(async (core) => {
@@ -633,8 +635,12 @@ async function main() {
633
635
  usage();
634
636
  return;
635
637
  }
638
+ if (command === "analysis-worker") {
639
+ if (!subcommand || !/^[0-9a-f-]{36}$/.test(subcommand)) throw new import_metadata.CoreFailure("INVALID_INPUT", "Invalid analysis task ID.");
640
+ return (0, import_metadata.runAnalysisWorker)((0, import_metadata.loadConfig)(), subcommand, __filename);
641
+ }
636
642
  if (command === "setup-worker") {
637
- if (!subcommand || !/^[0-9a-f-]{36}$/.test(subcommand)) throw new import_metadata.CoreFailure("INVALID_INPUT", "\u65E0\u6548\u7684\u5B89\u88C5\u64CD\u4F5C\u6807\u8BC6\u3002");
643
+ if (!subcommand || !/^[0-9a-f-]{36}$/.test(subcommand)) throw new import_metadata.CoreFailure("INVALID_INPUT", "Invalid install operation id.");
638
644
  const config = (0, import_metadata.loadConfig)();
639
645
  return (0, import_metadata.runInstallation)(config, () => installTools(config), subcommand);
640
646
  }
@@ -657,7 +663,7 @@ async function main() {
657
663
  if (command === "targets") {
658
664
  if (subcommand === "add") {
659
665
  const source = positional(args);
660
- if (!source) throw new import_metadata.CoreFailure("SOURCE_REQUIRED", "\u7F3A\u5C11\u5E94\u7528\u94FE\u63A5\u6216 ID\u3002");
666
+ if (!source) throw new import_metadata.CoreFailure("SOURCE_REQUIRED", "Missing app link or ID.");
661
667
  return addTarget(source);
662
668
  }
663
669
  if (subcommand === "list") return listTargets();
@@ -668,30 +674,30 @@ async function main() {
668
674
  return analyze(source, analyzeArgs);
669
675
  }
670
676
  if (command === "report") {
671
- if (!subcommand) throw new import_metadata.CoreFailure("TARGET_REQUIRED", "\u7F3A\u5C11 target ID\u3002");
677
+ if (!subcommand) throw new import_metadata.CoreFailure("TARGET_REQUIRED", "Missing target ID.");
672
678
  return report(subcommand);
673
679
  }
674
680
  if (command === "jobs" && subcommand === "get") {
675
681
  const jobId = positional(args);
676
- if (!jobId) throw new import_metadata.CoreFailure("JOB_REQUIRED", "\u7F3A\u5C11 job ID\u3002");
682
+ if (!jobId) throw new import_metadata.CoreFailure("JOB_REQUIRED", "Missing job ID.");
677
683
  return getJob(jobId);
678
684
  }
679
685
  if (command === "search") {
680
- if (!subcommand) throw new import_metadata.CoreFailure("TARGET_REQUIRED", "\u7F3A\u5C11 target ID\u3002");
686
+ if (!subcommand) throw new import_metadata.CoreFailure("TARGET_REQUIRED", "Missing target ID.");
681
687
  return searchFindings(subcommand, args[0], args);
682
688
  }
683
689
  if (command === "native") return nativeCommand(subcommand, args);
684
690
  if (command === "evidence") {
685
- if (!subcommand) throw new import_metadata.CoreFailure("TARGET_REQUIRED", "\u7F3A\u5C11 target ID\u3002");
691
+ if (!subcommand) throw new import_metadata.CoreFailure("TARGET_REQUIRED", "Missing target ID.");
686
692
  return evidence(subcommand);
687
693
  }
688
694
  if (command === "engine" && subcommand === "install") {
689
695
  const file = args[0];
690
- if (!file) throw new import_metadata.CoreFailure("ENGINE_REQUIRED", "\u7F3A\u5C11 engine \u6587\u4EF6\u8DEF\u5F84\u3002");
696
+ if (!file) throw new import_metadata.CoreFailure("ENGINE_REQUIRED", "Missing engine file path.");
691
697
  return installEngine(file, args);
692
698
  }
693
699
  if (command === "compare") {
694
- if (!subcommand) throw new import_metadata.CoreFailure("TARGET_REQUIRED", "\u7F3A\u5C11 target ID\u3002");
700
+ if (!subcommand) throw new import_metadata.CoreFailure("TARGET_REQUIRED", "Missing target ID.");
695
701
  return compare(subcommand, args[0], args[1], args);
696
702
  }
697
703
  if (command === "capabilities") return capabilities(subcommand);
@@ -702,7 +708,7 @@ async function main() {
702
708
  return;
703
709
  }
704
710
  usage();
705
- throw new import_metadata.CoreFailure("UNKNOWN_COMMAND", `\u672A\u77E5\u547D\u4EE4\uFF1A${command}`);
711
+ throw new import_metadata.CoreFailure("UNKNOWN_COMMAND", `Unknown command: ${command}`);
706
712
  }
707
713
  void main().catch((error) => {
708
714
  printError(error);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../package.json","../src/mcp-config.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { spawn } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport {\n AppAnalysisService,\n CoreFailure,\n CoreServer,\n installEngineArtifact,\n VersionComparisonService,\n loadConfig,\n resolveTool,\n OnboardingService,\n runInstallation,\n missingSetupTools,\n trackInstallationChild,\n type AnalysisGoal,\n type AnalyzeAppResult\n} from \"@zhuixin/metadata\";\nimport {\n CHANNELS,\n type CapabilityDescriptor,\n type HealthInfo,\n type JobRecord,\n type TargetRecord\n} from \"@zhuixin/shared\";\n\nimport { version } from \"../package.json\";\nimport { writeMcpConfig } from \"./mcp-config.js\";\n\nconst GOALS: AnalysisGoal[] = [\n \"tech_stack\",\n \"feature_map\",\n \"network\",\n \"native_risk\",\n \"report\"\n];\n\nfunction usage(): void {\n console.log(`AppTrace ${version}\n\nUsage:\n apptrace setup [--json] [--install] [--write-config <path>] [--restart-client]\n apptrace doctor [--json]\n apptrace auth login\n apptrace auth status\n apptrace auth open\n apptrace setup --write-config <path> --install-skill\n apptrace targets add <app-store-url-or-id>\n apptrace targets list\n apptrace analyze [app-store-url-or-id] [--target <target-id>] [--goals ...] [--force]\n apptrace report <target-id>\n apptrace jobs get <job-id>\n apptrace search <target-id> [query] [--type route,module] [--limit 50]\n apptrace native search <target-id> <query> [--kind procedures|strings]\n apptrace native decompile <target-id> <function-name>\n apptrace evidence <target-id>\n apptrace compare <target-id> <left-artifact-id> <right-artifact-id> [--force]\n apptrace capabilities [target-id]\n apptrace engine install <engine-file> [--sha256 <digest>]\n apptrace mcp\n\nExamples:\n apptrace analyze https://apps.apple.com/cn/app/id1616124777\n apptrace analyze 1616124777 --goals tech_stack,feature_map,native_risk,report\n`);\n}\n\nfunction option(args: string[], name: string): string | undefined {\n const index = args.indexOf(name);\n if (index >= 0 && args[index + 1]) return args[index + 1];\n const prefix = `${name}=`;\n const inline = args.find((arg) => arg.startsWith(prefix));\n return inline ? inline.slice(prefix.length) : undefined;\n}\n\nfunction hasFlag(args: string[], name: string): boolean {\n return args.includes(name);\n}\n\nfunction positional(args: string[]): string | undefined {\n return args.find((arg) => !arg.startsWith(\"--\"));\n}\n\nfunction parseGoals(value: string | undefined): AnalysisGoal[] {\n if (!value) return [\"tech_stack\", \"feature_map\", \"report\"];\n const goals = value\n .split(\",\")\n .map((item) => item.trim())\n .filter(Boolean);\n const invalid = goals.filter(\n (goal): goal is string => !GOALS.includes(goal as AnalysisGoal)\n );\n if (invalid.length > 0) {\n throw new CoreFailure(\n \"INVALID_INPUT\",\n `未知分析目标:${invalid.join(\", \")}。可选:${GOALS.join(\", \")}`\n );\n }\n return goals as AnalysisGoal[];\n}\n\nfunction printJson(value: unknown): void {\n process.stdout.write(`${JSON.stringify(value, null, 2)}\\n`);\n}\n\nfunction printError(error: unknown): void {\n if (error instanceof CoreFailure) {\n console.error(`${error.code}: ${error.message}`);\n if (error.code === \"AUTH_REQUIRED\" || error.code === \"KEYCHAIN_LOCKED\") {\n console.error(\"Next: apptrace auth login\");\n } else if (error.code === \"LICENSE_REQUIRED\") {\n console.error(\"Next: 在 iPhone App Store 获取或购买该应用后重试。\");\n }\n return;\n }\n console.error(error instanceof Error ? error.stack ?? error.message : String(error));\n}\n\nfunction withCore<T>(operation: (core: CoreServer) => Promise<T>): Promise<T> {\n const core = new CoreServer({ config: loadConfig(), emit: () => {} });\n return operation(core).finally(() => core.close());\n}\n\nfunction interactive(\n command: string,\n args: string[],\n env?: NodeJS.ProcessEnv,\n onSpawn?: (pid: number) => void\n): Promise<number | null> {\n return new Promise((resolve, reject) => {\n const isWindowsCommand =\n process.platform === \"win32\" &&\n [\".cmd\", \".bat\"].includes(path.extname(command).toLowerCase());\n const child = isWindowsCommand\n ? spawn(\n process.env.ComSpec || \"cmd.exe\",\n [\"/d\", \"/s\", \"/c\", `\"${command}\" ${args.join(\" \")}`],\n {\n stdio: \"inherit\",\n windowsVerbatimArguments: true,\n env: env ? { ...process.env, ...env } : process.env\n }\n )\n : spawn(command, args, {\n stdio: \"inherit\",\n env: env ? { ...process.env, ...env } : process.env\n });\n if (child.pid && onSpawn) {\n try { onSpawn(child.pid); }\n catch (error) { child.kill(); reject(error); return; }\n }\n child.on(\"error\", reject);\n child.on(\"exit\", (code) => resolve(code));\n });\n}\n\nasync function doctor(json: boolean): Promise<void> {\n const config = loadConfig();\n await withCore(async (core) => {\n const health = (await core.handle(CHANNELS.health, {})) as HealthInfo;\n const capabilities = (await core.handle(\n CHANNELS.capabilitiesList,\n {}\n )) as CapabilityDescriptor[];\n const result = { config, health, capabilities };\n if (json) {\n printJson(result);\n return;\n }\n console.log(`workspace: ${health.workspaceRoot}`);\n console.log(`host: ${health.hostPlatform}`);\n console.log(`store: ${health.store}`);\n console.log(\"tools:\");\n for (const tool of health.tools ?? []) {\n console.log(\n ` ${tool.available ? \"OK \" : \"MISS\"} ${tool.label}: ${tool.path}`\n );\n }\n console.log(\"capabilities:\");\n for (const capability of capabilities) {\n console.log(\n ` ${capability.available ? \"OK \" : \"NO \"} ${capability.id}${\n capability.reason ? ` — ${capability.reason}` : \"\"\n }`\n );\n }\n });\n}\n\nfunction createEngineLauncher(workspaceRoot: string): string {\n const reaScript = require.resolve(\"rea-agents/scripts/rea.mjs\");\n const tools = path.join(workspaceRoot, \"tools\");\n const javaHome = path.join(tools, \"jdk\");\n const ghidraHome = path.join(tools, \"ghidra_12.1.4_PUBLIC\");\n const ghidraUserHome = path.join(workspaceRoot, \".ghidra-home\");\n fs.mkdirSync(path.join(tools, \"bin\"), { recursive: true, mode: 0o700 });\n fs.mkdirSync(ghidraUserHome, { recursive: true, mode: 0o700 });\n const launcher = path.join(\n tools,\n \"bin\",\n process.platform === \"win32\" ? \"rea.cmd\" : \"rea\"\n );\n if (process.platform === \"win32\") {\n fs.writeFileSync(\n launcher,\n [\n \"@echo off\",\n `set \"JAVA_HOME=${javaHome}\"`,\n `set \"GHIDRA_INSTALL_DIR=${ghidraHome}\"`,\n `set \"GHIDRA_USER_HOME=${ghidraUserHome}\"`,\n 'set \"PATH=%JAVA_HOME%\\\\bin;%PATH%\"',\n `\"${process.execPath}\" \"${reaScript}\" %*`,\n \"\"\n ].join(\"\\r\\n\"),\n { mode: 0o700 }\n );\n } else {\n fs.writeFileSync(\n launcher,\n [\n \"#!/usr/bin/env bash\",\n \"set -euo pipefail\",\n `export JAVA_HOME=${JSON.stringify(javaHome)}`,\n `export GHIDRA_INSTALL_DIR=${JSON.stringify(ghidraHome)}`,\n `export GHIDRA_USER_HOME=${JSON.stringify(ghidraUserHome)}`,\n 'export PATH=\"$JAVA_HOME/bin:$PATH\"',\n 'export JAVA_TOOL_OPTIONS=\"${JAVA_TOOL_OPTIONS:-} -Duser.home=$GHIDRA_USER_HOME\"',\n `exec ${JSON.stringify(process.execPath)} ${JSON.stringify(reaScript)} \"$@\"`,\n \"\"\n ].join(\"\\n\"),\n { mode: 0o700 }\n );\n }\n return launcher;\n}\n\nasync function installTools(\n config: ReturnType<typeof loadConfig>,\n json = false\n): Promise<void> {\n if (missingSetupTools(config).length === 0) {\n const managedLauncher = path.join(config.workspaceRoot, \"tools\", \"bin\", process.platform === \"win32\" ? \"rea.cmd\" : \"rea\");\n if (path.resolve(config.analysisEnginePath) === managedLauncher) {\n createEngineLauncher(config.workspaceRoot);\n }\n process.stderr.write(\"Tools already available; download skipped.\\n\");\n return;\n }\n if (process.platform === \"win32\" && process.arch === \"x64\") {\n const script = path.resolve(__dirname, \"scripts/bootstrap-tools.ps1\");\n if (!fs.existsSync(script)) {\n throw new CoreFailure(\"TOOL_INSTALLER_MISSING\", `找不到安装脚本:${script}`);\n }\n process.stderr.write(\n \"Installing JDK, Ghidra and ipatool for Windows x64. This can take several minutes.\\n\"\n );\n const code = await interactive(\"powershell.exe\", [\n \"-NoProfile\",\n \"-ExecutionPolicy\",\n \"Bypass\",\n \"-File\",\n script,\n \"-WorkspaceRoot\",\n config.workspaceRoot\n ], {\n // PowerShell 7's inherited module paths can hide Windows PowerShell 5.1 modules.\n PSModulePath: path.join(process.env.SystemRoot || \"C:\\\\Windows\", \"System32\", \"WindowsPowerShell\", \"v1.0\", \"Modules\"),\n APPTRACE_WORKSPACE_ROOT: config.workspaceRoot,\n APPTRACE_TOOLS_DIR: path.join(config.workspaceRoot, \"tools\"),\n ...(process.env.APPTRACE_IPATOOL_SHA256\n ? { APPTRACE_IPATOOL_SHA256: process.env.APPTRACE_IPATOOL_SHA256 }\n : {})\n }, (pid) => trackInstallationChild(config, pid));\n if (code !== 0) {\n throw new CoreFailure(\n \"TOOL_INSTALL_FAILED\",\n `工具安装失败,退出码:${code ?? \"unknown\"}`\n );\n }\n const launcher = createEngineLauncher(config.workspaceRoot);\n process.stderr.write(`Analysis engine launcher: ${launcher}\\n`);\n return;\n }\n if (process.platform !== \"darwin\" || process.arch !== \"arm64\") {\n throw new CoreFailure(\n \"TOOL_INSTALL_UNSUPPORTED\",\n \"自动安装器当前支持 macOS arm64 和 Windows x64;其他平台请使用 APPTRACE_ENGINE_PATH 和 APPTRACE_IPATOOL_PATH。\"\n );\n }\n const script = path.resolve(__dirname, \"scripts/bootstrap-tools.sh\");\n if (!fs.existsSync(script)) {\n throw new CoreFailure(\"TOOL_INSTALLER_MISSING\", `找不到安装脚本:${script}`);\n }\n process.stderr.write(\n \"Installing JDK, Ghidra and ipatool. This can take several minutes.\\n\"\n );\n const code = await interactive(\"/bin/bash\", [script], {\n APPTRACE_WORKSPACE_ROOT: config.workspaceRoot,\n APPTRACE_TOOLS_DIR: path.join(config.workspaceRoot, \"tools\")\n }, (pid) => trackInstallationChild(config, pid));\n if (code !== 0) {\n throw new CoreFailure(\n \"TOOL_INSTALL_FAILED\",\n `工具安装失败,退出码:${code ?? \"unknown\"}`\n );\n }\n const launcher = createEngineLauncher(config.workspaceRoot);\n process.stderr.write(`Analysis engine launcher: ${launcher}\\n`);\n}\n\nasync function setup(json: boolean, args: string[]): Promise<void> {\n const configPath = option(args, \"--write-config\");\n if (hasFlag(args, \"--install-skill\") && !configPath) throw new CoreFailure(\"CONFIG_REQUIRED\", \"安装 Skill 时请同时指定 --write-config。\");\n let config = loadConfig();\n if (hasFlag(args, \"--install\")) {\n await runInstallation(config, () => installTools(config, json));\n config = loadConfig();\n }\n await doctor(json);\n if (json && !configPath) return;\n const serverEntry = resolveMcpEntry();\n const env: Record<string, string> = {\n APPTRACE_WORKSPACE_ROOT: config.workspaceRoot,\n APPTRACE_CLI_PATH: __filename\n };\n const ipatool = resolveTool(config.ipatoolPath);\n if (ipatool) env.APPTRACE_IPATOOL_PATH = ipatool;\n if (fs.existsSync(config.enginePath)) {\n env.APPTRACE_ENGINE_PATH = config.enginePath;\n }\n // Only pin the analysis engine when it really exists, otherwise the engine\n // inherits a path that can never work instead of resolving its own default.\n if (fs.existsSync(config.analysisEnginePath)) {\n env.APPTRACE_ANALYSIS_ENGINE_PATH = config.analysisEnginePath;\n }\n if (configPath) {\n const absolute = path.resolve(configPath);\n writeMcpConfig(absolute, { command: process.execPath, args: [serverEntry], env });\n if (hasFlag(args, \"--install-skill\")) {\n const destination = path.join(path.dirname(absolute), \"skills\", \"apptrace\", \"SKILL.md\");\n fs.mkdirSync(path.dirname(destination), { recursive: true });\n if (fs.existsSync(destination)) fs.copyFileSync(destination, `${destination}.${Date.now()}.bak`);\n fs.copyFileSync(path.join(__dirname, \"skills\", \"apptrace\", \"SKILL.md\"), destination);\n }\n if (json) {\n process.stderr.write(`MCP config written: ${absolute}\\n`);\n return;\n }\n console.log(`配置已写入:${absolute}`);\n }\n const escape = (value: string) => value.replace(/\\\\/g, \"\\\\\\\\\");\n const envDisplay = Object.entries(env)\n .map(([key, value]) => `${key} = \"${escape(value)}\"`)\n .join(\", \");\n console.log(`\nMCP 配置(客户端读取这一段,键名保持英文):\n\n[mcp_servers.apptrace]\ncommand = \"${escape(process.execPath)}\"\nargs = [\"${escape(serverEntry)}\"]\nenv = { ${envDisplay} }\n\n重启 Agent 客户端后生效。加 --restart-client 可以让 AppTrace 直接重启它。\n`);\n if (configPath && hasFlag(args, \"--restart-client\")) {\n const client = clientForConfig(path.resolve(configPath));\n if (!client.app) {\n throw new CoreFailure(\n \"RESTART_UNSUPPORTED\",\n `${client.name} 需要你自己重启(退出后重新打开)。`\n );\n }\n await restartClient(client.app);\n console.log(`已重启 ${client.name}。`);\n }\n}\n\nfunction clientForConfig(configPath: string): { name: string; app?: string } {\n const normalized = configPath.replace(/\\\\/g, \"/\").toLowerCase();\n if (normalized.includes(\"claude_desktop_config.json\")) {\n return { name: \"Claude Desktop\", app: \"Claude\" };\n }\n if (normalized.includes(\"/.codex/\")) return { name: \"Codex\", app: \"Codex\" };\n if (normalized.includes(\"/.cursor/\")) return { name: \"Cursor\", app: \"Cursor\" };\n if (normalized.endsWith(\".mcp.json\")) return { name: \"Claude Code\" };\n return { name: \"Agent 客户端\" };\n}\n\nasync function restartClient(app: string): Promise<void> {\n if (process.platform !== \"darwin\") {\n throw new CoreFailure(\n \"RESTART_UNSUPPORTED\",\n \"当前系统不支持自动重启客户端,请手动退出后重新打开。\"\n );\n }\n await interactive(\"/usr/bin/osascript\", [\"-e\", `quit app \"${app}\"`]);\n await new Promise((resolve) => setTimeout(resolve, 1200));\n await interactive(\"/usr/bin/open\", [\"-a\", app]);\n}\n\n/**\n * Global installs nest workspace packages under the CLI package\n * (node_modules/@zhuixin/cli/node_modules/@zhuixin/mcp), so a fixed\n * ../../mcp path only works inside the monorepo.\n */\nfunction resolveMcpEntry(): string {\n try {\n return require.resolve(\"@zhuixin/mcp\");\n } catch {\n return path.resolve(__dirname, \"../../mcp/dist/index.js\");\n }\n}\n\nasync function authLogin(): Promise<void> {\n const config = loadConfig();\n const ipatool = resolveTool(config.ipatoolPath);\n if (!ipatool) {\n throw new CoreFailure(\n \"TOOL_NOT_INSTALLED\",\n `找不到 ipatool(${config.ipatoolPath})。请先运行 apptrace setup --install,成功后再运行 apptrace auth login。`\n );\n }\n const code = await interactive(ipatool, [\"auth\", \"login\"]);\n if (code !== 0) {\n throw new CoreFailure(\n \"AUTH_FAILED\",\n `ipatool 登录没有成功,退出码:${code ?? \"unknown\"}`\n );\n }\n await authStatus();\n}\n\nasync function authStatus(): Promise<void> {\n await withCore(async (core) => {\n const status = await core.handle(CHANNELS.accountStatus, { force: true });\n printJson(status);\n });\n}\n\nasync function addTarget(source: string): Promise<void> {\n await withCore(async (core) => {\n const resolved = (await core.handle(CHANNELS.targetsResolve, {\n input: source\n })) as { resolutionId: string };\n const target = (await core.handle(CHANNELS.targetsCreate, {\n resolutionId: resolved.resolutionId\n })) as TargetRecord;\n printJson(target);\n });\n}\n\nasync function listTargets(): Promise<void> {\n await withCore(async (core) => {\n printJson(await core.handle(CHANNELS.targetsList, {}));\n });\n}\n\nasync function analyze(source: string | undefined, args: string[]): Promise<void> {\n const goals = parseGoals(option(args, \"--goals\"));\n const json = hasFlag(args, \"--json\");\n const force = hasFlag(args, \"--force\");\n const targetId = option(args, \"--target\");\n if (!source && !targetId) {\n throw new CoreFailure(\"SOURCE_REQUIRED\", \"缺少应用链接、ID 或 --target。\");\n }\n await withCore(async (core) => {\n const service = new AppAnalysisService(core);\n const result = await service.analyzeApp({\n source: targetId ? undefined : source,\n targetId,\n goals,\n force,\n onProgress: (event) => {\n if (!json) {\n process.stderr.write(\n `[${event.step}] ${event.status}${\n event.message ? ` — ${event.message}` : \"\"\n }\\n`\n );\n }\n }\n });\n if (json) {\n printJson(result);\n } else {\n printAnalysis(result);\n }\n });\n}\n\nfunction printAnalysis(result: AnalyzeAppResult): void {\n console.log(`\\n${result.targetName}`);\n console.log(`Target: ${result.targetId}`);\n console.log(`Artifact: ${result.artifact.sha256}`);\n console.log(\n `Technology: ${result.technology?.hints.join(\" / \") || \"unknown\"}`\n );\n console.log(`Routes: ${result.routes?.count ?? 0}`);\n console.log(`Modules: ${result.modules?.count ?? 0}`);\n console.log(`Network endpoints: ${result.networkEndpoints?.count ?? 0}`);\n if (result.native) {\n console.log(\n `Native: ${result.native.procedureCount ?? \"?\"} procedures, ${\n result.native.stringCount ?? \"?\"\n } strings`\n );\n if (result.native.warning) console.log(`Warning: ${result.native.warning}`);\n }\n if (result.reportPath) console.log(`Report: ${result.reportPath}`);\n for (const evidencePath of result.evidencePaths) {\n console.log(`Evidence: ${evidencePath}`);\n }\n if (result.limitations.length > 0) {\n console.log(\"Limitations:\");\n for (const limitation of result.limitations.slice(0, 6)) {\n console.log(` - ${limitation}`);\n }\n }\n}\n\nasync function report(targetId: string): Promise<void> {\n await withCore(async (core) => {\n const service = new AppAnalysisService(core);\n printJson(await service.runCapability(targetId, \"generate:report\"));\n });\n}\n\nasync function getJob(jobId: string): Promise<void> {\n await withCore(async (core) => {\n const result = (await core.handle(CHANNELS.jobsGet, { jobId })) as {\n job: JobRecord;\n events: unknown[];\n };\n printJson({ ...result, events: result.events.slice(-50) });\n });\n}\n\nasync function searchFindings(\n targetId: string,\n query: string | undefined,\n args: string[]\n): Promise<void> {\n const types = option(args, \"--type\")\n ?.split(\",\")\n .map((value) => value.trim())\n .filter(Boolean);\n const limit = Number(option(args, \"--limit\") ?? 50);\n await withCore(async (core) => {\n printJson(\n await core.handle(CHANNELS.findingsSearch, {\n targetId,\n query,\n types,\n limit: Number.isFinite(limit) ? limit : 50\n })\n );\n });\n}\n\nasync function nativeCommand(\n action: string | undefined,\n args: string[]\n): Promise<void> {\n const targetId = args[0];\n const value = args[1];\n if (!targetId || !value) {\n throw new CoreFailure(\n \"INVALID_INPUT\",\n \"用法:apptrace native <search|decompile> <target-id> <query-or-function>\"\n );\n }\n await withCore(async (core) => {\n const service = new AppAnalysisService(core);\n if (action === \"search\") {\n printJson(\n await service.runCapability(targetId, \"native:search\", {\n query: value,\n kind: option(args, \"--kind\") ?? \"procedures\",\n mode: option(args, \"--mode\") ?? \"literal\"\n })\n );\n return;\n }\n if (action === \"decompile\") {\n printJson(\n await service.runCapability(targetId, \"native:decompile\", {\n functionName: value\n })\n );\n return;\n }\n throw new CoreFailure(\n \"INVALID_INPUT\",\n \"原生命令只支持 search 或 decompile。\"\n );\n });\n}\n\nasync function evidence(targetId: string): Promise<void> {\n await withCore(async (core) => {\n printJson(\n await core.handle(CHANNELS.evidenceList, {\n targetId\n })\n );\n });\n}\n\nasync function installEngine(file: string, args: string[]): Promise<void> {\n const config = loadConfig();\n const expected = option(args, \"--sha256\");\n const installed = installEngineArtifact(config.workspaceRoot, file, expected);\n printJson(installed);\n}\n\nasync function compare(\n targetId: string | undefined,\n leftArtifactId: string | undefined,\n rightArtifactId: string | undefined,\n args: string[]\n): Promise<void> {\n if (!targetId || !leftArtifactId || !rightArtifactId) {\n throw new CoreFailure(\n \"INVALID_INPUT\",\n \"用法:apptrace compare <target-id> <left-artifact-id> <right-artifact-id>\"\n );\n }\n await withCore(async (core) => {\n printJson(\n await new VersionComparisonService(core).compare(\n targetId,\n leftArtifactId,\n rightArtifactId,\n { force: hasFlag(args, \"--force\") }\n )\n );\n });\n}\n\nasync function capabilities(targetId?: string): Promise<void> {\n await withCore(async (core) => {\n printJson(\n await core.handle(CHANNELS.capabilitiesList, {\n ...(targetId ? { targetId } : {})\n })\n );\n });\n}\n\nasync function main(): Promise<void> {\n const [command, subcommand, ...args] = process.argv.slice(2);\n if (!command || [\"help\", \"-h\", \"--help\"].includes(command)) {\n usage();\n return;\n }\n if (command === \"setup-worker\") {\n if (!subcommand || !/^[0-9a-f-]{36}$/.test(subcommand)) throw new CoreFailure(\"INVALID_INPUT\", \"无效的安装操作标识。\");\n const config = loadConfig();\n return runInstallation(config, () => installTools(config), subcommand);\n }\n if (command === \"setup\") {\n const setupArgs =\n subcommand?.startsWith(\"--\") && subcommand\n ? [subcommand, ...args]\n : args;\n return setup(hasFlag(setupArgs, \"--json\"), setupArgs);\n }\n if (command === \"doctor\") {\n const doctorArgs =\n subcommand?.startsWith(\"--\") && subcommand\n ? [subcommand, ...args]\n : args;\n return doctor(hasFlag(doctorArgs, \"--json\"));\n }\n if (command === \"auth\") {\n if (subcommand === \"open\") {\n printJson(await new OnboardingService(loadConfig(), __filename).openLogin());\n return;\n }\n if (subcommand === \"login\") return authLogin();\n if (subcommand === \"status\") return authStatus();\n }\n if (command === \"targets\") {\n if (subcommand === \"add\") {\n const source = positional(args);\n if (!source) throw new CoreFailure(\"SOURCE_REQUIRED\", \"缺少应用链接或 ID。\");\n return addTarget(source);\n }\n if (subcommand === \"list\") return listTargets();\n }\n if (command === \"analyze\") {\n const analyzeArgs =\n subcommand?.startsWith(\"--\") && subcommand\n ? [subcommand, ...args]\n : args;\n const source =\n subcommand && !subcommand.startsWith(\"--\")\n ? subcommand\n : positional(analyzeArgs);\n return analyze(source, analyzeArgs);\n }\n if (command === \"report\") {\n if (!subcommand) throw new CoreFailure(\"TARGET_REQUIRED\", \"缺少 target ID。\");\n return report(subcommand);\n }\n if (command === \"jobs\" && subcommand === \"get\") {\n const jobId = positional(args);\n if (!jobId) throw new CoreFailure(\"JOB_REQUIRED\", \"缺少 job ID。\");\n return getJob(jobId);\n }\n if (command === \"search\") {\n if (!subcommand) throw new CoreFailure(\"TARGET_REQUIRED\", \"缺少 target ID。\");\n return searchFindings(subcommand, args[0], args);\n }\n if (command === \"native\") return nativeCommand(subcommand, args);\n if (command === \"evidence\") {\n if (!subcommand) throw new CoreFailure(\"TARGET_REQUIRED\", \"缺少 target ID。\");\n return evidence(subcommand);\n }\n if (command === \"engine\" && subcommand === \"install\") {\n const file = args[0];\n if (!file) throw new CoreFailure(\"ENGINE_REQUIRED\", \"缺少 engine 文件路径。\");\n return installEngine(file, args);\n }\n if (command === \"compare\") {\n if (!subcommand) throw new CoreFailure(\"TARGET_REQUIRED\", \"缺少 target ID。\");\n return compare(subcommand, args[0], args[1], args);\n }\n if (command === \"capabilities\") return capabilities(subcommand);\n if (command === \"mcp\") {\n process.env.APPTRACE_CLI_PATH = __filename;\n const { startMcpServer } = await import(\"@zhuixin/mcp\");\n await startMcpServer(loadConfig());\n return;\n }\n usage();\n throw new CoreFailure(\"UNKNOWN_COMMAND\", `未知命令:${command}`);\n}\n\nvoid main().catch((error) => {\n printError(error);\n process.exitCode = 1;\n});\n","{\n \"name\": \"@zhuixin/cli\",\n \"version\": \"0.2.2\",\n \"license\": \"Apache-2.0\",\n \"description\": \"AppTrace CLI for evidence-backed application analysis\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/zhuixin-labs/app-evidence.git\",\n \"directory\": \"packages/cli\"\n },\n \"homepage\": \"https://github.com/zhuixin-labs/app-evidence\",\n \"bugs\": {\"url\": \"https://github.com/zhuixin-labs/app-evidence/issues\"},\n \"keywords\": [\"mcp\", \"ai-agents\", \"app-analysis\", \"static-analysis\", \"ios\", \"ipa\"],\n \"publishConfig\": {\"access\": \"public\"},\n \"engines\": {\"node\": \">=22.19\"},\n \"main\": \"dist/index.js\",\n \"bin\": {\n \"apptrace\": \"dist/index.js\"\n },\n \"files\": [\"dist\"],\n \"scripts\": {\n \"build\": \"tsup\",\n \"typecheck\": \"tsc --noEmit -p tsconfig.json\"\n },\n \"dependencies\": {\n \"@zhuixin/metadata\": \"workspace:*\",\n \"@zhuixin/mcp\": \"workspace:*\",\n \"@zhuixin/shared\": \"workspace:*\",\n \"rea-agents\": \"6.4.0\",\n \"smol-toml\": \"^1.9.0\"\n },\n \"devDependencies\": {\n \"tsup\": \"^8.5.0\"\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\nimport { parse, stringify } from \"smol-toml\";\n\ninterface ServerConfig {\n command: string;\n args: string[];\n env: Record<string, string>;\n}\n\nfunction object(value: unknown): Record<string, any> {\n if (value === undefined) return {};\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(\"MCP configuration must contain objects/tables.\");\n }\n return value as Record<string, any>;\n}\n\nexport function writeMcpConfig(filename: string, server: ServerConfig): void {\n const extension = path.extname(filename).toLowerCase();\n if (![\".json\", \".toml\"].includes(extension)) {\n throw new Error(\"--write-config 只支持 .json 或 .toml 文件。\");\n }\n const original = fs.existsSync(filename) ? fs.readFileSync(filename, \"utf8\") : undefined;\n const config = object(original?.trim()\n ? extension === \".json\" ? JSON.parse(original) : parse(original)\n : {});\n const key = extension === \".json\" ? \"mcpServers\" : \"mcp_servers\";\n const servers = object(config[key]);\n const previous = object(servers.apptrace);\n const environment = { ...object(previous.env), ...server.env };\n for (const [key, placeholder] of [[\"APPTRACE_IPATOOL_PATH\", \"ipatool\"], [\"APPTRACE_ANALYSIS_ENGINE_PATH\", \"rea\"]]) {\n if (!server.env[key] && environment[key] === placeholder) delete environment[key];\n }\n servers.apptrace = { ...previous, ...server, env: environment };\n config[key] = servers;\n const output = extension === \".json\" ? JSON.stringify(config, null, 2) + \"\\n\" : stringify(config);\n fs.mkdirSync(path.dirname(filename), { recursive: true, mode: 0o700 });\n // Keep the original text (including TOML comments) recoverable before serialization.\n if (original !== undefined) {\n fs.writeFileSync(`${filename}.${randomUUID()}.bak`, original, { mode: 0o600, flag: \"wx\" });\n }\n const temporary = `${filename}.${randomUUID()}.tmp`;\n try {\n fs.writeFileSync(temporary, output, { mode: 0o600, flag: \"wx\" });\n fs.renameSync(temporary, filename);\n } finally {\n if (fs.existsSync(temporary)) fs.unlinkSync(temporary);\n }\n}\n"],"mappings":"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","names":["import_node_fs","import_node_path","path","fs","key","path","capabilities","fs","script","code","launcher"]}
1
+ {"version":3,"sources":["../src/index.ts","../package.json","../src/mcp-config.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { spawn } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport {\n AppAnalysisService,\n CoreFailure,\n CoreServer,\n installEngineArtifact,\n VersionComparisonService,\n loadConfig,\n resolveTool,\n OnboardingService,\n runInstallation,\n setupToolsReady,\n trackInstallationChild,\n runAnalysisWorker,\n recoverAnalysisTasks,\n requireBackgroundCredentials,\n type AnalysisGoal,\n type AnalyzeAppResult\n} from \"@zhuixin/metadata\";\nimport {\n CHANNELS,\n type AccountStatus,\n type CapabilityDescriptor,\n type HealthInfo,\n type JobRecord,\n type TargetRecord\n} from \"@zhuixin/shared\";\n\nimport { version } from \"../package.json\";\nimport { writeMcpConfig } from \"./mcp-config.js\";\n\nconst GOALS: AnalysisGoal[] = [\n \"tech_stack\",\n \"feature_map\",\n \"network\",\n \"native_risk\",\n \"report\"\n];\n\nfunction usage(): void {\n console.log(`AppTrace ${version}\n\nUsage:\n apptrace setup [--json] [--install] [--write-config <path>] [--restart-client]\n apptrace doctor [--json]\n apptrace auth login\n apptrace auth status\n apptrace auth open\n apptrace setup --write-config <path> --install-skill\n apptrace targets add <app-store-url-or-id>\n apptrace targets list\n apptrace analyze [app-store-url-or-id] [--target <target-id>] [--goals ...] [--force]\n apptrace report <target-id>\n apptrace jobs get <job-id>\n apptrace search <target-id> [query] [--type route,module] [--limit 50]\n apptrace native search <target-id> <query> [--kind procedures|strings]\n apptrace native decompile <target-id> <function-name>\n apptrace evidence <target-id>\n apptrace compare <target-id> <left-artifact-id> <right-artifact-id> [--force]\n apptrace capabilities [target-id]\n apptrace engine install <engine-file> [--sha256 <digest>]\n apptrace mcp\n\nExamples:\n apptrace analyze https://apps.apple.com/cn/app/id1616124777\n apptrace analyze 1616124777 --goals tech_stack,feature_map,native_risk,report\n`);\n}\n\nfunction option(args: string[], name: string): string | undefined {\n const index = args.indexOf(name);\n if (index >= 0 && args[index + 1]) return args[index + 1];\n const prefix = `${name}=`;\n const inline = args.find((arg) => arg.startsWith(prefix));\n return inline ? inline.slice(prefix.length) : undefined;\n}\n\nfunction hasFlag(args: string[], name: string): boolean {\n return args.includes(name);\n}\n\nfunction positional(args: string[]): string | undefined {\n return args.find((arg) => !arg.startsWith(\"--\"));\n}\n\nfunction parseGoals(value: string | undefined): AnalysisGoal[] {\n if (!value) return [\"tech_stack\", \"feature_map\", \"report\"];\n const goals = value\n .split(\",\")\n .map((item) => item.trim())\n .filter(Boolean);\n const invalid = goals.filter(\n (goal): goal is string => !GOALS.includes(goal as AnalysisGoal)\n );\n if (invalid.length > 0) {\n throw new CoreFailure(\n \"INVALID_INPUT\",\n `Unknown analysis goal: ${invalid.join(\", \")}. Available: ${GOALS.join(\", \")}`\n );\n }\n return goals as AnalysisGoal[];\n}\n\nfunction printJson(value: unknown): void {\n process.stdout.write(`${JSON.stringify(value, null, 2)}\\n`);\n}\n\nfunction printError(error: unknown): void {\n if (error instanceof CoreFailure) {\n console.error(`${error.code}: ${error.message}`);\n if (error.code === \"AUTH_REQUIRED\" || error.code === \"KEYCHAIN_LOCKED\") {\n console.error(\"Next: apptrace auth login\");\n } else if (error.code === \"LICENSE_REQUIRED\") {\n console.error(\"Next: get or purchase the app in the App Store, then retry.\");\n }\n return;\n }\n console.error(error instanceof Error ? error.stack ?? error.message : String(error));\n}\n\nfunction withCore<T>(operation: (core: CoreServer) => Promise<T>): Promise<T> {\n const core = new CoreServer({ config: loadConfig(), emit: () => {} });\n return operation(core).finally(() => core.close());\n}\n\nfunction interactive(\n command: string,\n args: string[],\n env?: NodeJS.ProcessEnv,\n onSpawn?: (pid: number) => void\n): Promise<number | null> {\n return new Promise((resolve, reject) => {\n const isWindowsCommand =\n process.platform === \"win32\" &&\n [\".cmd\", \".bat\"].includes(path.extname(command).toLowerCase());\n const child = isWindowsCommand\n ? spawn(\n process.env.ComSpec || \"cmd.exe\",\n [\"/d\", \"/s\", \"/c\", `\"${command}\" ${args.join(\" \")}`],\n {\n stdio: \"inherit\",\n windowsVerbatimArguments: true,\n env: env ? { ...process.env, ...env } : process.env\n }\n )\n : spawn(command, args, {\n stdio: \"inherit\",\n env: env ? { ...process.env, ...env } : process.env\n });\n if (child.pid && onSpawn) {\n try { onSpawn(child.pid); }\n catch (error) { child.kill(); reject(error); return; }\n }\n child.on(\"error\", reject);\n child.on(\"exit\", (code) => resolve(code));\n });\n}\n\nasync function doctor(json: boolean): Promise<void> {\n const config = loadConfig();\n await withCore(async (core) => {\n const health = (await core.handle(CHANNELS.health, {})) as HealthInfo;\n const capabilities = (await core.handle(\n CHANNELS.capabilitiesList,\n {}\n )) as CapabilityDescriptor[];\n const result = { config, health, capabilities };\n if (json) {\n printJson(result);\n return;\n }\n console.log(`workspace: ${health.workspaceRoot}`);\n console.log(`host: ${health.hostPlatform}`);\n console.log(`store: ${health.store}`);\n console.log(\"tools:\");\n for (const tool of health.tools ?? []) {\n console.log(\n ` ${tool.available ? \"OK \" : \"MISS\"} ${tool.label}: ${tool.path}`\n );\n }\n console.log(\"capabilities:\");\n for (const capability of capabilities) {\n console.log(\n ` ${capability.available ? \"OK \" : \"NO \"} ${capability.id}${\n capability.reason ? ` — ${capability.reason}` : \"\"\n }`\n );\n }\n });\n}\n\nfunction createEngineLauncher(workspaceRoot: string): string {\n const reaScript = require.resolve(\"rea-agents/scripts/rea.mjs\");\n const tools = path.join(workspaceRoot, \"tools\");\n const javaHome = path.join(tools, \"jdk\");\n const ghidraHome = path.join(tools, \"ghidra_12.1.4_PUBLIC\");\n const ghidraUserHome = path.join(workspaceRoot, \".ghidra-home\");\n fs.mkdirSync(path.join(tools, \"bin\"), { recursive: true, mode: 0o700 });\n fs.mkdirSync(ghidraUserHome, { recursive: true, mode: 0o700 });\n const launcher = path.join(\n tools,\n \"bin\",\n process.platform === \"win32\" ? \"rea.cmd\" : \"rea\"\n );\n if (process.platform === \"win32\") {\n fs.writeFileSync(\n launcher,\n [\n \"@echo off\",\n `set \"JAVA_HOME=${javaHome}\"`,\n `set \"GHIDRA_INSTALL_DIR=${ghidraHome}\"`,\n `set \"GHIDRA_USER_HOME=${ghidraUserHome}\"`,\n 'set \"PATH=%JAVA_HOME%\\\\bin;%PATH%\"',\n `\"${process.execPath}\" \"${reaScript}\" %*`,\n \"\"\n ].join(\"\\r\\n\"),\n { mode: 0o700 }\n );\n } else {\n fs.writeFileSync(\n launcher,\n [\n \"#!/usr/bin/env bash\",\n \"set -euo pipefail\",\n `export JAVA_HOME=${JSON.stringify(javaHome)}`,\n `export GHIDRA_INSTALL_DIR=${JSON.stringify(ghidraHome)}`,\n `export GHIDRA_USER_HOME=${JSON.stringify(ghidraUserHome)}`,\n 'export PATH=\"$JAVA_HOME/bin:$PATH\"',\n 'export JAVA_TOOL_OPTIONS=\"${JAVA_TOOL_OPTIONS:-} -Duser.home=$GHIDRA_USER_HOME\"',\n `exec ${JSON.stringify(process.execPath)} ${JSON.stringify(reaScript)} \"$@\"`,\n \"\"\n ].join(\"\\n\"),\n { mode: 0o700 }\n );\n }\n return launcher;\n}\n\nasync function installTools(\n config: ReturnType<typeof loadConfig>,\n json = false\n): Promise<void> {\n if (await setupToolsReady(config)) {\n const managedLauncher = path.join(config.workspaceRoot, \"tools\", \"bin\", process.platform === \"win32\" ? \"rea.cmd\" : \"rea\");\n if (path.resolve(config.analysisEnginePath) === managedLauncher) {\n createEngineLauncher(config.workspaceRoot);\n }\n process.stderr.write(\"Tools already available; download skipped.\\n\");\n return;\n }\n if (process.platform === \"win32\" && process.arch === \"x64\") {\n const script = path.resolve(__dirname, \"scripts/bootstrap-tools.ps1\");\n if (!fs.existsSync(script)) {\n throw new CoreFailure(\"TOOL_INSTALLER_MISSING\", `Installer script not found: ${script}`);\n }\n process.stderr.write(\n \"Installing JDK, Ghidra and ipatool for Windows x64. This can take several minutes.\\n\"\n );\n const code = await interactive(\"powershell.exe\", [\n \"-NoProfile\",\n \"-ExecutionPolicy\",\n \"Bypass\",\n \"-File\",\n script,\n \"-WorkspaceRoot\",\n config.workspaceRoot\n ], {\n // PowerShell 7's inherited module paths can hide Windows PowerShell 5.1 modules.\n PSModulePath: path.join(process.env.SystemRoot || \"C:\\\\Windows\", \"System32\", \"WindowsPowerShell\", \"v1.0\", \"Modules\"),\n APPTRACE_WORKSPACE_ROOT: config.workspaceRoot,\n APPTRACE_TOOLS_DIR: path.join(config.workspaceRoot, \"tools\")\n }, (pid) => trackInstallationChild(config, pid));\n if (code !== 0) {\n throw new CoreFailure(\n \"TOOL_INSTALL_FAILED\",\n `Tool installation failed with exit code ${code ?? \"unknown\"}`\n );\n }\n const launcher = createEngineLauncher(config.workspaceRoot);\n process.stderr.write(`Analysis engine launcher: ${launcher}\\n`);\n return;\n }\n if (process.platform !== \"darwin\" || process.arch !== \"arm64\") {\n throw new CoreFailure(\n \"TOOL_INSTALL_UNSUPPORTED\",\n \"The automatic installer supports macOS arm64 and Windows x64. On other platforms set APPTRACE_ENGINE_PATH and APPTRACE_IPATOOL_PATH.\"\n );\n }\n const script = path.resolve(__dirname, \"scripts/bootstrap-tools.sh\");\n if (!fs.existsSync(script)) {\n throw new CoreFailure(\"TOOL_INSTALLER_MISSING\", `Installer script not found: ${script}`);\n }\n process.stderr.write(\n \"Installing JDK, Ghidra and ipatool. This can take several minutes.\\n\"\n );\n const code = await interactive(\"/bin/bash\", [script], {\n APPTRACE_WORKSPACE_ROOT: config.workspaceRoot,\n APPTRACE_TOOLS_DIR: path.join(config.workspaceRoot, \"tools\")\n }, (pid) => trackInstallationChild(config, pid));\n if (code !== 0) {\n throw new CoreFailure(\n \"TOOL_INSTALL_FAILED\",\n `Tool installation failed with exit code ${code ?? \"unknown\"}`\n );\n }\n const launcher = createEngineLauncher(config.workspaceRoot);\n process.stderr.write(`Analysis engine launcher: ${launcher}\\n`);\n}\n\nasync function setup(json: boolean, args: string[]): Promise<void> {\n const configPath = option(args, \"--write-config\");\n if (hasFlag(args, \"--install-skill\") && !configPath) throw new CoreFailure(\"CONFIG_REQUIRED\", \"--install-skill requires --write-config.\");\n let config = loadConfig();\n if (hasFlag(args, \"--install\")) {\n await runInstallation(config, () => installTools(config, json));\n config = loadConfig();\n }\n await doctor(json);\n if (json && !configPath) return;\n const serverEntry = resolveMcpEntry();\n const env: Record<string, string> = {\n APPTRACE_WORKSPACE_ROOT: config.workspaceRoot,\n APPTRACE_CLI_PATH: __filename\n };\n const ipatool = resolveTool(config.ipatoolPath);\n if (ipatool) env.APPTRACE_IPATOOL_PATH = ipatool;\n if (fs.existsSync(config.enginePath)) {\n env.APPTRACE_ENGINE_PATH = config.enginePath;\n }\n // Only pin the analysis engine when it really exists, otherwise the engine\n // inherits a path that can never work instead of resolving its own default.\n if (fs.existsSync(config.analysisEnginePath)) {\n env.APPTRACE_ANALYSIS_ENGINE_PATH = config.analysisEnginePath;\n }\n if (configPath) {\n const absolute = path.resolve(configPath);\n writeMcpConfig(absolute, { command: process.execPath, args: [serverEntry], env });\n if (hasFlag(args, \"--install-skill\")) {\n const destination = path.join(path.dirname(absolute), \"skills\", \"apptrace\", \"SKILL.md\");\n fs.mkdirSync(path.dirname(destination), { recursive: true });\n if (fs.existsSync(destination)) fs.copyFileSync(destination, `${destination}.${Date.now()}.bak`);\n fs.copyFileSync(path.join(__dirname, \"skills\", \"apptrace\", \"SKILL.md\"), destination);\n }\n if (json) {\n process.stderr.write(`MCP config written: ${absolute}\\n`);\n return;\n }\n console.log(`MCP config written: ${absolute}`);\n }\n const escape = (value: string) => value.replace(/\\\\/g, \"\\\\\\\\\");\n const envDisplay = Object.entries(env)\n .map(([key, value]) => `${key} = \"${escape(value)}\"`)\n .join(\", \");\n console.log(`\nMCP configuration:\n\n[mcp_servers.apptrace]\ncommand = \"${escape(process.execPath)}\"\nargs = [\"${escape(serverEntry)}\"]\nenv = { ${envDisplay} }\n\nRestart the agent client to apply. Add --restart-client to let AppTrace restart it.\n`);\n if (configPath && hasFlag(args, \"--restart-client\")) {\n const client = clientForConfig(path.resolve(configPath));\n if (!client.app) {\n throw new CoreFailure(\n \"RESTART_UNSUPPORTED\",\n `${client.name} has no desktop process to restart; quit and reopen it yourself.`\n );\n }\n await restartClient(client.app);\n console.log(`Restarted ${client.name}.`);\n }\n}\n\nfunction clientForConfig(configPath: string): { name: string; app?: string } {\n const normalized = configPath.replace(/\\\\/g, \"/\").toLowerCase();\n if (normalized.includes(\"claude_desktop_config.json\")) {\n return { name: \"Claude Desktop\", app: \"Claude\" };\n }\n if (normalized.includes(\"/.codex/\")) return { name: \"Codex\", app: \"Codex\" };\n if (normalized.includes(\"/.cursor/\")) return { name: \"Cursor\", app: \"Cursor\" };\n if (normalized.endsWith(\".mcp.json\")) return { name: \"Claude Code\" };\n return { name: \"Agent client\" };\n}\n\nasync function restartClient(app: string): Promise<void> {\n if (process.platform !== \"darwin\") {\n throw new CoreFailure(\n \"RESTART_UNSUPPORTED\",\n \"Automatic client restart is only supported on macOS; quit and reopen the client yourself.\"\n );\n }\n await interactive(\"/usr/bin/osascript\", [\"-e\", `quit app \"${app}\"`]);\n await new Promise((resolve) => setTimeout(resolve, 1200));\n await interactive(\"/usr/bin/open\", [\"-a\", app]);\n}\n\n/**\n * Global installs nest workspace packages under the CLI package\n * (node_modules/@zhuixin/cli/node_modules/@zhuixin/mcp), so a fixed\n * ../../mcp path only works inside the monorepo.\n */\nfunction resolveMcpEntry(): string {\n try {\n return require.resolve(\"@zhuixin/mcp\");\n } catch {\n return path.resolve(__dirname, \"../../mcp/dist/index.js\");\n }\n}\n\nasync function authLogin(): Promise<void> {\n const config = loadConfig();\n const ipatool = resolveTool(config.ipatoolPath);\n if (!ipatool) {\n throw new CoreFailure(\n \"TOOL_NOT_INSTALLED\",\n `ipatool not found (${config.ipatoolPath}). Run apptrace setup --install first, then apptrace auth login.`\n );\n }\n await requireBackgroundCredentials(config);\n const code = await interactive(ipatool, [\"auth\", \"login\"]);\n if (code !== 0) {\n throw new CoreFailure(\n \"AUTH_FAILED\",\n `ipatool login failed with exit code ${code ?? \"unknown\"}`\n );\n }\n await authStatus(true);\n await recoverAnalysisTasks(config, __filename);\n}\n\nasync function authStatus(requireAuthenticated = false): Promise<void> {\n await withCore(async (core) => {\n const status = await core.handle(CHANNELS.accountStatus, { force: true }) as AccountStatus;\n printJson(status);\n if (requireAuthenticated && !status.authenticated) throw new CoreFailure(status.errorCode ?? \"BACKGROUND_AUTH_FAILED\", status.message ?? \"Background credential verification failed after sign-in.\");\n });\n}\n\nasync function addTarget(source: string): Promise<void> {\n await withCore(async (core) => {\n const resolved = (await core.handle(CHANNELS.targetsResolve, {\n input: source\n })) as { resolutionId: string };\n const target = (await core.handle(CHANNELS.targetsCreate, {\n resolutionId: resolved.resolutionId\n })) as TargetRecord;\n printJson(target);\n });\n}\n\nasync function listTargets(): Promise<void> {\n await withCore(async (core) => {\n printJson(await core.handle(CHANNELS.targetsList, {}));\n });\n}\n\nasync function analyze(source: string | undefined, args: string[]): Promise<void> {\n const goals = parseGoals(option(args, \"--goals\"));\n const json = hasFlag(args, \"--json\");\n const force = hasFlag(args, \"--force\");\n const targetId = option(args, \"--target\");\n if (!source && !targetId) {\n throw new CoreFailure(\"SOURCE_REQUIRED\", \"Missing app link, ID, or --target.\");\n }\n await withCore(async (core) => {\n const service = new AppAnalysisService(core);\n const result = await service.analyzeApp({\n source: targetId ? undefined : source,\n targetId,\n goals,\n force,\n onProgress: (event) => {\n if (!json) {\n process.stderr.write(\n `[${event.step}] ${event.status}${\n event.message ? ` — ${event.message}` : \"\"\n }\\n`\n );\n }\n }\n });\n if (json) {\n printJson(result);\n } else {\n printAnalysis(result);\n }\n });\n}\n\nfunction printAnalysis(result: AnalyzeAppResult): void {\n console.log(`\\n${result.targetName}`);\n console.log(`Target: ${result.targetId}`);\n console.log(`Artifact: ${result.artifact.sha256}`);\n console.log(\n `Technology: ${result.technology?.hints.join(\" / \") || \"unknown\"}`\n );\n console.log(`Routes: ${result.routes?.count ?? 0}`);\n console.log(`Modules: ${result.modules?.count ?? 0}`);\n console.log(`Network endpoints: ${result.networkEndpoints?.count ?? 0}`);\n if (result.native) {\n console.log(\n `Native: ${result.native.procedureCount ?? \"?\"} procedures, ${\n result.native.stringCount ?? \"?\"\n } strings`\n );\n if (result.native.warning) console.log(`Warning: ${result.native.warning}`);\n }\n if (result.reportPath) console.log(`Report: ${result.reportPath}`);\n for (const evidencePath of result.evidencePaths) {\n console.log(`Evidence: ${evidencePath}`);\n }\n if (result.limitations.length > 0) {\n console.log(\"Limitations:\");\n for (const limitation of result.limitations.slice(0, 6)) {\n console.log(` - ${limitation}`);\n }\n }\n}\n\nasync function report(targetId: string): Promise<void> {\n await withCore(async (core) => {\n const service = new AppAnalysisService(core);\n printJson(await service.runCapability(targetId, \"generate:report\"));\n });\n}\n\nasync function getJob(jobId: string): Promise<void> {\n await withCore(async (core) => {\n const result = (await core.handle(CHANNELS.jobsGet, { jobId })) as {\n job: JobRecord;\n events: unknown[];\n };\n printJson({ ...result, events: result.events.slice(-50) });\n });\n}\n\nasync function searchFindings(\n targetId: string,\n query: string | undefined,\n args: string[]\n): Promise<void> {\n const types = option(args, \"--type\")\n ?.split(\",\")\n .map((value) => value.trim())\n .filter(Boolean);\n const limit = Number(option(args, \"--limit\") ?? 50);\n await withCore(async (core) => {\n printJson(\n await core.handle(CHANNELS.findingsSearch, {\n targetId,\n query,\n types,\n limit: Number.isFinite(limit) ? limit : 50\n })\n );\n });\n}\n\nasync function nativeCommand(\n action: string | undefined,\n args: string[]\n): Promise<void> {\n const targetId = args[0];\n const value = args[1];\n if (!targetId || !value) {\n throw new CoreFailure(\n \"INVALID_INPUT\",\n \"Usage: apptrace native <search|decompile> <target-id> <query-or-function>\"\n );\n }\n await withCore(async (core) => {\n const service = new AppAnalysisService(core);\n if (action === \"search\") {\n printJson(\n await service.runCapability(targetId, \"native:search\", {\n query: value,\n kind: option(args, \"--kind\") ?? \"procedures\",\n mode: option(args, \"--mode\") ?? \"literal\"\n })\n );\n return;\n }\n if (action === \"decompile\") {\n printJson(\n await service.runCapability(targetId, \"native:decompile\", {\n functionName: value\n })\n );\n return;\n }\n throw new CoreFailure(\n \"INVALID_INPUT\",\n \"Native commands support only search or decompile.\"\n );\n });\n}\n\nasync function evidence(targetId: string): Promise<void> {\n await withCore(async (core) => {\n printJson(\n await core.handle(CHANNELS.evidenceList, {\n targetId\n })\n );\n });\n}\n\nasync function installEngine(file: string, args: string[]): Promise<void> {\n const config = loadConfig();\n const expected = option(args, \"--sha256\");\n const installed = installEngineArtifact(config.workspaceRoot, file, expected);\n printJson(installed);\n}\n\nasync function compare(\n targetId: string | undefined,\n leftArtifactId: string | undefined,\n rightArtifactId: string | undefined,\n args: string[]\n): Promise<void> {\n if (!targetId || !leftArtifactId || !rightArtifactId) {\n throw new CoreFailure(\n \"INVALID_INPUT\",\n \"Usage: apptrace compare <target-id> <left-artifact-id> <right-artifact-id>\"\n );\n }\n await withCore(async (core) => {\n printJson(\n await new VersionComparisonService(core).compare(\n targetId,\n leftArtifactId,\n rightArtifactId,\n { force: hasFlag(args, \"--force\") }\n )\n );\n });\n}\n\nasync function capabilities(targetId?: string): Promise<void> {\n await withCore(async (core) => {\n printJson(\n await core.handle(CHANNELS.capabilitiesList, {\n ...(targetId ? { targetId } : {})\n })\n );\n });\n}\n\nasync function main(): Promise<void> {\n const [command, subcommand, ...args] = process.argv.slice(2);\n if (!command || [\"help\", \"-h\", \"--help\"].includes(command)) {\n usage();\n return;\n }\n if (command === \"analysis-worker\") {\n if (!subcommand || !/^[0-9a-f-]{36}$/.test(subcommand)) throw new CoreFailure(\"INVALID_INPUT\", \"Invalid analysis task ID.\");\n return runAnalysisWorker(loadConfig(), subcommand, __filename);\n }\n if (command === \"setup-worker\") {\n if (!subcommand || !/^[0-9a-f-]{36}$/.test(subcommand)) throw new CoreFailure(\"INVALID_INPUT\", \"Invalid install operation id.\");\n const config = loadConfig();\n return runInstallation(config, () => installTools(config), subcommand);\n }\n if (command === \"setup\") {\n const setupArgs =\n subcommand?.startsWith(\"--\") && subcommand\n ? [subcommand, ...args]\n : args;\n return setup(hasFlag(setupArgs, \"--json\"), setupArgs);\n }\n if (command === \"doctor\") {\n const doctorArgs =\n subcommand?.startsWith(\"--\") && subcommand\n ? [subcommand, ...args]\n : args;\n return doctor(hasFlag(doctorArgs, \"--json\"));\n }\n if (command === \"auth\") {\n if (subcommand === \"open\") {\n printJson(await new OnboardingService(loadConfig(), __filename).openLogin());\n return;\n }\n if (subcommand === \"login\") return authLogin();\n if (subcommand === \"status\") return authStatus();\n }\n if (command === \"targets\") {\n if (subcommand === \"add\") {\n const source = positional(args);\n if (!source) throw new CoreFailure(\"SOURCE_REQUIRED\", \"Missing app link or ID.\");\n return addTarget(source);\n }\n if (subcommand === \"list\") return listTargets();\n }\n if (command === \"analyze\") {\n const analyzeArgs =\n subcommand?.startsWith(\"--\") && subcommand\n ? [subcommand, ...args]\n : args;\n const source =\n subcommand && !subcommand.startsWith(\"--\")\n ? subcommand\n : positional(analyzeArgs);\n return analyze(source, analyzeArgs);\n }\n if (command === \"report\") {\n if (!subcommand) throw new CoreFailure(\"TARGET_REQUIRED\", \"Missing target ID.\");\n return report(subcommand);\n }\n if (command === \"jobs\" && subcommand === \"get\") {\n const jobId = positional(args);\n if (!jobId) throw new CoreFailure(\"JOB_REQUIRED\", \"Missing job ID.\");\n return getJob(jobId);\n }\n if (command === \"search\") {\n if (!subcommand) throw new CoreFailure(\"TARGET_REQUIRED\", \"Missing target ID.\");\n return searchFindings(subcommand, args[0], args);\n }\n if (command === \"native\") return nativeCommand(subcommand, args);\n if (command === \"evidence\") {\n if (!subcommand) throw new CoreFailure(\"TARGET_REQUIRED\", \"Missing target ID.\");\n return evidence(subcommand);\n }\n if (command === \"engine\" && subcommand === \"install\") {\n const file = args[0];\n if (!file) throw new CoreFailure(\"ENGINE_REQUIRED\", \"Missing engine file path.\");\n return installEngine(file, args);\n }\n if (command === \"compare\") {\n if (!subcommand) throw new CoreFailure(\"TARGET_REQUIRED\", \"Missing target ID.\");\n return compare(subcommand, args[0], args[1], args);\n }\n if (command === \"capabilities\") return capabilities(subcommand);\n if (command === \"mcp\") {\n process.env.APPTRACE_CLI_PATH = __filename;\n const { startMcpServer } = await import(\"@zhuixin/mcp\");\n await startMcpServer(loadConfig());\n return;\n }\n usage();\n throw new CoreFailure(\"UNKNOWN_COMMAND\", `Unknown command: ${command}`);\n}\n\nvoid main().catch((error) => {\n printError(error);\n process.exitCode = 1;\n});\n","{\n \"name\": \"@zhuixin/cli\",\n \"version\": \"0.2.4\",\n \"license\": \"Apache-2.0\",\n \"description\": \"AppTrace CLI for evidence-backed application analysis\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/zhuixin-labs/app-evidence.git\",\n \"directory\": \"packages/cli\"\n },\n \"homepage\": \"https://github.com/zhuixin-labs/app-evidence\",\n \"bugs\": {\"url\": \"https://github.com/zhuixin-labs/app-evidence/issues\"},\n \"keywords\": [\"mcp\", \"ai-agents\", \"app-analysis\", \"static-analysis\", \"ios\", \"ipa\"],\n \"publishConfig\": {\"access\": \"public\"},\n \"engines\": {\"node\": \">=22.19\"},\n \"main\": \"dist/index.js\",\n \"bin\": {\n \"apptrace\": \"dist/index.js\"\n },\n \"files\": [\"dist\"],\n \"scripts\": {\n \"build\": \"tsup\",\n \"typecheck\": \"tsc --noEmit -p tsconfig.json\"\n },\n \"dependencies\": {\n \"@zhuixin/metadata\": \"workspace:*\",\n \"@zhuixin/mcp\": \"workspace:*\",\n \"@zhuixin/shared\": \"workspace:*\",\n \"rea-agents\": \"6.4.0\",\n \"smol-toml\": \"^1.9.0\"\n },\n \"devDependencies\": {\n \"tsup\": \"^8.5.0\"\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\nimport { parse, stringify } from \"smol-toml\";\n\ninterface ServerConfig {\n command: string;\n args: string[];\n env: Record<string, string>;\n}\n\nfunction object(value: unknown): Record<string, any> {\n if (value === undefined) return {};\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(\"MCP configuration must contain objects/tables.\");\n }\n return value as Record<string, any>;\n}\n\nexport function writeMcpConfig(filename: string, server: ServerConfig): void {\n const extension = path.extname(filename).toLowerCase();\n if (![\".json\", \".toml\"].includes(extension)) {\n throw new Error(\"--write-config accepts only .json or .toml files.\");\n }\n const original = fs.existsSync(filename) ? fs.readFileSync(filename, \"utf8\") : undefined;\n const config = object(original?.trim()\n ? extension === \".json\" ? JSON.parse(original) : parse(original)\n : {});\n const key = extension === \".json\" ? \"mcpServers\" : \"mcp_servers\";\n const servers = object(config[key]);\n const previous = object(servers.apptrace);\n const environment = { ...object(previous.env), ...server.env };\n for (const [key, placeholder] of [[\"APPTRACE_IPATOOL_PATH\", \"ipatool\"], [\"APPTRACE_ANALYSIS_ENGINE_PATH\", \"rea\"]]) {\n if (!server.env[key] && environment[key] === placeholder) delete environment[key];\n }\n servers.apptrace = { ...previous, ...server, env: environment };\n config[key] = servers;\n const output = extension === \".json\" ? JSON.stringify(config, null, 2) + \"\\n\" : stringify(config);\n fs.mkdirSync(path.dirname(filename), { recursive: true, mode: 0o700 });\n // Keep the original text (including TOML comments) recoverable before serialization.\n if (original !== undefined) {\n fs.writeFileSync(`${filename}.${randomUUID()}.bak`, original, { mode: 0o600, flag: \"wx\" });\n }\n const temporary = `${filename}.${randomUUID()}.tmp`;\n try {\n fs.writeFileSync(temporary, output, { mode: 0o600, flag: \"wx\" });\n fs.renameSync(temporary, filename);\n } finally {\n if (fs.existsSync(temporary)) fs.unlinkSync(temporary);\n }\n}\n"],"mappings":"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","names":["import_node_fs","import_node_path","path","fs","key","path","capabilities","fs","script","code","launcher"]}
@@ -86,31 +86,11 @@ if (-not (Test-Path (Join-Path $JdkHome "bin\java.exe"))) {
86
86
  Remove-ToolDirectory $TempJdk
87
87
  }
88
88
 
89
- # ipatool 2.7.0 Windows x64
90
- $IpatoolUrl = "https://github.com/majd/ipatool/releases/download/v2.7.0/ipatool-2.7.0-windows-amd64.tar.gz"
91
- $IpatoolZip = Join-Path $Downloads "ipatool-2.7.0-windows-amd64.tar.gz"
92
- Download $IpatoolUrl $IpatoolZip
93
- if ($env:APPTRACE_IPATOOL_SHA256) {
94
- Assert-Sha256 $IpatoolZip $env:APPTRACE_IPATOOL_SHA256
95
- } else {
96
- Assert-Sha256 $IpatoolZip "b729608716908be287c2e6972f9e8cd05f879810ae5d182fc856c9e57cca0dd5"
97
- }
98
- $IpatoolHome = Join-Path $Tools "ipatool"
99
- if (-not (Test-Path (Join-Path $IpatoolHome "bin\ipatool.exe"))) {
100
- $TempIpatool = Join-Path $Tools "ipatool.tmp"
101
- Remove-ToolDirectory $TempIpatool
102
- New-Item -ItemType Directory -Force -Path $TempIpatool | Out-Null
103
- & tar.exe -xzf $IpatoolZip -C $TempIpatool
104
- if ($LASTEXITCODE -ne 0) { throw "Failed to extract ipatool archive." }
105
- $Exe = Get-Item -LiteralPath (Join-Path $TempIpatool "bin\ipatool-2.7.0-windows-amd64.exe") -ErrorAction SilentlyContinue
106
- if (-not $Exe) { throw "ipatool.exe not found in archive." }
107
- Remove-ToolDirectory $IpatoolHome
108
- New-Item -ItemType Directory -Force -Path (Join-Path $IpatoolHome "bin") | Out-Null
109
- Copy-Item -LiteralPath $Exe.FullName -Destination (Join-Path $IpatoolHome "bin\ipatool.exe") -Force
110
- Remove-ToolDirectory $TempIpatool
111
- }
89
+ # ipatool 2.7.0 with user-scoped Windows DPAPI credentials.
90
+ & (Join-Path $PSScriptRoot 'build-ipatool.ps1') -ToolsRoot $Tools
91
+ if ($LASTEXITCODE -ne 0) { throw 'Windows credential adapter installation failed' }
112
92
 
113
93
  Write-Host "Ghidra: $GhidraHome"
114
94
  Write-Host "JDK: $JdkHome"
115
- Write-Host "ipatool: $(Join-Path $IpatoolHome 'bin\ipatool.exe')"
95
+ Write-Host "ipatool: $(Join-Path $Tools 'ipatool\bin\ipatool-dpapi-v1.exe')"
116
96
  Write-Host "Windows tool bootstrap complete."
@@ -0,0 +1,33 @@
1
+ param([Parameter(Mandatory=$true)][string]$ToolsRoot)
2
+ $ErrorActionPreference = 'Stop'
3
+ $ToolsRoot = [IO.Path]::GetFullPath($ToolsRoot)
4
+ $Cache = Join-Path $ToolsRoot 'downloads'
5
+ New-Item -ItemType Directory -Force -Path $Cache | Out-Null
6
+ function Fetch([string]$Url, [string]$File, [string]$Hash) {
7
+ if (-not (Test-Path -LiteralPath $File)) { Invoke-WebRequest -Uri $Url -OutFile $File }
8
+ if ((Get-FileHash -LiteralPath $File -Algorithm SHA256).Hash.ToLower() -ne $Hash) { throw "Checksum mismatch: $File" }
9
+ }
10
+ $GoZip = Join-Path $Cache 'go1.27.3.windows-amd64.zip'
11
+ Fetch 'https://go.dev/dl/go1.27.3.windows-amd64.zip' $GoZip 'f26081f438319757f8a1b46d7d0b2c5ec7583d4038e485e0aa725cfde7885fff'
12
+ $GoRoot = Join-Path $ToolsRoot 'go-sdk-1.27.3'
13
+ if (-not (Test-Path (Join-Path $GoRoot 'go/bin/go.exe'))) { Expand-Archive -LiteralPath $GoZip -DestinationPath $GoRoot -Force }
14
+ $SourceZip = Join-Path $Cache 'ipatool-4484b55.zip'
15
+ Fetch 'https://codeload.github.com/majd/ipatool/zip/4484b55d366e177ae6c89758c61588712c572fad' $SourceZip 'b9d497f70e4756720bef8081059c4a40fa3f9719ea8d001cc2bb76a2de5eb00d'
16
+ $SourceRoot = Join-Path $ToolsRoot 'ipatool-source-dpapi-v1'
17
+ Expand-Archive -LiteralPath $SourceZip -DestinationPath $SourceRoot -Force
18
+ $Source = Join-Path $SourceRoot 'ipatool-4484b55d366e177ae6c89758c61588712c572fad'
19
+ $DefaultKeyring = Join-Path $Source 'cmd/keyring_default.go'
20
+ [IO.File]::WriteAllText($DefaultKeyring, ([IO.File]::ReadAllText($DefaultKeyring).Replace('//go:build !ios', '//go:build !ios && !windows')))
21
+ Copy-Item -Path (Join-Path $PSScriptRoot 'ipatool-overlay/*.go') -Destination (Join-Path $Source 'cmd') -Force
22
+ $Go = Join-Path $GoRoot 'go/bin/go.exe'
23
+ $Destination = Join-Path $ToolsRoot 'ipatool/bin'
24
+ New-Item -ItemType Directory -Force -Path $Destination | Out-Null
25
+ Push-Location $Source
26
+ try {
27
+ & $Go test ./cmd -run '^TestDPAPI' -count=1
28
+ if ($LASTEXITCODE -ne 0) { throw 'DPAPI credential verification failed' }
29
+ & $Go build -trimpath -ldflags '-s -w -X github.com/majd/ipatool/v2/cmd.version=2.7.0-apptrace-dpapi-v1' -o (Join-Path $Destination 'ipatool-dpapi-v1.exe') .
30
+ if ($LASTEXITCODE -ne 0) { throw 'ipatool build failed' }
31
+ Copy-Item -LiteralPath (Join-Path $Source 'LICENSE') -Destination (Join-Path $Destination 'IPATOOL-LICENSE') -Force
32
+ } finally { Pop-Location }
33
+ Write-Host 'ipatool Windows DPAPI adapter built and verified.'
@@ -0,0 +1,132 @@
1
+ //go:build windows
2
+
3
+ package cmd
4
+
5
+ import (
6
+ "crypto/sha256"
7
+ "errors"
8
+ "fmt"
9
+ "io"
10
+ "os"
11
+ "path/filepath"
12
+ "runtime"
13
+ "syscall"
14
+ "time"
15
+ "unsafe"
16
+
17
+ "github.com/byteness/keyring"
18
+ "github.com/majd/ipatool/v2/pkg/keychain"
19
+ )
20
+
21
+ // The OS user protects these bytes across processes. Never fall back to the
22
+ // legacy passphrase file backend: that would recreate the login/read split.
23
+ type dpapiKeyring struct{ directory string }
24
+ type dataBlob struct {
25
+ Size uint32
26
+ Data *byte
27
+ }
28
+
29
+ var crypt32 = syscall.NewLazyDLL("crypt32.dll")
30
+ var kernel32 = syscall.NewLazyDLL("kernel32.dll")
31
+
32
+ func openKeyring(config keyring.Config) (keychain.Keyring, error) {
33
+ directory := filepath.Join(config.FileDir, "apptrace-dpapi-v1")
34
+ if err := os.MkdirAll(directory, 0700); err != nil {
35
+ return nil, err
36
+ }
37
+ return &dpapiKeyring{directory: directory}, nil
38
+ }
39
+
40
+ func protectData(input []byte, decrypt bool) ([]byte, error) {
41
+ var source, output dataBlob
42
+ source.Size = uint32(len(input))
43
+ if len(input) > 0 {
44
+ source.Data = &input[0]
45
+ }
46
+ name := "CryptProtectData"
47
+ if decrypt {
48
+ name = "CryptUnprotectData"
49
+ }
50
+ // CRYPTPROTECT_UI_FORBIDDEN, deliberately without LOCAL_MACHINE.
51
+ ok, _, err := crypt32.NewProc(name).Call(uintptr(unsafe.Pointer(&source)), 0, 0, 0, 0, 1, uintptr(unsafe.Pointer(&output)))
52
+ runtime.KeepAlive(input)
53
+ if ok == 0 {
54
+ return nil, fmt.Errorf("APPTRACE_CREDENTIAL_ACCESS_FAILED: %w", err)
55
+ }
56
+ defer kernel32.NewProc("LocalFree").Call(uintptr(unsafe.Pointer(output.Data)))
57
+ return append([]byte(nil), unsafe.Slice(output.Data, int(output.Size))...), nil
58
+ }
59
+
60
+ func (k *dpapiKeyring) filename(key string) string {
61
+ return filepath.Join(k.directory, fmt.Sprintf("%x.bin", sha256.Sum256([]byte(key))))
62
+ }
63
+
64
+ func (k *dpapiKeyring) Get(key string) (keyring.Item, error) {
65
+ filename, err := syscall.UTF16PtrFromString(k.filename(key))
66
+ if err != nil {
67
+ return keyring.Item{}, err
68
+ }
69
+ // Permit atomic replacement while another process reads the old version.
70
+ handle, err := syscall.CreateFile(filename, syscall.GENERIC_READ, syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|4, nil, syscall.OPEN_EXISTING, syscall.FILE_ATTRIBUTE_NORMAL, 0)
71
+ if errors.Is(err, syscall.ERROR_FILE_NOT_FOUND) {
72
+ return keyring.Item{}, keyring.ErrKeyNotFound
73
+ }
74
+ if err != nil {
75
+ return keyring.Item{}, fmt.Errorf("APPTRACE_CREDENTIAL_ACCESS_FAILED: %w", err)
76
+ }
77
+ file := os.NewFile(uintptr(handle), k.filename(key))
78
+ defer file.Close()
79
+ data, err := io.ReadAll(file)
80
+ if err != nil {
81
+ return keyring.Item{}, fmt.Errorf("APPTRACE_CREDENTIAL_ACCESS_FAILED: %w", err)
82
+ }
83
+ plain, err := protectData(data, true)
84
+ return keyring.Item{Key: key, Data: plain}, err
85
+ }
86
+
87
+ func (k *dpapiKeyring) Set(item keyring.Item) error {
88
+ if _, err := k.Get(item.Key); err != nil && !errors.Is(err, keyring.ErrKeyNotFound) {
89
+ return err
90
+ }
91
+ data, err := protectData(item.Data, false)
92
+ if err != nil {
93
+ return err
94
+ }
95
+ file, err := os.CreateTemp(k.directory, "credential-*.tmp")
96
+ if err != nil {
97
+ return err
98
+ }
99
+ defer os.Remove(file.Name())
100
+ if _, err = file.Write(data); err != nil {
101
+ file.Close()
102
+ return err
103
+ }
104
+ if err = file.Sync(); err != nil {
105
+ file.Close()
106
+ return err
107
+ }
108
+ if err = file.Close(); err != nil {
109
+ return err
110
+ }
111
+ // Windows may transiently deny replacement while closing a shared reader.
112
+ // Keep the old complete ciphertext until replacement succeeds.
113
+ for attempt := 0; attempt < 30; attempt++ {
114
+ err = os.Rename(file.Name(), k.filename(item.Key))
115
+ if err == nil {
116
+ return nil
117
+ }
118
+ if !errors.Is(err, syscall.ERROR_ACCESS_DENIED) && !errors.Is(err, syscall.Errno(32)) {
119
+ return err
120
+ }
121
+ time.Sleep(10 * time.Millisecond)
122
+ }
123
+ return err
124
+ }
125
+
126
+ func (k *dpapiKeyring) Remove(key string) error {
127
+ err := os.Remove(k.filename(key))
128
+ if errors.Is(err, os.ErrNotExist) {
129
+ return keyring.ErrKeyNotFound
130
+ }
131
+ return err
132
+ }
@@ -0,0 +1,85 @@
1
+ //go:build windows
2
+
3
+ package cmd
4
+
5
+ import (
6
+ "bytes"
7
+ "github.com/byteness/keyring"
8
+ "os"
9
+ "os/exec"
10
+ "sync"
11
+ "testing"
12
+ )
13
+
14
+ func TestDPAPICrossProcess(t *testing.T) {
15
+ if dir := os.Getenv("APPTRACE_DPAPI_TEST_DIR"); dir != "" {
16
+ store := &dpapiKeyring{directory: dir}
17
+ item, err := store.Get("test-only")
18
+ if err != nil || !bytes.Equal(item.Data, bytes.Repeat([]byte("test-only-secret"), 1000)) {
19
+ t.Fatal("cross-process read failed", err)
20
+ }
21
+ return
22
+ }
23
+ store := &dpapiKeyring{directory: t.TempDir()}
24
+ secret := bytes.Repeat([]byte("test-only-secret"), 1000)
25
+ if err := store.Set(keyring.Item{Key: "test-only", Data: secret}); err != nil {
26
+ t.Fatal(err)
27
+ }
28
+ ciphertext, _ := os.ReadFile(store.filename("test-only"))
29
+ if bytes.Contains(ciphertext, []byte("test-only-secret")) {
30
+ t.Fatal("plaintext on disk")
31
+ }
32
+ child := exec.Command(os.Args[0], "-test.run=^TestDPAPICrossProcess$")
33
+ child.Env = append(os.Environ(), "APPTRACE_DPAPI_TEST_DIR="+store.directory)
34
+ if output, err := child.CombinedOutput(); err != nil {
35
+ t.Fatalf("child failed: %s %v", output, err)
36
+ }
37
+ if err := store.Set(keyring.Item{Key: "test-only", Data: []byte("updated")}); err != nil {
38
+ t.Fatal("replace failed", err)
39
+ }
40
+ if err := store.Remove("test-only"); err != nil {
41
+ t.Fatal(err)
42
+ }
43
+ if _, err := store.Get("test-only"); err != keyring.ErrKeyNotFound {
44
+ t.Fatal("missing credential classification", err)
45
+ }
46
+ }
47
+
48
+ func TestDPAPIRejectsCorruption(t *testing.T) {
49
+ store := &dpapiKeyring{directory: t.TempDir()}
50
+ if err := os.WriteFile(store.filename("corrupt"), []byte("invalid cipher"), 0600); err != nil {
51
+ t.Fatal(err)
52
+ }
53
+ if _, err := store.Get("corrupt"); err == nil {
54
+ t.Fatal("corruption accepted")
55
+ }
56
+ if err := store.Set(keyring.Item{Key: "corrupt", Data: []byte("replacement")}); err == nil {
57
+ t.Fatal("corrupt credential overwritten")
58
+ }
59
+ }
60
+
61
+ func TestDPAPIConcurrentReaders(t *testing.T) {
62
+ store := &dpapiKeyring{directory: t.TempDir()}
63
+ if err := store.Set(keyring.Item{Key: "concurrent", Data: []byte("valid")}); err != nil {
64
+ t.Fatal(err)
65
+ }
66
+ var readers sync.WaitGroup
67
+ for i := 0; i < 4; i++ {
68
+ readers.Add(1)
69
+ go func() {
70
+ defer readers.Done()
71
+ for j := 0; j < 30; j++ {
72
+ item, err := store.Get("concurrent")
73
+ if err != nil || string(item.Data) != "valid" {
74
+ t.Error("partial credential read", err)
75
+ }
76
+ }
77
+ }()
78
+ }
79
+ for j := 0; j < 30; j++ {
80
+ if err := store.Set(keyring.Item{Key: "concurrent", Data: []byte("valid")}); err != nil {
81
+ t.Error(err)
82
+ }
83
+ }
84
+ readers.Wait()
85
+ }
@@ -24,14 +24,15 @@ When the user provides an App Store URL or App ID and asks what the app uses,
24
24
  what it contains, or for a report:
25
25
 
26
26
  1. Call `analyze_app` with the source and the smallest useful set of goals.
27
- If it returns `awaiting_setup`, preserve the complete `resume.arguments`.
28
- Follow `next_tool` automatically: `prepare_environment` installs the fixed
29
- toolchain, `open_login` opens the user's local Windows sign-in window, and
30
- `get_setup_status(wait_seconds=20)` waits for progress. Report meaningful
31
- stage changes; never ask the user to run installation commands that a tool
32
- can run. When state is `ready`, call `analyze_app` with the saved arguments.
33
- This is agent-driven resumption, not an independently scheduled analysis.
34
- When analysis is running it returns `runId`.
27
+ Retain its `runId` from the first response, including `queued`, `preparing`
28
+ and `awaiting_auth`. A persistent local worker installs missing tools, opens
29
+ Windows sign-in once and resumes the original task after background account
30
+ verification. Poll `get_analysis_run(run_id=runId, wait_seconds=20)` until
31
+ completed or failed. Do not end the turn just because login is pending and
32
+ do not ask the user to confirm completion or resend `analyze_app`.
33
+ Report meaningful stage changes; credentials belong only in the local window.
34
+ If `stage=login_cancelled`, explain that the task is saved and wait for an
35
+ explicit retry; do not reopen the window automatically.
35
36
  2. Use `get_analysis_run` to wait for or retrieve the completed result.
36
37
  3. Prefer goals `tech_stack`, `feature_map`, and `report` for a first pass.
37
38
  4. Add `network` when the user asks about APIs or data flow.
@@ -45,15 +46,26 @@ what it contains, or for a report:
45
46
  If a tool returns `AUTH_REQUIRED` or `KEYCHAIN_LOCKED`:
46
47
 
47
48
  1. Call `open_login`. Tell the user to enter credentials only in the local window.
48
- 2. Poll `get_setup_status(wait_seconds=20, require_account=true)` while `login_pending`; do not open
49
- repeated windows. When ready, resume the saved request without asking the
50
- user to repeat their goal.
49
+ 2. For an existing analysis task, poll `get_analysis_run` with its saved ID;
50
+ the worker resumes automatically. For standalone setup, poll
51
+ `get_setup_status(wait_seconds=20, require_account=true)`. Do not open repeated
52
+ windows or ask the user to repeat their goal.
51
53
  3. Never ask for, capture, screenshot, log, or relay passwords, verification
52
54
  codes, keychain passphrases, cookies, or tokens. Only the user handles them.
53
55
  4. If a login attempt is cancelled or fails, explain the state and wait for the
54
56
  user to request another attempt. Do not repeatedly reopen a dismissed window.
55
- 5. On hosts without automatic login windows, give the single fallback command
56
- `apptrace auth login` and resume after the user confirms completion.
57
+ 5. On hosts without automatic login windows, give the fallback command
58
+ `apptrace auth login` and keep querying the saved task. Confirmation in chat
59
+ is not a substitute for background account verification.
60
+
61
+ Windows requires the AppTrace DPAPI adapter. `IPATOOL_UPDATE_REQUIRED` means the
62
+ managed tool needs an update or a custom tool path points to an old version.
63
+ An update preserves the old file keyring but requires one local sign-in into
64
+ the new Windows-user-protected store. `CREDENTIAL_ACCESS_FAILED` means the store
65
+ cannot be read/decrypted; do not overwrite it or treat that as logged out.
66
+ Use `cancel_analysis_run` only when the user requests cancellation. Waiting
67
+ tasks survive MCP disconnection; after 30 idle minutes a worker stops waiting
68
+ and retains the task, resumed by the next MCP connection or explicit login.
57
69
 
58
70
  For `failed`, distinguish account-check failure from installation failure.
59
71
  Do not infer that the user is logged out from a timeout or unknown response.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhuixin/cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "license": "Apache-2.0",
5
5
  "description": "AppTrace CLI for evidence-backed application analysis",
6
6
  "repository": {
@@ -36,9 +36,9 @@
36
36
  "dependencies": {
37
37
  "rea-agents": "6.4.0",
38
38
  "smol-toml": "^1.9.0",
39
- "@zhuixin/mcp": "0.2.2",
40
- "@zhuixin/metadata": "0.2.2",
41
- "@zhuixin/shared": "0.2.2"
39
+ "@zhuixin/metadata": "0.2.4",
40
+ "@zhuixin/shared": "0.2.4",
41
+ "@zhuixin/mcp": "0.2.4"
42
42
  },
43
43
  "devDependencies": {
44
44
  "tsup": "^8.5.0"