@shiplens/cli 1.4.3 → 1.4.5

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.
@@ -3,7 +3,7 @@ name: shiplens-analytics
3
3
  description: Shiplens Web Analytics — Agent behavior protocol, diagnostics, and data analysis execution.
4
4
  ---
5
5
 
6
- # Shiplens Web Analytics Skill (v3.0)
6
+ # Shiplens Web Analytics Skill (v3.1)
7
7
 
8
8
  Shiplens provides web user behavior analytics via CLI and MCP. This document defines how an AI Agent should diagnose, onboard, and analyze data for any project using Shiplens.
9
9
 
@@ -86,43 +86,42 @@ Account status values:
86
86
 
87
87
  ## 3. Data Analysis Protocol (6 Steps)
88
88
 
89
- When the user asks any data or analytics question:
89
+ When processing any analytics or telemetry request:
90
90
 
91
- **Step 1 — Check overrides**: Read \`.shiplens/learnings.md\` if it exists. Apply user preferences tailored to specific analysis scenarios (date range, metrics, filters).
91
+ **Step 1 — Read Learnings**: Check \`.shiplens/learnings.md\` for project overrides (date range, metrics, cohort intervals).
92
92
 
93
- **Step 2 — Ground in context**: Read \`.shiplens/contexts/<app_id>.md\` (or \`shiplens context show --json\`). Use real page names and button labels, not raw IDs.
93
+ **Step 2 — Read Context**: Read \`.shiplens/contexts/<app_id>.md\` (or run \`shiplens context show --json\`) to ground numbers in real page routes and button labels.
94
94
 
95
- **Step 3 — Find scenario**: Open \`prompts/prompts_cli_en.md\` (or \`_zh.md\`), read the **Scenario Outline** at the top, locate the matching scenario, then read its full execution steps.
95
+ **Step 3 — Route Execution Path (Tri-Route Decision)**:
96
+ - **Route A (Exact Action ID)**: If input contains an \`action_id\` (e.g. \`lifecycle_stage\` or \`Action: <id>\`), run \`shiplens action <id> --json\` to retrieve prescribed steps and commands.
97
+ - **Route B (Scenario Preset Match)**: If input is natural language matching one of the 42 textbook growth/retention scenarios, run \`shiplens action <id> --json\` for that scenario.
98
+ - **Route C (Ad-Hoc Autonomous Composition)**: If input is a custom, open-ended question without a preset action, autonomously select and compose CLI commands from the Atomic Toolset (§4) to query required metrics.
96
99
 
97
- **Step 4 — Execute**: Run the prescribed CLI commands with overrides from Step 1 applied.
100
+ **Step 4 — Execute**: Run selected CLI commands with Step 1 overrides applied. Always include \`--json\`.
98
101
 
99
- **Step 5 — Synthesize**: Translate numbers into actionable business insights, grounded in context from Step 2.
102
+ **Step 5 — Synthesize**: Translate numbers into concrete business conclusions, metric benchmarks, and actionable next steps using UI terminology from Step 2.
100
103
 
101
- **Step 6 — Offer to remember**: If the user corrected your approach (e.g., "always compare 14-day retention for cohorts"), ask: *"Save this preference to \`.shiplens/learnings.md\` for future analyses?"* If yes, write it (§6).
102
-
103
- ### Priority
104
- - **Priority 1**: \`.shiplens/learnings.md\` — user's project-specific overrides
105
- - **Priority 2**: \`prompts/prompts_cli_*.md\` — 42 textbook analysis scenarios
104
+ **Step 6 — Record**: If user clarifies or corrects preferences (e.g., "always use 14d for cohort retention"), ask to persist to \`.shiplens/learnings.md\` (§6).
106
105
 
107
106
  ---
108
107
 
109
- ## 4. CLI Commands
110
-
111
- | Command | Purpose |
112
- |---------|---------|
113
- | \`shiplens init --json\` | Project onboarding (§2) |
114
- | \`shiplens doctor --json\` | Diagnose config, SDK, network, credentials |
115
- | \`shiplens summary --range 7d --json\` | PV, UV, bounce rate, top geos, devices |
116
- | \`shiplens query --metric <m> --range 7d --json\` | Multi-dimensional metrics and funnels |
117
- | \`shiplens sql --query "<sql>" --json\` | Read-only SQL on ClickHouse |
118
- | \`shiplens pages --range 7d --json\` | Page visits and dwell times |
119
- | \`shiplens paths --range 7d --json\` | User flow and navigation paths |
120
- | \`shiplens heatmap --template <id> --json\` | Click heatmaps and skeleton wireframes |
121
- | \`shiplens dashboards create --title "..." --prompt "..." --json\` | AI-generated dashboards |
122
- | \`shiplens context show --json\` | Show business context |
123
- | \`shiplens auth bind --email <email> --json\` | Send Magic Link for activation |
124
- | \`shiplens mcp serve\` | Start local stdio MCP proxy for IDE/Agent |
125
- | \`shiplens projects delete --app-id <id> --json\` | Delete project (**requires confirmation**, §5) |
108
+ ## 4. Atomic CLI Toolset & Composition Matrix
109
+
110
+ | Command | Capability | Typical Scenarios & Composition |
111
+ |---------|------------|---------------------------------|
112
+ | \`shiplens summary --range 7d --json\` | Macro Overview | Total UV, PV, bounce rates, top geos, device distribution |
113
+ | \`shiplens query --metric <m> --range 7d --json\` | Multidimensional Metrics | Retention matrix (\`daily_retention\`), funnels (\`conversion_funnel\`), pageview trends |
114
+ | \`shiplens pages --range 7d --json\` | Page Performance | Route visit counts, average dwell time, high-traffic pages |
115
+ | \`shiplens paths --range 7d --json\` | User Journeys | Entry-to-exit flow, drop-off routes, navigation transitions |
116
+ | \`shiplens heatmap --template <id> --json\` | UI & Click Patterns | Button click distributions, wireframe skeleton click rates |
117
+ | \`shiplens sql --query "<sql>" --json\` | Custom Slicing | Multi-filter joins, power user segmentation, ad-hoc event queries |
118
+ | \`shiplens dashboards create --title "..." --prompt "..." --json\` | Dashboard Creation | AI-generated 12-column live dashboards |
119
+ | \`shiplens action [id] [--list] --json\` | Action Presets | 42 textbook scenario steps, commands, and theory |
120
+ | \`shiplens doctor --json\` | Diagnostics | Diagnose config, SDK, network, credentials |
121
+ | \`shiplens context show --json\` | Business Context | Inspect mapped page routes and UI button semantics |
122
+ | \`shiplens auth bind --email <email> --json\` | Activation | Send Magic Link email for project binding |
123
+ | \`shiplens mcp serve\` | MCP Server | Start local stdio MCP proxy for IDE/Agent |
124
+ | \`shiplens projects delete --app-id <id> --json\` | Deletion | Delete project (**requires confirmation**, §5) |
126
125
 
127
126
  ---
128
127
 
package/lib/cli.js CHANGED
@@ -14,8 +14,9 @@ const { handleDashboards } = require('./commands/dashboards');
14
14
  const { handleDoctor } = require('./commands/doctor');
15
15
  const { handleContext } = require('./commands/context');
16
16
  const { handleMcp } = require('./commands/mcp');
17
+ const { handleAction } = require('./commands/action');
17
18
 
18
- let VERSION = '1.3.0';
19
+ let VERSION = '1.4.4';
19
20
  try {
20
21
  const pkg = require('../package.json');
21
22
  if (pkg.version) VERSION = pkg.version;
@@ -90,6 +91,7 @@ Core Commands:
90
91
  dashboards AI dashboard management and one-click creation (list, create [--ai])
91
92
  doctor Run full end-to-end diagnostics on SDK, credentials, and network
92
93
  context Manage business context dictionary (push, pull, show)
94
+ action Retrieve deterministic steps, commands, and theory for analysis actions (list, <action_id>)
93
95
  mcp serve Run local MCP proxy server with device credentials
94
96
 
95
97
  auth Subcommands:
@@ -244,6 +246,9 @@ async function runCLI(argv = process.argv.slice(2)) {
244
246
  case 'context':
245
247
  await handleContext(subcommand || 'show', args.slice(2), flags, ctx);
246
248
  break;
249
+ case 'action':
250
+ await handleAction(cmdArgs, flags, ctx);
251
+ break;
247
252
  case 'mcp':
248
253
  await handleMcp(subcommand || 'serve');
249
254
  break;
@@ -0,0 +1,112 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+
4
+ let actionsCache = null;
5
+
6
+ function loadActions() {
7
+ if (actionsCache) return actionsCache;
8
+ const filePath = path.join(__dirname, '..', 'assets', 'actions.json');
9
+ try {
10
+ if (fs.existsSync(filePath)) {
11
+ const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
12
+ actionsCache = data.actions || [];
13
+ } else {
14
+ actionsCache = [];
15
+ }
16
+ } catch (e) {
17
+ actionsCache = [];
18
+ }
19
+ return actionsCache;
20
+ }
21
+
22
+ function findSimilarActions(id, allActions) {
23
+ const normalized = (id || '').toLowerCase().replace(/[-_\s]+/g, '');
24
+ return allActions
25
+ .filter((a) => {
26
+ const aNorm = a.id.toLowerCase().replace(/[-_\s]+/g, '');
27
+ return aNorm.includes(normalized) || normalized.includes(aNorm) || a.title.toLowerCase().includes(id.toLowerCase());
28
+ })
29
+ .slice(0, 3)
30
+ .map((a) => a.id);
31
+ }
32
+
33
+ async function handleAction(cmdArgs, flags, ctx) {
34
+ const actions = loadActions();
35
+ const rawId = flags.id || (cmdArgs[0] && cmdArgs[0] !== 'list' ? cmdArgs[0] : null);
36
+ const isList = flags.list || cmdArgs[0] === 'list' || !rawId;
37
+
38
+ if (isList) {
39
+ const listData = {
40
+ ok: true,
41
+ total: actions.length,
42
+ actions: actions.map((a) => ({
43
+ id: a.id,
44
+ title: a.title,
45
+ category: a.category,
46
+ commands: a.commands,
47
+ })),
48
+ };
49
+
50
+ ctx.output(listData, () => {
51
+ console.log(`\n📋 Shiplens Action Preset Library (${actions.length} Scenarios)\n`);
52
+ const grouped = {};
53
+ for (const a of actions) {
54
+ if (!grouped[a.category]) grouped[a.category] = [];
55
+ grouped[a.category].push(a);
56
+ }
57
+ for (const [cat, items] of Object.entries(grouped)) {
58
+ console.log(` 📂 ${cat}:`);
59
+ for (const item of items) {
60
+ console.log(` • ${item.id.padEnd(28)} - ${item.title}`);
61
+ }
62
+ console.log('');
63
+ }
64
+ console.log('💡 Usage: shiplens action <action_id> [--json]');
65
+ });
66
+ return;
67
+ }
68
+
69
+ const targetId = rawId.toLowerCase().trim();
70
+ const matched = actions.find((a) => a.id.toLowerCase() === targetId);
71
+
72
+ if (!matched) {
73
+ const suggestions = findSimilarActions(targetId, actions);
74
+ const err = new Error(`Action preset '${rawId}' not found.` + (suggestions.length > 0 ? ` Did you mean: ${suggestions.join(', ')}?` : ''));
75
+ err.code = 'ACTION_NOT_FOUND';
76
+ err.suggestions = suggestions;
77
+ throw err;
78
+ }
79
+
80
+ const result = {
81
+ ok: true,
82
+ action: matched,
83
+ };
84
+
85
+ ctx.output(result, () => {
86
+ console.log(`\n🎯 [${matched.id}] ${matched.title}`);
87
+ console.log(`📂 Category: ${matched.category}`);
88
+ if (matched.suffix) console.log(`🏷️ Suffix: ${matched.suffix}`);
89
+ console.log(`\n📝 Execution Steps:`);
90
+ for (const s of matched.steps) {
91
+ console.log(` ${s}`);
92
+ }
93
+ if (matched.commands && matched.commands.length > 0) {
94
+ console.log(`\n⚡ Prescribed CLI Commands:`);
95
+ for (const c of matched.commands) {
96
+ console.log(` $ ${c}`);
97
+ }
98
+ }
99
+ if (matched.foundation) {
100
+ console.log(`\n📚 Analysis Foundation:\n ${matched.foundation}`);
101
+ }
102
+ if (matched.source) {
103
+ console.log(`\n📖 Source:\n ${matched.source}`);
104
+ }
105
+ console.log('');
106
+ });
107
+ }
108
+
109
+ module.exports = {
110
+ handleAction,
111
+ loadActions,
112
+ };
@@ -30,49 +30,29 @@ async function handleInit(args, flags, ctx) {
30
30
 
31
31
  // 1. Safety check for existing project configuration
32
32
  const existing = detectExistingApp(wd);
33
+ let isReusedExisting = false;
34
+
33
35
  if (existing.has_existing && !flags.force) {
34
- if (ctx.isJSON) {
35
- const result = {
36
- ok: false,
37
- code: 'PROJECT_EXISTS_LOCALLY',
38
- message: `Existing Shiplens configuration detected (App ID: ${existing.app_id}) in ${existing.source_file}. ` +
39
- `Option 1 [Recommended]: Keep existing statistics and project ID. ` +
40
- `Option 2: Use --force to overwrite and request a new App ID from cloud (Note: Old dashboard stops receiving new data).`,
41
- existing_app_id: existing.app_id,
42
- source_file: existing.source_file,
43
- project_name: existing.project_name || projectName,
44
- dashboard_url: `${ctx.client.baseURL}/dashboard/${existing.app_id}`,
45
- };
46
- ctx.output(result);
47
- process.exitCode = 1;
48
- return;
49
- } else if (!process.stdin.isTTY) {
50
- console.log(`\n⚠️ Existing Shiplens project detected!`);
51
- console.log(`📌 App ID: ${existing.app_id} (Source: ${existing.source_file})`);
52
- console.log(`📊 Dashboard: ${ctx.client.baseURL}/dashboard/${existing.app_id}`);
53
- console.log(`🛑 Non-interactive terminal detected (CI/Agent). Existing configuration preserved.`);
54
- console.log(`💡 Option 1 [Recommended]: Keep existing setup, retain App ID and historical statistics;`);
55
- console.log(`💡 Option 2: Overwrite with --force to create a new project:`);
56
- console.log(` npx.cmd --yes @shiplens/cli init --force\n`);
57
- return;
36
+ if (ctx.isJSON || !process.stdin.isTTY) {
37
+ // Non-interactive or JSON mode (Agent/CI): Fast-forward / Idempotent reuse of existing config
38
+ isReusedExisting = true;
58
39
  } else {
59
40
  console.log(`\n⚠️ Existing Shiplens project detected!`);
60
41
  console.log(`📌 App ID: ${existing.app_id} (Source: ${existing.source_file})`);
61
42
  console.log(`📊 Dashboard: ${ctx.client.baseURL}/dashboard/${existing.app_id}`);
62
- console.log(`\n💡 Option 1 [Recommended]: Keep existing setup, retain App ID and historical statistics;`);
63
- console.log(`💡 Option 2: Overwrite with --force to request a brand new App ID from cloud (Note: Old dashboard stops receiving new data, old and new data cannot be merged):`);
43
+ console.log(`\n💡 Option 1 [Recommended]: Keep existing setup, retain App ID and historical statistics (Press Enter or N);`);
44
+ console.log(`💡 Option 2: Overwrite with --force to request a brand new App ID from cloud (y):`);
64
45
  console.log(` npx.cmd --yes @shiplens/cli init --force\n`);
65
46
 
66
47
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
67
48
  const confirmed = await new Promise((resolve) => {
68
- rl.question('Overwrite and create a new project? (y/N): ', (ans) => {
49
+ rl.question('Overwrite and create a brand new project? (y/N): ', (ans) => {
69
50
  rl.close();
70
51
  resolve(ans.trim().toLowerCase() === 'y' || ans.trim().toLowerCase() === 'yes');
71
52
  });
72
53
  });
73
54
  if (!confirmed) {
74
- console.log('🛑 Operation cancelled. Existing configuration preserved.');
75
- return;
55
+ isReusedExisting = true;
76
56
  }
77
57
  }
78
58
  }
@@ -80,40 +60,52 @@ async function handleInit(args, flags, ctx) {
80
60
  // 2. Inject AI Skill (.agents/skills/shiplens/SKILL.md & Agent rules)
81
61
  const skillFile = injectSkill(wd);
82
62
 
83
- // 3. Register project on cloud
63
+ // 3. Register project on cloud (or reuse existing)
84
64
  let appId = '';
85
65
  let dashboardUrl = '';
86
66
  let accountStatus = 'not_logged_in_unlinked';
87
67
  let accountStatusText = 'Not Logged In (Default state after first installation or no valid local credentials)';
88
68
 
89
- try {
90
- const connResp = await ctx.client.connect({
91
- project_name: projectName,
92
- description,
93
- industry,
94
- genre_id: genreId,
95
- subgenre_id: subgenreId,
96
- feature_tags: featureTagIds.join(','),
97
- platform: framework,
98
- });
99
- appId = connResp.app_id;
100
- dashboardUrl = connResp.dashboard_url || `${ctx.client.baseURL}/dashboard/${appId}`;
101
-
69
+ if (isReusedExisting) {
70
+ appId = existing.app_id;
71
+ dashboardUrl = `${ctx.client.baseURL}/dashboard/${appId}`;
102
72
  if (ctx.resolvedAuth && ctx.resolvedAuth.is_present) {
103
- if (connResp.account_linked !== false && connResp.linked !== false && !connResp.unlinked) {
104
- accountStatus = 'logged_in_linked';
105
- accountStatusText = 'Logged In (Project linked to account)';
106
- } else {
107
- accountStatus = 'logged_in_unlinked';
108
- accountStatusText = 'Logged In (Project not linked to account)';
109
- }
73
+ accountStatus = 'logged_in_linked';
74
+ accountStatusText = 'Logged In (Project linked to account)';
110
75
  } else {
111
76
  accountStatus = 'not_logged_in_unlinked';
112
77
  accountStatusText = 'Not Logged In (Default state after first installation or no valid local credentials)';
113
78
  }
114
- } catch (netErr) {
115
- netErr.code = netErr.code || 'CONNECT_FAILED';
116
- throw netErr;
79
+ } else {
80
+ try {
81
+ const connResp = await ctx.client.connect({
82
+ project_name: projectName,
83
+ description,
84
+ industry,
85
+ genre_id: genreId,
86
+ subgenre_id: subgenreId,
87
+ feature_tags: featureTagIds.join(','),
88
+ platform: framework,
89
+ });
90
+ appId = connResp.app_id;
91
+ dashboardUrl = connResp.dashboard_url || `${ctx.client.baseURL}/dashboard/${appId}`;
92
+
93
+ if (ctx.resolvedAuth && ctx.resolvedAuth.is_present) {
94
+ if (connResp.account_linked !== false && connResp.linked !== false && !connResp.unlinked) {
95
+ accountStatus = 'logged_in_linked';
96
+ accountStatusText = 'Logged In (Project linked to account)';
97
+ } else {
98
+ accountStatus = 'logged_in_unlinked';
99
+ accountStatusText = 'Logged In (Project not linked to account)';
100
+ }
101
+ } else {
102
+ accountStatus = 'not_logged_in_unlinked';
103
+ accountStatusText = 'Not Logged In (Default state after first installation or no valid local credentials)';
104
+ }
105
+ } catch (netErr) {
106
+ netErr.code = netErr.code || 'CONNECT_FAILED';
107
+ throw netErr;
108
+ }
117
109
  }
118
110
 
119
111
  // 4. Inject SDK code
@@ -188,6 +180,8 @@ async function handleInit(args, flags, ctx) {
188
180
 
189
181
  const result = {
190
182
  ok: true,
183
+ reconnected: isReusedExisting,
184
+ reused_existing: isReusedExisting,
191
185
  app_id: appId,
192
186
  project_name: projectName,
193
187
  description: description || undefined,
@@ -209,12 +203,17 @@ async function handleInit(args, flags, ctx) {
209
203
  bound_email: targetEmail || undefined,
210
204
  elapsed_ms: elapsed,
211
205
  atomic_completed: true,
206
+ message: isReusedExisting
207
+ ? `Existing Shiplens project detected and successfully verified (App ID: ${appId}).`
208
+ : `Shiplens SDK successfully integrated and project created.`,
212
209
  };
213
210
 
214
211
  ctx.output(result, () => {
215
212
  console.log(`\n==================================================`);
216
- console.log(`✅ Shiplens SDK successfully integrated (${elapsed} ms)\n`);
217
- console.log(`📦 Project & Dashboard Information`);
213
+ console.log(isReusedExisting
214
+ ? `✅ Shiplens SDK verified & reconnected (${elapsed} ms)\n`
215
+ : `✅ Shiplens SDK successfully integrated (${elapsed} ms)\n`);
216
+ console.log(`📦 Project & Dashboard Information` + (isReusedExisting ? ` (Existing setup preserved)` : ''));
218
217
  console.log(`Project Name: ${projectName}`);
219
218
  if (description) {
220
219
  console.log(`Description: ${description}`);
package/lib/injector.js CHANGED
@@ -394,7 +394,7 @@ async function installSDKDependency(dir, pkgManager = 'npm') {
394
394
  },
395
395
  ];
396
396
 
397
- const TIER1_DELAY_MS = 20 * 1000; // 20s
397
+ const TIER1_DELAY_MS = 5 * 1000; // 5s (fast-forward backup mirrors on high latency)
398
398
  const TOTAL_TIMEOUT_MS = 5 * 60 * 1000; // 5min
399
399
 
400
400
  return new Promise((resolve) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shiplens/cli",
3
- "version": "1.4.3",
3
+ "version": "1.4.5",
4
4
  "description": "Shiplens CLI — Automated Web User Analytics & AI Agent Analysis Engine",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -28,7 +28,10 @@
28
28
  },
29
29
  "homepage": "https://shiplens.dev",
30
30
  "scripts": {
31
- "test": "node test/cli.test.js"
31
+ "test": "node test/cli.test.js",
32
+ "sync-prompts": "node scripts/sync-prompts.js",
33
+ "prepack": "node scripts/sync-prompts.js",
34
+ "prepublishOnly": "node scripts/sync-prompts.js"
32
35
  },
33
36
  "files": [
34
37
  "bin",
package/prompts/README.md CHANGED
@@ -8,9 +8,8 @@ This directory hosts the deterministic CLI scenario-based prompt libraries for S
8
8
 
9
9
  ```text
10
10
  prompts/
11
- ├── README.md # Multilingual architecture overview
12
- ├── prompts_cli_en.md # [English] 42 deterministic CLI analysis scenarios
13
- └── prompts_cli_zh.md # [中文] 42 个确定性 CLI 分析场景
11
+ ├── README.md # Prompt architecture overview
12
+ └── prompts_cli_en-US.md # [English] 42 deterministic CLI analysis scenarios
14
13
  ```
15
14
 
16
15
  ---