@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/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 +59 -35
- package/lib/assets/skill.js +164 -87
- package/lib/cli.js +55 -55
- 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,17 +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
|
+
|
|
29
|
+
let pkgVer = '1.3.1';
|
|
30
|
+
try {
|
|
31
|
+
pkgVer = require('../package.json').version || pkgVer;
|
|
32
|
+
} catch (e) {}
|
|
28
33
|
|
|
29
|
-
const payload = body ? (typeof body === 'string' ? body : JSON.stringify(body)) : null;
|
|
30
34
|
const headers = {
|
|
31
35
|
'Content-Type': 'application/json',
|
|
32
|
-
'User-Agent':
|
|
36
|
+
'User-Agent': `Shiplens-CLI/${pkgVer} (Node.js)`,
|
|
33
37
|
};
|
|
38
|
+
|
|
34
39
|
if (this.secret) {
|
|
35
40
|
headers['Authorization'] = `Bearer ${this.secret}`;
|
|
36
41
|
}
|
|
37
|
-
|
|
42
|
+
|
|
43
|
+
let payload = null;
|
|
44
|
+
if (body !== null && body !== undefined) {
|
|
45
|
+
payload = typeof body === 'string' ? body : JSON.stringify(body);
|
|
38
46
|
headers['Content-Length'] = Buffer.byteLength(payload);
|
|
39
47
|
}
|
|
40
48
|
|
|
@@ -48,16 +56,14 @@ class APIClient {
|
|
|
48
56
|
};
|
|
49
57
|
|
|
50
58
|
return new Promise((resolve, reject) => {
|
|
51
|
-
const req =
|
|
52
|
-
let
|
|
53
|
-
res.on('data', (chunk) => {
|
|
54
|
-
resData += chunk;
|
|
55
|
-
});
|
|
59
|
+
const req = transport.request(options, (res) => {
|
|
60
|
+
let raw = '';
|
|
61
|
+
res.on('data', (chunk) => { raw += chunk; });
|
|
56
62
|
res.on('end', () => {
|
|
57
63
|
let json = null;
|
|
58
64
|
try {
|
|
59
|
-
json = JSON.parse(
|
|
60
|
-
} catch
|
|
65
|
+
json = JSON.parse(raw);
|
|
66
|
+
} catch {
|
|
61
67
|
json = null;
|
|
62
68
|
}
|
|
63
69
|
|
|
@@ -71,12 +77,12 @@ class APIClient {
|
|
|
71
77
|
case 429: code = ERROR_CODES.RATE_LIMITED; break;
|
|
72
78
|
case 400: case 422: code = ERROR_CODES.INVALID_SQL; break;
|
|
73
79
|
}
|
|
74
|
-
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}`;
|
|
75
81
|
const err = new Error(msg);
|
|
76
82
|
err.code = (json && json.code) || code;
|
|
77
83
|
err.statusCode = res.statusCode;
|
|
78
84
|
err.status = res.statusCode;
|
|
79
|
-
err.response = json ||
|
|
85
|
+
err.response = json || raw;
|
|
80
86
|
err.ok = false;
|
|
81
87
|
return reject(err);
|
|
82
88
|
}
|
|
@@ -85,16 +91,16 @@ class APIClient {
|
|
|
85
91
|
});
|
|
86
92
|
});
|
|
87
93
|
|
|
88
|
-
req.on('error', (err) => {
|
|
89
|
-
const error = new Error(`Network connection error: ${err.message}`);
|
|
90
|
-
error.code = ERROR_CODES.NETWORK_FAILED;
|
|
91
|
-
error.ok = false;
|
|
92
|
-
reject(error);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
94
|
req.on('timeout', () => {
|
|
96
95
|
req.destroy();
|
|
97
|
-
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}`);
|
|
98
104
|
error.code = ERROR_CODES.NETWORK_FAILED;
|
|
99
105
|
error.ok = false;
|
|
100
106
|
reject(error);
|
|
@@ -107,26 +113,26 @@ class APIClient {
|
|
|
107
113
|
});
|
|
108
114
|
}
|
|
109
115
|
|
|
110
|
-
// 15
|
|
116
|
+
// 15 秒极速建立连接/上报项目 (POST /api/connect)
|
|
111
117
|
async connect(data) {
|
|
112
118
|
const res = await this.request('POST', '/api/connect', data);
|
|
113
119
|
if (typeof res === 'object') res.ok = true;
|
|
114
120
|
return res;
|
|
115
121
|
}
|
|
116
122
|
|
|
117
|
-
//
|
|
123
|
+
// 一键发送 Magic Link 邮箱绑定 (POST /api/auth/email/start)
|
|
118
124
|
async startEmail(data) {
|
|
119
125
|
const res = await this.request('POST', '/api/auth/email/start', data);
|
|
120
126
|
if (typeof res === 'object') res.ok = true;
|
|
121
127
|
return res;
|
|
122
128
|
}
|
|
123
129
|
|
|
124
|
-
//
|
|
130
|
+
// 检查邮箱绑定/激活状态 (GET /api/auth/email-status?token=...)
|
|
125
131
|
async checkEmailStatus(token) {
|
|
126
132
|
return this.request('GET', `/api/auth/email-status?token=${encodeURIComponent(token)}`);
|
|
127
133
|
}
|
|
128
134
|
|
|
129
|
-
//
|
|
135
|
+
// startEmail 指数退避重试
|
|
130
136
|
async startEmailWithRetry(data, retry = 2, baseMs = 500) {
|
|
131
137
|
let lastErr;
|
|
132
138
|
for (let i = 0; i <= retry; i++) {
|
|
@@ -143,53 +149,60 @@ class APIClient {
|
|
|
143
149
|
throw lastErr;
|
|
144
150
|
}
|
|
145
151
|
|
|
146
|
-
//
|
|
152
|
+
// 查询离线 Access Secret 列表 (GET /api/auth/api-keys)
|
|
147
153
|
async listAccessSecrets() {
|
|
148
154
|
return this.request('GET', '/api/auth/api-keys');
|
|
149
155
|
}
|
|
150
156
|
|
|
151
|
-
//
|
|
157
|
+
// 创建离线 Access Secret (POST /api/auth/api-keys)
|
|
152
158
|
async createAccessSecret(data) {
|
|
153
159
|
return this.request('POST', '/api/auth/api-keys', data);
|
|
154
160
|
}
|
|
155
161
|
|
|
156
|
-
//
|
|
162
|
+
// 吊销离线 Access Secret (DELETE /api/auth/api-keys/:keyId)
|
|
157
163
|
async revokeAccessSecret(apiKeyId) {
|
|
158
164
|
return this.request('DELETE', `/api/auth/api-keys/${encodeURIComponent(apiKeyId)}`);
|
|
159
165
|
}
|
|
160
166
|
|
|
167
|
+
// 获取当前登录用户信息 (GET /api/me)
|
|
161
168
|
async me() {
|
|
162
169
|
const res = await this.request('GET', '/api/me');
|
|
163
170
|
if (typeof res === 'object') res.ok = true;
|
|
164
171
|
return res;
|
|
165
172
|
}
|
|
166
173
|
|
|
174
|
+
// 获取用户项目列表 (GET /api/apps)
|
|
167
175
|
async listProjects() {
|
|
168
176
|
const res = await this.request('GET', '/api/apps');
|
|
169
177
|
if (typeof res === 'object') res.ok = true;
|
|
170
178
|
return res;
|
|
171
179
|
}
|
|
172
180
|
|
|
181
|
+
// 绑定项目到当前登录账号 (POST /api/projects/bind)
|
|
173
182
|
async bindProject(appId, projectName = '') {
|
|
174
183
|
return this.request('POST', '/api/projects/bind', { app_id: appId, project_name: projectName });
|
|
175
184
|
}
|
|
176
185
|
|
|
186
|
+
// 更新项目分类法配置 (PUT /api/apps/:appId/taxonomy)
|
|
177
187
|
async updateTaxonomy(appId, data) {
|
|
178
188
|
const res = await this.request('PUT', `/api/apps/${encodeURIComponent(appId)}/taxonomy`, data);
|
|
179
189
|
if (typeof res === 'object') res.ok = true;
|
|
180
190
|
return res;
|
|
181
191
|
}
|
|
182
192
|
|
|
193
|
+
// 获取项目分类法配置 (GET /api/apps/:appId/taxonomy)
|
|
183
194
|
async getTaxonomy(appId) {
|
|
184
195
|
const res = await this.request('GET', `/api/apps/${encodeURIComponent(appId)}/taxonomy`);
|
|
185
196
|
if (typeof res === 'object') res.ok = true;
|
|
186
197
|
return res;
|
|
187
198
|
}
|
|
188
199
|
|
|
200
|
+
// 删除项目 (DELETE /api/apps/:appId)
|
|
189
201
|
async deleteProject(appId) {
|
|
190
202
|
return this.request('DELETE', `/api/apps/${encodeURIComponent(appId)}`);
|
|
191
203
|
}
|
|
192
204
|
|
|
205
|
+
// 综合指标分析查询 (POST /api/ai/apps/:appId/analytics/query)
|
|
193
206
|
async queryAnalytics(appId, queryReq) {
|
|
194
207
|
const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
|
|
195
208
|
const actualReq = typeof appId === 'object' ? appId : queryReq;
|
|
@@ -205,7 +218,7 @@ class APIClient {
|
|
|
205
218
|
return this.queryAnalytics(appId, queryReq);
|
|
206
219
|
}
|
|
207
220
|
|
|
208
|
-
//
|
|
221
|
+
// 执行只读 SQL 查询 (POST /api/mcp/apps/:appId/sql)
|
|
209
222
|
async executeSQL(appId, queryStr) {
|
|
210
223
|
const start = Date.now();
|
|
211
224
|
let actualAppId = appId;
|
|
@@ -224,6 +237,7 @@ class APIClient {
|
|
|
224
237
|
return res;
|
|
225
238
|
}
|
|
226
239
|
|
|
240
|
+
// 产品概览数据 (GET /api/ai/apps/:appId/summary)
|
|
227
241
|
async summary(appId, range = '7d', env = 'production') {
|
|
228
242
|
const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
|
|
229
243
|
const actualRange = typeof appId === 'object' ? (appId.range || range) : range;
|
|
@@ -234,6 +248,7 @@ class APIClient {
|
|
|
234
248
|
return res;
|
|
235
249
|
}
|
|
236
250
|
|
|
251
|
+
// 页面访问与停留时长统计 (GET /api/ai/apps/:appId/pages)
|
|
237
252
|
async pages(appId, range = '7d', env = 'production', limit = 10) {
|
|
238
253
|
const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
|
|
239
254
|
const actualRange = typeof appId === 'object' ? (appId.range || range) : range;
|
|
@@ -249,6 +264,7 @@ class APIClient {
|
|
|
249
264
|
return this.pages(data);
|
|
250
265
|
}
|
|
251
266
|
|
|
267
|
+
// 用户行为路径/流转分析 (GET /api/ai/apps/:appId/paths)
|
|
252
268
|
async paths(appId, range = '7d', env = 'production') {
|
|
253
269
|
const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
|
|
254
270
|
const actualRange = typeof appId === 'object' ? (appId.range || range) : range;
|
|
@@ -266,6 +282,7 @@ class APIClient {
|
|
|
266
282
|
return this.paths(data);
|
|
267
283
|
}
|
|
268
284
|
|
|
285
|
+
// 全局行为画布拓扑 (GET /api/ai/apps/:appId/behavior-canvas)
|
|
269
286
|
async behaviorCanvas(appId, range = '7d', env = 'production') {
|
|
270
287
|
const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
|
|
271
288
|
const actualRange = typeof appId === 'object' ? (appId.range || range) : range;
|
|
@@ -276,6 +293,7 @@ class APIClient {
|
|
|
276
293
|
return res;
|
|
277
294
|
}
|
|
278
295
|
|
|
296
|
+
// 点击热力图与骨架图 (GET /api/ai/apps/:appId/pages/:templateId/heatmap)
|
|
279
297
|
async heatmap(appId, templateId, env = 'production', domHash = '') {
|
|
280
298
|
const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
|
|
281
299
|
const actualTemplateId = typeof appId === 'object' ? appId.template_id : templateId;
|
|
@@ -296,12 +314,14 @@ class APIClient {
|
|
|
296
314
|
return this.heatmap(data);
|
|
297
315
|
}
|
|
298
316
|
|
|
317
|
+
// 查询仪表盘列表 (GET /api/apps/:appId/dashboards)
|
|
299
318
|
async listDashboards(appId) {
|
|
300
319
|
const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
|
|
301
320
|
const res = await this.request('GET', `/api/apps/${encodeURIComponent(actualAppId)}/dashboards`);
|
|
302
321
|
return Array.isArray(res) ? res : (res.dashboards || []);
|
|
303
322
|
}
|
|
304
323
|
|
|
324
|
+
// 创建自定义仪表盘 (POST /api/apps/:appId/dashboards)
|
|
305
325
|
async createDashboard(appId, data) {
|
|
306
326
|
const actualAppId = typeof appId === 'object' ? (appId.app_id || '') : appId;
|
|
307
327
|
const actualData = typeof appId === 'object' ? appId : data;
|
|
@@ -310,12 +330,14 @@ class APIClient {
|
|
|
310
330
|
return res;
|
|
311
331
|
}
|
|
312
332
|
|
|
333
|
+
// 测试网络连接与服务端连通性
|
|
313
334
|
async testConnectivity() {
|
|
314
335
|
const start = Date.now();
|
|
315
336
|
await this.request('GET', '/api/ai/tools');
|
|
316
337
|
return Date.now() - start;
|
|
317
338
|
}
|
|
318
339
|
|
|
340
|
+
// 获取指标元数据 Schema (GET /api/ai/apps/:appId/analytics/schema)
|
|
319
341
|
async getAnalyticsSchema(appId) {
|
|
320
342
|
const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
|
|
321
343
|
const res = await this.request('GET', `/api/ai/apps/${encodeURIComponent(actualAppId)}/analytics/schema`);
|
|
@@ -323,33 +345,35 @@ class APIClient {
|
|
|
323
345
|
return res;
|
|
324
346
|
}
|
|
325
347
|
|
|
326
|
-
//
|
|
348
|
+
// 自然语言 AI 生成仪表盘 (POST /api/dashboard)
|
|
327
349
|
async createAIDashboard(data) {
|
|
328
350
|
const res = await this.request('POST', '/api/dashboard', data);
|
|
329
351
|
if (typeof res === 'object') res.ok = true;
|
|
330
352
|
return res;
|
|
331
353
|
}
|
|
332
354
|
|
|
333
|
-
//
|
|
355
|
+
// 获取项目业务上下文 (GET /api/apps/:appId/context) - 5秒超时
|
|
334
356
|
async getProjectContext(appId) {
|
|
335
357
|
const res = await this.request('GET', `/api/apps/${encodeURIComponent(appId)}/context`, null, { timeout: 5000 });
|
|
336
358
|
if (typeof res === 'object') res.ok = true;
|
|
337
359
|
return res;
|
|
338
360
|
}
|
|
339
361
|
|
|
340
|
-
//
|
|
362
|
+
// 上传/同步项目业务上下文 (PUT /api/apps/:appId/context) - 5秒超时
|
|
341
363
|
async uploadProjectContext(appId, data) {
|
|
342
364
|
const res = await this.request('PUT', `/api/apps/${encodeURIComponent(appId)}/context`, data, { timeout: 5000 });
|
|
343
365
|
if (typeof res === 'object') res.ok = true;
|
|
344
366
|
return res;
|
|
345
367
|
}
|
|
346
368
|
|
|
347
|
-
//
|
|
369
|
+
// 删除云端项目业务上下文 (DELETE /api/apps/:appId/context) - 5秒超时
|
|
348
370
|
async deleteProjectContext(appId) {
|
|
349
|
-
|
|
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;
|
|
350
374
|
}
|
|
351
375
|
|
|
352
|
-
//
|
|
376
|
+
// 带重试的请求方法
|
|
353
377
|
async requestWithRetry(method, path, body = null, maxRetries = 2) {
|
|
354
378
|
let lastErr = null;
|
|
355
379
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|