@shiplens/cli 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/injector.js CHANGED
@@ -95,7 +95,7 @@ function detectProject(dir = process.cwd()) {
95
95
  }
96
96
  }
97
97
 
98
- // Extract description fallback from README if missing
98
+ // 若无 description,尝试从 README.md 提取第一段有效文本
99
99
  if (!result.description) {
100
100
  const readmeCandidates = ['README.md', 'readme.md', 'README.MD'];
101
101
  for (const r of readmeCandidates) {
@@ -113,7 +113,7 @@ function detectProject(dir = process.cwd()) {
113
113
  }
114
114
  }
115
115
 
116
- // 4-level taxonomy inference
116
+ // 4 级分类自动推断
117
117
  const taxonomy = inferTaxonomy({
118
118
  name: result.project_name,
119
119
  description: result.description,
@@ -286,7 +286,7 @@ function injectCRA(dir, appId) {
286
286
  if (content.includes('@shiplens/sdk') || content.includes('initShiplens') || content.includes(appId)) {
287
287
  return cand;
288
288
  }
289
- const injection = `\n// Shiplens SDK - User Analytics\nconst { initShiplens } = require('@shiplens/sdk');\ninitShiplens({ appId: '${appId}' });\n`;
289
+ const injection = `\n// Shiplens SDK - 用户行为数据采集\nconst { initShiplens } = require('@shiplens/sdk');\ninitShiplens({ appId: '${appId}' });\n`;
290
290
  fs.writeFileSync(p, content + injection, 'utf8');
291
291
  return cand;
292
292
  }
@@ -351,7 +351,7 @@ async function installSDKDependency(dir, pkgManager = 'npm') {
351
351
  const sources = [
352
352
  {
353
353
  id: 'npmjs',
354
- name: 'npm registry',
354
+ name: 'NPM 官方源 (npm registry)',
355
355
  getCommand: (pm) => {
356
356
  if (pm === 'pnpm') return `pnpm${ext} add @shiplens/sdk`;
357
357
  if (pm === 'yarn') return `yarn${ext} add @shiplens/sdk`;
@@ -361,7 +361,7 @@ async function installSDKDependency(dir, pkgManager = 'npm') {
361
361
  },
362
362
  {
363
363
  id: 'npmmirror',
364
- name: 'Alibaba Cloud mirror (npmmirror)',
364
+ name: '阿里云/淘宝镜像源 (npmmirror)',
365
365
  getCommand: (pm) => {
366
366
  const mirror = '--registry=https://registry.npmmirror.com';
367
367
  if (pm === 'pnpm') return `pnpm${ext} add @shiplens/sdk ${mirror}`;
@@ -372,7 +372,7 @@ async function installSDKDependency(dir, pkgManager = 'npm') {
372
372
  },
373
373
  {
374
374
  id: 'github',
375
- name: 'GitHub mirror',
375
+ name: 'GitHub 源码镜像',
376
376
  getCommand: (pm) => {
377
377
  const repoUrl = 'https://github.com/Hyperlong/shiplens-sdk.git';
378
378
  if (pm === 'pnpm') return `pnpm${ext} add ${repoUrl}`;
@@ -383,7 +383,7 @@ async function installSDKDependency(dir, pkgManager = 'npm') {
383
383
  },
384
384
  {
385
385
  id: 'cdn',
386
- name: 'Shiplens CDN mirror',
386
+ name: 'Shiplens 官方 CDN 镜像',
387
387
  getCommand: (pm) => {
388
388
  const cdnUrl = 'https://cdn.shiplens.dev/packages/shiplens-sdk.tgz';
389
389
  if (pm === 'pnpm') return `pnpm${ext} add ${cdnUrl}`;
@@ -394,8 +394,8 @@ async function installSDKDependency(dir, pkgManager = 'npm') {
394
394
  },
395
395
  ];
396
396
 
397
- const TIER1_DELAY_MS = 20 * 1000; // 20s
398
- const TOTAL_TIMEOUT_MS = 5 * 60 * 1000; // 5min
397
+ const TIER1_DELAY_MS = 20 * 1000; // 20秒阶梯
398
+ const TOTAL_TIMEOUT_MS = 5 * 60 * 1000; // 5分钟总超时
399
399
 
400
400
  return new Promise((resolve) => {
401
401
  let isDone = false;
@@ -473,25 +473,25 @@ async function installSDKDependency(dir, pkgManager = 'npm') {
473
473
  cleanupAndFinish({
474
474
  success: false,
475
475
  manager: pkgManager,
476
- error: 'All 4 download sources failed or timed out',
476
+ error: '4 个下载源均已失败或超时',
477
477
  });
478
478
  }
479
479
  };
480
480
 
481
- // t = 0s: Start Tier 1 (npmjs)
481
+ // t = 0s: 启动 Tier 1 (npmjs)
482
482
  runSource(sources[0]);
483
483
 
484
- // t = 20s: If still running, keep npmjs alive and start npmmirror + GitHub + CDN
484
+ // t = 20s: 若仍未完成,保持 npmjs 并发启动 npmmirror + GitHub + CDN
485
485
  tier2Timer = setTimeout(() => {
486
486
  startBackupSources();
487
487
  }, TIER1_DELAY_MS);
488
488
 
489
- // t = 5min: Total timeout across all sources
489
+ // t = 5min: 全部源总超时
490
490
  totalTimer = setTimeout(() => {
491
491
  cleanupAndFinish({
492
492
  success: false,
493
493
  manager: pkgManager,
494
- error: 'SDK installation timed out after 5 minutes across all download sources',
494
+ error: '所有下载源在 5 分钟后均已超时',
495
495
  });
496
496
  }, TOTAL_TIMEOUT_MS);
497
497
  });
@@ -508,14 +508,14 @@ function getGitEmail() {
508
508
  function injectSkill(dir = process.cwd()) {
509
509
  const { SKILL_CONTENT } = require('./assets/skill');
510
510
 
511
- // 1. Generate generic Agent Skill (.agents/skills/shiplens/SKILL.md)
511
+ // 1. 生成通用 Agent Skill 目录 (.agents/skills/shiplens/SKILL.md)
512
512
  const agentSkillDir = path.join(dir, '.agents', 'skills', 'shiplens');
513
513
  try {
514
514
  fs.mkdirSync(agentSkillDir, { recursive: true });
515
515
  fs.writeFileSync(path.join(agentSkillDir, 'SKILL.md'), SKILL_CONTENT, 'utf8');
516
516
  } catch (e) {}
517
517
 
518
- // 2. Generate Cursor rule (.cursor/rules/shiplens.mdc)
518
+ // 2. 生成 Cursor 专属规则 (.cursor/rules/shiplens.mdc)
519
519
  const cursorRulesDir = path.join(dir, '.cursor', 'rules');
520
520
  try {
521
521
  fs.mkdirSync(cursorRulesDir, { recursive: true });
@@ -526,7 +526,7 @@ function injectSkill(dir = process.cwd()) {
526
526
  }
527
527
 
528
528
  function detectExistingApp(dir = process.cwd()) {
529
- // 1. Check local .shiplens.json state machine
529
+ // 1. 检查本地 .shiplens.json 状态机
530
530
  const cfgPath = path.join(dir, '.shiplens.json');
531
531
  if (fs.existsSync(cfgPath)) {
532
532
  try {
@@ -543,7 +543,7 @@ function detectExistingApp(dir = process.cwd()) {
543
543
  } catch (e) {}
544
544
  }
545
545
 
546
- // 2. Check code files for existing instrumentation
546
+ // 2. 检查代码入口中的插桩
547
547
  const checkFiles = [
548
548
  'src/app/layout.tsx', 'app/layout.tsx',
549
549
  'src/app/layout.jsx', 'app/layout.jsx',
@@ -567,6 +567,7 @@ function detectExistingApp(dir = process.cwd()) {
567
567
  if (fs.existsSync(p)) {
568
568
  try {
569
569
  const content = fs.readFileSync(p, 'utf8');
570
+ // 匹配 appId: 'sl_live_...' 或 data-app-id="sl_live_..."
570
571
  const matchAppId = content.match(/appId\s*:\s*['"]([^'"]+)['"]/);
571
572
  if (matchAppId && matchAppId[1]) {
572
573
  return {
@@ -583,6 +584,7 @@ function detectExistingApp(dir = process.cwd()) {
583
584
  source_file: rel,
584
585
  };
585
586
  }
587
+ // 如果包含 @shiplens/sdk 或 cdn.shiplens.dev 但未提取到具体 ID
586
588
  if (content.includes('@shiplens/sdk') || content.includes('cdn.shiplens.dev/sdk.js') || content.includes('initShiplens')) {
587
589
  return {
588
590
  has_existing: true,
@@ -594,7 +596,7 @@ function detectExistingApp(dir = process.cwd()) {
594
596
  }
595
597
  }
596
598
 
597
- // 3. Check package.json dependencies
599
+ // 3. 检查 package.json 依赖
598
600
  const pkgPath = path.join(dir, 'package.json');
599
601
  if (fs.existsSync(pkgPath)) {
600
602
  try {
@@ -613,22 +615,6 @@ function detectExistingApp(dir = process.cwd()) {
613
615
  return { has_existing: false };
614
616
  }
615
617
 
616
- function autoGitCommit(dir = process.cwd(), message = 'feat: Integrate Shiplens SDK and generate configuration & dashboards') {
617
- try {
618
- execSync('git rev-parse --is-inside-work-tree', { cwd: dir, stdio: 'ignore' });
619
- const status = execSync('git status --porcelain', { cwd: dir, encoding: 'utf8' }).trim();
620
- if (status) {
621
- execSync('git add .', { cwd: dir, stdio: 'ignore' });
622
- execSync(`git commit -m "${message}"`, { cwd: dir, stdio: 'ignore' });
623
- const hash = execSync('git rev-parse --short HEAD', { cwd: dir, encoding: 'utf8' }).trim();
624
- return { committed: true, hash, message };
625
- }
626
- } catch (e) {
627
- return { committed: false, error: e.message };
628
- }
629
- return { committed: false, reason: 'no_changes' };
630
- }
631
-
632
618
  module.exports = {
633
619
  FRAMEWORKS,
634
620
  detectPackageManager,
@@ -638,5 +624,5 @@ module.exports = {
638
624
  injectSkill,
639
625
  installSDKDependency,
640
626
  getGitEmail,
641
- autoGitCommit,
642
627
  };
628
+
package/lib/mcp-config.js CHANGED
@@ -38,7 +38,7 @@ function getMcpServer(client, url) {
38
38
  }
39
39
 
40
40
  function getMcpConfig(client, url) {
41
- if (!CLIENTS.includes(client)) throw makeError(`Unsupported MCP client: ${client}`, 'UNSUPPORTED_MCP_CLIENT');
41
+ if (!CLIENTS.includes(client)) throw makeError(`不支持的 MCP 客户端: ${client}`, 'UNSUPPORTED_MCP_CLIENT');
42
42
  return { mcpServers: { shiplens: getMcpServer(client, url) } };
43
43
  }
44
44
 
@@ -49,7 +49,7 @@ function readJsonConfig(filePath) {
49
49
  if (!value || Array.isArray(value) || typeof value !== 'object') throw new Error('root must be object');
50
50
  return value;
51
51
  } catch (error) {
52
- throw makeError(`Failed to read MCP config at ${filePath}: invalid JSON`, 'INVALID_MCP_CONFIG');
52
+ throw makeError(`无法读取 MCP 配置 ${filePath}:请先修复 JSON 后重试`, 'INVALID_MCP_CONFIG');
53
53
  }
54
54
  }
55
55
 
@@ -70,8 +70,8 @@ function mergeJsonMcpConfig(client, url, filePath) {
70
70
  }
71
71
 
72
72
  function configureMcpClient(client, url, options = {}) {
73
- if (!CLIENTS.includes(client)) throw makeError(`Unsupported MCP client: ${client}`, 'UNSUPPORTED_MCP_CLIENT');
74
- if (!url) throw makeError('MCP URL is required', 'MCP_URL_REQUIRED');
73
+ if (!CLIENTS.includes(client)) throw makeError(`不支持的 MCP 客户端: ${client}`, 'UNSUPPORTED_MCP_CLIENT');
74
+ if (!url) throw makeError('MCP 地址不能为空', 'MCP_URL_REQUIRED');
75
75
 
76
76
  if (client === 'manual') {
77
77
  return { written: false, already_configured: false, path: null, config: getMcpConfig('manual', url) };
@@ -88,7 +88,7 @@ function configureMcpClient(client, url, options = {}) {
88
88
  try {
89
89
  exec(codexCommand, ['mcp', 'add', 'shiplens', '--', process.platform === 'win32' ? 'npx.cmd' : 'npx', '--yes', 'shiplens-cli', 'mcp', 'serve'], { stdio: 'ignore' });
90
90
  } catch (error) {
91
- throw makeError('Failed to configure Codex MCP. Ensure Codex Desktop is installed and codex CLI is available in PATH.', 'CODEX_MCP_CONFIG_FAILED');
91
+ throw makeError('无法调用 Codex MCP 配置命令,请确认 Codex Desktop 已安装并可在终端中运行 codex', 'CODEX_MCP_CONFIG_FAILED');
92
92
  }
93
93
  return { written: true, already_configured: false, path: configPath, config: getMcpConfig('codex', url) };
94
94
  }
package/lib/taxonomy.js CHANGED
@@ -11,6 +11,7 @@ function loadTaxonomy() {
11
11
  if (fs.existsSync(filePath)) {
12
12
  taxonomyCache = JSON.parse(fs.readFileSync(filePath, 'utf8'));
13
13
  } else {
14
+ // 容错兜底
14
15
  taxonomyCache = { genres: [] };
15
16
  }
16
17
  } catch (e) {
@@ -73,15 +74,15 @@ function buildIndex() {
73
74
  }
74
75
 
75
76
  const CATEGORY_DISPLAY_NAMES = {
76
- core_features: 'Core Features',
77
- monetization: 'Monetization',
78
- social_engagement: 'Social & Growth',
79
- technical_platform: 'Technical Platform',
80
- content_theme: 'Content & Compliance',
77
+ core_features: '核心功能',
78
+ monetization: '变现模式',
79
+ social_engagement: '社交促活',
80
+ technical_platform: '技术平台',
81
+ content_theme: '内容与合规',
81
82
  };
82
83
 
83
84
  /**
84
- * Automatically infer 4-level taxonomy and feature tags from project context
85
+ * 根据项目的名称、简介、依赖、目录特征自动推断最契合的 4 级分类与特性标签
85
86
  */
86
87
  function inferTaxonomy(projectContext = {}) {
87
88
  const { genresMap, subgenresMap, tagsMap } = buildIndex();
@@ -102,7 +103,7 @@ function inferTaxonomy(projectContext = {}) {
102
103
  let matchedSubgenreId = 'developer_tools';
103
104
  const matchedTags = new Set();
104
105
 
105
- // 1. Game detection
106
+ // 1. 判断是否为 Game
106
107
  const isGame =
107
108
  depKeys.some((d) => ['phaser', 'pixi.js', 'three', 'kaboom', 'melonjs'].includes(d)) ||
108
109
  /\b(game|arcade|rpg|puzzle|casual|roguelike|fps|chess|cards)\b/.test(combinedText);
@@ -111,7 +112,7 @@ function inferTaxonomy(projectContext = {}) {
111
112
  matchedGenreId = 'game_casual_puzzle';
112
113
  matchedSubgenreId = 'merge_mechanic_puzzle';
113
114
  } else {
114
- // App branch inference
115
+ // App 分支推断
115
116
  if (/\b(vpn|proxy|shadowsocks|v2ray|wireguard|clash)\b/.test(combinedText)) {
116
117
  matchedGenreId = 'utilities';
117
118
  matchedSubgenreId = 'net_vpn_proxy';
@@ -119,6 +120,7 @@ function inferTaxonomy(projectContext = {}) {
119
120
  matchedGenreId = 'utilities';
120
121
  matchedSubgenreId = 'system_cleaner_optimizer';
121
122
  } else if (/\b(calc|turnip|price|profit|currency|converter|calculator|finance|financial|stock|crypto|accounting|tax|invoice|budget)\b/.test(combinedText)) {
123
+ // 计算器 / 金融理财 / 价格收益预测
122
124
  matchedGenreId = 'finance_fintech';
123
125
  if (/\b(stock|trading|invest)\b/.test(combinedText)) {
124
126
  matchedSubgenreId = 'stock_trading_investment';
@@ -148,7 +150,7 @@ function inferTaxonomy(projectContext = {}) {
148
150
  }
149
151
  }
150
152
 
151
- // Ensure valid genre and subgenre
153
+ // 确保 genre subgenre 真实有效
152
154
  if (!genresMap.has(matchedGenreId)) {
153
155
  matchedGenreId = genresMap.keys().next().value || 'utilities';
154
156
  }
@@ -159,11 +161,11 @@ function inferTaxonomy(projectContext = {}) {
159
161
 
160
162
  const currentSubObj = currentGenreObj.subgenres.get(matchedSubgenreId) || subgenresMap.get(matchedSubgenreId);
161
163
 
162
- // 2. Pick feature tags from categories
164
+ // 2. 从当前 Sub-genre 5 大类别中智能挑选特性标签 (Feature Tags)
163
165
  if (currentSubObj && currentSubObj.tag_categories) {
164
166
  const cats = currentSubObj.tag_categories;
165
167
 
166
- // A. Core Features (select 2~3)
168
+ // A. 核心功能 (Core Features) - 2~3
167
169
  const coreList = cats.core_features || [];
168
170
  if (coreList.length > 0) {
169
171
  let added = 0;
@@ -182,7 +184,7 @@ function inferTaxonomy(projectContext = {}) {
182
184
  }
183
185
  }
184
186
 
185
- // B. Monetization (select 1)
187
+ // B. 变现模式 (Monetization) - 选 1
186
188
  const monList = cats.monetization || [];
187
189
  if (monList.length > 0) {
188
190
  let monTag = null;
@@ -196,7 +198,7 @@ function inferTaxonomy(projectContext = {}) {
196
198
  if (monTag) matchedTags.add(monTag.tag_id);
197
199
  }
198
200
 
199
- // C. Technical Platform (select 1)
201
+ // C. 技术平台 (Technical Platform) - 1
200
202
  const techList = cats.technical_platform || [];
201
203
  if (techList.length > 0) {
202
204
  let techTag = techList[0];
@@ -210,14 +212,14 @@ function inferTaxonomy(projectContext = {}) {
210
212
  if (techTag) matchedTags.add(techTag.tag_id);
211
213
  }
212
214
 
213
- // D. Social Engagement (select 1)
215
+ // D. 社交与促活 (Social Engagement) - 1
214
216
  const socList = cats.social_engagement || [];
215
217
  if (socList.length > 0) {
216
218
  matchedTags.add(socList[0].tag_id);
217
219
  }
218
220
  }
219
221
 
220
- // Assemble tag details
222
+ // 最终组装结构化对象
221
223
  const tagDetails = [];
222
224
  for (const tagId of matchedTags) {
223
225
  const info = tagsMap.get(tagId);
@@ -229,11 +231,12 @@ function inferTaxonomy(projectContext = {}) {
229
231
  category_name: CATEGORY_DISPLAY_NAMES[info.category] || info.category,
230
232
  });
231
233
  } else {
234
+ // 兜底虚拟 tag
232
235
  tagDetails.push({
233
236
  tag_id: tagId,
234
237
  name: tagId.replace(/_/g, ' '),
235
238
  category: 'core_features',
236
- category_name: 'Core Features',
239
+ category_name: '核心功能',
237
240
  });
238
241
  }
239
242
  }
@@ -254,7 +257,7 @@ function inferTaxonomy(projectContext = {}) {
254
257
  }
255
258
 
256
259
  /**
257
- * Format taxonomy summary into 4-Level structure
260
+ * 格式化输出为 4-Level 汇报格式
258
261
  */
259
262
  function formatTaxonomySummary(taxonomy) {
260
263
  if (!taxonomy || !taxonomy.genre) return '';
@@ -270,9 +273,9 @@ function formatTaxonomySummary(taxonomy) {
270
273
  }
271
274
 
272
275
  const lines = [
273
- ` • Genre (L1): ${genreStr}`,
274
- ` • Sub-genre (L2): ${subgenreStr}`,
275
- ` • Feature Tags:`,
276
+ ` • 一级大类 (Genre): ${genreStr}`,
277
+ ` • 二级子类 (Sub-genre): ${subgenreStr}`,
278
+ ` • 特性标签 (Feature Tags):`,
276
279
  ];
277
280
 
278
281
  for (const [catKey, items] of Object.entries(categorized)) {
@@ -284,7 +287,7 @@ function formatTaxonomySummary(taxonomy) {
284
287
  }
285
288
 
286
289
  /**
287
- * Resolve taxonomy from genreId, subgenreId, and tagIdList
290
+ * 校验或通过 ID 组合反查
288
291
  */
289
292
  function resolveTaxonomyFromIDs(genreId, subgenreId, tagIdList = []) {
290
293
  const { genresMap, subgenresMap, tagsMap } = buildIndex();
@@ -324,7 +327,7 @@ function resolveTaxonomyFromIDs(genreId, subgenreId, tagIdList = []) {
324
327
  tag_id: tid,
325
328
  name: tid.replace(/_/g, ' '),
326
329
  category: 'core_features',
327
- category_name: 'Core Features',
330
+ category_name: '核心功能',
328
331
  });
329
332
  }
330
333
  }
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@shiplens/cli",
3
- "version": "1.3.0",
4
- "description": "Shiplens CLI (In Development) - 15-second zero-config analytics initialization and full telemetry analysis engine",
3
+ "version": "1.4.0",
4
+ "description": "Shiplens CLI Web 用户行为数据统计与 AI Agent 智能分析引擎",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
7
- "shiplens-cli": "bin/shiplens.js",
8
- "shiplens": "bin/shiplens.js"
7
+ "shiplens-cli": "./bin/shiplens.js",
8
+ "shiplens": "./bin/shiplens.js"
9
9
  },
10
10
  "keywords": [
11
11
  "shiplens",
@@ -33,6 +33,7 @@
33
33
  "bin",
34
34
  "lib",
35
35
  "prompts",
36
+ "docs",
36
37
  "README.md",
37
38
  "LICENSE"
38
39
  ],
package/prompts/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Shiplens Prompt Libraries
1
+ # Shiplens Prompt Libraries
2
2
 
3
3
  This directory hosts the deterministic CLI scenario-based prompt libraries for Shiplens.
4
4
 
@@ -9,13 +9,19 @@ This directory hosts the deterministic CLI scenario-based prompt libraries for S
9
9
  ```text
10
10
  prompts/
11
11
  ├── README.md # Multilingual architecture overview
12
- ├── prompts_cli_en.md # [English - Official] Deterministic CLI execution presets (42 Scenarios)
13
- └── ... # Future language extensions (ja, ko, de, fr, es, etc.)
12
+ ├── prompts_cli_en.md # [English] 42 deterministic CLI analysis scenarios
13
+ └── prompts_cli_zh.md # [中文] 42 个确定性 CLI 分析场景
14
14
  ```
15
15
 
16
16
  ---
17
17
 
18
18
  ## ⚡ Execution & Dynamic Overrides
19
19
 
20
- 1. **Deterministic Execution**: Every scenario in `prompts_cli_en.md` provides explicit, reproducible CLI commands and SQL queries paired with textbook analytical foundations.
21
- 2. **Dynamic Overrides**: Local rules defined in `.shiplens/learnings.md` override default parameters (such as `--range`, `--grain`, or target funnel routes) during AI Agent execution.
20
+ 1. **Deterministic Execution**: Every scenario provides explicit, reproducible CLI commands and SQL queries paired with textbook analytical foundations.
21
+ 2. **Dynamic Overrides**: Local rules defined in `.shiplens/learnings.md` override default parameters (such as `--range`, `--grain`, or target funnel routes) during AI Agent execution.
22
+ 3. **Scenario Outline**: Each file includes a categorized outline at the top for quick scenario discovery. Agent should read the outline first, then jump to the matching scenario.
23
+
24
+ ## 🌐 Multilingual Architecture
25
+
26
+ - Scenario IDs, titles, and structure are **1:1 aligned** across languages.
27
+ - Add new languages by creating `prompts_cli_<lang>.md` with the same outline structure and scenario ordering.