@shiplens/cli 1.3.1 → 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/README.md CHANGED
@@ -1,95 +1,71 @@
1
- # 🚀 Shiplens CLI (In Development)
1
+ # 🚀 Shiplens CLI
2
2
 
3
- > **Fast User Telemetry, Instant SDK Instrumentation & AI Analytics Engine for Modern Web Apps.**
3
+ > **Shiplens 官方命令行工具 面向 Web 前端的用户行为数据统计与 AI Agent 智能分析引擎。**
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@shiplens/cli.svg)](https://www.npmjs.com/package/@shiplens/cli)
6
6
  [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
7
7
  [![Node Version](https://img.shields.io/badge/node-%3E%3D16.0.0-brightgreen.svg)](package.json)
8
- [![Status](https://img.shields.io/badge/status-active%20development-orange.svg)]()
9
8
 
10
9
  ---
11
10
 
12
- > [!NOTE]
13
- > **Active Development Preview**: This package is currently in active development. Features and API endpoints are being iterated rapidly. 50,000 monthly telemetry events are included for free upon registration.
11
+ Shiplens CLI 是 [shiplens.dev](https://shiplens.dev) 官方配套的命令行工具。它为前端项目提供自动化的数据统计接入,并与各类 AI Agent(Cursor、Windsurf、Codex、Claude 等)无缝协同,实现对话式数据查询与自动化分析。
14
12
 
15
13
  ---
16
14
 
17
- ## Instant Setup (Under 15 Seconds)
15
+ ## 🌟 核心功能
18
16
 
19
- Run in the root of any Next.js, Vite, Vue, or HTML project:
17
+ - **自动化接入与代码插桩**:自动识别前端项目框架(Next.jsViteVue、HTML 等),无感注入 `@shiplens/sdk` 数据采集代码;
18
+ - **用户行为数据统计**:秒级查看全站流量大盘、访问路径、页面留存与点击热力图;
19
+ - **AI Agent 智能分析**:支持通过 CLI 与本地 stdio MCP 代理(`shiplens mcp serve`)与 AI 对话,让 Agent 直接读取产品数据并输出教科书级业务洞察;
20
+ - **AI 实时看板生成**:一句话自然语言诉求,云端自动拼装 12 列响应式数据看板;
21
+ - **业务上下文与动态自适应**:自动提取页面功能文案辅助 AI 理解业务,并支持按项目自适应记忆个性化分析偏好。
20
22
 
21
- ```bash
22
- # Zero-install execution via npx
23
- npx --yes @shiplens/cli init --json
24
-
25
- # Windows PowerShell (Avoid execution policy blocking)
26
- npx.cmd --yes @shiplens/cli init --json
27
-
28
- # In China or restricted networks (Alibaba Cloud / npmmirror fallback)
29
- npx --yes --registry=https://registry.npmmirror.com @shiplens/cli init --json
30
- ```
23
+ ---
31
24
 
32
- ### What `init` Does Automatically:
33
- 1. **Detects Framework**: Automatically recognizes Next.js (App / Pages router), Vite (React / Svelte), Vue 3, or plain HTML.
34
- 2. **Injects Tracking SDK**: Inserts `@shiplens/sdk` tracking snippets into your entry files without breaking your code layout.
35
- 3. **Connects Cloud Project**: Registers the project, generates a live responsive dashboard URL, and writes local configuration (`.shiplens.json`).
36
- 4. **Installs Dependency**: Adds `@shiplens/sdk` via your project package manager (`npm`, `pnpm`, `yarn`, or `bun`) with 4-tier download fallback (NPM -> Alibaba npmmirror -> GitHub -> CDN, 5s timeout & 2 retries per tier).
37
- 5. **Deploys AI Skills**: Injects `.agents/skills/shiplens/SKILL.md` and Cursor rules (`.cursor/rules/shiplens.mdc`) for seamless LLM Agent integration.
38
- 6. **Performs Git Commit**: Automatically commits all instrumentation changes atomically.
25
+ ## 快速开始
39
26
 
40
- ---
27
+ 在任何前端项目根目录下执行初始化命令:
41
28
 
42
- ## 🧠 Dynamic Overrides & Adaptive Learning
29
+ ```bash
30
+ # 通用终端
31
+ npx --yes @shiplens/cli init
43
32
 
44
- Shiplens introduces an adaptive learning loop for AI Agents:
33
+ # Windows PowerShell
34
+ npx.cmd --yes @shiplens/cli init
45
35
 
46
- ```text
47
- [User Prompt] [Match Preset] ➔ [Apply .shiplens/learnings.md (Priority 1)] ➔ [Execute CLI] ➔ [Update Learnings]
36
+ # 备用镜像源
37
+ npx --yes --registry=https://registry.npmmirror.com @shiplens/cli init
48
38
  ```
49
39
 
50
- - **Priority 1 (Dynamic Overrides)**: Local `.shiplens/learnings.md` rules always override default CLI parameters (e.g. customized date ranges, funnel goals, or granular filters).
51
- - **Priority 2 (Deterministic Base)**: Standard 42 scenario-based CLI execution presets in `prompts/cli/prompt_cli_en.md`.
52
-
53
40
  ---
54
41
 
55
- ## 📊 Core Commands
42
+ ## 📊 常用命令速查
56
43
 
57
- | Command | Description |
44
+ | 命令 | 说明 |
58
45
  | :--- | :--- |
59
- | `npx @shiplens/cli init --json` | 15-second zero-config analytics onboarding |
60
- | `npx @shiplens/cli doctor --json` | End-to-end diagnostics on SDK, network, and credentials |
61
- | `npx @shiplens/cli summary --range 7d --json` | Traffic overview: PV, UV, bounce rates, geos, devices |
62
- | `npx @shiplens/cli query --metric pageviews --json` | Multi-dimensional metrics & funnel queries |
63
- | `npx @shiplens/cli sql --query "<sql>" --json` | Sandboxed read-only ClickHouse SQL execution |
64
- | `npx @shiplens/cli pages --range 7d --json` | Page-level visits and average dwell times |
65
- | `npx @shiplens/cli paths --range 7d --json` | User journeys and Sankey transition paths |
66
- | `npx @shiplens/cli heatmap --template <id> --json` | Click heatmaps & skeleton wireframe snapshots |
67
- | `npx @shiplens/cli dashboards create --prompt "..." --json` | AI-driven dashboard generation |
68
- | `npx @shiplens/cli auth bind --email <email> --json` | Request Magic Link for quota activation & authorization |
69
- | `npx @shiplens/cli auth configure --client cursor --json` | Auto-configure MCP servers in IDEs |
70
-
71
- ---
72
-
73
- ## 🔒 Security & Sandboxing
74
-
75
- - **Tenant Isolation**: Strict per-project `app_id` isolation across all telemetry queries.
76
- - **Read-Only ClickHouse Sandbox**: AST query validator enforces `SELECT`-only operations, mandatory time bounds, and maximum 1000-row limits.
77
- - **Safe Secrets Handling**: Credential tokens and secrets are automatically masked in all console outputs (`sk_live_...9f2a`).
46
+ | `shiplens init` | 为当前项目接入数据统计并下发 AI Skill 规范 |
47
+ | `shiplens summary` | 查看产品流量大盘概览(PV、UV、时长、受众分布) |
48
+ | `shiplens query` | 多维指标聚合与用户转化漏斗查询 |
49
+ | `shiplens sql` | 执行自定义安全只读数据分析查询 |
50
+ | `shiplens pages` | 页面级访问量与停留时长统计 |
51
+ | `shiplens heatmap` | 查看页面点击热力分布与骨架图 |
52
+ | `shiplens dashboards create` | 通过自然语言生成官方数据看板 |
53
+ | `shiplens doctor` | 检查本地配置、代码插桩与网络通道健康度 |
54
+ | `shiplens context show` | 查看当前项目的业务上下文与页面功能文案 |
55
+ | `shiplens auth bind` | 绑定邮箱并激活设备数据分析授权 |
56
+ | `shiplens mcp serve` | 启动本地 stdio MCP 代理服务供 AI Agent 调度 |
78
57
 
79
58
  ---
80
59
 
81
- ## 📁 Documentation & Prompts
60
+ ## 🔗 相关资源
82
61
 
83
- - [CLI Architecture & Roadmap](./docs/01_CLI_ARCHITECTURE_AND_ROADMAP.md)
84
- - [Complete Command Reference](./docs/02_CLI_COMMAND_REFERENCE.md)
85
- - [AI Skill & Dynamic Overrides Spec](./docs/03_SKILL_SPEC_AND_PROMPTS.md)
86
- - [CLI Execution Prompts (42 Scenarios)](./prompts/cli/prompt_cli_en.md)
87
- - [Web Showcase Prompts (42 Scenarios)](./prompts/web/prompt_web_en.md)
88
- - [Backend API Specification](./docs/BACKEND_API_SPEC.md)
89
- - [Project Context Specification](./docs/SHIPLENS_CONTEXT_API_SPEC.md)
62
+ - 官方网站:[https://shiplens.dev](https://shiplens.dev)
63
+ - 常见问题与支持:[https://shiplens.dev/faq](https://shiplens.dev/faq)
64
+ - 全命令参考手册:[`docs/02_SHIPLENS_CLI_全命令参考手册.md`](./docs/02_SHIPLENS_CLI_全命令参考手册.md)
90
65
 
91
66
  ---
92
67
 
93
- ## 📜 License
68
+ ## 📜 开源协议
94
69
 
95
70
  Apache-2.0 © Shiplens Team
71
+
@@ -0,0 +1,205 @@
1
+ # Shiplens CLI 全命令参考手册 (Reference Manual)
2
+
3
+ > 本文档定义了 Shiplens CLI 所有命令的输入参数、选项、使用语法与输出说明。供 AI Agent 调用。
4
+
5
+ ---
6
+
7
+ ## 全局选项 (Global Flags)
8
+
9
+ 所有子命令均继承以下全局选项:
10
+
11
+ | 选项 | 类型 | 说明 |
12
+ | :--- | :--- | :--- |
13
+ | `--json` | bool | 强制以标准 JSON 格式输出结果(AI Agent 必选) |
14
+ | `--app-id <string>` | string | 显式指定目标项目 `app_id`(默认自动读取 `./.shiplens.json`) |
15
+ | `--env <string>` | string | 目标环境:`production`(默认)或 `staging` |
16
+ | `--secret <string>` | string | 显式传入 Access Secret 鉴权密钥(覆盖本地配置) |
17
+ | `--api-url <string>` | string | 自定义后端 API 基址(默认为 `http://120.26.230.33`) |
18
+ | `-v, --version` | flag | 输出当前 CLI 版本号 |
19
+ | `-h, --help` | flag | 打印当前命令的帮助说明 |
20
+
21
+ > **Windows 终端免拦截**:在 Windows 下调用时必须使用 `npx.cmd --yes shiplens-cli <命令>`,严禁裸敲 `npx`,避免 PowerShell 脚本策略拦截。
22
+
23
+ ---
24
+
25
+ ## 1. 项目接入与初始化 (`init`)
26
+
27
+ ```bash
28
+ shiplens init [options]
29
+ ```
30
+
31
+ ### 选项说明:
32
+ - `--name <string>`:项目名称(默认从 `package.json` 自动提取)
33
+ - `--description <string>`:项目功能简介与定位描述
34
+ - `--industry <string>`:行业分类标识
35
+ - `--genre <id>`:Level 1 大类 ID(如 `utilities`, `finance_fintech`)
36
+ - `--subgenre <id>`:Level 2 子类别 ID(如 `developer_tools`)
37
+ - `--tags <tag1,tag2>`:Level 4 特性标签 ID 列表(逗号分隔,最多 10 个)
38
+ - `--email <email>`:绑定邮箱(传 `auto` 时自动读取 `git config user.email`)
39
+ - `--framework <type>`:强制指定框架类型(`nextjs-app`, `nextjs-pages`, `vite`, `vue`, `html`)
40
+ - `--force`:强制覆盖本地已存在的 Shiplens 配置与项目编号
41
+ - `--no-install`:跳过包管理器 `install` 依赖安装步骤
42
+
43
+ ---
44
+
45
+ ## 2. 身份与凭证管理 (`auth`)
46
+
47
+ ### 子命令列表:
48
+ - `shiplens auth status`:检查当前鉴权凭证的有效性
49
+ - `shiplens auth set [secret]`:将 Access Secret 写入本地 `~/.shiplens/config.json`
50
+ - `shiplens auth whoami`:查询当前登录账户详情与绑定的项目信息
51
+ - `shiplens auth logout`:清除本地存储的所有凭证
52
+ - `shiplens auth bind --email <email>`:请求 Magic Link 激活邮件绑定
53
+ - `shiplens auth mcp-config --client <client>`:输出指定 Agent 客户端的 stdio MCP 配置
54
+ - `shiplens auth configure --client <client>`:自动将 MCP 配置写入目标客户端(`cursor`, `codex`, `claude`, `antigravity`, `manual`)
55
+ - `shiplens auth secret list/create/revoke`:管理离线自动化 Access Secret 密钥
56
+
57
+ ---
58
+
59
+ ## 3. 项目管理与绑定 (`projects`)
60
+
61
+ ### 子命令列表:
62
+ - `shiplens projects list`:列出当前账户名下的所有项目
63
+ - `shiplens projects bind`:将当前本地目录的项目与云端账户绑定
64
+ - `shiplens projects delete [--force]`:删除项目及其全部历史数据
65
+
66
+ ---
67
+
68
+ ## 4. 多维结构化指标分析 (`query`)
69
+
70
+ ```bash
71
+ shiplens query [options]
72
+ ```
73
+
74
+ ### 选项说明:
75
+ - `--metric <name>` / `--metrics <m1,m2>`:查询指标名称(如 `pageviews`, `daily_retention`, `bounce_rate`, `conversion_funnel`)
76
+ - `--range <range>`:时间窗口(`24h`, `7d`, `14d`, `30d`, `90d`)
77
+ - `--grain <grain>`:聚合时间粒度(`hour`, `day`, `week`, `month`)
78
+ - `--group-by <dim>`:分组维度(`path`, `template_id`, `country`, `browser`, `device_type` 等)
79
+ - `--filter <key=value>`:多维过滤条件
80
+ - `--limit <num>`:返回数据行数限制(默认: 30)
81
+ - `--file <path>`:从 JSON 文件载入完整的 `AnalyticsQueryRequest` 查询结构
82
+
83
+ ---
84
+
85
+ ## 5. 只读安全 SQL 沙箱 (`sql`)
86
+
87
+ ```bash
88
+ shiplens sql --query "<sql>" [options]
89
+ ```
90
+
91
+ ### 选项说明:
92
+ - `--query "<sql>"` / `--sql "<sql>"`:只读 SQL 查询语句
93
+ - `--stdin`:从标准输入流读取 SQL 语句(管道输入专用)
94
+
95
+ ---
96
+
97
+ ## 6. 产品大盘概览 (`summary`)
98
+
99
+ ```bash
100
+ shiplens summary [--range <range>]
101
+ ```
102
+
103
+ 返回指定周期内的 PV、UV、会话数、平均停留时长、跳出率及受众地域分布。
104
+
105
+ ---
106
+
107
+ ## 7. 页面分析与行为路径 (`pages` / `paths` / `canvas`)
108
+
109
+ - `shiplens pages [--range 7d]`:获取页面维度的 PV、UV、平均停留时长与跳出率列表
110
+ - `shiplens paths [--range 7d]`:获取高频页面流转路径拓扑
111
+ - `shiplens canvas [--range 7d]`:获取完整的节点关系与全景可视化拓扑
112
+
113
+ ---
114
+
115
+ ## 8. 页面点击热力图 (`heatmap`)
116
+
117
+ ```bash
118
+ shiplens heatmap --template <template_id> [--range 7d]
119
+ ```
120
+
121
+ 拉取目标页面的点击热力坐标、交互事件分布与页面黑白骨架图。
122
+
123
+ ---
124
+
125
+ ## 9. AI 实时看板管理 (`dashboards`)
126
+
127
+ - `shiplens dashboards list`:查看已创建的 AI 数据看板
128
+ - `shiplens dashboards create --title "<标题>" --prompt "<分析诉求>"`:一键创建并生成在线实时看板链接
129
+
130
+ ---
131
+
132
+ ## 10. 环境与连通性体检 (`doctor`)
133
+
134
+ ```bash
135
+ shiplens doctor
136
+ ```
137
+
138
+ 一键自检 5 大核心项:本地配置、SDK 安装、代码插桩、网络连通性及凭证有效性。
139
+
140
+ ---
141
+
142
+ ## 11. 业务上下文同步 (`context`)
143
+
144
+ - `shiplens context show`:查看当前产品的 `.shiplens/contexts/<app_id>.md` 业务上下文内容
145
+ - `shiplens context push`:将本地页面/按钮功能描述同步至云端
146
+ - `shiplens context pull`:从云端拉取已持久化的业务上下文文件
147
+
148
+ ---
149
+
150
+ ## 12. 本地 stdio MCP 代理服务 (`mcp serve`)
151
+
152
+ 启动本地 stdio MCP 代理通道,自动读取当前目录的 `shiplens.env` 设备凭证并安全转发 MCP 工具调用。
153
+
154
+ ---
155
+
156
+ ## 13. 全系统前后端接口调用拓扑 (12 步端到端数据流)
157
+
158
+ 全系统从用户接入、激活到数据分析的完整数据链路:
159
+
160
+ ```mermaid
161
+ sequenceDiagram
162
+ participant User as 用户 / AI Agent
163
+ participant CLI as Shiplens CLI
164
+ participant Cloud as Cloud API 云端
165
+ participant SDK as @shiplens/sdk
166
+ participant MCP as 本地 stdio MCP 代理
167
+
168
+ Note over User,MCP: 一、 极速初始化与激活流程 (步骤 1-7)
169
+ User->>CLI: 1. npx @shiplens/cli init
170
+ CLI->>Cloud: 2. POST /api/connect (注册项目及 4 级行业分类)
171
+ Cloud-->>CLI: 返回 app_id + 临时 dashboard_url
172
+ CLI->>CLI: 3. 智能检测框架并注入 SDK 采集代码
173
+ CLI->>CLI: 4. 4级降级安装 @shiplens/sdk 依赖
174
+ CLI->>CLI: 5. 扫描页面文案生成 .shiplens/contexts/<app_id>.md
175
+ User->>CLI: 6. shiplens auth bind --email <用户邮箱>
176
+ CLI->>Cloud: POST /api/auth/start-email (发送 Magic Link)
177
+ Cloud-->>User: 7. 发送激活邮件 → 用户点击链接完成激活
178
+ Cloud-->>CLI: 下发设备凭证至本地 shiplens.env (0600权限)
179
+
180
+ Note over User,MCP: 二、 数据分析与查询链路 (步骤 8-12)
181
+ User->>CLI: 8. 执行 shiplens summary / query / sql
182
+ CLI->>CLI: 9. 加载 shiplens.env 凭证与 learnings 覆盖规则
183
+ CLI->>Cloud: 10. 发起携带身份签名的 API 请求
184
+ Cloud-->>CLI: 11. 返回聚合指标 / 漏斗数据 / 热力坐标
185
+ CLI-->>User: 12. 结构化 JSON 交付 → AI 输出深度洞察
186
+
187
+ Note over User,MCP: 三、 MCP 协议代理接入 (备选)
188
+ User->>MCP: shiplens mcp serve (stdio)
189
+ MCP->>Cloud: 携带本地凭据透传调用远程 MCP 服务
190
+ Cloud-->>MCP: 返回数据响应
191
+ MCP-->>User: 交付结构化工具调用结果
192
+ ```
193
+
194
+ ### 核心组件角色与职责分工
195
+
196
+ | 核心组件 | 角色职责与定位 |
197
+ |---------|---------------|
198
+ | **Shiplens CLI** (`@shiplens/cli`) | 15 秒极速插桩、本地配置管理、直接数据分析与诊断 |
199
+ | **Cloud API** | 项目注册、Magic Link 鉴权、数据多维聚合与看板托管 |
200
+ | **SDK** (`@shiplens/sdk`) | 客户端无感 PV/点击/自定义事件采集、DOM 骨架哈希映射 |
201
+ | **`shiplens.env`** | 设备级凭证(Access Secret),0600 安全权限,自动 .gitignore 保护 |
202
+ | **本地 stdio MCP 代理** (`shiplens mcp serve`) | 供 Cursor/Antigravity/Codex 等 IDE 的 stdio 通道,携带本地凭据安全转发 |
203
+ | **`./.shiplens.json`** | 本地项目状态机缓存(`app_id`、项目名、Schema 更新时间戳) |
204
+ | **`.shiplens/contexts/<app_id>.md`** | 业务上下文字典(页面路由、按钮文本、功能语义说明) |
205
+ | **`.shiplens/learnings.md`** | 项目专属动态偏好覆盖(自定义时间周期、目标漏斗、过滤规则) |
package/lib/api.js CHANGED
@@ -24,21 +24,25 @@ class APIClient {
24
24
  const fullURL = `${this.baseURL}${path}`;
25
25
  const parsed = new URL(fullURL);
26
26
  const isHttps = parsed.protocol === 'https:';
27
- const lib = isHttps ? https : http;
27
+ const transport = isHttps ? https : http;
28
28
 
29
- const payload = body ? (typeof body === 'string' ? body : JSON.stringify(body)) : null;
30
- let pkgVer = '1.3.0';
29
+ let pkgVer = '1.3.1';
31
30
  try {
32
31
  pkgVer = require('../package.json').version || pkgVer;
33
32
  } catch (e) {}
33
+
34
34
  const headers = {
35
35
  'Content-Type': 'application/json',
36
36
  'User-Agent': `Shiplens-CLI/${pkgVer} (Node.js)`,
37
37
  };
38
+
38
39
  if (this.secret) {
39
40
  headers['Authorization'] = `Bearer ${this.secret}`;
40
41
  }
41
- if (payload) {
42
+
43
+ let payload = null;
44
+ if (body !== null && body !== undefined) {
45
+ payload = typeof body === 'string' ? body : JSON.stringify(body);
42
46
  headers['Content-Length'] = Buffer.byteLength(payload);
43
47
  }
44
48
 
@@ -52,16 +56,14 @@ class APIClient {
52
56
  };
53
57
 
54
58
  return new Promise((resolve, reject) => {
55
- const req = lib.request(options, (res) => {
56
- let resData = '';
57
- res.on('data', (chunk) => {
58
- resData += chunk;
59
- });
59
+ const req = transport.request(options, (res) => {
60
+ let raw = '';
61
+ res.on('data', (chunk) => { raw += chunk; });
60
62
  res.on('end', () => {
61
63
  let json = null;
62
64
  try {
63
- json = JSON.parse(resData);
64
- } catch (e) {
65
+ json = JSON.parse(raw);
66
+ } catch {
65
67
  json = null;
66
68
  }
67
69
 
@@ -75,12 +77,12 @@ class APIClient {
75
77
  case 429: code = ERROR_CODES.RATE_LIMITED; break;
76
78
  case 400: case 422: code = ERROR_CODES.INVALID_SQL; break;
77
79
  }
78
- const msg = (json && (json.message || json.error || (json.detail && (typeof json.detail === 'string' ? json.detail : JSON.stringify(json.detail))))) || resData || `HTTP ${res.statusCode}`;
80
+ const msg = (json && (json.message || json.error || (json.detail && (typeof json.detail === 'string' ? json.detail : JSON.stringify(json.detail))))) || raw || `HTTP ${res.statusCode}`;
79
81
  const err = new Error(msg);
80
82
  err.code = (json && json.code) || code;
81
83
  err.statusCode = res.statusCode;
82
84
  err.status = res.statusCode;
83
- err.response = json || resData;
85
+ err.response = json || raw;
84
86
  err.ok = false;
85
87
  return reject(err);
86
88
  }
@@ -89,16 +91,16 @@ class APIClient {
89
91
  });
90
92
  });
91
93
 
92
- req.on('error', (err) => {
93
- const error = new Error(`Network connection error: ${err.message}`);
94
- error.code = ERROR_CODES.NETWORK_FAILED;
95
- error.ok = false;
96
- reject(error);
97
- });
98
-
99
94
  req.on('timeout', () => {
100
95
  req.destroy();
101
- const error = new Error(`Request timed out (${options.timeout}ms)`);
96
+ const err = new Error(`请求超时 (${options.timeout}ms): ${method} ${path}`);
97
+ err.code = ERROR_CODES.NETWORK_FAILED;
98
+ err.ok = false;
99
+ reject(err);
100
+ });
101
+
102
+ req.on('error', (err) => {
103
+ const error = new Error(`网络连接异常: ${err.message}`);
102
104
  error.code = ERROR_CODES.NETWORK_FAILED;
103
105
  error.ok = false;
104
106
  reject(error);
@@ -111,26 +113,26 @@ class APIClient {
111
113
  });
112
114
  }
113
115
 
114
- // 15-second connect & project registration (POST /api/connect)
116
+ // 15 秒极速建立连接/上报项目 (POST /api/connect)
115
117
  async connect(data) {
116
118
  const res = await this.request('POST', '/api/connect', data);
117
119
  if (typeof res === 'object') res.ok = true;
118
120
  return res;
119
121
  }
120
122
 
121
- // Send Magic Link for email binding (POST /api/auth/email/start)
123
+ // 一键发送 Magic Link 邮箱绑定 (POST /api/auth/email/start)
122
124
  async startEmail(data) {
123
125
  const res = await this.request('POST', '/api/auth/email/start', data);
124
126
  if (typeof res === 'object') res.ok = true;
125
127
  return res;
126
128
  }
127
129
 
128
- // Check email activation status (GET /api/auth/email-status?token=...)
130
+ // 检查邮箱绑定/激活状态 (GET /api/auth/email-status?token=...)
129
131
  async checkEmailStatus(token) {
130
132
  return this.request('GET', `/api/auth/email-status?token=${encodeURIComponent(token)}`);
131
133
  }
132
134
 
133
- // Exponential backoff retry for startEmail
135
+ // startEmail 指数退避重试
134
136
  async startEmailWithRetry(data, retry = 2, baseMs = 500) {
135
137
  let lastErr;
136
138
  for (let i = 0; i <= retry; i++) {
@@ -147,53 +149,60 @@ class APIClient {
147
149
  throw lastErr;
148
150
  }
149
151
 
150
- // List offline Access Secrets (GET /api/auth/api-keys)
152
+ // 查询离线 Access Secret 列表 (GET /api/auth/api-keys)
151
153
  async listAccessSecrets() {
152
154
  return this.request('GET', '/api/auth/api-keys');
153
155
  }
154
156
 
155
- // Create offline Access Secret (POST /api/auth/api-keys)
157
+ // 创建离线 Access Secret (POST /api/auth/api-keys)
156
158
  async createAccessSecret(data) {
157
159
  return this.request('POST', '/api/auth/api-keys', data);
158
160
  }
159
161
 
160
- // Revoke offline Access Secret (DELETE /api/auth/api-keys/:keyId)
162
+ // 吊销离线 Access Secret (DELETE /api/auth/api-keys/:keyId)
161
163
  async revokeAccessSecret(apiKeyId) {
162
164
  return this.request('DELETE', `/api/auth/api-keys/${encodeURIComponent(apiKeyId)}`);
163
165
  }
164
166
 
167
+ // 获取当前登录用户信息 (GET /api/me)
165
168
  async me() {
166
169
  const res = await this.request('GET', '/api/me');
167
170
  if (typeof res === 'object') res.ok = true;
168
171
  return res;
169
172
  }
170
173
 
174
+ // 获取用户项目列表 (GET /api/apps)
171
175
  async listProjects() {
172
176
  const res = await this.request('GET', '/api/apps');
173
177
  if (typeof res === 'object') res.ok = true;
174
178
  return res;
175
179
  }
176
180
 
181
+ // 绑定项目到当前登录账号 (POST /api/projects/bind)
177
182
  async bindProject(appId, projectName = '') {
178
183
  return this.request('POST', '/api/projects/bind', { app_id: appId, project_name: projectName });
179
184
  }
180
185
 
186
+ // 更新项目分类法配置 (PUT /api/apps/:appId/taxonomy)
181
187
  async updateTaxonomy(appId, data) {
182
188
  const res = await this.request('PUT', `/api/apps/${encodeURIComponent(appId)}/taxonomy`, data);
183
189
  if (typeof res === 'object') res.ok = true;
184
190
  return res;
185
191
  }
186
192
 
193
+ // 获取项目分类法配置 (GET /api/apps/:appId/taxonomy)
187
194
  async getTaxonomy(appId) {
188
195
  const res = await this.request('GET', `/api/apps/${encodeURIComponent(appId)}/taxonomy`);
189
196
  if (typeof res === 'object') res.ok = true;
190
197
  return res;
191
198
  }
192
199
 
200
+ // 删除项目 (DELETE /api/apps/:appId)
193
201
  async deleteProject(appId) {
194
202
  return this.request('DELETE', `/api/apps/${encodeURIComponent(appId)}`);
195
203
  }
196
204
 
205
+ // 综合指标分析查询 (POST /api/ai/apps/:appId/analytics/query)
197
206
  async queryAnalytics(appId, queryReq) {
198
207
  const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
199
208
  const actualReq = typeof appId === 'object' ? appId : queryReq;
@@ -209,7 +218,7 @@ class APIClient {
209
218
  return this.queryAnalytics(appId, queryReq);
210
219
  }
211
220
 
212
- // executeSQL (POST /api/mcp/apps/:appId/sql, payload: { sql: queryStr })
221
+ // 执行只读 SQL 查询 (POST /api/mcp/apps/:appId/sql)
213
222
  async executeSQL(appId, queryStr) {
214
223
  const start = Date.now();
215
224
  let actualAppId = appId;
@@ -228,6 +237,7 @@ class APIClient {
228
237
  return res;
229
238
  }
230
239
 
240
+ // 产品概览数据 (GET /api/ai/apps/:appId/summary)
231
241
  async summary(appId, range = '7d', env = 'production') {
232
242
  const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
233
243
  const actualRange = typeof appId === 'object' ? (appId.range || range) : range;
@@ -238,6 +248,7 @@ class APIClient {
238
248
  return res;
239
249
  }
240
250
 
251
+ // 页面访问与停留时长统计 (GET /api/ai/apps/:appId/pages)
241
252
  async pages(appId, range = '7d', env = 'production', limit = 10) {
242
253
  const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
243
254
  const actualRange = typeof appId === 'object' ? (appId.range || range) : range;
@@ -253,6 +264,7 @@ class APIClient {
253
264
  return this.pages(data);
254
265
  }
255
266
 
267
+ // 用户行为路径/流转分析 (GET /api/ai/apps/:appId/paths)
256
268
  async paths(appId, range = '7d', env = 'production') {
257
269
  const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
258
270
  const actualRange = typeof appId === 'object' ? (appId.range || range) : range;
@@ -270,6 +282,7 @@ class APIClient {
270
282
  return this.paths(data);
271
283
  }
272
284
 
285
+ // 全局行为画布拓扑 (GET /api/ai/apps/:appId/behavior-canvas)
273
286
  async behaviorCanvas(appId, range = '7d', env = 'production') {
274
287
  const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
275
288
  const actualRange = typeof appId === 'object' ? (appId.range || range) : range;
@@ -280,6 +293,7 @@ class APIClient {
280
293
  return res;
281
294
  }
282
295
 
296
+ // 点击热力图与骨架图 (GET /api/ai/apps/:appId/pages/:templateId/heatmap)
283
297
  async heatmap(appId, templateId, env = 'production', domHash = '') {
284
298
  const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
285
299
  const actualTemplateId = typeof appId === 'object' ? appId.template_id : templateId;
@@ -300,12 +314,14 @@ class APIClient {
300
314
  return this.heatmap(data);
301
315
  }
302
316
 
317
+ // 查询仪表盘列表 (GET /api/apps/:appId/dashboards)
303
318
  async listDashboards(appId) {
304
319
  const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
305
320
  const res = await this.request('GET', `/api/apps/${encodeURIComponent(actualAppId)}/dashboards`);
306
321
  return Array.isArray(res) ? res : (res.dashboards || []);
307
322
  }
308
323
 
324
+ // 创建自定义仪表盘 (POST /api/apps/:appId/dashboards)
309
325
  async createDashboard(appId, data) {
310
326
  const actualAppId = typeof appId === 'object' ? (appId.app_id || '') : appId;
311
327
  const actualData = typeof appId === 'object' ? appId : data;
@@ -314,12 +330,14 @@ class APIClient {
314
330
  return res;
315
331
  }
316
332
 
333
+ // 测试网络连接与服务端连通性
317
334
  async testConnectivity() {
318
335
  const start = Date.now();
319
336
  await this.request('GET', '/api/ai/tools');
320
337
  return Date.now() - start;
321
338
  }
322
339
 
340
+ // 获取指标元数据 Schema (GET /api/ai/apps/:appId/analytics/schema)
323
341
  async getAnalyticsSchema(appId) {
324
342
  const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
325
343
  const res = await this.request('GET', `/api/ai/apps/${encodeURIComponent(actualAppId)}/analytics/schema`);
@@ -327,33 +345,35 @@ class APIClient {
327
345
  return res;
328
346
  }
329
347
 
330
- // Natural language AI dashboard creation (POST /api/dashboard)
348
+ // 自然语言 AI 生成仪表盘 (POST /api/dashboard)
331
349
  async createAIDashboard(data) {
332
350
  const res = await this.request('POST', '/api/dashboard', data);
333
351
  if (typeof res === 'object') res.ok = true;
334
352
  return res;
335
353
  }
336
354
 
337
- // Retrieve project business context (GET /api/apps/:appId/context) - 5s timeout
355
+ // 获取项目业务上下文 (GET /api/apps/:appId/context) - 5秒超时
338
356
  async getProjectContext(appId) {
339
357
  const res = await this.request('GET', `/api/apps/${encodeURIComponent(appId)}/context`, null, { timeout: 5000 });
340
358
  if (typeof res === 'object') res.ok = true;
341
359
  return res;
342
360
  }
343
361
 
344
- // Upload/sync project business context (PUT /api/apps/:appId/context) - 5s timeout
362
+ // 上传/同步项目业务上下文 (PUT /api/apps/:appId/context) - 5秒超时
345
363
  async uploadProjectContext(appId, data) {
346
364
  const res = await this.request('PUT', `/api/apps/${encodeURIComponent(appId)}/context`, data, { timeout: 5000 });
347
365
  if (typeof res === 'object') res.ok = true;
348
366
  return res;
349
367
  }
350
368
 
351
- // Delete project business context (DELETE /api/apps/:appId/context) - 5s timeout
369
+ // 删除云端项目业务上下文 (DELETE /api/apps/:appId/context) - 5秒超时
352
370
  async deleteProjectContext(appId) {
353
- return this.request('DELETE', `/api/apps/${encodeURIComponent(appId)}/context`, null, { timeout: 5000 });
371
+ const res = await this.request('DELETE', `/api/apps/${encodeURIComponent(appId)}/context`, null, { timeout: 5000 });
372
+ if (typeof res === 'object') res.ok = true;
373
+ return res;
354
374
  }
355
375
 
356
- // Request with exponential backoff retry for general methods
376
+ // 带重试的请求方法
357
377
  async requestWithRetry(method, path, body = null, maxRetries = 2) {
358
378
  let lastErr = null;
359
379
  for (let attempt = 0; attempt <= maxRetries; attempt++) {