@shiplens/cli 1.4.2 → 1.4.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/lib/injector.js CHANGED
@@ -95,7 +95,7 @@ function detectProject(dir = process.cwd()) {
95
95
  }
96
96
  }
97
97
 
98
- // 若无 description,尝试从 README.md 提取第一段有效文本
98
+ // Extract description fallback from README if missing
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 级分类自动推断
116
+ // 4-level taxonomy inference
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 - 用户行为数据采集\nconst { initShiplens } = require('@shiplens/sdk');\ninitShiplens({ appId: '${appId}' });\n`;
289
+ const injection = `\n// Shiplens SDK - User Analytics\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 官方源 (npm registry)',
354
+ name: '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: '阿里云/淘宝镜像源 (npmmirror)',
364
+ name: 'Alibaba Cloud mirror (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 源码镜像',
375
+ name: 'GitHub mirror',
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 镜像',
386
+ name: 'Shiplens CDN mirror',
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; // 20秒阶梯
398
- const TOTAL_TIMEOUT_MS = 5 * 60 * 1000; // 5分钟总超时
397
+ const TIER1_DELAY_MS = 20 * 1000; // 20s
398
+ const TOTAL_TIMEOUT_MS = 5 * 60 * 1000; // 5min
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: '4 个下载源均已失败或超时',
476
+ error: 'All 4 download sources failed or timed out',
477
477
  });
478
478
  }
479
479
  };
480
480
 
481
- // t = 0s: 启动 Tier 1 (npmjs)
481
+ // t = 0s: Start Tier 1 (npmjs)
482
482
  runSource(sources[0]);
483
483
 
484
- // t = 20s: 若仍未完成,保持 npmjs 并发启动 npmmirror + GitHub + CDN
484
+ // t = 20s: If still running, keep npmjs alive and start npmmirror + GitHub + CDN
485
485
  tier2Timer = setTimeout(() => {
486
486
  startBackupSources();
487
487
  }, TIER1_DELAY_MS);
488
488
 
