@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 +38 -62
- package/docs/02_SHIPLENS_CLI_/345/205/250/345/221/275/344/273/244/345/217/202/350/200/203/346/211/213/345/206/214.md +205 -0
- package/lib/api.js +55 -35
- package/lib/assets/skill.js +164 -87
- package/lib/cli.js +55 -59
- package/lib/commands/auth.js +41 -36
- package/lib/commands/context.js +18 -13
- package/lib/commands/dashboards.js +24 -16
- package/lib/commands/doctor.js +34 -43
- package/lib/commands/heatmap.js +9 -14
- package/lib/commands/init.js +54 -62
- package/lib/commands/mcp.js +2 -2
- package/lib/commands/pages.js +23 -19
- package/lib/commands/projects.js +13 -13
- package/lib/commands/query.js +12 -10
- package/lib/commands/sql.js +5 -4
- package/lib/commands/summary.js +7 -7
- package/lib/injector.js +22 -36
- package/lib/mcp-config.js +5 -5
- package/lib/taxonomy.js +25 -22
- package/package.json +5 -4
- package/prompts/README.md +11 -5
- package/prompts/prompts_cli_zh.md +743 -0
- package/prompts/prompts_cli_en.md +0 -671
package/README.md
CHANGED
|
@@ -1,95 +1,71 @@
|
|
|
1
|
-
# 🚀 Shiplens CLI
|
|
1
|
+
# 🚀 Shiplens CLI
|
|
2
2
|
|
|
3
|
-
> **
|
|
3
|
+
> **Shiplens 官方命令行工具 — 面向 Web 前端的用户行为数据统计与 AI Agent 智能分析引擎。**
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@shiplens/cli)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](package.json)
|
|
8
|
-
[]()
|
|
9
8
|
|
|
10
9
|
---
|
|
11
10
|
|
|
12
|
-
|
|
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
|
-
##
|
|
15
|
+
## 🌟 核心功能
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
- **自动化接入与代码插桩**:自动识别前端项目框架(Next.js、Vite、Vue、HTML 等),无感注入 `@shiplens/sdk` 数据采集代码;
|
|
18
|
+
- **用户行为数据统计**:秒级查看全站流量大盘、访问路径、页面留存与点击热力图;
|
|
19
|
+
- **AI Agent 智能分析**:支持通过 CLI 与本地 stdio MCP 代理(`shiplens mcp serve`)与 AI 对话,让 Agent 直接读取产品数据并输出教科书级业务洞察;
|
|
20
|
+
- **AI 实时看板生成**:一句话自然语言诉求,云端自动拼装 12 列响应式数据看板;
|
|
21
|
+
- **业务上下文与动态自适应**:自动提取页面功能文案辅助 AI 理解业务,并支持按项目自适应记忆个性化分析偏好。
|
|
20
22
|
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29
|
+
```bash
|
|
30
|
+
# 通用终端
|
|
31
|
+
npx --yes @shiplens/cli init
|
|
43
32
|
|
|
44
|
-
|
|
33
|
+
# Windows PowerShell
|
|
34
|
+
npx.cmd --yes @shiplens/cli init
|
|
45
35
|
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
## 📊
|
|
42
|
+
## 📊 常用命令速查
|
|
56
43
|
|
|
57
|
-
|
|
|
44
|
+
| 命令 | 说明 |
|
|
58
45
|
| :--- | :--- |
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
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
|
-
##
|
|
60
|
+
## 🔗 相关资源
|
|
82
61
|
|
|
83
|
-
- [
|
|
84
|
-
- [
|
|
85
|
-
- [
|
|
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
|
-
## 📜
|
|
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
|
|
27
|
+
const transport = isHttps ? https : http;
|
|
28
28
|
|
|
29
|
-
|
|
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
|
-
|
|
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 =
|
|
56
|
-
let
|
|
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(
|
|
64
|
-
} catch
|
|
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))))) ||
|
|
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 ||
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
369
|
+
// 删除云端项目业务上下文 (DELETE /api/apps/:appId/context) - 5秒超时
|
|
352
370
|
async deleteProjectContext(appId) {
|
|
353
|
-
|
|
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
|
-
//
|
|
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++) {
|