@shiplens/cli 1.2.7

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 ADDED
@@ -0,0 +1,89 @@
1
+ # 🚀 Shiplens CLI (In Development)
2
+
3
+ > **Fast User Telemetry, Instant SDK Instrumentation & AI Analytics Engine for Modern Web Apps.**
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@shiplens/cli.svg)](https://www.npmjs.com/package/@shiplens/cli)
6
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
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
+
10
+ ---
11
+
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.
14
+
15
+ ---
16
+
17
+ ## ⚡ Instant Setup (Under 15 Seconds)
18
+
19
+ Run in the root of any Next.js, Vite, Vue, or HTML project:
20
+
21
+ ```bash
22
+ # Zero-install execution via npx
23
+ npx --yes @shiplens/cli init --json
24
+ ```
25
+
26
+ ### What `init` Does Automatically:
27
+ 1. **Detects Framework**: Automatically recognizes Next.js (App / Pages router), Vite (React / Svelte), Vue 3, or plain HTML.
28
+ 2. **Injects Tracking SDK**: Inserts `@shiplens/sdk` tracking snippets into your entry files without breaking your code layout.
29
+ 3. **Connects Cloud Project**: Registers the project, generates a live responsive dashboard URL, and writes local configuration (`.shiplens.json`).
30
+ 4. **Installs Dependency**: Adds `@shiplens/sdk` via your project package manager (`npm`, `pnpm`, `yarn`, or `bun`).
31
+ 5. **Deploys AI Skills**: Injects `.agents/skills/shiplens/SKILL.md` and Cursor rules (`.cursor/rules/shiplens.mdc`) for seamless LLM Agent integration.
32
+ 6. **Performs Git Commit**: Automatically commits all instrumentation changes atomically.
33
+
34
+ ---
35
+
36
+ ## 🧠 Dynamic Overrides & Adaptive Learning
37
+
38
+ Shiplens introduces an adaptive learning loop for AI Agents:
39
+
40
+ ```text
41
+ [User Prompt] ➔ [Match Preset] ➔ [Apply .shiplens/learnings.md (Priority 1)] ➔ [Execute CLI] ➔ [Update Learnings]
42
+ ```
43
+
44
+ - **Priority 1 (Dynamic Overrides)**: Local `.shiplens/learnings.md` rules always override default CLI parameters (e.g. customized date ranges, funnel goals, or granular filters).
45
+ - **Priority 2 (Deterministic Base)**: Standard 42 scenario-based CLI execution presets in `prompts/cli/prompt_cli_en.md`.
46
+
47
+ ---
48
+
49
+ ## 📊 Core Commands
50
+
51
+ | Command | Description |
52
+ | :--- | :--- |
53
+ | `npx @shiplens/cli init --json` | 15-second zero-config analytics onboarding |
54
+ | `npx @shiplens/cli doctor --json` | End-to-end diagnostics on SDK, network, and credentials |
55
+ | `npx @shiplens/cli summary --range 7d --json` | Traffic overview: PV, UV, bounce rates, geos, devices |
56
+ | `npx @shiplens/cli query --metric pageviews --json` | Multi-dimensional metrics & funnel queries |
57
+ | `npx @shiplens/cli sql --query "<sql>" --json` | Sandboxed read-only ClickHouse SQL execution |
58
+ | `npx @shiplens/cli pages --range 7d --json` | Page-level visits and average dwell times |
59
+ | `npx @shiplens/cli paths --range 7d --json` | User journeys and Sankey transition paths |
60
+ | `npx @shiplens/cli heatmap --template <id> --json` | Click heatmaps & skeleton wireframe snapshots |
61
+ | `npx @shiplens/cli dashboards create --prompt "..." --json` | AI-driven dashboard generation |
62
+ | `npx @shiplens/cli auth bind --email <email> --json` | Request Magic Link for quota activation & authorization |
63
+ | `npx @shiplens/cli auth configure --client cursor --json` | Auto-configure MCP servers in IDEs |
64
+
65
+ ---
66
+
67
+ ## 🔒 Security & Sandboxing
68
+
69
+ - **Tenant Isolation**: Strict per-project `app_id` isolation across all telemetry queries.
70
+ - **Read-Only ClickHouse Sandbox**: AST query validator enforces `SELECT`-only operations, mandatory time bounds, and maximum 1000-row limits.
71
+ - **Safe Secrets Handling**: Credential tokens and secrets are automatically masked in all console outputs (`sk_live_...9f2a`).
72
+
73
+ ---
74
+
75
+ ## 📁 Documentation & Prompts
76
+
77
+ - [CLI Architecture & Roadmap](./docs/01_CLI_ARCHITECTURE_AND_ROADMAP.md)
78
+ - [Complete Command Reference](./docs/02_CLI_COMMAND_REFERENCE.md)
79
+ - [AI Skill & Dynamic Overrides Spec](./docs/03_SKILL_SPEC_AND_PROMPTS.md)
80
+ - [CLI Execution Prompts (42 Scenarios)](./prompts/cli/prompt_cli_en.md)
81
+ - [Web Showcase Prompts (42 Scenarios)](./prompts/web/prompt_web_en.md)
82
+ - [Backend API Specification](./docs/BACKEND_API_SPEC.md)
83
+ - [Project Context Specification](./docs/SHIPLENS_CONTEXT_API_SPEC.md)
84
+
85
+ ---
86
+
87
+ ## 📜 License
88
+
89
+ Apache-2.0 © Shiplens Team
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { runCLI } = require('../lib/cli');
4
+
5
+ runCLI(process.argv.slice(2));
package/lib/api.js ADDED
@@ -0,0 +1,376 @@
1
+ const http = require('http');
2
+ const https = require('https');
3
+ const { URL } = require('url');
4
+
5
+ const ERROR_CODES = {
6
+ UNAUTHENTICATED: 'UNAUTHENTICATED',
7
+ APP_NOT_FOUND: 'APP_NOT_FOUND',
8
+ PERMISSION_DENIED: 'PERMISSION_DENIED',
9
+ RATE_LIMITED: 'RATE_LIMITED',
10
+ INVALID_SQL: 'INVALID_SQL',
11
+ PROJECT_EXISTS: 'PROJECT_EXISTS',
12
+ INJECTION_FAILED: 'INJECTION_FAILED',
13
+ NETWORK_FAILED: 'NETWORK_FAILED',
14
+ INTERNAL_ERROR: 'INTERNAL_ERROR',
15
+ };
16
+
17
+ class APIClient {
18
+ constructor(baseURL = 'http://120.26.230.33', secret = '') {
19
+ this.baseURL = (baseURL || 'http://120.26.230.33').replace(/\/+$/, '');
20
+ this.secret = secret;
21
+ }
22
+
23
+ async request(method, path, body = null, reqOptions = {}) {
24
+ const fullURL = `${this.baseURL}${path}`;
25
+ const parsed = new URL(fullURL);
26
+ const isHttps = parsed.protocol === 'https:';
27
+ const lib = isHttps ? https : http;
28
+
29
+ const payload = body ? (typeof body === 'string' ? body : JSON.stringify(body)) : null;
30
+ const headers = {
31
+ 'Content-Type': 'application/json',
32
+ 'User-Agent': 'Shiplens-CLI/1.2.7 (Node.js)',
33
+ };
34
+ if (this.secret) {
35
+ headers['Authorization'] = `Bearer ${this.secret}`;
36
+ }
37
+ if (payload) {
38
+ headers['Content-Length'] = Buffer.byteLength(payload);
39
+ }
40
+
41
+ const options = {
42
+ hostname: parsed.hostname,
43
+ port: parsed.port || (isHttps ? 443 : 80),
44
+ path: parsed.pathname + parsed.search,
45
+ method,
46
+ headers,
47
+ timeout: reqOptions.timeout || 15000,
48
+ };
49
+
50
+ return new Promise((resolve, reject) => {
51
+ const req = lib.request(options, (res) => {
52
+ let resData = '';
53
+ res.on('data', (chunk) => {
54
+ resData += chunk;
55
+ });
56
+ res.on('end', () => {
57
+ let json = null;
58
+ try {
59
+ json = JSON.parse(resData);
60
+ } catch (e) {
61
+ json = null;
62
+ }
63
+
64
+ if (res.statusCode >= 400) {
65
+ let code = ERROR_CODES.INTERNAL_ERROR;
66
+ switch (res.statusCode) {
67
+ case 401: code = ERROR_CODES.UNAUTHENTICATED; break;
68
+ case 403: code = ERROR_CODES.PERMISSION_DENIED; break;
69
+ case 404: code = ERROR_CODES.APP_NOT_FOUND; break;
70
+ case 409: code = ERROR_CODES.PROJECT_EXISTS; break;
71
+ case 429: code = ERROR_CODES.RATE_LIMITED; break;
72
+ case 400: case 422: code = ERROR_CODES.INVALID_SQL; break;
73
+ }
74
+ const msg = (json && (json.message || json.error || (json.detail && (typeof json.detail === 'string' ? json.detail : JSON.stringify(json.detail))))) || resData || `HTTP ${res.statusCode}`;
75
+ const err = new Error(msg);
76
+ err.code = (json && json.code) || code;
77
+ err.statusCode = res.statusCode;
78
+ err.status = res.statusCode;
79
+ err.response = json || resData;
80
+ err.ok = false;
81
+ return reject(err);
82
+ }
83
+
84
+ resolve(json !== null ? json : { ok: true });
85
+ });
86
+ });
87
+
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
+ req.on('timeout', () => {
96
+ req.destroy();
97
+ const error = new Error(`Request timed out (${options.timeout}ms)`);
98
+ error.code = ERROR_CODES.NETWORK_FAILED;
99
+ error.ok = false;
100
+ reject(error);
101
+ });
102
+
103
+ if (payload) {
104
+ req.write(payload);
105
+ }
106
+ req.end();
107
+ });
108
+ }
109
+
110
+ // 15-second connect & project registration (POST /api/connect)
111
+ async connect(data) {
112
+ const res = await this.request('POST', '/api/connect', data);
113
+ if (typeof res === 'object') res.ok = true;
114
+ return res;
115
+ }
116
+
117
+ // Send Magic Link for email binding (POST /api/auth/email/start)
118
+ async startEmail(data) {
119
+ const res = await this.request('POST', '/api/auth/email/start', data);
120
+ if (typeof res === 'object') res.ok = true;
121
+ return res;
122
+ }
123
+
124
+ // Check email activation status (GET /api/auth/email-status?token=...)
125
+ async checkEmailStatus(token) {
126
+ return this.request('GET', `/api/auth/email-status?token=${encodeURIComponent(token)}`);
127
+ }
128
+
129
+ // Exponential backoff retry for startEmail
130
+ async startEmailWithRetry(data, retry = 2, baseMs = 500) {
131
+ let lastErr;
132
+ for (let i = 0; i <= retry; i++) {
133
+ try {
134
+ return await this.startEmail(data);
135
+ } catch (err) {
136
+ lastErr = err;
137
+ if (err.status && err.status >= 400 && err.status < 500) throw err;
138
+ if (i < retry) {
139
+ await new Promise((r) => setTimeout(r, baseMs * Math.pow(2, i)));
140
+ }
141
+ }
142
+ }
143
+ throw lastErr;
144
+ }
145
+
146
+ // List offline Access Secrets (GET /api/auth/api-keys)
147
+ async listAccessSecrets() {
148
+ return this.request('GET', '/api/auth/api-keys');
149
+ }
150
+
151
+ // Create offline Access Secret (POST /api/auth/api-keys)
152
+ async createAccessSecret(data) {
153
+ return this.request('POST', '/api/auth/api-keys', data);
154
+ }
155
+
156
+ // Revoke offline Access Secret (DELETE /api/auth/api-keys/:keyId)
157
+ async revokeAccessSecret(apiKeyId) {
158
+ return this.request('DELETE', `/api/auth/api-keys/${encodeURIComponent(apiKeyId)}`);
159
+ }
160
+
161
+ async me() {
162
+ const res = await this.request('GET', '/api/me');
163
+ if (typeof res === 'object') res.ok = true;
164
+ return res;
165
+ }
166
+
167
+ async listProjects() {
168
+ const res = await this.request('GET', '/api/apps');
169
+ if (typeof res === 'object') res.ok = true;
170
+ return res;
171
+ }
172
+
173
+ async bindProject(appId, projectName = '') {
174
+ return this.request('POST', '/api/projects/bind', { app_id: appId, project_name: projectName });
175
+ }
176
+
177
+ async updateTaxonomy(appId, data) {
178
+ const res = await this.request('PUT', `/api/apps/${encodeURIComponent(appId)}/taxonomy`, data);
179
+ if (typeof res === 'object') res.ok = true;
180
+ return res;
181
+ }
182
+
183
+ async getTaxonomy(appId) {
184
+ const res = await this.request('GET', `/api/apps/${encodeURIComponent(appId)}/taxonomy`);
185
+ if (typeof res === 'object') res.ok = true;
186
+ return res;
187
+ }
188
+
189
+ async deleteProject(appId) {
190
+ return this.request('DELETE', `/api/apps/${encodeURIComponent(appId)}`);
191
+ }
192
+
193
+ async queryAnalytics(appId, queryReq) {
194
+ const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
195
+ const actualReq = typeof appId === 'object' ? appId : queryReq;
196
+ const res = await this.request('POST', `/api/ai/apps/${encodeURIComponent(actualAppId)}/analytics/query`, actualReq);
197
+ if (typeof res === 'object') {
198
+ res.ok = true;
199
+ res.app_id = actualAppId;
200
+ }
201
+ return res;
202
+ }
203
+
204
+ async query(appId, queryReq) {
205
+ return this.queryAnalytics(appId, queryReq);
206
+ }
207
+
208
+ // executeSQL (POST /api/mcp/apps/:appId/sql, payload: { sql: queryStr })
209
+ async executeSQL(appId, queryStr) {
210
+ const start = Date.now();
211
+ let actualAppId = appId;
212
+ let sql = queryStr;
213
+ if (typeof appId === 'object') {
214
+ actualAppId = appId.app_id;
215
+ sql = appId.sql || appId.query;
216
+ } else if (typeof queryStr === 'object') {
217
+ sql = queryStr.sql || queryStr.query;
218
+ }
219
+ const res = await this.request('POST', `/api/mcp/apps/${encodeURIComponent(actualAppId)}/sql`, { sql });
220
+ if (typeof res === 'object') {
221
+ res.ok = true;
222
+ if (!res.elapsed_ms) res.elapsed_ms = Date.now() - start;
223
+ }
224
+ return res;
225
+ }
226
+
227
+ async summary(appId, range = '7d', env = 'production') {
228
+ const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
229
+ const actualRange = typeof appId === 'object' ? (appId.range || range) : range;
230
+ const actualEnv = typeof appId === 'object' ? (appId.env || env) : env;
231
+ const qs = new URLSearchParams({ range: actualRange, env: actualEnv }).toString();
232
+ const res = await this.request('GET', `/api/ai/apps/${encodeURIComponent(actualAppId)}/summary?${qs}`);
233
+ if (typeof res === 'object') res.ok = true;
234
+ return res;
235
+ }
236
+
237
+ async pages(appId, range = '7d', env = 'production', limit = 10) {
238
+ const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
239
+ const actualRange = typeof appId === 'object' ? (appId.range || range) : range;
240
+ const actualEnv = typeof appId === 'object' ? (appId.env || env) : env;
241
+ const actualLimit = typeof appId === 'object' ? (appId.limit || limit) : limit;
242
+ const qs = new URLSearchParams({ range: actualRange, env: actualEnv, limit: String(actualLimit) }).toString();
243
+ const res = await this.request('GET', `/api/ai/apps/${encodeURIComponent(actualAppId)}/pages?${qs}`);
244
+ if (typeof res === 'object') res.ok = true;
245
+ return res;
246
+ }
247
+
248
+ async getPages(data) {
249
+ return this.pages(data);
250
+ }
251
+
252
+ async paths(appId, range = '7d', env = 'production') {
253
+ const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
254
+ const actualRange = typeof appId === 'object' ? (appId.range || range) : range;
255
+ const actualEnv = typeof appId === 'object' ? (appId.env || env) : env;
256
+ const qs = new URLSearchParams({ range: actualRange, env: actualEnv }).toString();
257
+ const res = await this.request('GET', `/api/ai/apps/${encodeURIComponent(actualAppId)}/paths?${qs}`);
258
+ if (typeof res === 'object') res.ok = true;
259
+ return res;
260
+ }
261
+
262
+ async queryPaths(data) {
263
+ if (data && data.mode === 'canvas') {
264
+ return this.behaviorCanvas(data);
265
+ }
266
+ return this.paths(data);
267
+ }
268
+
269
+ async behaviorCanvas(appId, range = '7d', env = 'production') {
270
+ const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
271
+ const actualRange = typeof appId === 'object' ? (appId.range || range) : range;
272
+ const actualEnv = typeof appId === 'object' ? (appId.env || env) : env;
273
+ const qs = new URLSearchParams({ range: actualRange, env: actualEnv }).toString();
274
+ const res = await this.request('GET', `/api/ai/apps/${encodeURIComponent(actualAppId)}/behavior-canvas?${qs}`);
275
+ if (typeof res === 'object') res.ok = true;
276
+ return res;
277
+ }
278
+
279
+ async heatmap(appId, templateId, env = 'production', domHash = '') {
280
+ const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
281
+ const actualTemplateId = typeof appId === 'object' ? appId.template_id : templateId;
282
+ const actualEnv = typeof appId === 'object' ? (appId.env || env) : env;
283
+ const actualDomHash = typeof appId === 'object' ? (appId.dom_hash || domHash) : domHash;
284
+ const params = { env: actualEnv };
285
+ if (actualDomHash) params.dom_hash = actualDomHash;
286
+ const qs = new URLSearchParams(params).toString();
287
+ const res = await this.request('GET', `/api/ai/apps/${encodeURIComponent(actualAppId)}/pages/${encodeURIComponent(actualTemplateId)}/heatmap?${qs}`);
288
+ if (typeof res === 'object') {
289
+ res.ok = true;
290
+ if (!res.template_id) res.template_id = actualTemplateId;
291
+ }
292
+ return res;
293
+ }
294
+
295
+ async getHeatmap(data) {
296
+ return this.heatmap(data);
297
+ }
298
+
299
+ async listDashboards(appId) {
300
+ const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
301
+ const res = await this.request('GET', `/api/apps/${encodeURIComponent(actualAppId)}/dashboards`);
302
+ return Array.isArray(res) ? res : (res.dashboards || []);
303
+ }
304
+
305
+ async createDashboard(appId, data) {
306
+ const actualAppId = typeof appId === 'object' ? (appId.app_id || '') : appId;
307
+ const actualData = typeof appId === 'object' ? appId : data;
308
+ const res = await this.request('POST', `/api/apps/${encodeURIComponent(actualAppId)}/dashboards`, actualData);
309
+ if (typeof res === 'object') res.ok = true;
310
+ return res;
311
+ }
312
+
313
+ async testConnectivity() {
314
+ const start = Date.now();
315
+ await this.request('GET', '/api/ai/tools');
316
+ return Date.now() - start;
317
+ }
318
+
319
+ async getAnalyticsSchema(appId) {
320
+ const actualAppId = typeof appId === 'object' ? appId.app_id : appId;
321
+ const res = await this.request('GET', `/api/ai/apps/${encodeURIComponent(actualAppId)}/analytics/schema`);
322
+ if (typeof res === 'object') res.ok = true;
323
+ return res;
324
+ }
325
+
326
+ // Natural language AI dashboard creation (POST /api/dashboard)
327
+ async createAIDashboard(data) {
328
+ const res = await this.request('POST', '/api/dashboard', data);
329
+ if (typeof res === 'object') res.ok = true;
330
+ return res;
331
+ }
332
+
333
+ // Retrieve project business context (GET /api/apps/:appId/context) - 5s timeout
334
+ async getProjectContext(appId) {
335
+ const res = await this.request('GET', `/api/apps/${encodeURIComponent(appId)}/context`, null, { timeout: 5000 });
336
+ if (typeof res === 'object') res.ok = true;
337
+ return res;
338
+ }
339
+
340
+ // Upload/sync project business context (PUT /api/apps/:appId/context) - 5s timeout
341
+ async uploadProjectContext(appId, data) {
342
+ const res = await this.request('PUT', `/api/apps/${encodeURIComponent(appId)}/context`, data, { timeout: 5000 });
343
+ if (typeof res === 'object') res.ok = true;
344
+ return res;
345
+ }
346
+
347
+ // Delete project business context (DELETE /api/apps/:appId/context) - 5s timeout
348
+ async deleteProjectContext(appId) {
349
+ return this.request('DELETE', `/api/apps/${encodeURIComponent(appId)}/context`, null, { timeout: 5000 });
350
+ }
351
+
352
+ // Request with exponential backoff retry for general methods
353
+ async requestWithRetry(method, path, body = null, maxRetries = 2) {
354
+ let lastErr = null;
355
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
356
+ try {
357
+ return await this.request(method, path, body);
358
+ } catch (err) {
359
+ lastErr = err;
360
+ if (err.statusCode && err.statusCode < 500 && err.statusCode !== 429) {
361
+ throw err;
362
+ }
363
+ if (attempt < maxRetries) {
364
+ const delay = Math.pow(2, attempt) * 500;
365
+ await new Promise((r) => setTimeout(r, delay));
366
+ }
367
+ }
368
+ }
369
+ throw lastErr;
370
+ }
371
+ }
372
+
373
+ module.exports = {
374
+ APIClient,
375
+ ERROR_CODES,
376
+ };
@@ -0,0 +1,127 @@
1
+ const SKILL_CONTENT = `---
2
+ name: shiplens-analytics
3
+ description: Shiplens Web User Telemetry & AI Dashboard Skill Specification. Includes project onboarding, diagnostics, CLI & MCP dual integration, authentication, metadata caching, safe SQL analytics, and single-prompt workflow.
4
+ ---
5
+
6
+ # Shiplens Web Analytics Skill Specification (v2.0)
7
+
8
+ > 💡 **Core Overview & Official Reference**:
9
+ > 1. **Dual Track Integration**: Supports instant onboarding via **Shiplens CLI** (\`shiplens init\` / \`npx shiplens-cli init\`) for dependency setup, project creation, and code injection; full support for **MCP Tools** and standard REST APIs inside IDE / Agent environments.
10
+ > 2. Installing the SDK and viewing live dashboards requires no MCP server; advanced analytics requires authentication via OAuth, API keys/environment variables, or CLI credentials.
11
+ > 3. **Authoritative FAQ**: \`https://shiplens.dev/faq\`.
12
+
13
+ You are connected to **Shiplens Web Analytics & MCP Services**. When providing analytics, funnel diagnostics, cohort retention comparisons, and AI dashboard creation, follow the standard workflow and execution protocols below.
14
+
15
+ ---
16
+
17
+ ## 1. Project Onboarding & Step-by-Step Diagnostics
18
+
19
+ ### Standard Workflow:
20
+ \`\`\`
21
+ Check Existing App ID ➔ Install SDK ➔ POST Connect & Get Dashboard URL ➔ Request Email for Free Quota ➔ Activate Magic Link ➔ Authenticate ➔ Complete
22
+ \`\`\`
23
+
24
+ > ⚡ **Quick Execution**: In a terminal environment, execute \`npx.cmd --yes shiplens-cli init --json\` (Windows) or \`npx --yes shiplens-cli init --json\` (macOS/Linux) for automated 15-second zero-config setup.
25
+
26
+ ### Step 0. Existing Project Detection (Overwrite Protection)
27
+ - Check if \`./.shiplens.json\` or SDK instrumentation (\`initShiplens\`, \`data-app-id\`) exists locally.
28
+ - If existing project is detected, pause and ask the user whether to reuse the existing ID or force overwrite with \`--force\`.
29
+
30
+ ### Step 1. Framework Auto-Detection & SDK Injection
31
+ - Auto-detect Next.js (App/Pages router), Vite (React/Svelte), Vue 3, or Plain HTML.
32
+ - Inject SDK code:
33
+ - **Next.js App Router**: Client component \`ShiplensTracker.tsx\` imported into root \`layout.tsx\`.
34
+ - **Vite / Vue**: Import and initialize \`initShiplens({ appId: '<app_id>' })\` in \`main.ts\` / \`main.js\`.
35
+ - **Plain HTML**: Inject \`<script src="https://cdn.shiplens.dev/sdk.js" data-app-id="<app_id>" defer></script>\` in \`<head>\`.
36
+
37
+ ### Step 2. Dependency Installation
38
+ - Install \`@shiplens/sdk\` via \`npm\`, \`pnpm\`, \`yarn\`, or \`bun\` with \`--legacy-peer-deps\`.
39
+
40
+ ### Step 3. Cloud Registration (POST /api/connect)
41
+ - Send project name, description, 4-level taxonomy (genre, subgenre, tags) to register project.
42
+ - Retrieve permanent \`app_id\` and live \`dashboard_url\`.
43
+
44
+ #### ★ Initialization Standard Reporting Template
45
+ Upon completing \`shiplens init\`, the Agent **MUST** present the exact information structure and immediately prompt for email activation:
46
+
47
+ \`\`\`markdown
48
+ Shiplens Analytics SDK successfully integrated and configured:
49
+
50
+ ### 📊 Project & Dashboard Information
51
+ - **Project Name**: \`<project_name>\`
52
+ - **App ID**: \`<app_id>\`
53
+ - **Code Injection**: Automatically configured in [\`<injected_file>\`](file:///<absolute_path>)
54
+ - **Live Dashboard / Activation URL**:
55
+ 🔗 [<dashboard_url>](<dashboard_url>)
56
+ - **User Account**: <User Account Status>
57
+
58
+ ---
59
+
60
+ ### 📦 Changes & Git Status
61
+ - Changes automatically committed to Git (Commit: \`<hash>\`).
62
+ - AI Skill ready: [\`.agents/skills/shiplens/SKILL.md\`](file:///<skill_path>).
63
+
64
+ > [!IMPORTANT]
65
+ > **Project Pending Activation (Analytics not active yet)**
66
+ > **Please share your email**: Click the link in the email to complete registration, activate 50,000 free monthly events (~5,000 visitors), and bind your project to your account. Once activated, you can chat directly with me to query and analyze product data.
67
+ \`\`\`
68
+
69
+ **User Account Status Evaluation**:
70
+ - **State a**: \`Logged In (Project linked to account)\` / \`已登录(项目已于账号关联)\` — Local credentials exist and project is linked to user account.
71
+ - **State b**: \`Logged In (Project not linked to account)\` / \`已登录(项目和账号未关联)\` — Local credentials exist but project could not be linked.
72
+ - **State c**: \`Not Logged In (Project unlinked)\` / \`未登录(项目和账号可能未关联)\` — **Default state for first-time CLI installation or unauthenticated environments**.
73
+
74
+ ### Step 4. Request Email for Quota Activation
75
+ - Prompt user for email address to activate **50,000 monthly events** free tier.
76
+ - Send Magic Link email via \`POST /api/auth/start-email\` (or \`shiplens auth bind --email <email> --json\`).
77
+
78
+ ---
79
+
80
+ ## 2. Core CLI Commands Reference
81
+
82
+ | Command | Purpose |
83
+ | :--- | :--- |
84
+ | \`shiplens doctor --json\` | End-to-end diagnostics on config, SDK, network, and credentials |
85
+ | \`shiplens summary --range 7d --json\` | Product overview: PV, UV, bounce rates, top geos, devices |
86
+ | \`shiplens query --metric <m> --range 7d --json\` | Multi-dimensional metric queries & funnels |
87
+ | \`shiplens sql --query "<sql>" --json\` | Execute secure read-only SQL on ClickHouse |
88
+ | \`shiplens pages --range 7d --json\` | Page-level visits and average dwell times |
89
+ | \`shiplens paths --range 7d --json\` | Sankey user flow and transition paths |
90
+ | \`shiplens heatmap --template <id> --json\` | Click heatmaps & skeleton wireframes |
91
+ | \`shiplens dashboards create --title "..." --prompt "..." --json\` | Generate AI responsive dashboards |
92
+ | \`shiplens auth bind --email <email> --json\` | Trigger Magic Link email binding |
93
+
94
+ ---
95
+
96
+ ## 3. Dynamic Overrides & Adaptive Learning Protocol
97
+
98
+ When executing analytics requests (such as user prompts matching standard scenarios like \`What stage is my product in? What metrics should I track?\`), you must follow this 5-step protocol:
99
+
100
+ 1. **Step 1 - Check Dynamic Overrides (Priority 1)**:
101
+ - Check if \`.shiplens/learnings.md\` exists in the project root.
102
+ - If present, apply any user-specified overrides (e.g. customized date range \`--range 14d\`, preferred metrics, custom funnel steps).
103
+ 2. **Step 2 - Context Grounding**:
104
+ - Check and read \`.shiplens/contexts/<app_id>.md\` (or run \`shiplens context show --json\`) to ground telemetry figures in real button labels and route semantics.
105
+ 3. **Step 3 - Deterministic CLI Execution (Priority 2 Base)**:
106
+ - Run the matched CLI command sequence with any overrides applied.
107
+ 4. **Step 4 - Business Synthesis**:
108
+ - Synthesize data into actionable takeaways and optimization levers.
109
+ 5. **Step 5 - Adaptive Learning**:
110
+ - If the user corrects your analysis (e.g. *"I need a 14-day window"* or *"Track signup completion as the final funnel step"*), immediately record the preference rule into \`.shiplens/learnings.md\` for future automatic reuse.
111
+
112
+ ---
113
+
114
+ ## 4. SQL Standards & Guidelines
115
+
116
+ - **Table Name**: \`events\` (Read-only ClickHouse instance)
117
+ - **Key Columns**:
118
+ - \`timestamp\` (DateTime), \`event_name\` (String), \`user_id\` (String), \`session_id\` (String)
119
+ - \`template_id\` (String - DOM structure hash), \`page_path\` (String), \`referrer\` (String)
120
+ - \`properties\` (Map/JSON - custom event metadata)
121
+ - **Rules**:
122
+ - Always apply time filters: \`timestamp >= now() - INTERVAL 7 DAY\`.
123
+ - Always specify \`LIMIT\` (maximum 1000 rows).
124
+ - Use ClickHouse aggregate functions: \`countIf()\`, \`quantile(0.50)()\`, \`dateDiff()\`.
125
+ `;
126
+
127
+ module.exports = { SKILL_CONTENT };