siluzan-tso-cli 1.1.26 → 1.1.27-beta.2

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.
Files changed (30) hide show
  1. package/README.md +2 -1
  2. package/dist/index.js +721 -102
  3. package/dist/skill/AGENTS.md +3 -1
  4. package/dist/skill/SKILL.md +5 -4
  5. package/dist/skill/_meta.json +2 -2
  6. package/dist/skill/assets/market-analysis-rules.md +134 -0
  7. package/dist/skill/references/README.md +3 -1
  8. package/dist/skill/references/accounts/accounts.md +18 -4
  9. package/dist/skill/references/accounts/finance.md +32 -32
  10. package/dist/skill/references/accounts/open-account-google-ui.md +1 -1
  11. package/dist/skill/references/analytics/market-analysis-guide.md +118 -0
  12. package/dist/skill/references/analytics/rag.md +1 -1
  13. package/dist/skill/references/analytics/reporting.md +5 -5
  14. package/dist/skill/references/analytics/website-diagnosis-guide.md +3 -3
  15. package/dist/skill/references/core/agent-conventions.md +1 -1
  16. package/dist/skill/references/core/cli-enums.md +140 -0
  17. package/dist/skill/references/core/playbooks.md +35 -2
  18. package/dist/skill/references/core/setup.md +5 -5
  19. package/dist/skill/references/core/skill-authoring.md +1 -1
  20. package/dist/skill/references/core/tips.md +1 -1
  21. package/dist/skill/references/core/workflows.md +4 -4
  22. package/dist/skill/references/misc/tso-home.md +2 -2
  23. package/dist/skill/report-templates/market-analysis-report.md +40 -0
  24. package/dist/skill/report-templates/website-diagnosis-report.html +1869 -420
  25. package/dist/skill/report-templates/website-diagnosis-report.md +11 -1
  26. package/dist/skill/report-templates/website-diagnosis-report.runtime.js +0 -0
  27. package/dist/skill/scripts/install.ps1 +3 -3
  28. package/dist/skill/scripts/install.sh +3 -3
  29. package/eval/cases/no-legacy-json-flag.scenario.json +1 -1
  30. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3328,7 +3328,7 @@ var DEFAULT_API_BASE;
3328
3328
  var init_defaults = __esm({
3329
3329
  "src/config/defaults.ts"() {
3330
3330
  "use strict";
3331
- DEFAULT_API_BASE = "https://tso-api.siluzan.com";
3331
+ DEFAULT_API_BASE = "https://tso-api-ci.siluzan.com";
3332
3332
  }
3333
3333
  });
3334
3334
 
@@ -101225,8 +101225,8 @@ var init_http_retry = __esm({
101225
101225
  });
101226
101226
 
101227
101227
  // src/utils/batch-manifest.ts
101228
- import * as fs13 from "fs/promises";
101229
- import * as path18 from "path";
101228
+ import * as fs14 from "fs/promises";
101229
+ import * as path19 from "path";
101230
101230
  import { randomUUID as randomUUID2 } from "crypto";