489
- // t = 5min: 全部源总超时
489
+ // t = 5min: Total timeout across all sources
490
490
  totalTimer = setTimeout(() => {
491
491
  cleanupAndFinish({
492
492
  success: false,
493
493
  manager: pkgManager,
494
- error: '所有下载源在 5 分钟后均已超时',
494
+ error: 'SDK installation timed out after 5 minutes across all download sources',
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. 生成通用 Agent Skill 目录 (.agents/skills/shiplens/SKILL.md)
511
+ // 1. Generate generic 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. 生成 Cursor 专属规则 (.cursor/rules/shiplens.mdc)
518
+ // 2. Generate Cursor rule (.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. 检查本地 .shiplens.json 状态机
529
+ // 1. Check local .shiplens.json state machine
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. 检查代码入口中的插桩
546
+ // 2. Check code files for existing instrumentation
547
547
  const checkFiles = [
548
548
  'src/app/layout.tsx', 'app/layout.tsx',
549
549
  'src/app/layout.jsx', 'app/layout.jsx',
@@ -567,7 +567,6 @@ 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_..."
571
570
  const matchAppId = content.match(/appId\s*:\s*['"]([^'"]+)['"]/);
572
571
  if (matchAppId && matchAppId[1]) {
573
572
  return {
@@ -584,7 +583,6 @@ function detectExistingApp(dir = process.cwd()) {
584
583
  source_file: rel,
585
584
  };
586
585
  }
587
- // 如果包含 @shiplens/sdk 或 cdn.shiplens.dev 但未提取到具体 ID
588
586
  if (content.includes('@shiplens/sdk') || content.includes('cdn.shiplens.dev/sdk.js') || content.includes('initShiplens')) {
589
587
  return {
590
588
  has_existing: true,
@@ -596,7 +594,7 @@ function detectExistingApp(dir = process.cwd()) {
596
594
  }
597
595
  }
598
596
 
599
- // 3. 检查 package.json 依赖
597
+ // 3. Check package.json dependencies
600
598
  const pkgPath = path.join(dir, 'package.json');
601
599
  if (fs.existsSync(pkgPath)) {
602
600
  try {
@@ -625,4 +623,3 @@ module.exports = {
625
623
  installSDKDependency,
626
624
  getGitEmail,
627
625
  };
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(`不支持的 MCP 客户端: ${client}`, 'UNSUPPORTED_MCP_CLIENT');
41
+ if (!CLIENTS.includes(client)) throw makeError(`Unsupported MCP client: ${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(`无法读取 MCP 配置 ${filePath}:请先修复 JSON 后重试`, 'INVALID_MCP_CONFIG');
52
+ throw makeError(`Failed to read MCP config at ${filePath}: invalid 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(`不支持的 MCP 客户端: ${client}`, 'UNSUPPORTED_MCP_CLIENT');
74
- if (!url) throw makeError('MCP 地址不能为空', 'MCP_URL_REQUIRED');
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');
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('无法调用 Codex MCP 配置命令,请确认 Codex Desktop 已安装并可在终端中运行 codex', 'CODEX_MCP_CONFIG_FAILED');
91
+ throw makeError('Failed to configure Codex MCP. Ensure Codex Desktop is installed and codex CLI is available in PATH.', '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,7 +11,6 @@ function loadTaxonomy() {
11
11
  if (fs.existsSync(filePath)) {
12
12
  taxonomyCache = JSON.parse(fs.readFileSync(filePath, 'utf8'));
13
13
  } else {
14
- // 容错兜底
15
14
  taxonomyCache = { genres: [] };
16
15
  }
17
16
  } catch (e) {
@@ -74,15 +73,15 @@ function buildIndex() {
74
73
  }
75
74
 
76
75
  const CATEGORY_DISPLAY_NAMES = {
77
- core_features: '核心功能',
78
- monetization: '变现模式',
79
- social_engagement: '社交促活',
80
- technical_platform: '技术平台',
81
- content_theme: '内容与合规',
76
+ core_features: 'Core Features',
77
+ monetization: 'Monetization',
78
+ social_engagement: 'Social & Growth',
79
+ technical_platform: 'Technical Platform',
80
+ content_theme: 'Content & Compliance',
82
81
  };
83
82
 
84
83
  /**
85
- * 根据项目的名称、简介、依赖、目录特征自动推断最契合的 4 级分类与特性标签
84
+ * Automatically infer 4-level taxonomy and feature tags from project context
86
85
  */
87
86
  function inferTaxonomy(projectContext = {}) {
88
87
  const { genresMap, subgenresMap, tagsMap } = buildIndex();
@@ -103,7 +102,7 @@ function inferTaxonomy(projectContext = {}) {
103
102
  let matchedSubgenreId = 'developer_tools';
104
103
  const matchedTags = new Set();
105
104
 
106
- // 1. 判断是否为 Game
105
+ // 1. Game detection
107
106
  const isGame =
108
107
  depKeys.some((d) => ['phaser', 'pixi.js', 'three', 'kaboom', 'melonjs'].includes(d)) ||
109
108
  /\b(game|arcade|rpg|puzzle|casual|roguelike|fps|chess|cards)\b/.test(combinedText);
@@ -112,7 +111,7 @@ function inferTaxonomy(projectContext = {}) {
112
111
  matchedGenreId = 'game_casual_puzzle';
113
112
  matchedSubgenreId = 'merge_mechanic_puzzle';
114
113
  } else {
115
- // App 分支推断
114
+ // App branch inference
116
115
  if (/\b(vpn|proxy|shadowsocks|v2ray|wireguard|clash)\b/.test(combinedText)) {
117
116
  matchedGenreId = 'utilities';
118
117
  matchedSubgenreId = 'net_vpn_proxy';
@@ -120,7 +119,6 @@ function inferTaxonomy(projectContext = {}) {
120
119
  matchedGenreId = 'utilities';
121
120
  matchedSubgenreId = 'system_cleaner_optimizer';
122
121
  } else if (/\b(calc|turnip|price|profit|currency|converter|calculator|finance|financial|stock|crypto|accounting|tax|invoice|budget)\b/.test(combinedText)) {
123
- // 计算器 / 金融理财 / 价格收益预测
124
122
  matchedGenreId = 'finance_fintech';
125
123
  if (/\b(stock|trading|invest)\b/.test(combinedText)) {
126
124
  matchedSubgenreId = 'stock_trading_investment';
@@ -150,7 +148,7 @@ function inferTaxonomy(projectContext = {}) {
150
148
  }
151
149
  }
152
150
 
153
- // 确保 genre subgenre 真实有效
151
+ // Ensure valid genre and subgenre
154
152
  if (!genresMap.has(matchedGenreId)) {
155
153
  matchedGenreId = genresMap.keys().next().value || 'utilities';
156
154
  }
@@ -161,11 +159,11 @@ function inferTaxonomy(projectContext = {}) {
161
159
 
162
160
  const currentSubObj = currentGenreObj.subgenres.get(matchedSubgenreId) || subgenresMap.get(matchedSubgenreId);
163
161
 
164
- // 2. 从当前 Sub-genre 5 大类别中智能挑选特性标签 (Feature Tags)
162
+ // 2. Pick feature tags from categories
165
163
  if (currentSubObj && currentSubObj.tag_categories) {
166
164
  const cats = currentSubObj.tag_categories;
167
165
 
168
- // A. 核心功能 (Core Features) - 2~3
166
+ // A. Core Features (select 2~3)
169
167
  const coreList = cats.core_features || [];
170
168
  if (coreList.length > 0) {
171
169
  let added = 0;
@@ -184,7 +182,7 @@ function inferTaxonomy(projectContext = {}) {
184
182
  }
185
183
  }
186
184
 
187
- // B. 变现模式 (Monetization) - 选 1
185
+ // B. Monetization (select 1)
188
186
  const monList = cats.monetization || [];
189
187
  if (monList.length > 0) {
190
188
  let monTag = null;
@@ -198,7 +196,7 @@ function inferTaxonomy(projectContext = {}) {
198
196
  if (monTag) matchedTags.add(monTag.tag_id);
199
197
  }
200
198
 
201
- // C. 技术平台 (Technical Platform) - 1
199
+ // C. Technical Platform (select 1)
202
200
  const techList = cats.technical_platform || [];
203
201
  if (techList.length > 0) {
204
202
  let techTag = techList[0];
@@ -212,14 +210,14 @@ function inferTaxonomy(projectContext = {}) {
212
210
  if (techTag) matchedTags.add(techTag.tag_id);
213
211
  }
214
212
 
215
- // D. 社交与促活 (Social Engagement) - 1
213
+ // D. Social Engagement (select 1)
216
214
  const socList = cats.social_engagement || [];
217
215
  if (socList.length > 0) {
218
216
  matchedTags.add(socList[0].tag_id);
219
217
  }
220
218
  }
221
219
 
222
- // 最终组装结构化对象
220
+ // Assemble tag details
223
221
  const tagDetails = [];
224
222
  for (const tagId of matchedTags) {
225
223
  const info = tagsMap.get(tagId);
@@ -231,12 +229,11 @@ function inferTaxonomy(projectContext = {}) {
231
229
  category_name: CATEGORY_DISPLAY_NAMES[info.category] || info.category,
232
230
  });
233
231
  } else {
234
- // 兜底虚拟 tag
235
232
  tagDetails.push({
236
233
  tag_id: tagId,
237
234
  name: tagId.replace(/_/g, ' '),
238
235
  category: 'core_features',
239
- category_name: '核心功能',
236
+ category_name: 'Core Features',
240
237
  });
241
238
  }
242
239
  }
@@ -257,7 +254,7 @@ function inferTaxonomy(projectContext = {}) {
257
254
  }
258
255
 
259
256
  /**
260
- * 格式化输出为 4-Level 汇报格式
257
+ * Format taxonomy summary into 4-Level structure
261
258
  */
262
259
  function formatTaxonomySummary(taxonomy) {
263
260
  if (!taxonomy || !taxonomy.genre) return '';
@@ -273,9 +270,9 @@ function formatTaxonomySummary(taxonomy) {
273
270
  }
274
271
 
275
272
  const lines = [
276
- ` • 一级大类 (Genre): ${genreStr}`,
277
- ` • 二级子类 (Sub-genre): ${subgenreStr}`,
278
- ` • 特性标签 (Feature Tags):`,
273
+ ` • Genre (L1): ${genreStr}`,
274
+ ` • Sub-genre (L2): ${subgenreStr}`,
275
+ ` • Feature Tags:`,
279
276
  ];
280
277
 
281
278
  for (const [catKey, items] of Object.entries(categorized)) {
@@ -287,7 +284,7 @@ function formatTaxonomySummary(taxonomy) {
287
284
  }
288
285
 
289
286
  /**
290
- * 校验或通过 ID 组合反查
287
+ * Resolve taxonomy from genreId, subgenreId, and tagIdList
291
288
  */
292
289
  function resolveTaxonomyFromIDs(genreId, subgenreId, tagIdList = []) {
293
290
  const { genresMap, subgenresMap, tagsMap } = buildIndex();
@@ -327,7 +324,7 @@ function resolveTaxonomyFromIDs(genreId, subgenreId, tagIdList = []) {
327
324
  tag_id: tid,
328
325
  name: tid.replace(/_/g, ' '),
329
326
  category: 'core_features',
330
- category_name: '核心功能',
327
+ category_name: 'Core Features',
331
328
  });
332
329
  }
333
330
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shiplens/cli",
3
- "version": "1.4.2",
4
- "description": "Shiplens CLI — Web 用户行为数据统计与 AI Agent 智能分析引擎",
3
+ "version": "1.4.4",
4
+ "description": "Shiplens CLI — Automated Web User Analytics & AI Agent Analysis Engine",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
7
7
  "cli": "./bin/shiplens.js",
@@ -28,7 +28,10 @@
28
28
  },
29
29
  "homepage": "https://shiplens.dev",
30
30
  "scripts": {
31
- "test": "node test/cli.test.js"
31
+ "test": "node test/cli.test.js",
32
+ "sync-prompts": "node scripts/sync-prompts.js",
33
+ "prepack": "node scripts/sync-prompts.js",
34
+ "prepublishOnly": "node scripts/sync-prompts.js"
32
35
  },
33
36
  "files": [
34
37
  "bin",
package/prompts/README.md CHANGED
@@ -8,9 +8,8 @@ This directory hosts the deterministic CLI scenario-based prompt libraries for S
8
8
 
9
9
  ```text
10
10
  prompts/
11
- ├── README.md # Multilingual architecture overview
12
- ├── prompts_cli_en.md # [English] 42 deterministic CLI analysis scenarios
13
- └── prompts_cli_zh.md # [中文] 42 个确定性 CLI 分析场景
11
+ ├── README.md # Prompt architecture overview
12
+ └── prompts_cli_en-US.md # [English] 42 deterministic CLI analysis scenarios
14
13
  ```
15
14
 
16
15
  ---