101231
101231
  function generateRunId(now = /* @__PURE__ */ new Date()) {
101232
101232
  const pad = (n) => String(n).padStart(2, "0");
@@ -101240,26 +101240,26 @@ function isValidRunId(id) {
101240
101240
  }
101241
101241
  function resolveBatchPaths(baseDir, runId) {
101242
101242
  if (!isValidRunId(runId)) throw new Error(`\u975E\u6CD5 runId\uFF1A${runId}`);
101243
- const runDir = path18.resolve(baseDir, runId);
101244
- const stateDir = path18.join(runDir, "state");
101243
+ const runDir = path19.resolve(baseDir, runId);
101244
+ const stateDir = path19.join(runDir, "state");
101245
101245
  return {
101246
101246
  runDir,
101247
- runManifestFile: path18.join(runDir, "run-manifest.json"),
101248
- progressFile: path18.join(runDir, "progress.json"),
101249
- accountsFile: path18.join(runDir, "accounts.json"),
101250
- resultsDir: path18.join(runDir, "results"),
101251
- errorsDir: path18.join(runDir, "errors"),
101247
+ runManifestFile: path19.join(runDir, "run-manifest.json"),
101248
+ progressFile: path19.join(runDir, "progress.json"),
101249
+ accountsFile: path19.join(runDir, "accounts.json"),
101250
+ resultsDir: path19.join(runDir, "results"),
101251
+ errorsDir: path19.join(runDir, "errors"),
101252
101252
  stateDir,
101253
- tasksLogFile: path18.join(stateDir, "tasks.jsonl"),
101254
- lockFile: path18.join(stateDir, "tasks.lock")
101253
+ tasksLogFile: path19.join(stateDir, "tasks.jsonl"),
101254
+ lockFile: path19.join(stateDir, "tasks.lock")
101255
101255
  };
101256
101256
  }
101257
101257
  async function ensureAccountDirs(paths, accountId) {
101258
101258
  const safe = sanitizeAccountSegment(accountId);
101259
- const results = path18.join(paths.resultsDir, safe);
101260
- const errors = path18.join(paths.errorsDir, safe);
101261
- await fs13.mkdir(results, { recursive: true });
101262
- await fs13.mkdir(errors, { recursive: true });
101259
+ const results = path19.join(paths.resultsDir, safe);
101260
+ const errors = path19.join(paths.errorsDir, safe);
101261
+ await fs14.mkdir(results, { recursive: true });
101262
+ await fs14.mkdir(errors, { recursive: true });
101263
101263
  return { results, errors };
101264
101264
  }
101265
101265
  function sanitizeAccountSegment(accountId) {
@@ -101274,20 +101274,20 @@ function sanitizeAccountSegment(accountId) {
101274
101274
  return slug;
101275
101275
  }
101276
101276
  async function atomicWriteFile(target, content) {
101277
- const dir = path18.dirname(target);
101278
- await fs13.mkdir(dir, { recursive: true });
101279
- const tmp = path18.join(dir, `.tmp-${randomUUID2()}-${path18.basename(target)}`);
101277
+ const dir = path19.dirname(target);
101278
+ await fs14.mkdir(dir, { recursive: true });
101279
+ const tmp = path19.join(dir, `.tmp-${randomUUID2()}-${path19.basename(target)}`);
101280
101280
  try {
101281
- await fs13.writeFile(tmp, content, "utf8");
101282
- await fs13.rename(tmp, target);
101281
+ await fs14.writeFile(tmp, content, "utf8");
101282
+ await fs14.rename(tmp, target);
101283
101283
  } catch (e) {
101284
- await fs13.rm(tmp, { force: true }).catch(() => void 0);
101284
+ await fs14.rm(tmp, { force: true }).catch(() => void 0);
101285
101285
  throw e;
101286
101286
  }
101287
101287
  }
101288
101288
  async function readJsonIfExists(file) {
101289
101289
  try {
101290
- const raw = await fs13.readFile(file, "utf8");
101290
+ const raw = await fs14.readFile(file, "utf8");
101291
101291
  return JSON.parse(raw);
101292
101292
  } catch (e) {
101293
101293
  if (e.code === "ENOENT") return null;
@@ -101317,12 +101317,12 @@ function readProgress(paths) {
101317
101317
  return readJsonIfExists(paths.progressFile);
101318
101318
  }
101319
101319
  async function appendTaskLog(paths, entry) {
101320
- await fs13.mkdir(paths.stateDir, { recursive: true });
101321
- await fs13.appendFile(paths.tasksLogFile, JSON.stringify(entry) + "\n", "utf8");
101320
+ await fs14.mkdir(paths.stateDir, { recursive: true });
101321
+ await fs14.appendFile(paths.tasksLogFile, JSON.stringify(entry) + "\n", "utf8");
101322
101322
  }
101323
101323
  async function readTaskLog(paths) {
101324
101324
  try {
101325
- const raw = await fs13.readFile(paths.tasksLogFile, "utf8");
101325
+ const raw = await fs14.readFile(paths.tasksLogFile, "utf8");
101326
101326
  return raw.split("\n").filter((s) => s.trim() !== "").map((line) => JSON.parse(line));
101327
101327
  } catch (e) {
101328
101328
  if (e.code === "ENOENT") return [];
@@ -101377,9 +101377,9 @@ function emptyProgress(runId, totalTasks, accountConcurrency, sectionConcurrency
101377
101377
  };
101378
101378
  }
101379
101379
  async function tryAcquireLock(paths) {
101380
- await fs13.mkdir(paths.stateDir, { recursive: true });
101380
+ await fs14.mkdir(paths.stateDir, { recursive: true });
101381
101381
  try {
101382
- const handle = await fs13.open(paths.lockFile, "wx");
101382
+ const handle = await fs14.open(paths.lockFile, "wx");
101383
101383
  await handle.writeFile(`${process.pid}@${(/* @__PURE__ */ new Date()).toISOString()}`);
101384
101384
  await handle.close();
101385
101385
  return true;
@@ -101389,7 +101389,7 @@ async function tryAcquireLock(paths) {
101389
101389
  }
101390
101390
  }
101391
101391
  async function releaseLock(paths) {
101392
- await fs13.rm(paths.lockFile, { force: true }).catch(() => void 0);
101392
+ await fs14.rm(paths.lockFile, { force: true }).catch(() => void 0);
101393
101393
  }
101394
101394
  var BATCH_SCHEMA_VERSION;
101395
101395
  var init_batch_manifest = __esm({
@@ -101401,8 +101401,8 @@ var init_batch_manifest = __esm({
101401
101401
 
101402
101402
  // src/utils/batch-runner.ts
101403
101403
  import { performance as performance2 } from "perf_hooks";
101404
- import * as path19 from "path";
101405
- import * as fs14 from "fs/promises";
101404
+ import * as path20 from "path";
101405
+ import * as fs15 from "fs/promises";
101406
101406
  async function runPool(items, concurrencyRef, worker, signal) {
101407
101407
  let cursor = 0;
101408
101408
  const launch = () => {
@@ -101541,7 +101541,7 @@ async function runBatch(opts) {
101541
101541
  }
101542
101542
  const inner = {
101543
101543
  ...emptyProgress(
101544
- opts.paths.runDir.split(path19.sep).pop() ?? "run",
101544
+ opts.paths.runDir.split(path20.sep).pop() ?? "run",
101545
101545
  allTasks.length,
101546
101546
  opts.accountConcurrency,
101547
101547
  opts.sectionConcurrency
@@ -101671,7 +101671,7 @@ async function runBatch(opts) {
101671
101671
  }
101672
101672
  );
101673
101673
  await writer({
101674
- snapshotDir: path19.join(opts.paths.resultsDir, sanitizeAccountSegment(task.accountId)),
101674
+ snapshotDir: path20.join(opts.paths.resultsDir, sanitizeAccountSegment(task.accountId)),
101675
101675
  section: task.section,
101676
101676
  accountId: task.accountId,
101677
101677
  dateRange: result.dateRange,
@@ -101744,7 +101744,7 @@ async function runBatch(opts) {
101744
101744
  };
101745
101745
  }
101746
101746
  async function writeTaskError(paths, errorsDir, task, err, cls) {
101747
- const file = path19.join(errorsDir, `${sanitizeSection(task.section)}.error.json`);
101747
+ const file = path20.join(errorsDir, `${sanitizeSection(task.section)}.error.json`);
101748
101748
  const payload = {
101749
101749
  schemaVersion: 1,
101750
101750
  taskId: task.id,
@@ -101790,10 +101790,10 @@ async function persistRunMetadata(input) {
101790
101790
  accountsTotal: input.accounts.length,
101791
101791
  invocation: input.invocation
101792
101792
  };
101793
- await fs14.mkdir(paths.runDir, { recursive: true });
101794
- await fs14.mkdir(paths.stateDir, { recursive: true });
101795
- await fs14.mkdir(paths.resultsDir, { recursive: true });
101796
- await fs14.mkdir(paths.errorsDir, { recursive: true });
101793
+ await fs15.mkdir(paths.runDir, { recursive: true });
101794
+ await fs15.mkdir(paths.stateDir, { recursive: true });
101795
+ await fs15.mkdir(paths.resultsDir, { recursive: true });
101796
+ await fs15.mkdir(paths.errorsDir, { recursive: true });
101797
101797
  await writeRunManifest(paths, manifest);
101798
101798
  await writeAccounts(paths, input.accounts);
101799
101799
  return paths;
@@ -101856,7 +101856,7 @@ __export(google_analysis_batch_exports, {
101856
101856
  parseAccountIds: () => parseAccountIds,
101857
101857
  registerGoogleAnalysisBatchCommands: () => registerGoogleAnalysisBatchCommands
101858
101858
  });
101859
- import * as path20 from "path";
101859
+ import * as path21 from "path";
101860
101860
  import { performance as performance3 } from "perf_hooks";
101861
101861
  function parseSections(input) {
101862
101862
  if (!input || !input.trim()) return DEFAULT_SECTIONS;
@@ -102403,7 +102403,7 @@ var init_google_analysis_batch = __esm({
102403
102403
  });
102404
102404
 
102405
102405
  // src/commands/google-analysis/register-cli.ts
102406
- import * as path21 from "path";
102406
+ import * as path22 from "path";
102407
102407
  import { performance as performance4 } from "perf_hooks";
102408
102408
  function resolveSectionList(sections, exclude) {
102409
102409
  const allNames = SECTIONS.map((s) => s.name);
@@ -102493,7 +102493,7 @@ async function runAllSections(opts) {
102493
102493
  const results = await runWithConcurrency(tasks, concurrency);
102494
102494
  const succeeded = results.filter((r) => r.ok).length;
102495
102495
  const failed = results.length - succeeded;
102496
- const absoluteSnapshotDir = path21.resolve(opts.jsonOut);
102496
+ const absoluteSnapshotDir = path22.resolve(opts.jsonOut);
102497
102497
  const summary = {
102498
102498
  kind: "siluzan-tso-google-analysis-snapshot-batch",
102499
102499
  absoluteSnapshotDir,
@@ -102726,8 +102726,8 @@ var init_google_analysis3 = __esm({
102726
102726
  // src/index.ts
102727
102727
  init_dist();
102728
102728
  init_dist();
102729
- import * as fs15 from "fs";
102730
- import * as path23 from "path";
102729
+ import * as fs16 from "fs";
102730
+ import * as path24 from "path";
102731
102731
  import { fileURLToPath as fileURLToPath5 } from "url";
102732
102732
  import { Command } from "commander";
102733
102733
 
@@ -103212,8 +103212,8 @@ function deriveWebUrl(apiBaseUrl) {
103212
103212
  }
103213
103213
  var TSO_UMI_ROUTE_PREFIX = "/v3umijs/tso";
103214
103214
  function buildTsoPageWebUrl(webUrl, tsoSubRoute, extraQuery) {
103215
- const path24 = tsoSubRoute.replace(/^\/+/, "");
103216
- const umiPath = `${TSO_UMI_ROUTE_PREFIX}/${path24}`;
103215
+ const path25 = tsoSubRoute.replace(/^\/+/, "");
103216
+ const umiPath = `${TSO_UMI_ROUTE_PREFIX}/${path25}`;
103217
103217
  const q = new URLSearchParams({ tso: umiPath });
103218
103218
  if (extraQuery) {
103219
103219
  for (const [key, value] of Object.entries(extraQuery)) {
@@ -103221,7 +103221,7 @@ function buildTsoPageWebUrl(webUrl, tsoSubRoute, extraQuery) {
103221
103221
  if (v) q.set(key, v);
103222
103222
  }
103223
103223
  }
103224
- return `${webUrl.replace(/\/+$/, "")}/v3/foreign_trade/tso/${path24}?${q.toString()}`;
103224
+ return `${webUrl.replace(/\/+$/, "")}/v3/foreign_trade/tso/${path25}?${q.toString()}`;
103225
103225
  }
103226
103226
  function cmdConfigShow() {
103227
103227
  const shared = readSharedConfig();
@@ -104295,6 +104295,15 @@ async function fetchTikTokAccountByMediaCustomerId(config, mediaCustomerId, verb
104295
104295
  const match = items.find((it) => String(it.ma?.mediaCustomerId ?? "") === id);
104296
104296
  return match ?? null;
104297
104297
  }
104298
+ function stripListAccountsEnrichmentFields(item) {
104299
+ delete item.ma.remainingAccountBudget;
104300
+ delete item.ma.platformStatus;
104301
+ delete item.ma.aritScore;
104302
+ const maExt = item.ma;
104303
+ for (const key of ["spend", "impressions", "clicks", "conversions", "costPerClick"]) {
104304
+ delete maExt[key];
104305
+ }
104306
+ }
104298
104307
  async function fetchGoogleAccountByMediaCustomerId(config, mediaCustomerId, verbose) {
104299
104308
  const cfg = PLATFORM_CONFIG.Google;
104300
104309
  const params = new URLSearchParams();
@@ -104734,7 +104743,7 @@ async function runListAccounts(opts) {
104734
104743
  process.exit(1);
104735
104744
  }
104736
104745
  }
104737
- if (items.length > 0 && !opts.quick) {
104746
+ if (items.length > 0 && opts.detail) {
104738
104747
  const groups = /* @__PURE__ */ new Map();
104739
104748
  for (const item of items) {
104740
104749
  const id = item.ma.mediaCustomerId;
@@ -104808,6 +104817,10 @@ async function runListAccounts(opts) {
104808
104817
  targetItem.ma.aritScore = report.summaryScore ?? "-";
104809
104818
  }
104810
104819
  }
104820
+ } else if (items.length > 0) {
104821
+ for (const item of items) {
104822
+ stripListAccountsEnrichmentFields(item);
104823
+ }
104811
104824
  }
104812
104825
  const oauthFailWarning = (() => {
104813
104826
  if (items.length < 5) return null;
@@ -104835,8 +104848,8 @@ async function runListAccounts(opts) {
104835
104848
  const totalInfo = total !== void 0 ? `\uFF0C\u5171 ${total} \u6761` : "";
104836
104849
  let listHeader = `
104837
104850
  \u5E7F\u544A\u8D26\u6237\u5217\u8868\uFF08\u7B2C ${page} \u9875\uFF0C\u672C\u9875 ${items.length} \u6761${totalInfo}\uFF09`;
104838
- if (opts.quick) {
104839
- listHeader += "\n \uFF08\u5FEB\u901F\u6A21\u5F0F\uFF1A\u672A\u5408\u5E76\u4F59\u989D\u3001\u6295\u653E\u6D88\u8017\u4E0E Arit \u5F97\u5206\uFF1B\u8868\u4E2D\u5BF9\u5E94\u5217\u4E3A\u300C-\u300D\u3002TikTok/Meta \u4ECD\u5408\u5E76\u5217\u8868\u63A5\u53E3\u540C\u5305\u5185\u7684 adList\u3002\uFF09";
104851
+ if (!opts.detail) {
104852
+ listHeader += "\n \uFF08\u9ED8\u8BA4\u5217\u8868\uFF1A\u672A\u5408\u5E76\u4F59\u989D\u4E0E\u6D88\u8017\uFF0CJSON \u4E0D\u542B\u76F8\u5173\u5B57\u6BB5\uFF1B\u52A0 --detail \u53EF\u62C9\u53D6\u771F\u5B9E\u6570\u503C\uFF0C\u54CD\u5E94\u8F83\u6162\u3002TikTok/Meta \u4ECD\u5408\u5E76\u5217\u8868\u63A5\u53E3\u540C\u5305\u5185\u7684 adList\u3002\uFF09";
104840
104853
  }
104841
104854
  listHeader += "\n";
104842
104855
  console.log(listHeader);
@@ -104899,9 +104912,9 @@ function register6(program2) {
104899
104912
  "\u843D\u76D8\uFF08\u76EE\u5F55\u6216 *.json \u6587\u4EF6\u8DEF\u5F84\uFF09\u5E76\u66F4\u65B0 cli-manifest[-<\u67E5\u8BE2id>].json\uFF1B\u76EE\u5F55\u6A21\u5F0F\u6587\u4EF6\u540D\u4E3A `<section>[-<\u67E5\u8BE2id>].json`\uFF1Bstdout \u4E00\u884C\u6458\u8981 JSON\uFF0C\u542B outlineFile\uFF08TS \u5F0F\u7C7B\u578B\u5728 `*.outline.txt`\uFF09",
104900
104913
  void 0
104901
104914
  ).option(
104902
- "--quick",
104903
- "\u9ED8\u8BA4\u5F00\u542F\uFF0C\u8868\u683C\u4E0E JSON \u4E2D\u4F59\u989D\u3001\u6D88\u8017\u3001\u5C55\u793A/\u70B9\u51FB/\u8F6C\u5316/CPC\u3001Arit \u7B49\u5C06\u4E3A\u7A7A\u6216\u300C-\u300D\uFF0C\u5982\u679C\u5173\u95ED\u540E\uFF0C\u8BF7\u6C42\u8FD4\u56DE\u901F\u5EA6\u5C06\u53D8\u5F97\u6781\u6162",
104904
- true
104915
+ "--detail",
104916
+ "\u5408\u5E76\u4F59\u989D\u4E0E\u8FD1\u671F\u6D88\u8017\uFF08\u53CA\u5C55\u793A/\u70B9\u51FB/\u8F6C\u5316/CPC\u3001Arit \u5F97\u5206\uFF09\uFF1B\u672A\u4F20\u65F6 JSON \u4E0D\u542B\u8FD9\u4E9B\u5B57\u6BB5\uFF0C\u5217\u8868\u66F4\u5FEB",
104917
+ false
104905
104918
  ).option("--unicode", "\u4F7F\u7528 Unicode \u7EBF\u6846\u8868\u683C\uFF08cli-table3\uFF09\uFF1B\u9ED8\u8BA4 ASCII +-|", false).option("--plain", "\u5DF2\u9ED8\u8BA4 ASCII \u7EBF\u6846\uFF0C\u65E0\u9700\u518D\u4F20\uFF1B\u4FDD\u7559\u517C\u5BB9\u65E7\u811A\u672C", false).option(
104906
104919
  "--refresh-dp",
104907
104920
  "\u5F3A\u5236\u91CD\u62C9 Datapermission \u540E\u518D\u8BF7\u6C42\u5217\u8868\uFF08\u7528\u4E8E\u300C\u7B2C\u4E8C\u6B21\u62C9\u53D6\u5168\u90E8\u5931\u6548\u300D\u7C7B\u4F1A\u8BDD\u5F02\u5E38\u7684\u4E00\u952E\u6392\u67E5\uFF09",
@@ -104916,7 +104929,7 @@ function register6(program2) {
104916
104929
  page: opts.page,
104917
104930
  pageSize: opts.pageSize,
104918
104931
  jsonOut: opts.jsonOut,
104919
- quick: opts.quick,
104932
+ detail: opts.detail,
104920
104933
  unicode: opts.unicode,
104921
104934
  refreshDp: opts.refreshDp,
104922
104935
  verbose: opts.verbose
@@ -110395,7 +110408,7 @@ function cloneKeywordBlockShell(block) {
110395
110408
  delete shell["matchTypeV2"];
110396
110409
  return shell;
110397
110410
  }
110398
- function splitKeywordsForBatchJobBlockIfMixed(block, path24, warnings) {
110411
+ function splitKeywordsForBatchJobBlockIfMixed(block, path25, warnings) {
110399
110412
  const declaredUi = matchTypeV2ToUi(readBlockMatchTypeRaw(block));
110400
110413
  const entries = collectKeywordEntriesFromBlock(block);
110401
110414
  if (entries.length === 0) return [block];
@@ -110414,7 +110427,7 @@ function splitKeywordsForBatchJobBlockIfMixed(block, path24, warnings) {
110414
110427
  (ui) => matchTypeUiToV2(ui)
110415
110428
  );
110416
110429
  warnings.push(
110417
- `${path24} \u542B\u591A\u79CD\u5339\u914D\u7C7B\u578B\uFF0C\u5DF2\u81EA\u52A8\u62C6\u5206\u4E3A ${groups.size} \u4E2A KeywordsForBatchJob \u5757\uFF08${labels.join("\u3001")}\uFF09`
110430
+ `${path25} \u542B\u591A\u79CD\u5339\u914D\u7C7B\u578B\uFF0C\u5DF2\u81EA\u52A8\u62C6\u5206\u4E3A ${groups.size} \u4E2A KeywordsForBatchJob \u5757\uFF08${labels.join("\u3001")}\uFF09`
110418
110431
  );
110419
110432
  const shell = cloneKeywordBlockShell(block);
110420
110433
  const splitBlocks = [];
@@ -110434,16 +110447,16 @@ function splitKeywordsForBatchJobBlockIfMixed(block, path24, warnings) {
110434
110447
  }
110435
110448
  return splitBlocks;
110436
110449
  }
110437
- function validateKeywordCore(core, path24, errors, lengthViolations) {
110450
+ function validateKeywordCore(core, path25, errors, lengthViolations) {
110438
110451
  const trimmed = core.trim();
110439
110452
  if (!trimmed) {
110440
- errors.push(`${path24} \u5173\u952E\u8BCD\u8BCD\u5E72\u4E0D\u80FD\u4E3A\u7A7A`);
110453
+ errors.push(`${path25} \u5173\u952E\u8BCD\u8BCD\u5E72\u4E0D\u80FD\u4E3A\u7A7A`);
110441
110454
  return false;
110442
110455
  }
110443
110456
  if (trimmed.length > GOOGLE_KEYWORD_MAX_CORE_LENGTH) {
110444
110457
  if (lengthViolations) {
110445
110458
  pushLengthViolation(lengthViolations, {
110446
- path: path24,
110459
+ path: path25,
110447
110460
  field: "KeywordText",
110448
110461
  kind: "keyword_core",
110449
110462
  limit: GOOGLE_KEYWORD_MAX_CORE_LENGTH,
@@ -110453,13 +110466,13 @@ function validateKeywordCore(core, path24, errors, lengthViolations) {
110453
110466
  });
110454
110467
  }
110455
110468
  errors.push(
110456
- `${path24} \u5173\u952E\u8BCD\u8BCD\u5E72\u8D85\u8FC7 ${GOOGLE_KEYWORD_MAX_CORE_LENGTH} \u5B57\u7B26\uFF08\u5F53\u524D ${trimmed.length}\uFF09\uFF1A"${trimmed}"`
110469
+ `${path25} \u5173\u952E\u8BCD\u8BCD\u5E72\u8D85\u8FC7 ${GOOGLE_KEYWORD_MAX_CORE_LENGTH} \u5B57\u7B26\uFF08\u5F53\u524D ${trimmed.length}\uFF09\uFF1A"${trimmed}"`
110457
110470
  );
110458
110471
  return false;
110459
110472
  }
110460
110473
  if (!VALID_CORE_REGEX.test(trimmed)) {
110461
110474
  errors.push(
110462
- `${path24} \u542B Google \u4E0D\u5141\u8BB8\u7684\u5B57\u7B26\uFF08\u8BCD\u5E72\u4EC5\u5141\u8BB8\u5B57\u6BCD/\u6570\u5B57/\u7A7A\u683C/\u8FDE\u5B57\u7B26/\u53E5\u70B9\uFF09\uFF1A"${trimmed.slice(0, 40)}${trimmed.length > 40 ? "\u2026" : ""}"`
110475
+ `${path25} \u542B Google \u4E0D\u5141\u8BB8\u7684\u5B57\u7B26\uFF08\u8BCD\u5E72\u4EC5\u5141\u8BB8\u5B57\u6BCD/\u6570\u5B57/\u7A7A\u683C/\u8FDE\u5B57\u7B26/\u53E5\u70B9\uFF09\uFF1A"${trimmed.slice(0, 40)}${trimmed.length > 40 ? "\u2026" : ""}"`
110463
110476
  );
110464
110477
  return false;
110465
110478
  }
@@ -110516,24 +110529,24 @@ function canonicalizeKeywordBatchBlock(block, resolvedMatchV2, normalizedTexts,
110516
110529
  }
110517
110530
  }
110518
110531
  }
110519
- function pushKeywordAutoFixWarning(warnings, path24, fieldLabel, trimmedRaw, formatted, declaredUi, beforeUi, matchType, inferredMatchType) {
110532
+ function pushKeywordAutoFixWarning(warnings, path25, fieldLabel, trimmedRaw, formatted, declaredUi, beforeUi, matchType, inferredMatchType) {
110520
110533
  if (inferredMatchType) {
110521
110534
  warnings.push(
110522
- `${path24}.${fieldLabel} \u672A\u6307\u5B9A MatchTypeV2\uFF0C\u5DF2\u6309\u8BCD\u9762\u63A8\u65AD\u4E3A ${matchTypeUiToV2(matchType)}`
110535
+ `${path25}.${fieldLabel} \u672A\u6307\u5B9A MatchTypeV2\uFF0C\u5DF2\u6309\u8BCD\u9762\u63A8\u65AD\u4E3A ${matchTypeUiToV2(matchType)}`
110523
110536
  );
110524
110537
  return;
110525
110538
  }
110526
110539
  if (declaredUi && beforeUi !== declaredUi) {
110527
110540
  warnings.push(
110528
- `${path24}.${fieldLabel} MatchTypeV2=${matchTypeUiToV2(declaredUi)} \u4E0E\u8BCD\u9762\u4E0D\u4E00\u81F4\uFF0C\u5DF2\u81EA\u52A8\u4FEE\u590D\u4E3A\uFF1A${formatted}`
110541
+ `${path25}.${fieldLabel} MatchTypeV2=${matchTypeUiToV2(declaredUi)} \u4E0E\u8BCD\u9762\u4E0D\u4E00\u81F4\uFF0C\u5DF2\u81EA\u52A8\u4FEE\u590D\u4E3A\uFF1A${formatted}`
110529
110542
  );
110530
110543
  return;
110531
110544
  }
110532
110545
  if (formatted !== trimmedRaw) {
110533
- warnings.push(`${path24}.${fieldLabel} \u5DF2\u81EA\u52A8\u4FEE\u590D\u8BCD\u9762\uFF1A${trimmedRaw} \u2192 ${formatted}`);
110546
+ warnings.push(`${path25}.${fieldLabel} \u5DF2\u81EA\u52A8\u4FEE\u590D\u8BCD\u9762\uFF1A${trimmedRaw} \u2192 ${formatted}`);
110534
110547
  }
110535
110548
  }
110536
- function normalizeKeywordTextList(texts, matchTypeRaw, path24, fieldLabel, errors, warnings, lengthViolations) {
110549
+ function normalizeKeywordTextList(texts, matchTypeRaw, path25, fieldLabel, errors, warnings, lengthViolations) {
110537
110550
  const declaredUi = matchTypeV2ToUi(matchTypeRaw);
110538
110551
  const normalized = [];
110539
110552
  const seen = /* @__PURE__ */ new Set();
@@ -110541,7 +110554,7 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path24, fieldLabel, error
110541
110554
  for (let k = 0; k < texts.length; k++) {
110542
110555
  const raw = texts[k];
110543
110556
  if (typeof raw !== "string" || !raw.trim()) {
110544
- errors.push(`${path24}.${fieldLabel}[${k}] \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
110557
+ errors.push(`${path25}.${fieldLabel}[${k}] \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
110545
110558
  continue;
110546
110559
  }
110547
110560
  const trimmedRaw = collapseDuplicateSpacesInKeywordText(raw.trim());
@@ -110549,7 +110562,7 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path24, fieldLabel, error
110549
110562
  const { formatted, matchType, inferredMatchType } = normalizeKeywordSurface(trimmedRaw, matchTypeRaw);
110550
110563
  pushKeywordAutoFixWarning(
110551
110564
  warnings,
110552
- path24,
110565
+ path25,
110553
110566
  fieldLabel,
110554
110567
  trimmedRaw,
110555
110568
  formatted,
@@ -110559,10 +110572,10 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path24, fieldLabel, error
110559
110572
  inferredMatchType
110560
110573
  );
110561
110574
  const core = unwrapKeywordDisplayTextForEdit(formatted);
110562
- if (!validateKeywordCore(core, `${path24}.${fieldLabel}[${k}]`, errors, lengthViolations)) continue;
110575
+ if (!validateKeywordCore(core, `${path25}.${fieldLabel}[${k}]`, errors, lengthViolations)) continue;
110563
110576
  const dedupeKey = `${matchTypeUiToV2(matchType)}:${core.toLowerCase()}`;
110564
110577
  if (seen.has(dedupeKey)) {
110565
- warnings.push(`${path24}.${fieldLabel}[${k}] \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
110578
+ warnings.push(`${path25}.${fieldLabel}[${k}] \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
110566
110579
  continue;
110567
110580
  }
110568
110581
  seen.add(dedupeKey);
@@ -110571,12 +110584,12 @@ function normalizeKeywordTextList(texts, matchTypeRaw, path24, fieldLabel, error
110571
110584
  }
110572
110585
  return { normalized, resolvedUi };
110573
110586
  }
110574
- function normalizeKeywordsForBatchJobBlock(block, path24, errors, warnings, lengthViolations) {
110587
+ function normalizeKeywordsForBatchJobBlock(block, path25, errors, warnings, lengthViolations) {
110575
110588
  const matchTypeRaw = readBlockMatchTypeRaw(block);
110576
110589
  const declaredUi = matchTypeV2ToUi(matchTypeRaw);
110577
110590
  if (matchTypeRaw !== void 0 && declaredUi === null) {
110578
110591
  errors.push(
110579
- `${path24}.MatchTypeV2 \u65E0\u6548\uFF08${String(matchTypeRaw)}\uFF09\uFF0C\u5408\u6CD5\u503C\uFF1ABROAD | PHRASE | EXACT`
110592
+ `${path25}.MatchTypeV2 \u65E0\u6548\uFF08${String(matchTypeRaw)}\uFF09\uFF0C\u5408\u6CD5\u503C\uFF1ABROAD | PHRASE | EXACT`
110580
110593
  );
110581
110594
  return;
110582
110595
  }
@@ -110591,7 +110604,7 @@ function normalizeKeywordsForBatchJobBlock(block, path24, errors, warnings, leng
110591
110604
  const result = normalizeKeywordTextList(
110592
110605
  texts,
110593
110606
  matchTypeRaw,
110594
- path24,
110607
+ path25,
110595
110608
  "KeywordText",
110596
110609
  errors,
110597
110610
  warnings,
@@ -110600,7 +110613,7 @@ function normalizeKeywordsForBatchJobBlock(block, path24, errors, warnings, leng
110600
110613
  normalizedTexts = result.normalized;
110601
110614
  if (result.resolvedUi) resolvedUi = result.resolvedUi;
110602
110615
  if (normalizedTexts.length === 0 && texts.length > 0) {
110603
- errors.push(`${path24}.KeywordText \u7ECF\u6821\u9A8C\u540E\u65E0\u6709\u6548\u5173\u952E\u8BCD\uFF0C\u8BF7\u4FEE\u6B63\u8BCD\u9762\u6216 MatchTypeV2`);
110616
+ errors.push(`${path25}.KeywordText \u7ECF\u6821\u9A8C\u540E\u65E0\u6709\u6548\u5173\u952E\u8BCD\uFF0C\u8BF7\u4FEE\u6B63\u8BCD\u9762\u6216 MatchTypeV2`);
110604
110617
  }
110605
110618
  }
110606
110619
  if (hasItems && items) {
@@ -110608,12 +110621,12 @@ function normalizeKeywordsForBatchJobBlock(block, path24, errors, warnings, leng
110608
110621
  for (let k = 0; k < items.length; k++) {
110609
110622
  const item = items[k];
110610
110623
  if (!item || typeof item !== "object") {
110611
- errors.push(`${path24}.Items[${k}] \u5FC5\u987B\u662F\u5BF9\u8C61`);
110624
+ errors.push(`${path25}.Items[${k}] \u5FC5\u987B\u662F\u5BF9\u8C61`);
110612
110625
  continue;
110613
110626
  }
110614
110627
  const raw = readItemText(item);
110615
110628
  if (raw === null || !raw.trim()) {
110616
- errors.push(`${path24}.Items[${k}].Text \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
110629
+ errors.push(`${path25}.Items[${k}].Text \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`);
110617
110630
  continue;
110618
110631
  }
110619
110632
  const trimmedRaw = collapseDuplicateSpacesInKeywordText(raw.trim());
@@ -110621,7 +110634,7 @@ function normalizeKeywordsForBatchJobBlock(block, path24, errors, warnings, leng
110621
110634
  const { formatted, matchType, inferredMatchType } = normalizeKeywordSurface(trimmedRaw, matchTypeRaw);
110622
110635
  pushKeywordAutoFixWarning(
110623
110636
  warnings,
110624
- path24,
110637
+ path25,
110625
110638
  `Items[${k}].Text`,
110626
110639
  trimmedRaw,
110627
110640
  formatted,
@@ -110631,10 +110644,10 @@ function normalizeKeywordsForBatchJobBlock(block, path24, errors, warnings, leng
110631
110644
  inferredMatchType
110632
110645
  );
110633
110646
  const core = unwrapKeywordDisplayTextForEdit(formatted);
110634
- if (!validateKeywordCore(core, `${path24}.Items[${k}].Text`, errors, lengthViolations)) continue;
110647
+ if (!validateKeywordCore(core, `${path25}.Items[${k}].Text`, errors, lengthViolations)) continue;
110635
110648
  const dedupeKey = `${matchTypeUiToV2(matchType)}:${core.toLowerCase()}`;
110636
110649
  if (seen.has(dedupeKey)) {
110637
- warnings.push(`${path24}.Items[${k}].Text \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
110650
+ warnings.push(`${path25}.Items[${k}].Text \u4E0E\u540C\u7EC4\u91CD\u590D\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A${formatted}`);
110638
110651
  continue;
110639
110652
  }
110640
110653
  seen.add(dedupeKey);
@@ -113242,12 +113255,12 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
113242
113255
  for (let ki = 0; ki < texts.length; ki++) {
113243
113256
  const t = texts[ki];
113244
113257
  if (typeof t !== "string" || !t.trim()) continue;
113245
- const path24 = `${groupPath}.KeywordsForBatchJob[${bi}].KeywordText[${ki}]`;
113258
+ const path25 = `${groupPath}.KeywordsForBatchJob[${bi}].KeywordText[${ki}]`;
113246
113259
  const key = keywordKey(t, matchTypeV2);
113247
113260
  if (!liveKwKeys.has(key)) {
113248
113261
  missing.push({
113249
113262
  layer: "keyword",
113250
- path: path24,
113263
+ path: path25,
113251
113264
  adGroupName: groupName,
113252
113265
  plannedContent: `\u8BCD\u9762: ${t} | \u5339\u914D: ${matchTypeV2}`,
113253
113266
  liveNote: `\u540C\u7EC4\u5DF2\u6709 ${liveKwInGroup.length} \u6761\u5173\u952E\u8BCD\uFF0C\u65E0\u952E ${key}`,
@@ -113265,12 +113278,12 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
113265
113278
  for (let ai = 0; ai < ads.length; ai++) {
113266
113279
  const ad = asRecord2(ads[ai]);
113267
113280
  if (!ad) continue;
113268
- const path24 = `${groupPath}.AdsForBatchJob[${ai}]`;
113281
+ const path25 = `${groupPath}.AdsForBatchJob[${ai}]`;
113269
113282
  const primary = pickString(ad["headlinePart1"], ad["AdTitle"]);
113270
113283
  if (!primary) {
113271
113284
  missing.push({
113272
113285
  layer: "ad",
113273
- path: path24,
113286
+ path: path25,
113274
113287
  adGroupName: groupName,
113275
113288
  plannedContent: `AdsForBatchJob[${ai}]\uFF08\u7F3A\u5C11 headlinePart1\uFF0C\u65E0\u6CD5\u6BD4\u5BF9\uFF09`,
113276
113289
  liveNote: `\u540C\u7EC4\u5DF2\u6709 ${liveAdsInGroup.length} \u6761\u521B\u610F`,
@@ -113283,7 +113296,7 @@ function compareCampaignCreateToLive(cfg, campaignId, live, meta) {
113283
113296
  if (!found) {
113284
113297
  missing.push({
113285
113298
  layer: "ad",
113286
- path: path24,
113299
+ path: path25,
113287
113300
  adGroupName: groupName,
113288
113301
  plannedContent: `RSA \u9996\u6807\u9898: ${primary}${finalUrl ? ` | \u843D\u5730\u9875: ${finalUrl}` : ""}`,
113289
113302
  liveNote: `\u540C\u7EC4\u5DF2\u6709 ${liveAdsInGroup.length} \u6761 RSA\uFF0C\u65E0\u6B64\u9996\u6807\u9898`,
@@ -114481,13 +114494,13 @@ function pmaxChannelTypesUrl(googleApiUrl) {
114481
114494
  }
114482
114495
  function pmaxCampaignUrl(googleApiUrl, accountId, campaignId) {
114483
114496
  const base = googleApiUrl.replace(/\/$/, "");
114484
- const path24 = `${base}/accounts/${accountId}/campaign/pmax`;
114485
- return campaignId ? `${path24}/${campaignId}` : path24;
114497
+ const path25 = `${base}/accounts/${accountId}/campaign/pmax`;
114498
+ return campaignId ? `${path25}/${campaignId}` : path25;
114486
114499
  }
114487
114500
  function pmaxAssetGroupUrl(googleApiUrl, accountId, assetGroupId, suffix) {
114488
114501
  const base = googleApiUrl.replace(/\/$/, "");
114489
- const path24 = `${base}/accounts/${accountId}/campaign/pmax/asset-group/${assetGroupId}`;
114490
- return suffix ? `${path24}/${suffix.replace(/^\//, "")}` : path24;
114502
+ const path25 = `${base}/accounts/${accountId}/campaign/pmax/asset-group/${assetGroupId}`;
114503
+ return suffix ? `${path25}/${suffix.replace(/^\//, "")}` : path25;
114491
114504
  }
114492
114505
  function pmaxCampaignAssetGroupUrl(googleApiUrl, accountId, campaignId) {
114493
114506
  const base = googleApiUrl.replace(/\/$/, "");
@@ -121637,15 +121650,34 @@ async function runWebsiteDiagnosisCollect(opts) {
121637
121650
  }
121638
121651
 
121639
121652
  // src/commands/website-diagnosis/render-report.ts
121640
- import fs12 from "fs/promises";
121653
+ import fs12 from "fs";
121654
+ import fsPromises from "fs/promises";
121641
121655
  import path17 from "path";
121642
121656
  import { fileURLToPath as fileURLToPath4 } from "url";
121643
- function websiteDiagnosisReportTemplatePath() {
121657
+ var TEMPLATE_BASENAMES = {
121658
+ html: "website-diagnosis-report.html",
121659
+ runtime: "website-diagnosis-report.runtime.js"
121660
+ };
121661
+ function resolveSkillTemplatePath(basename11) {
121644
121662
  const dir = path17.dirname(fileURLToPath4(import.meta.url));
121645
- return path17.join(dir, "skill", "siluzan-ads", "report-templates", "website-diagnosis-report.html");
121663
+ const candidates = [
121664
+ path17.join(dir, "skill", "report-templates", basename11),
121665
+ path17.join(dir, "skill", "siluzan-ads", "report-templates", basename11),
121666
+ path17.join(dir, "..", "..", "assets", "siluzan-ads", "report-templates", basename11)
121667
+ ];
121668
+ for (const p of candidates) {
121669
+ if (fs12.existsSync(p)) return p;
121670
+ }
121671
+ return candidates[0];
121672
+ }
121673
+ function websiteDiagnosisReportTemplatePath() {
121674
+ return resolveSkillTemplatePath(TEMPLATE_BASENAMES.html);
121675
+ }
121676
+ function websiteDiagnosisRuntimePath() {
121677
+ return resolveSkillTemplatePath(TEMPLATE_BASENAMES.runtime);
121646
121678
  }
121647
121679
  function readJsonFile(filePath) {
121648
- const raw = fs12.readFile(filePath, "utf8");
121680
+ const raw = fsPromises.readFile(filePath, "utf8");
121649
121681
  return raw.then((text) => {
121650
121682
  try {
121651
121683
  return JSON.parse(text);
@@ -121688,7 +121720,7 @@ async function runWebsiteDiagnosisRender(opts) {
121688
121720
  const templatePath = websiteDiagnosisReportTemplatePath();
121689
121721
  let html;
121690
121722
  try {
121691
- html = await fs12.readFile(templatePath, "utf8");
121723
+ html = await fsPromises.readFile(templatePath, "utf8");
121692
121724
  } catch {
121693
121725
  console.error(`
121694
121726
  \u274C \u672A\u627E\u5230\u62A5\u544A\u6A21\u677F\uFF1A${templatePath}
@@ -121699,12 +121731,26 @@ async function runWebsiteDiagnosisRender(opts) {
121699
121731
  const outPath = path17.resolve(
121700
121732
  opts.out ?? path17.join(path17.dirname(dataPath), "website-diagnosis-report.html")
121701
121733
  );
121702
- await fs12.mkdir(path17.dirname(outPath), { recursive: true });
121703
- await fs12.writeFile(outPath, injectReportData(html, data), "utf8");
121734
+ const outDir = path17.dirname(outPath);
121735
+ await fsPromises.mkdir(outDir, { recursive: true });
121736
+ await fsPromises.writeFile(outPath, injectReportData(html, data), "utf8");
121737
+ const runtimeSrc = websiteDiagnosisRuntimePath();
121738
+ const runtimeOut = path17.join(outDir, TEMPLATE_BASENAMES.runtime);
121739
+ try {
121740
+ await fsPromises.copyFile(runtimeSrc, runtimeOut);
121741
+ } catch {
121742
+ console.error(`
121743
+ \u274C \u672A\u627E\u5230\u62A5\u544A\u8FD0\u884C\u65F6\uFF1A${runtimeSrc}
121744
+ \u8BF7\u5148\u6267\u884C npm run build
121745
+ `);
121746
+ process.exit(1);
121747
+ }
121704
121748
  console.log(`
121705
121749
  \u2705 \u7F51\u7AD9\u8BCA\u65AD HTML \u62A5\u544A\u5DF2\u751F\u6210\uFF1A${outPath}
121706
121750
  `);
121707
- console.log("\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00\u8BE5\u6587\u4EF6\u5373\u53EF\u67E5\u770B\u56FE\u8868\u4E0E\u5B8C\u6574\u7AE0\u8282\u3002\n");
121751
+ console.log(` \u8FD0\u884C\u65F6\u811A\u672C\uFF1A${runtimeOut}
121752
+ `);
121753
+ console.log("\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00 HTML \u6587\u4EF6\u5373\u53EF\u67E5\u770B\u56FE\u8868\u4E0E\u5B8C\u6574\u7AE0\u8282\uFF08\u9700\u8054\u7F51\u52A0\u8F7D ECharts CDN\uFF09\u3002\n");
121708
121754
  }
121709
121755
 
121710
121756
  // src/commands/website-diagnosis/register.ts
@@ -121753,6 +121799,578 @@ function registerWebsiteDiagnosisCommands(program2) {
121753
121799
  });
121754
121800
  }
121755
121801
 
121802
+ // src/commands/market-analysis/run.ts
121803
+ init_auth();
121804
+ init_cli_json_snapshot();
121805
+
121806
+ // src/commands/market-analysis/shared.ts
121807
+ var DEFAULT_TARGET_MARKET = "\u5168\u7403";
121808
+ var DEFAULT_TIME_RANGE = "\u8FD112\u4E2A\u6708";
121809
+ function validateMarketAnalysisInput(info) {
121810
+ const { customerName, website, industry, coreProducts } = info;
121811
+ if (!customerName?.trim() && !website?.trim() && !industry?.trim() && !coreProducts?.trim()) {
121812
+ return "\u5BA2\u6237\u540D\u79F0\u3001\u5BA2\u6237\u7F51\u7AD9\u3001\u6240\u5C5E\u884C\u4E1A\u3001\u6838\u5FC3\u4EA7\u54C1\u987B\u81F3\u5C11\u63D0\u4F9B\u4E00\u9879\u3002";
121813
+ }
121814
+ return null;
121815
+ }
121816
+ function normalizeCustomerInfo(input) {
121817
+ return {
121818
+ ...input,
121819
+ targetMarket: input.targetMarket?.trim() || DEFAULT_TARGET_MARKET,
121820
+ timeRange: input.timeRange?.trim() || DEFAULT_TIME_RANGE
121821
+ };
121822
+ }
121823
+ function buildMarketAnalysisAgentHint() {
121824
+ return [
121825
+ "1) Read assets/market-analysis-rules.md\uFF08\u539F\u59CB\u4E1A\u52A1\u7EF4\u5EA6\u6E05\u5355\uFF09\u4E0E report-templates/market-analysis-report.md\uFF1B",
121826
+ "2) \u7528\u5BBF\u4E3B WebSearch/WebFetch \u6309\u7F3A\u5931\u7EF4\u5EA6\u8865\u5168\u516C\u5F00\u6570\u636E\uFF1B",
121827
+ "3) \u5C06\u5B8C\u6574 HTML \u5199\u5165 market-report.json\uFF1Brender \u4F1A\u6821\u9A8C\u5FC5\u542B\u7EF4\u5EA6\uFF0C\u7F3A\u9879\u5C06\u5931\u8D25\uFF1B",
121828
+ "4) siluzan-tso market-analysis render --data ./market-report.json --out ./market-analysis-report.html\u3002"
121829
+ ].join(" ");
121830
+ }
121831
+ function injectChartSizeLimitStyles(htmlContent) {
121832
+ if (!htmlContent) return htmlContent;
121833
+ const chartSizeLimitStyle = `
121834
+ <style>
121835
+ canvas {
121836
+ max-height: 400px !important;
121837
+ width: 100% !important;
121838
+ height: auto !important;
121839
+ }
121840
+ </style>
121841
+ `;
121842
+ if (htmlContent.includes("<head>")) {
121843
+ return htmlContent.replace("</head>", `${chartSizeLimitStyle}</head>`);
121844
+ }
121845
+ if (htmlContent.includes("<html")) {
121846
+ return htmlContent.replace(/(<html[^>]*>)/i, `$1<head>${chartSizeLimitStyle}</head>`);
121847
+ }
121848
+ return `${chartSizeLimitStyle}${htmlContent}`;
121849
+ }
121850
+ function extractHtmlFromResponse(data) {
121851
+ if (!data) return "";
121852
+ let htmlContent = "";
121853
+ const htmlBlockRegex = /```html\s*([\s\S]*?)```/;
121854
+ const match = data.match(htmlBlockRegex);
121855
+ if (match?.[1]) {
121856
+ htmlContent = match[1].trim();
121857
+ } else if (data.includes("<!DOCTYPE html>") || data.includes("<html")) {
121858
+ htmlContent = data.trim();
121859
+ } else {
121860
+ htmlContent = data;
121861
+ }
121862
+ return injectChartSizeLimitStyles(htmlContent);
121863
+ }
121864
+
121865
+ // src/commands/market-analysis/run.ts
121866
+ var WEBSITE_PREVIEW_CHARS = 8e3;
121867
+ async function runMarketAnalysisCollect(opts) {
121868
+ const customerInfo = {
121869
+ customerName: opts.customerName,
121870
+ website: opts.website,
121871
+ industry: opts.industry,
121872
+ coreProducts: opts.coreProducts,
121873
+ businessPosition: opts.businessPosition,
121874
+ targetMarket: opts.targetMarket,
121875
+ timeRange: opts.timeRange
121876
+ };
121877
+ const validationError = validateMarketAnalysisInput(customerInfo);
121878
+ if (validationError) {
121879
+ console.error(`
121880
+ \u274C ${validationError}
121881
+ `);
121882
+ process.exit(1);
121883
+ }
121884
+ const normalized = normalizeCustomerInfo(customerInfo);
121885
+ let websiteMeta;
121886
+ let websitePreview;
121887
+ let websiteError;
121888
+ const websiteUrl = normalized.website?.trim() ? normalizeWebsiteUrl(normalized.website) : "";
121889
+ if (websiteUrl && !opts.skipWebsite) {
121890
+ const config = loadConfig(opts.token);
121891
+ try {
121892
+ const html = await fetchWebsiteHtml(config, websiteUrl, opts.verbose);
121893
+ websiteMeta = {
121894
+ originUrl: html.originUrl,
121895
+ finalUrl: html.finalUrl,
121896
+ mimeType: html.mimeType,
121897
+ contentLength: html.contentLength,
121898
+ downloadTime: html.downloadTime
121899
+ };
121900
+ websitePreview = String(html.content ?? "").slice(0, WEBSITE_PREVIEW_CHARS);
121901
+ } catch (err) {
121902
+ websiteError = err instanceof Error ? err.message : String(err);
121903
+ }
121904
+ }
121905
+ const label = normalized.customerName?.trim() || normalized.website?.trim() || normalized.industry?.trim() || normalized.coreProducts?.trim() || "market";
121906
+ const payload = {
121907
+ customerInfo: normalized,
121908
+ targetMarket: normalized.targetMarket,
121909
+ timeRange: normalized.timeRange,
121910
+ collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
121911
+ ...websiteMeta ? { website: websiteMeta } : {},
121912
+ ...websitePreview ? { websitePreview } : {},
121913
+ ...websiteError ? { websiteError } : {},
121914
+ agentHint: buildMarketAnalysisAgentHint()
121915
+ };
121916
+ if (await emitCliJsonOrSnapshot(
121917
+ { jsonOut: opts.jsonOut },
121918
+ {
121919
+ section: "market-analysis-collect",
121920
+ commandLabel: "market-analysis collect",
121921
+ commandHint: label,
121922
+ payload
121923
+ }
121924
+ )) {
121925
+ return;
121926
+ }
121927
+ console.log(JSON.stringify(payload, null, 2));
121928
+ }
121929
+
121930
+ // src/commands/market-analysis/render-report.ts
121931
+ import fs13 from "fs/promises";
121932
+ import path18 from "path";
121933
+
121934
+ // src/commands/market-analysis/report-content.ts
121935
+ var MARKET_REPORT_DIMENSIONS = [
121936
+ // ── 封面与元信息 ──
121937
+ {
121938
+ id: "cover",
121939
+ chapter: "\u5C01\u9762",
121940
+ dimension: "\u62A5\u544A\u6807\u9898\u542B\u5BA2\u6237\u4E0E\u76EE\u6807\u5E02\u573A",
121941
+ hint: "\u987B\u542B\u300C\u6218\u7565\u5E02\u573A\u5206\u6790\u300D\u6216\u300CKA\u5BA2\u6237\u300D\u53CA\u76EE\u6807\u5E02\u573A\u5173\u952E\u8BCD"
121942
+ },
121943
+ {
121944
+ id: "cover-kpi",
121945
+ chapter: "\u5C01\u9762",
121946
+ dimension: "\u6838\u5FC3\u7ED3\u8BBA\u6216 KPI \u6458\u8981",
121947
+ hint: "\u5C01\u9762\u987B\u6709\u53EF\u626B\u8BFB\u7684\u7ED3\u8BBA\u6BB5\u6216 metric \u5361\u7247\uFF0C\u975E\u4EC5\u6807\u9898"
121948
+ },
121949
+ // ── 一、市场与趋势诊断 ──
121950
+ {
121951
+ id: "ch1",
121952
+ chapter: "\u4E00\u3001\u5E02\u573A\u4E0E\u8D8B\u52BF\u8BCA\u65AD",
121953
+ dimension: "\u7B2C\u4E00\u7AE0\u6807\u9898",
121954
+ hint: "\u987B\u51FA\u73B0\u300C\u5E02\u573A\u4E0E\u8D8B\u52BF\u8BCA\u65AD\u300D"
121955
+ },
121956
+ {
121957
+ id: "ch1-tam",
121958
+ chapter: "\u4E00\u3001\u5E02\u573A\u4E0E\u8D8B\u52BF\u8BCA\u65AD",
121959
+ dimension: "\u5168\u7403\u5E02\u573A\u89C4\u6A21\u4E0E\u589E\u957F\uFF08TAM/SAM/SOM\u3001CAGR\uFF09",
121960
+ hint: "\u987B\u542B TAM \u6216 SAM \u6216 SOM \u6216\u300C\u5E02\u573A\u89C4\u6A21\u300D+ CAGR \u6216\u589E\u957F\u7387"
121961
+ },
121962
+ {
121963
+ id: "ch1-value-chain",
121964
+ chapter: "\u4E00\u3001\u5E02\u573A\u4E0E\u8D8B\u52BF\u8BCA\u65AD",
121965
+ dimension: "\u5E02\u573A\u8D5B\u9053\u4E0E\u4EF7\u503C\u94FE/\u751F\u6001\u5B9A\u4F4D",
121966
+ hint: "\u987B\u542B\u300C\u4EF7\u503C\u94FE\u300D\u6216\u300C\u751F\u6001\u5B9A\u4F4D\u300D\u6216\u4EA7\u4E1A\u94FE\u73AF\u8282\u8868"
121967
+ },
121968
+ {
121969
+ id: "ch1-target-geo",
121970
+ chapter: "\u4E00\u3001\u5E02\u573A\u4E0E\u8D8B\u52BF\u8BCA\u65AD",
121971
+ dimension: "\u76EE\u6807\u5E02\u573A\u56FD\u5BB6/\u533A\u57DF\u62C6\u89E3",
121972
+ hint: "\u987B\u6309\u56FD\u5BB6\u6216\u533A\u57DF\u5BF9\u6BD4\u89C4\u6A21\u3001\u589E\u957F\u3001\u4F18\u5148\u7EA7\uFF08\u8868\u683C\u6216\u5206\u6BB5\uFF09"
121973
+ },
121974
+ {
121975
+ id: "ch1-pestel",
121976
+ chapter: "\u4E00\u3001\u5E02\u573A\u4E0E\u8D8B\u52BF\u8BCA\u65AD",
121977
+ dimension: "\u6280\u672F\u8D8B\u52BF\u4E0E PESTEL",
121978
+ hint: "\u987B\u542B PESTEL \u6216\u653F\u7B56/\u7ECF\u6D4E/\u793E\u4F1A/\u6280\u672F/\u73AF\u5883/\u6CD5\u89C4\u7EF4\u5EA6\u5206\u6790"
121979
+ },
121980
+ {
121981
+ id: "ch1-forecast",
121982
+ chapter: "\u4E00\u3001\u5E02\u573A\u4E0E\u8D8B\u52BF\u8BCA\u65AD",
121983
+ dimension: "3\u20135 \u5E74\u8D8B\u52BF\u9884\u6D4B\u4E0E\u5047\u8BBE",
121984
+ hint: "\u987B\u542B\u300C\u9884\u6D4B\u300D\u6216\u300C3\u300D\u5E74/\u300C5\u300D\u5E74\u8D8B\u52BF\u8868\u8FF0"
121985
+ },
121986
+ // ── 二、目标行业深度洞察 ──
121987
+ {
121988
+ id: "ch2",
121989
+ chapter: "\u4E8C\u3001\u76EE\u6807\u884C\u4E1A\u6DF1\u5EA6\u6D1E\u5BDF",
121990
+ dimension: "\u7B2C\u4E8C\u7AE0\u6807\u9898",
121991
+ hint: "\u987B\u51FA\u73B0\u300C\u884C\u4E1A\u6DF1\u5EA6\u6D1E\u5BDF\u300D\u6216\u300C\u76EE\u6807\u884C\u4E1A\u300D"
121992
+ },
121993
+ {
121994
+ id: "ch2-pain",
121995
+ chapter: "\u4E8C\u3001\u76EE\u6807\u884C\u4E1A\u6DF1\u5EA6\u6D1E\u5BDF",
121996
+ dimension: "\u884C\u4E1A\u6838\u5FC3\u75DB\u70B9\uFF08\u22654 \u9879\uFF09",
121997
+ hint: "\u987B\u5217\u4E3E\u81F3\u5C11 4 \u6761\u75DB\u70B9\uFF08\u75DB\u70B91\u20134 \u6216\u5217\u8868\u9879\uFF09"
121998
+ },
121999
+ {
122000
+ id: "ch2-compliance",
122001
+ chapter: "\u4E8C\u3001\u76EE\u6807\u884C\u4E1A\u6DF1\u5EA6\u6D1E\u5BDF",
122002
+ dimension: "\u6CD5\u89C4\u73AF\u5883\u4E0E\u5408\u89C4\u8981\u6C42",
122003
+ hint: "\u987B\u542B\u6CD5\u89C4/\u5408\u89C4\u8868\uFF0C\u542B\u5E94\u5BF9\u7B56\u7565\uFF1B\u5B9C\u542B\u5408\u89C4\u6EA2\u4EF7\u6216\u6210\u672C\u4F30\u7B97"
122004
+ },
122005
+ {
122006
+ id: "ch2-bpmn",
122007
+ chapter: "\u4E8C\u3001\u76EE\u6807\u884C\u4E1A\u6DF1\u5EA6\u6D1E\u5BDF",
122008
+ dimension: "\u5178\u578B\u573A\u666F\u4E0E BPMN \u6D41\u7A0B\u5BF9\u6BD4",
122009
+ hint: "\u987B\u542B\u300C\u539F\u6D41\u7A0B\u300D\u4E0E\u300C\u65B0\u6D41\u7A0B\u300D\u6216 BPMN \u6587\u672C + \u6548\u7387/\u6210\u672C\u91CF\u5316"
122010
+ },
122011
+ // ── 三、目标受众与场景 ──
122012
+ {
122013
+ id: "ch3",
122014
+ chapter: "\u4E09\u3001\u76EE\u6807\u53D7\u4F17\u4E0E\u573A\u666F",
122015
+ dimension: "\u7B2C\u4E09\u7AE0\u6807\u9898",
122016
+ hint: "\u987B\u51FA\u73B0\u300C\u76EE\u6807\u53D7\u4F17\u300D\u6216\u300C\u53D7\u4F17\u4E0E\u573A\u666F\u300D"
122017
+ },
122018
+ {
122019
+ id: "ch3-audience",
122020
+ chapter: "\u4E09\u3001\u76EE\u6807\u53D7\u4F17\u4E0E\u573A\u666F",
122021
+ dimension: "\u53D7\u4F17\u5206\u5C42\u4E0E\u7528\u6237\u753B\u50CF",
122022
+ hint: "\u987B\u542B\u7528\u6237\u7FA4\u4F53\u5206\u7C7B\u8868\uFF1A\u89C4\u6A21/\u7279\u5F81/\u75DB\u70B9/\u4EF7\u503C"
122023
+ },
122024
+ {
122025
+ id: "ch3-scenario",
122026
+ chapter: "\u4E09\u3001\u76EE\u6807\u53D7\u4F17\u4E0E\u573A\u666F",
122027
+ dimension: "\u4EA7\u54C1\u5E94\u7528\u573A\u666F\u4E0E\u4EF7\u503C\u5206\u6790",
122028
+ hint: "\u987B\u542B\u5E94\u7528\u573A\u666F\u3001\u5DEE\u5F02\u5316\u3001\u7528\u6237\u4EF7\u503C\u4E0E\u4F01\u4E1A\u4EF7\u503C"
122029
+ },
122030
+ // ── 四、竞争策略与定位 ──
122031
+ {
122032
+ id: "ch4",
122033
+ chapter: "\u56DB\u3001\u7ADE\u4E89\u7B56\u7565\u4E0E\u5B9A\u4F4D",
122034
+ dimension: "\u7B2C\u56DB\u7AE0\u6807\u9898",
122035
+ hint: "\u987B\u51FA\u73B0\u300C\u7ADE\u4E89\u7B56\u7565\u300D\u6216\u300C\u7ADE\u4E89\u300D+\u300C\u5B9A\u4F4D\u300D"
122036
+ },
122037
+ {
122038
+ id: "ch4-competitors",
122039
+ chapter: "\u56DB\u3001\u7ADE\u4E89\u7B56\u7565\u4E0E\u5B9A\u4F4D",
122040
+ dimension: "\u4E3B\u8981\u7ADE\u54C1\u8BC6\u522B",
122041
+ hint: "\u987B\u70B9\u540D \u22653 \u4E2A\u7ADE\u54C1\u6216\u5BF9\u6807\u54C1\u724C"
122042
+ },
122043
+ {
122044
+ id: "ch4-compare-table",
122045
+ chapter: "\u56DB\u3001\u7ADE\u4E89\u7B56\u7565\u4E0E\u5B9A\u4F4D",
122046
+ dimension: "\u529F\u80FD\u4E0E\u6027\u80FD\u5BF9\u6BD4\u8868\uFF08\u226510 \u9879 \xD7 \u22653 \u7ADE\u54C1\uFF09",
122047
+ hint: "\u987B\u6709\u7ADE\u54C1\u5BF9\u6BD4\u8868\uFF0C\u884C\u6570\u5145\u8DB3\uFF08\u5EFA\u8BAE \u226510 \u884C\u7EF4\u5EA6\uFF09"
122048
+ },
122049
+ {
122050
+ id: "ch4-gaps",
122051
+ chapter: "\u56DB\u3001\u7ADE\u4E89\u7B56\u7565\u4E0E\u5B9A\u4F4D",
122052
+ dimension: "\u56DB\u7C7B\u7ADE\u4E89\u7A7A\u767D\uFF08\u529F\u80FD/\u573A\u666F/\u7528\u6237\u7FA4/\u5730\u57DF\uFF09",
122053
+ hint: "\u987B\u8BC6\u522B\u529F\u80FD\u3001\u573A\u666F\u3001\u7528\u6237\u7FA4\u3001\u5730\u57DF\u4E2D\u81F3\u5C11\u4E09\u7C7B\u7A7A\u767D"
122054
+ },
122055
+ {
122056
+ id: "ch4-positioning",
122057
+ chapter: "\u56DB\u3001\u7ADE\u4E89\u7B56\u7565\u4E0E\u5B9A\u4F4D",
122058
+ dimension: "\u6838\u5FC3\u5B9A\u4F4D\u9648\u8FF0\u4E0E\u5DEE\u5F02\u5316\u7EF4\u5EA6",
122059
+ hint: "\u987B\u6709\u4E00\u53E5\u5B9A\u4F4D\u9648\u8FF0 + \u591A\u4E2A\u5DEE\u5F02\u5316\u7EF4\u5EA6"
122060
+ },
122061
+ // ── 五、增长与品牌策略 ──
122062
+ {
122063
+ id: "ch5",
122064
+ chapter: "\u4E94\u3001\u589E\u957F\u4E0E\u54C1\u724C\u7B56\u7565",
122065
+ dimension: "\u7B2C\u4E94\u7AE0\u6807\u9898",
122066
+ hint: "\u987B\u51FA\u73B0\u300C\u589E\u957F\u4E0E\u54C1\u724C\u300D\u6216 GTM"
122067
+ },
122068
+ {
122069
+ id: "ch5-gtm",
122070
+ chapter: "\u4E94\u3001\u589E\u957F\u4E0E\u54C1\u724C\u7B56\u7565",
122071
+ dimension: "GTM \u4E09\u9636\u6BB5\uFF08\u8BD5\u70B9\u2192\u590D\u5236\u2192\u751F\u6001\uFF09",
122072
+ hint: "\u987B\u542B\u4E09\u9636\u6BB5\u8FDB\u5165\u8DEF\u5F84\u8868\uFF1A\u76EE\u6807\u3001\u52A8\u4F5C\u3001KPI/ROI"
122073
+ },
122074
+ {
122075
+ id: "ch5-brand",
122076
+ chapter: "\u4E94\u3001\u589E\u957F\u4E0E\u54C1\u724C\u7B56\u7565",
122077
+ dimension: "\u54C1\u724C\u5B9A\u4F4D\u4E0E\u4F20\u64AD",
122078
+ hint: "\u987B\u542B\u54C1\u724C\u4E3B\u5F20\u3001\u4F20\u64AD\u6E20\u9053\u6216\u5185\u5BB9\u7B56\u7565"
122079
+ },
122080
+ {
122081
+ id: "ch5-tactics",
122082
+ chapter: "\u4E94\u3001\u589E\u957F\u4E0E\u54C1\u724C\u7B56\u7565",
122083
+ dimension: "\u8425\u9500\u4E0E\u9500\u552E\u6218\u672F",
122084
+ hint: "\u987B\u542B\u5185\u5BB9\u8425\u9500/\u6E20\u9053/KOL/\u9500\u552E\u6A21\u5F0F\u7B49\u6218\u672F\u63CF\u8FF0"
122085
+ },
122086
+ // ── 六、实施、资源与风险 ──
122087
+ {
122088
+ id: "ch6",
122089
+ chapter: "\u516D\u3001\u5B9E\u65BD\u3001\u8D44\u6E90\u4E0E\u98CE\u9669",
122090
+ dimension: "\u7B2C\u516D\u7AE0\u6807\u9898",
122091
+ hint: "\u987B\u51FA\u73B0\u300C\u5B9E\u65BD\u300D+\u300C\u98CE\u9669\u300D\u6216\u300C\u8D44\u6E90\u4E0E\u98CE\u9669\u300D"
122092
+ },
122093
+ {
122094
+ id: "ch6-milestone",
122095
+ chapter: "\u516D\u3001\u5B9E\u65BD\u3001\u8D44\u6E90\u4E0E\u98CE\u9669",
122096
+ dimension: "\u5B9E\u65BD\u8DEF\u5F84\u4E0E\u91CC\u7A0B\u7891\u4EFB\u52A1\u8868",
122097
+ hint: "\u987B\u542B\u9636\u6BB5/\u4EFB\u52A1/\u8D1F\u8D23\u4EBA/\u6210\u679C/\u6307\u6807\u7C7B\u4EFB\u52A1\u8868"
122098
+ },
122099
+ {
122100
+ id: "ch6-budget",
122101
+ chapter: "\u516D\u3001\u5B9E\u65BD\u3001\u8D44\u6E90\u4E0E\u98CE\u9669",
122102
+ dimension: "\u8D44\u6E90\u9700\u6C42\u4E0E\u9884\u7B97\u89C4\u5212",
122103
+ hint: "\u987B\u542B\u4EBA\u529B/\u6280\u672F/\u5E02\u573A\u8D44\u6E90\u6216\u9884\u7B97\u6BD4\u4F8B\u5206\u914D"
122104
+ },
122105
+ {
122106
+ id: "ch6-risk",
122107
+ chapter: "\u516D\u3001\u5B9E\u65BD\u3001\u8D44\u6E90\u4E0E\u98CE\u9669",
122108
+ dimension: "\u98CE\u9669\u77E9\u9635\u4E0E\u5E94\u5BF9\u7B56\u7565",
122109
+ hint: "\u987B\u542B\u5E02\u573A/\u7ADE\u4E89/\u6280\u672F/\u8FD0\u8425\u7B49\u98CE\u9669 + \u6BCF\u7C7B\u5E94\u5BF9\u63AA\u65BD"
122110
+ },
122111
+ // ── 附录 ──
122112
+ {
122113
+ id: "appendix",
122114
+ chapter: "\u9644\u5F55",
122115
+ dimension: "\u9644\u5F55\u7AE0\u8282",
122116
+ hint: "\u987B\u542B\u300C\u9644\u5F55\u300D\u6216\u300C\u6570\u636E\u6765\u6E90\u300D\u6E05\u5355"
122117
+ },
122118
+ {
122119
+ id: "appendix-sources",
122120
+ chapter: "\u9644\u5F55",
122121
+ dimension: "\u6570\u636E\u6765\u6E90\u6E05\u5355",
122122
+ hint: "\u987B\u5217\u673A\u6784/\u62A5\u544A\u540D/\u65E5\u671F\u6216 URL"
122123
+ },
122124
+ {
122125
+ id: "appendix-methods",
122126
+ chapter: "\u9644\u5F55",
122127
+ dimension: "\u8C03\u7814\u65B9\u6CD5\u8BF4\u660E",
122128
+ hint: "\u987B\u8BF4\u660E\u684C\u9762\u7814\u7A76/\u8BBF\u8C08/\u95EE\u5377\u7B49\u65B9\u6CD5"
122129
+ },
122130
+ {
122131
+ id: "appendix-glossary",
122132
+ chapter: "\u9644\u5F55",
122133
+ dimension: "\u672F\u8BED\u8868\uFF08ROI/CAGR/GTM/BPMN \u7B49\uFF09",
122134
+ hint: "\u987B\u89E3\u91CA\u81F3\u5C11 3 \u4E2A\u7F29\u5199"
122135
+ },
122136
+ // ── 呈现规范(原始 prompt HTML 要求)──
122137
+ {
122138
+ id: "viz-chartjs",
122139
+ chapter: "\u5448\u73B0",
122140
+ dimension: "Chart.js \u6570\u636E\u53EF\u89C6\u5316",
122141
+ hint: "\u987B\u542B chart.js \u5F15\u7528\u4E0E new Chart \u56FE\u8868"
122142
+ },
122143
+ {
122144
+ id: "viz-source",
122145
+ chapter: "\u5448\u73B0",
122146
+ dimension: "\u56FE\u8868/\u5173\u952E\u6570\u636E\u6765\u6E90\u811A\u6CE8",
122147
+ hint: "\u987B\u542B Source: \u6216\u300C\u6765\u6E90\u300D\u811A\u6CE8"
122148
+ }
122149
+ ];
122150
+ function countPainPoints(html) {
122151
+ const labeled = (html.match(/痛点\s*\d/g) ?? []).length;
122152
+ if (labeled >= 4) return labeled;
122153
+ const section = html.match(/痛点[\s\S]{0,3000}/)?.[0] ?? html;
122154
+ const listItems = (section.match(/<li[^>]*>/g) ?? []).length;
122155
+ return Math.max(labeled, listItems >= 4 ? 4 : listItems);
122156
+ }
122157
+ function competitorCompareTableRows(html) {
122158
+ const tables = html.match(/<table[\s\S]*?<\/table>/g) ?? [];
122159
+ let best = 0;
122160
+ for (const t of tables) {
122161
+ const hasSelf = /小米|我方|客户/.test(t);
122162
+ const rivalHits = ["Apple", "Samsung", "Google", "\u82F9\u679C", "\u4E09\u661F", "\u8C37\u6B4C", "\u534E\u4E3A"].filter(
122163
+ (b) => t.includes(b)
122164
+ ).length;
122165
+ if (!hasSelf || rivalHits < 2) continue;
122166
+ const rows = (t.match(/<tr/g) ?? []).length;
122167
+ if (rows > best) best = rows;
122168
+ }
122169
+ return best;
122170
+ }
122171
+ function mentionsCompetitors(html) {
122172
+ const brands = ["Apple", "Samsung", "Google", "\u82F9\u679C", "\u4E09\u661F", "\u8C37\u6B4C", "\u534E\u4E3A", "Motorola"];
122173
+ let n = 0;
122174
+ for (const b of brands) {
122175
+ if (html.includes(b)) n++;
122176
+ }
122177
+ return n >= 3;
122178
+ }
122179
+ var CONTENT_RULES = [
122180
+ {
122181
+ id: "cover",
122182
+ test: (h) => /战略市场分析|KA客户/.test(h) && (/目标市场|北美|全球|欧洲|亚太/.test(h) || /targetMarket/i.test(h))
122183
+ },
122184
+ { id: "cover-kpi", test: (h) => /核心结论|metric|kpi|结论/.test(h) },
122185
+ { id: "ch1", test: (h) => /市场与趋势诊断/.test(h) },
122186
+ {
122187
+ id: "ch1-tam",
122188
+ test: (h) => (/TAM|SAM|SOM/.test(h) || /市场规模/.test(h)) && /CAGR|增长率|增长趋势/.test(h)
122189
+ },
122190
+ { id: "ch1-value-chain", test: (h) => /价值链|生态定位|产业链/.test(h) },
122191
+ {
122192
+ id: "ch1-target-geo",
122193
+ test: (h) => (/美国|加拿大|墨西哥|中国|欧洲/.test(h) || /国家|区域/.test(h)) && /增长|规模|优先级|潜力/.test(h)
122194
+ },
122195
+ { id: "ch1-pestel", test: (h) => /PESTEL|政策|法规环境|技术趋势/.test(h) },
122196
+ { id: "ch1-forecast", test: (h) => /预测|3.?5年|未来\d/.test(h) },
122197
+ { id: "ch2", test: (h) => /行业深度洞察|目标行业/.test(h) },
122198
+ { id: "ch2-pain", test: (h, ctx) => ctx.painPointCount >= 4 },
122199
+ { id: "ch2-compliance", test: (h) => /法规|合规|FCC|隐私/.test(h) && /<table/.test(h) },
122200
+ { id: "ch2-bpmn", test: (h) => /原流程|BPMN|介入后|新流程/.test(h) && /效率|成本|提升|下降/.test(h) },
122201
+ { id: "ch3", test: (h) => /目标受众|受众与场景/.test(h) },
122202
+ { id: "ch3-audience", test: (h) => /用户|受众|画像|群体/.test(h) && /<table/.test(h) },
122203
+ { id: "ch3-scenario", test: (h) => /应用场景|场景|差异化|用户价值|企业价值/.test(h) },
122204
+ { id: "ch4", test: (h) => /竞争策略|竞争.{0,6}定位/.test(h) },
122205
+ { id: "ch4-competitors", test: (h) => mentionsCompetitors(h) },
122206
+ // 表头 1 行 + ≥10 项维度 ≈ ≥11 个 <tr>
122207
+ { id: "ch4-compare-table", test: (h) => competitorCompareTableRows(h) >= 11 },
122208
+ {
122209
+ id: "ch4-gaps",
122210
+ test: (h) => [/功能空白|功能.*空白/.test(h), /场景/.test(h), /用户群|用户/.test(h), /地域|地区|区域/.test(h)].filter(
122211
+ Boolean
122212
+ ).length >= 3
122213
+ },
122214
+ { id: "ch4-positioning", test: (h) => /定位陈述|定位:|核心定位|差异化/.test(h) },
122215
+ { id: "ch5", test: (h) => /增长与品牌|GTM/.test(h) },
122216
+ { id: "ch5-gtm", test: (h) => /试点|规模复制|生态扩展|三阶段/.test(h) && /KPI|ROI|指标/.test(h) },
122217
+ { id: "ch5-brand", test: (h) => /品牌|传播|主张/.test(h) },
122218
+ { id: "ch5-tactics", test: (h) => /内容营销|渠道|KOL|销售模式|营销/.test(h) },
122219
+ { id: "ch6", test: (h) => /实施/.test(h) && /风险|资源/.test(h) },
122220
+ { id: "ch6-milestone", test: (h) => /里程碑|阶段/.test(h) && /任务|负责人|成果|指标/.test(h) },
122221
+ { id: "ch6-budget", test: (h) => /预算|资源需求|人力|分配/.test(h) },
122222
+ { id: "ch6-risk", test: (h) => /风险/.test(h) && /应对|措施|矩阵/.test(h) },
122223
+ { id: "appendix", test: (h) => /附录|数据来源/.test(h) },
122224
+ { id: "appendix-sources", test: (h) => /数据来源|Source:|报告名称|发布日期|http/.test(h) },
122225
+ { id: "appendix-methods", test: (h) => /调研方法|桌面研究|访谈|问卷/.test(h) },
122226
+ { id: "appendix-glossary", test: (h) => [/ROI/.test(h), /CAGR/.test(h), /GTM/.test(h), /BPMN/.test(h)].filter(Boolean).length >= 3 },
122227
+ { id: "viz-chartjs", test: (h, ctx) => /chart\.js/i.test(h) && ctx.chartCount >= 1 },
122228
+ { id: "viz-source", test: (h) => /Source:|来源:|来源:/.test(h) }
122229
+ ];
122230
+ function validateMarketReportContent(html) {
122231
+ const ctx = {
122232
+ tableCount: (html.match(/<table/g) ?? []).length,
122233
+ chartCount: (html.match(/new Chart\(/g) ?? []).length,
122234
+ painPointCount: countPainPoints(html)
122235
+ };
122236
+ const dimById = new Map(MARKET_REPORT_DIMENSIONS.map((d) => [d.id, d]));
122237
+ const missing = [];
122238
+ for (const rule of CONTENT_RULES) {
122239
+ const meta = dimById.get(rule.id);
122240
+ if (!meta) continue;
122241
+ if (!rule.test(html, ctx)) {
122242
+ missing.push({
122243
+ id: meta.id,
122244
+ dimension: meta.dimension,
122245
+ chapter: meta.chapter,
122246
+ hint: meta.hint
122247
+ });
122248
+ }
122249
+ }
122250
+ return {
122251
+ ok: missing.length === 0,
122252
+ missing,
122253
+ stats: {
122254
+ tables: ctx.tableCount,
122255
+ charts: ctx.chartCount,
122256
+ painPoints: ctx.painPointCount,
122257
+ competitorTableRows: competitorCompareTableRows(html)
122258
+ }
122259
+ };
122260
+ }
122261
+ function formatMarketReportContentErrors(result) {
122262
+ if (result.ok) return "";
122263
+ const lines = result.missing.map((m) => ` - [${m.chapter}] ${m.dimension}\uFF1A${m.hint}`);
122264
+ return [
122265
+ `\u62A5\u544A\u7F3A\u5C11 ${result.missing.length} \u9879\u5FC5\u542B\u5185\u5BB9\uFF08\u5BF9\u9F50 TSO getMarketReport \u539F\u59CB\u4E1A\u52A1\u7EF4\u5EA6\uFF09\uFF1A`,
122266
+ ...lines,
122267
+ "",
122268
+ "\u8BF7 Read assets/market-analysis-rules.md\u300C\u539F\u59CB\u4E1A\u52A1\u7EF4\u5EA6\u6E05\u5355\u300D\uFF0CWebSearch \u8865\u5168\u7F3A\u5931\u7AE0\u8282\u540E\u91CD\u5199 market-report.json\u3002"
122269
+ ].join("\n");
122270
+ }
122271
+
122272
+ // src/commands/market-analysis/render-report.ts
122273
+ function asRecord4(value) {
122274
+ if (value && typeof value === "object" && !Array.isArray(value)) {
122275
+ return value;
122276
+ }
122277
+ throw new Error("\u6570\u636E\u987B\u4E3A JSON \u5BF9\u8C61");
122278
+ }
122279
+ function resolveMarketAnalysisHtml(data) {
122280
+ const html = data.htmlContent;
122281
+ if (typeof html === "string" && html.trim()) {
122282
+ return html;
122283
+ }
122284
+ const raw = data.rawResponse;
122285
+ if (typeof raw === "string" && raw.trim()) {
122286
+ return extractHtmlFromResponse(raw);
122287
+ }
122288
+ throw new Error("JSON \u4E2D\u7F3A\u5C11 htmlContent \u6216 rawResponse \u5B57\u6BB5");
122289
+ }
122290
+ async function runMarketAnalysisRender(opts) {
122291
+ const dataPath = path18.resolve(opts.dataFile);
122292
+ let dataRaw;
122293
+ try {
122294
+ dataRaw = JSON.parse(await fs13.readFile(dataPath, "utf8"));
122295
+ } catch {
122296
+ console.error(`
122297
+ \u274C \u65E0\u6CD5\u89E3\u6790 JSON\uFF1A${dataPath}
122298
+ `);
122299
+ process.exit(1);
122300
+ }
122301
+ let html;
122302
+ try {
122303
+ html = resolveMarketAnalysisHtml(asRecord4(dataRaw));
122304
+ } catch (err) {
122305
+ const message = err instanceof Error ? err.message : String(err);
122306
+ console.error(`
122307
+ \u274C ${message}
122308
+ `);
122309
+ process.exit(1);
122310
+ }
122311
+ if (!html.trim()) {
122312
+ console.error("\n\u274C \u62A5\u544A HTML \u5185\u5BB9\u4E3A\u7A7A\n");
122313
+ process.exit(1);
122314
+ }
122315
+ const contentCheck = validateMarketReportContent(html);
122316
+ if (!contentCheck.ok) {
122317
+ console.error(`
122318
+ \u274C ${formatMarketReportContentErrors(contentCheck)}
122319
+ `);
122320
+ console.error(
122321
+ ` \u7EDF\u8BA1\uFF1A${contentCheck.stats.tables} \u8868\uFF5C${contentCheck.stats.charts} \u56FE\uFF5C${contentCheck.stats.painPoints} \u75DB\u70B9\uFF5C\u7ADE\u54C1\u8868 ${contentCheck.stats.competitorTableRows} \u884C
122322
+ `
122323
+ );
122324
+ process.exit(1);
122325
+ }
122326
+ const outPath = path18.resolve(
122327
+ opts.out ?? path18.join(path18.dirname(dataPath), "market-analysis-report.html")
122328
+ );
122329
+ await fs13.mkdir(path18.dirname(outPath), { recursive: true });
122330
+ await fs13.writeFile(outPath, html, "utf8");
122331
+ const { stats } = contentCheck;
122332
+ console.log(`
122333
+ \u2705 \u5E02\u573A\u5206\u6790 HTML \u62A5\u544A\u5DF2\u751F\u6210\uFF1A${outPath}
122334
+ `);
122335
+ console.log(
122336
+ ` \u5FC5\u542B\u7EF4\u5EA6\u5DF2\u5168\u90E8\u8986\u76D6\uFF5C${stats.tables} \u8868\uFF5C${stats.charts} \u56FE\uFF5C${stats.painPoints} \u75DB\u70B9\uFF5C\u7ADE\u54C1\u8868 ${stats.competitorTableRows} \u884C
122337
+ `
122338
+ );
122339
+ console.log("\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00 HTML \u6587\u4EF6\u5373\u53EF\u67E5\u770B\u5B8C\u6574\u62A5\u544A\uFF08\u9700\u8054\u7F51\u52A0\u8F7D Bootstrap / Chart.js CDN\uFF09\u3002\n");
122340
+ }
122341
+
122342
+ // src/commands/market-analysis/register.ts
122343
+ function registerMarketAnalysisCommands(program2) {
122344
+ const root = program2.command("market-analysis").description("\u6218\u7565\u5E02\u573A\u5206\u6790\uFF1A\u91C7\u96C6\u5BA2\u6237\u4E0A\u4E0B\u6587\uFF0C\u7531 Agent \u8C03\u7814\u5E76\u751F\u6210 HTML \u62A5\u544A");
122345
+ root.command("collect").description(
122346
+ "\u91C7\u96C6\u5E02\u573A\u5206\u6790\u539F\u6599\uFF1A\u5BA2\u6237\u4FE1\u606F + \u53EF\u9009\u5B98\u7F51 HTML \u9884\u89C8\uFF1B\u62A5\u544A\u7531 Agent \u6309 market-analysis-rules.md \u751F\u6210"
122347
+ ).option("--customer-name <name>", "\u5BA2\u6237\u540D\u79F0").option("--website <url>", "\u5BA2\u6237\u7F51\u7AD9\uFF08\u53EF\u9009\u62C9\u53D6\u9996\u9875\u9884\u89C8\u4F9B Agent \u53C2\u8003\uFF09").option("--industry <industry>", "\u6240\u5C5E\u884C\u4E1A").option("--core-products <products>", "\u6838\u5FC3\u4EA7\u54C1").option("--business-position <position>", "\u5546\u4E1A\u5B9A\u4F4D").option("--target-market <market>", "\u76EE\u6807\u5E02\u573A\uFF08\u9ED8\u8BA4\uFF1A\u5168\u7403\uFF09").option("--time-range <range>", "\u65F6\u95F4\u8303\u56F4\uFF08\u9ED8\u8BA4\uFF1A\u8FD112\u4E2A\u6708\uFF09").option("--skip-website", "\u4E0D\u8BF7\u6C42\u5B98\u7F51 HTML\uFF08\u4EC5\u843D\u76D8\u5BA2\u6237\u53C2\u6570\uFF09").option("--token <token>", "JWT\uFF08\u62C9\u53D6\u5B98\u7F51\u65F6\u9700\u8981\uFF1B\u9ED8\u8BA4\u8BFB config / \u73AF\u5883\u53D8\u91CF\uFF09").option("--verbose", "\u6253\u5370\u8BF7\u6C42\u8BE6\u60C5").option("--json-out <dir>", "\u843D\u76D8 cli-manifest + JSON\uFF08\u63A8\u8350\uFF09").action(
122348
+ async (opts) => {
122349
+ await runMarketAnalysisCollect({
122350
+ customerName: opts.customerName,
122351
+ website: opts.website,
122352
+ industry: opts.industry,
122353
+ coreProducts: opts.coreProducts,
122354
+ businessPosition: opts.businessPosition,
122355
+ targetMarket: opts.targetMarket,
122356
+ timeRange: opts.timeRange,
122357
+ skipWebsite: opts.skipWebsite,
122358
+ token: opts.token,
122359
+ verbose: opts.verbose,
122360
+ jsonOut: opts.jsonOut
122361
+ });
122362
+ }
122363
+ );
122364
+ root.command("render").description(
122365
+ "\u6839\u636E Agent \u751F\u6210\u7684 market-report.json\uFF08\u542B htmlContent\uFF09\u5199\u51FA HTML \u7EC8\u7A3F"
122366
+ ).requiredOption("--data <file>", "Agent \u4EA7\u51FA\u7684 market-report.json").option("--out <file>", "\u8F93\u51FA HTML \u8DEF\u5F84\uFF08\u9ED8\u8BA4\u540C --data \u76EE\u5F55\uFF09").action(async (opts) => {
122367
+ await runMarketAnalysisRender({
122368
+ dataFile: opts.data,
122369
+ out: opts.out
122370
+ });
122371
+ });
122372
+ }
122373
+
121756
122374
  // src/index.ts
121757
122375
  init_google_analysis3();
121758
122376
  init_google_analysis_batch();
@@ -121860,7 +122478,7 @@ function endpointHintForFacebookSection(def, apiId) {
121860
122478
 
121861
122479
  // src/commands/facebook-analysis/run-batch.ts
121862
122480
  init_auth();
121863
- import * as path22 from "path";
122481
+ import * as path23 from "path";
121864
122482
  import { performance as performance5 } from "perf_hooks";
121865
122483
  init_version();
121866
122484
 
@@ -121964,7 +122582,7 @@ async function runAllFacebookSections(opts) {
121964
122582
  const results = await runWithConcurrency2(tasks, concurrency);
121965
122583
  const succeeded = results.filter((r) => r.ok).length;
121966
122584
  const failed = results.length - succeeded;
121967
- const absoluteSnapshotDir = path22.resolve(opts.jsonOut);
122585
+ const absoluteSnapshotDir = path23.resolve(opts.jsonOut);
121968
122586
  const summary = {
121969
122587
  kind: "siluzan-tso-facebook-analysis-snapshot-batch",
121970
122588
  absoluteSnapshotDir,
@@ -122013,9 +122631,9 @@ init_cli_json_snapshot();
122013
122631
  installProcessHandlers();
122014
122632
  function getVersion() {
122015
122633
  try {
122016
- const __dirname3 = path23.dirname(fileURLToPath5(import.meta.url));
122017
- const pkgPath = path23.join(__dirname3, "..", "package.json");
122018
- const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf8"));
122634
+ const __dirname3 = path24.dirname(fileURLToPath5(import.meta.url));
122635
+ const pkgPath = path24.join(__dirname3, "..", "package.json");
122636
+ const pkg = JSON.parse(fs16.readFileSync(pkgPath, "utf8"));
122019
122637
  return pkg.version ?? "0.0.0";
122020
122638
  } catch {
122021
122639
  return "0.0.0";
@@ -122023,7 +122641,7 @@ function getVersion() {
122023
122641
  }
122024
122642
  var program = new Command();
122025
122643
  program.name("siluzan-tso").description(
122026
- "Siluzan \u5E7F\u544A\u8D26\u6237\u7BA1\u7406\uFF1A\u8D26\u6237\u67E5\u8BE2\u3001\u4F59\u989D\u3001\u6295\u653E\u6570\u636E\u3001\u5F00\u6237\u7533\u8BF7\uFF08Google/TikTok/Yandex/Bing/Kwai\uFF09\u3001\n\u8D26\u53F7\u5206\u4EAB/\u89E3\u7ED1\u3001\u4F18\u5316\u62A5\u544A\u3001Google \u8D26\u6237\u5206\u6790\u7F51\u5173\uFF08google-analysis\uFF09\u3001\u7F51\u7AD9\u8BCA\u65AD\uFF08website-diagnosis\uFF09\u3001\u5145\u503C\u8F6C\u8D26\u3001\u5F00\u7968\u3001\u667A\u80FD\u9884\u8B66\u3001Google \u5E7F\u544A\u7BA1\u7406\uFF08\u542B\u5F02\u6B65\u6279\u91CF\uFF09\u3002"
122644
+ "Siluzan \u5E7F\u544A\u8D26\u6237\u7BA1\u7406\uFF1A\u8D26\u6237\u67E5\u8BE2\u3001\u4F59\u989D\u3001\u6295\u653E\u6570\u636E\u3001\u5F00\u6237\u7533\u8BF7\uFF08Google/TikTok/Yandex/Bing/Kwai\uFF09\u3001\n\u8D26\u53F7\u5206\u4EAB/\u89E3\u7ED1\u3001\u4F18\u5316\u62A5\u544A\u3001Google \u8D26\u6237\u5206\u6790\u7F51\u5173\uFF08google-analysis\uFF09\u3001\u7F51\u7AD9\u8BCA\u65AD\uFF08website-diagnosis\uFF09\u3001\u6218\u7565\u5E02\u573A\u5206\u6790\uFF08market-analysis\uFF0CAgent \u751F\u6210\u62A5\u544A\uFF09\u3001\u5145\u503C\u8F6C\u8D26\u3001\u5F00\u7968\u3001\u667A\u80FD\u9884\u8B66\u3001Google \u5E7F\u544A\u7BA1\u7406\uFF08\u542B\u5F02\u6B65\u6279\u91CF\uFF09\u3002"
122027
122645
  ).version(getVersion());
122028
122646
  program.option("--commit <text>", "\u5199\u64CD\u4F5C\u8BF4\u660E\uFF1B\u8BB0\u5165\u672C\u673A\u5199\u5BA1\u8BA1 JSONL");
122029
122647
  program.hook("preAction", async () => {
@@ -122067,7 +122685,8 @@ var REGISTRARS = [
122067
122685
  register23,
122068
122686
  register24,
122069
122687
  register25,
122070
- registerWebsiteDiagnosisCommands
122688
+ registerWebsiteDiagnosisCommands,
122689
+ registerMarketAnalysisCommands
122071
122690
  ];
122072
122691
  for (const reg of REGISTRARS) reg(program);
122073
122692
  function argvHasDeprecatedJsonFlag(argv) {