@gurulu/cli 0.3.4 β 0.4.1
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 +61 -24
- package/dist/api-client.js +1 -1
- package/dist/commands/add-server.js +13 -6
- package/dist/commands/alerts.d.ts +5 -0
- package/dist/commands/alerts.js +43 -15
- package/dist/commands/audiences.d.ts +3 -0
- package/dist/commands/audiences.js +34 -7
- package/dist/commands/events.d.ts +6 -0
- package/dist/commands/events.js +182 -1
- package/dist/commands/experiments.d.ts +4 -0
- package/dist/commands/experiments.js +46 -15
- package/dist/commands/funnels.d.ts +17 -0
- package/dist/commands/funnels.js +203 -0
- package/dist/commands/goals.d.ts +18 -0
- package/dist/commands/goals.js +214 -0
- package/dist/commands/install.d.ts +8 -0
- package/dist/commands/install.js +74 -4
- package/dist/commands/sourcemap.d.ts +17 -5
- package/dist/commands/sourcemap.js +73 -6
- package/dist/commands/watch.d.ts +45 -0
- package/dist/commands/watch.js +258 -0
- package/dist/frameworks/detect.js +29 -7
- package/dist/index.js +158 -13
- package/package.json +1 -1
- package/scripts/gurulu-agentic-install.mjs +225 -0
- package/scripts/gurulu-scan.lib.cjs +539 -19
- package/scripts/patches/astro.patch.cjs +1 -0
- package/scripts/patches/auto-instrument/hono.cjs +381 -0
- package/scripts/patches/auto-instrument/index.cjs +2 -0
- package/scripts/patches/auto-instrument/nextjs-app-router.cjs +13 -4
- package/scripts/patches/express.patch.cjs +2 -2
- package/scripts/patches/fastify.patch.cjs +1 -0
- package/scripts/patches/nestjs.patch.cjs +1 -0
- package/scripts/patches/nextjs-app-router.patch.cjs +2 -2
- package/scripts/patches/nextjs-pages.patch.cjs +1 -0
- package/scripts/patches/remix.patch.cjs +1 -0
- package/scripts/patches/sveltekit.patch.cjs +1 -0
- package/scripts/patches/vite-react.patch.cjs +1 -0
- package/scripts/patches/vue.patch.cjs +1 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @gurulu/cli
|
|
2
2
|
|
|
3
|
-
CLI
|
|
3
|
+
CLI for Gurulu.io β setup, diagnostics, data exploration, and AI-powered analytics from your terminal.
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
@@ -10,31 +10,76 @@ npx @gurulu/cli init
|
|
|
10
10
|
|
|
11
11
|
## Commands
|
|
12
12
|
|
|
13
|
+
### Authentication
|
|
14
|
+
|
|
15
|
+
| Command | Description |
|
|
16
|
+
|---------|-------------|
|
|
17
|
+
| `gurulu auth` | Authenticate via device-link flow (or `--key` for manual) |
|
|
18
|
+
| `gurulu login` | Authenticate with API key |
|
|
19
|
+
| `gurulu logout` | Remove a stored profile |
|
|
20
|
+
| `gurulu whoami` | Show current authentication state |
|
|
21
|
+
|
|
22
|
+
### Setup & Diagnostics
|
|
23
|
+
|
|
13
24
|
| Command | Description |
|
|
14
25
|
|---------|-------------|
|
|
15
26
|
| `gurulu init` | Set up Gurulu analytics (auto-detects framework) |
|
|
16
|
-
| `gurulu
|
|
17
|
-
| `gurulu events` | List detected events from your site |
|
|
18
|
-
| `gurulu status` | Quick health check |
|
|
19
|
-
| `gurulu doctor` | Comprehensive diagnostics |
|
|
27
|
+
| `gurulu install [path]` | Install Gurulu tracker in a repository |
|
|
20
28
|
| `gurulu add-server` | Add server-side SDK (@gurulu/node) |
|
|
29
|
+
| `gurulu status` | Check SDK health and connection |
|
|
30
|
+
| `gurulu doctor` | Diagnose setup issues |
|
|
31
|
+
| `gurulu config <action>` | Manage CLI profiles (list, use, show, delete) |
|
|
32
|
+
|
|
33
|
+
### Sites & Keys
|
|
34
|
+
|
|
35
|
+
| Command | Description |
|
|
36
|
+
|---------|-------------|
|
|
37
|
+
| `gurulu sites <action>` | Manage sites (list, create, show, delete, rotate-token) |
|
|
38
|
+
| `gurulu api-keys <action>` | Manage API keys (list, create, revoke, rotate) |
|
|
39
|
+
|
|
40
|
+
### Data & Events
|
|
41
|
+
|
|
42
|
+
| Command | Description |
|
|
43
|
+
|---------|-------------|
|
|
44
|
+
| `gurulu events <action>` | View ingested events (list, tail) |
|
|
45
|
+
| `gurulu insights <action>` | View daily insights (today, history, weekly) |
|
|
46
|
+
| `gurulu chat [question]` | Ask analytics questions in natural language (NL -> SQL) |
|
|
47
|
+
|
|
48
|
+
### Identity & Audiences
|
|
49
|
+
|
|
50
|
+
| Command | Description |
|
|
51
|
+
|---------|-------------|
|
|
52
|
+
| `gurulu identity <action>` | View identity state (decay, transfers, cdc-sources) |
|
|
53
|
+
| `gurulu audiences <action>` | View audiences (list, show) |
|
|
54
|
+
| `gurulu experiments <action>` | View experiments (list, show, results) |
|
|
55
|
+
|
|
56
|
+
### Integrations & Export
|
|
57
|
+
|
|
58
|
+
| Command | Description |
|
|
59
|
+
|---------|-------------|
|
|
60
|
+
| `gurulu warehouse <action>` | Warehouse exports (BigQuery) |
|
|
61
|
+
| `gurulu warehouses <action>` | View warehouse exports (list, runs) |
|
|
62
|
+
| `gurulu destinations <action>` | View activation destinations (list, show) |
|
|
63
|
+
| `gurulu db <action>` | Connect, list, sync, or remove database sources |
|
|
64
|
+
|
|
65
|
+
### Monitoring & Debugging
|
|
66
|
+
|
|
67
|
+
| Command | Description |
|
|
68
|
+
|---------|-------------|
|
|
69
|
+
| `gurulu alerts <action>` | View anomaly alerts (list, show, channels) |
|
|
70
|
+
| `gurulu sourcemap <action>` | Upload source maps for error deobfuscation |
|
|
71
|
+
| `gurulu audit <action>` | Stream or export the CLI audit log (tail, export) |
|
|
72
|
+
| `gurulu playground <action>` | View playground sessions (list) |
|
|
21
73
|
|
|
22
74
|
## Supported Frameworks
|
|
23
75
|
|
|
24
|
-
|
|
25
|
-
- React (Vite & CRA)
|
|
26
|
-
- Vue 3
|
|
27
|
-
- Nuxt 3
|
|
28
|
-
- Svelte & SvelteKit
|
|
29
|
-
- Astro
|
|
30
|
-
- Express
|
|
31
|
-
- NestJS
|
|
32
|
-
- Plain HTML
|
|
76
|
+
Next.js (App & Pages Router), React (Vite & CRA), Vue 3, Nuxt 3, Svelte & SvelteKit, Astro, Express, NestJS, Plain HTML.
|
|
33
77
|
|
|
34
78
|
## Authentication
|
|
35
79
|
|
|
36
80
|
```bash
|
|
37
|
-
gurulu
|
|
81
|
+
gurulu auth # Device-link flow (recommended)
|
|
82
|
+
gurulu login --api-key pak_live_x # Manual API key
|
|
38
83
|
```
|
|
39
84
|
|
|
40
85
|
Or set the `GURULU_API_KEY` environment variable.
|
|
@@ -52,15 +97,7 @@ gurulu init --site-id abc123 --token tok_xxx --no-interactive
|
|
|
52
97
|
Use `--json` flag for machine-readable output:
|
|
53
98
|
|
|
54
99
|
```bash
|
|
55
|
-
gurulu events --json
|
|
100
|
+
gurulu events list --json
|
|
56
101
|
gurulu status --json
|
|
57
102
|
gurulu doctor --json
|
|
58
103
|
```
|
|
59
|
-
|
|
60
|
-
## Development
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
npm install
|
|
64
|
-
npm run build
|
|
65
|
-
node bin/gurulu.js --help
|
|
66
|
-
```
|
package/dist/api-client.js
CHANGED
|
@@ -97,7 +97,7 @@ async function cliApi(path, init = {}) {
|
|
|
97
97
|
if (!init.skipAuth && profile) {
|
|
98
98
|
headers.set('authorization', `Bearer ${profile.secret_key}`);
|
|
99
99
|
}
|
|
100
|
-
if (init.body && !headers.has('content-type')) {
|
|
100
|
+
if (init.body && !headers.has('content-type') && !(init.body instanceof FormData)) {
|
|
101
101
|
headers.set('content-type', 'application/json');
|
|
102
102
|
}
|
|
103
103
|
let res;
|
|
@@ -46,11 +46,18 @@ async function addServerCommand(args) {
|
|
|
46
46
|
let serverApiKey = process.env.GURULU_SERVER_API_KEY;
|
|
47
47
|
if (!serverApiKey && profile) {
|
|
48
48
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
(0, ui_1.step)('Creating server API key...');
|
|
50
|
+
const data = await (0, api_client_1.cliApiJson)(`/api/cli/sites/${encodeURIComponent(siteId)}/server-keys`, {
|
|
51
|
+
preloadedProfile: profile,
|
|
52
|
+
json: { name: `cli-${new Date().toISOString().slice(0, 10)}` },
|
|
53
|
+
});
|
|
54
|
+
serverApiKey = data.key || '';
|
|
55
|
+
if (serverApiKey) {
|
|
56
|
+
(0, ui_1.success)(`Server API key created (${data.keyId})`);
|
|
57
|
+
}
|
|
51
58
|
}
|
|
52
59
|
catch (err) {
|
|
53
|
-
(0, ui_1.warn)(`Could not
|
|
60
|
+
(0, ui_1.warn)(`Could not create server API key: ${err.message}`);
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
63
|
if (!serverApiKey && !args.noInteractive) {
|
|
@@ -89,9 +96,9 @@ async function addServerCommand(args) {
|
|
|
89
96
|
}
|
|
90
97
|
// Step 2: Create server config file
|
|
91
98
|
const configFilePath = path_1.default.join(projectDir, 'src', 'lib', 'gurulu-server.ts');
|
|
92
|
-
const configCode = `import {
|
|
99
|
+
const configCode = `import { Gurulu } from '@gurulu/node';
|
|
93
100
|
|
|
94
|
-
export const gurulu = new
|
|
101
|
+
export const gurulu = new Gurulu({
|
|
95
102
|
siteId: process.env.GURULU_SITE_ID || '${siteId}',
|
|
96
103
|
apiKey: process.env.GURULU_SERVER_API_KEY || '',
|
|
97
104
|
});
|
|
@@ -134,7 +141,7 @@ export const gurulu = new GuruluNode({
|
|
|
134
141
|
console.log('');
|
|
135
142
|
(0, ui_1.step)('Import gurulu from src/lib/gurulu-server.ts in your API routes');
|
|
136
143
|
(0, ui_1.step)(`Use ${(0, ui_1.cyan)('gurulu.track(event, properties)')} to send server-side events`);
|
|
137
|
-
(0, ui_1.step)(`Use ${(0, ui_1.cyan)(
|
|
144
|
+
(0, ui_1.step)(`Use ${(0, ui_1.cyan)("await gurulu.identify({ userId: '...', anonymousId: '...', traits: {} })")} to identify users`);
|
|
138
145
|
(0, ui_1.step)(`Run ${(0, ui_1.cyan)('gurulu doctor')} to verify the setup`);
|
|
139
146
|
console.log('');
|
|
140
147
|
(0, ui_1.success)('Server SDK setup complete!');
|
|
@@ -12,9 +12,14 @@ export interface AlertsArgs {
|
|
|
12
12
|
json?: boolean;
|
|
13
13
|
profile?: string;
|
|
14
14
|
fromFile?: string;
|
|
15
|
+
site?: string;
|
|
16
|
+
id?: string;
|
|
15
17
|
name?: string;
|
|
16
18
|
type?: string;
|
|
17
19
|
metric?: string;
|
|
20
|
+
thresholdType?: string;
|
|
21
|
+
thresholdValue?: number;
|
|
22
|
+
channel?: string;
|
|
18
23
|
note?: string;
|
|
19
24
|
yes?: boolean;
|
|
20
25
|
dryRun?: boolean;
|
package/dist/commands/alerts.js
CHANGED
|
@@ -33,6 +33,8 @@ async function alertsCommand(args) {
|
|
|
33
33
|
}
|
|
34
34
|
async function listCmd(args) {
|
|
35
35
|
const qs = new URLSearchParams();
|
|
36
|
+
if (args.site)
|
|
37
|
+
qs.set('siteId', args.site);
|
|
36
38
|
if (args.severity)
|
|
37
39
|
qs.set('severity', args.severity);
|
|
38
40
|
if (args.acknowledged)
|
|
@@ -81,12 +83,28 @@ async function createCmd(args) {
|
|
|
81
83
|
let payload = {};
|
|
82
84
|
if (args.fromFile)
|
|
83
85
|
payload = (0, from_file_1.loadFromFile)(args.fromFile);
|
|
86
|
+
if (args.site)
|
|
87
|
+
payload.siteId = args.site;
|
|
88
|
+
if (args.name)
|
|
89
|
+
payload.name = args.name;
|
|
90
|
+
if (args.type)
|
|
91
|
+
payload.type = args.type;
|
|
84
92
|
if (args.metric)
|
|
85
93
|
payload.metric = args.metric;
|
|
86
94
|
if (args.severity)
|
|
87
95
|
payload.severity = args.severity;
|
|
88
|
-
if (
|
|
89
|
-
|
|
96
|
+
if (args.thresholdType)
|
|
97
|
+
payload.thresholdType = args.thresholdType;
|
|
98
|
+
if (args.thresholdValue !== undefined)
|
|
99
|
+
payload.thresholdValue = args.thresholdValue;
|
|
100
|
+
if (args.channel)
|
|
101
|
+
payload.channelId = args.channel;
|
|
102
|
+
if (!payload.name) {
|
|
103
|
+
(0, ui_1.error)('name is required (pass --name or --from-file)');
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
if (!payload.siteId) {
|
|
107
|
+
(0, ui_1.error)('site is required (pass --site)');
|
|
90
108
|
process.exit(1);
|
|
91
109
|
}
|
|
92
110
|
if (args.dryRun) {
|
|
@@ -98,7 +116,7 @@ async function createCmd(args) {
|
|
|
98
116
|
(0, dry_run_1.printDryRun)(body, args.json);
|
|
99
117
|
return;
|
|
100
118
|
}
|
|
101
|
-
const ok = await (0, confirm_1.promptConfirm)(`Create alert '${payload.
|
|
119
|
+
const ok = await (0, confirm_1.promptConfirm)(`Create alert '${payload.name}'?`, {
|
|
102
120
|
yes: args.yes,
|
|
103
121
|
defaultYes: true,
|
|
104
122
|
});
|
|
@@ -116,51 +134,61 @@ async function createCmd(args) {
|
|
|
116
134
|
(0, ui_1.success)(`Created alert ${body.alert?.id ?? ''}`);
|
|
117
135
|
}
|
|
118
136
|
async function updateCmd(args) {
|
|
119
|
-
|
|
120
|
-
|
|
137
|
+
const resolveTarget = args.id || args.target;
|
|
138
|
+
if (!resolveTarget) {
|
|
139
|
+
(0, ui_1.error)('Usage: gurulu alerts update <id> [--id ...] [--name ...] [--threshold-value ...]');
|
|
121
140
|
process.exit(1);
|
|
122
141
|
}
|
|
123
142
|
const payload = {};
|
|
143
|
+
if (args.name)
|
|
144
|
+
payload.name = args.name;
|
|
124
145
|
if (args.note !== undefined)
|
|
125
146
|
payload.note = args.note;
|
|
147
|
+
if (args.thresholdType)
|
|
148
|
+
payload.thresholdType = args.thresholdType;
|
|
149
|
+
if (args.thresholdValue !== undefined)
|
|
150
|
+
payload.thresholdValue = args.thresholdValue;
|
|
151
|
+
if (args.channel)
|
|
152
|
+
payload.channelId = args.channel;
|
|
126
153
|
if (args.ack === true)
|
|
127
154
|
payload.acknowledge = true;
|
|
128
155
|
if (args.dryRun) {
|
|
129
|
-
const body = await (0, api_client_1.cliApiJson)(`/api/cli/alerts/${encodeURIComponent(
|
|
156
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/alerts/${encodeURIComponent(resolveTarget)}?dryRun=1`, { profile: args.profile, method: 'PATCH', json: payload });
|
|
130
157
|
(0, dry_run_1.printDryRun)(body, args.json);
|
|
131
158
|
return;
|
|
132
159
|
}
|
|
133
|
-
const ok = await (0, confirm_1.promptConfirm)(`Update alert '${
|
|
160
|
+
const ok = await (0, confirm_1.promptConfirm)(`Update alert '${resolveTarget}'?`, {
|
|
134
161
|
yes: args.yes,
|
|
135
162
|
defaultYes: true,
|
|
136
163
|
});
|
|
137
164
|
if (!ok)
|
|
138
165
|
return (0, ui_1.info)('Aborted.');
|
|
139
|
-
const body = await (0, api_client_1.cliApiJson)(`/api/cli/alerts/${encodeURIComponent(
|
|
166
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/alerts/${encodeURIComponent(resolveTarget)}`, { profile: args.profile, method: 'PATCH', json: payload });
|
|
140
167
|
if (args.json) {
|
|
141
168
|
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
142
169
|
return;
|
|
143
170
|
}
|
|
144
|
-
(0, ui_1.success)(`Updated alert ${
|
|
171
|
+
(0, ui_1.success)(`Updated alert ${resolveTarget}`);
|
|
145
172
|
}
|
|
146
173
|
async function deleteCmd(args) {
|
|
147
|
-
|
|
148
|
-
|
|
174
|
+
const resolveTarget = args.id || args.target;
|
|
175
|
+
if (!resolveTarget) {
|
|
176
|
+
(0, ui_1.error)('Usage: gurulu alerts delete <id> [--id ...]');
|
|
149
177
|
process.exit(1);
|
|
150
178
|
}
|
|
151
179
|
if (args.dryRun) {
|
|
152
|
-
const body = await (0, api_client_1.cliApiJson)(`/api/cli/alerts/${encodeURIComponent(
|
|
180
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/alerts/${encodeURIComponent(resolveTarget)}?dryRun=1`, { profile: args.profile, method: 'DELETE' });
|
|
153
181
|
(0, dry_run_1.printDryRun)(body, args.json);
|
|
154
182
|
return;
|
|
155
183
|
}
|
|
156
|
-
const ok = await (0, confirm_1.promptConfirm)(`About to delete alert '${
|
|
184
|
+
const ok = await (0, confirm_1.promptConfirm)(`About to delete alert '${resolveTarget}'. Continue?`, { yes: args.yes, defaultYes: false });
|
|
157
185
|
if (!ok)
|
|
158
186
|
return (0, ui_1.info)('Aborted.');
|
|
159
|
-
await (0, api_client_1.cliApiJson)(`/api/cli/alerts/${encodeURIComponent(
|
|
187
|
+
await (0, api_client_1.cliApiJson)(`/api/cli/alerts/${encodeURIComponent(resolveTarget)}`, {
|
|
160
188
|
profile: args.profile,
|
|
161
189
|
method: 'DELETE',
|
|
162
190
|
});
|
|
163
|
-
(0, ui_1.success)(`Deleted alert ${
|
|
191
|
+
(0, ui_1.success)(`Deleted alert ${resolveTarget}`);
|
|
164
192
|
}
|
|
165
193
|
// ββ channels ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
166
194
|
async function channelsCmd(args) {
|
|
@@ -30,7 +30,8 @@ async function audiencesCommand(args) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function listCmd(args) {
|
|
33
|
-
const
|
|
33
|
+
const qs = args.site ? `?siteId=${encodeURIComponent(args.site)}` : '';
|
|
34
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/audiences${qs}`, {
|
|
34
35
|
profile: args.profile,
|
|
35
36
|
});
|
|
36
37
|
const audiences = body.audiences || [];
|
|
@@ -88,12 +89,27 @@ async function createCmd(args) {
|
|
|
88
89
|
}
|
|
89
90
|
if (args.name)
|
|
90
91
|
payload.name = args.name;
|
|
92
|
+
if (args.site)
|
|
93
|
+
payload.siteId = args.site;
|
|
91
94
|
if (args.description !== undefined)
|
|
92
95
|
payload.description = args.description;
|
|
96
|
+
if (args.rules) {
|
|
97
|
+
try {
|
|
98
|
+
payload.rules = JSON.parse(args.rules);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
(0, ui_1.error)('--rules must be valid JSON');
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
93
105
|
if (!payload.name) {
|
|
94
106
|
(0, ui_1.error)('name is required (pass --name or --from-file)');
|
|
95
107
|
process.exit(1);
|
|
96
108
|
}
|
|
109
|
+
if (!payload.siteId) {
|
|
110
|
+
(0, ui_1.error)('site is required (pass --site)');
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
97
113
|
const qs = args.dryRun ? '?dryRun=1' : '';
|
|
98
114
|
if (args.dryRun) {
|
|
99
115
|
const body = await (0, api_client_1.cliApiJson)(`/api/cli/audiences${qs}`, {
|
|
@@ -121,11 +137,12 @@ async function createCmd(args) {
|
|
|
121
137
|
(0, ui_1.success)(`Created audience ${body.audience?.id ?? ''}`);
|
|
122
138
|
}
|
|
123
139
|
async function updateCmd(args) {
|
|
124
|
-
|
|
125
|
-
|
|
140
|
+
const resolveTarget = args.id || args.target;
|
|
141
|
+
if (!resolveTarget) {
|
|
142
|
+
(0, ui_1.error)('Usage: gurulu audiences update <name-or-id> [--id ...] [--name ...] [--rules ...]');
|
|
126
143
|
process.exit(1);
|
|
127
144
|
}
|
|
128
|
-
const id = await resolveId(
|
|
145
|
+
const id = await resolveId(resolveTarget, args.profile);
|
|
129
146
|
let payload = {};
|
|
130
147
|
if (args.fromFile) {
|
|
131
148
|
payload = (0, from_file_1.loadFromFile)(args.fromFile);
|
|
@@ -134,6 +151,15 @@ async function updateCmd(args) {
|
|
|
134
151
|
payload.name = args.name;
|
|
135
152
|
if (args.description !== undefined)
|
|
136
153
|
payload.description = args.description;
|
|
154
|
+
if (args.rules) {
|
|
155
|
+
try {
|
|
156
|
+
payload.rules = JSON.parse(args.rules);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
(0, ui_1.error)('--rules must be valid JSON');
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
137
163
|
const qs = args.dryRun ? '?dryRun=1' : '';
|
|
138
164
|
if (args.dryRun) {
|
|
139
165
|
const body = await (0, api_client_1.cliApiJson)(`/api/cli/audiences/${encodeURIComponent(id)}${qs}`, { profile: args.profile, method: 'PATCH', json: payload });
|
|
@@ -156,11 +182,12 @@ async function updateCmd(args) {
|
|
|
156
182
|
(0, ui_1.success)(`Updated audience ${id}`);
|
|
157
183
|
}
|
|
158
184
|
async function deleteCmd(args) {
|
|
159
|
-
|
|
160
|
-
|
|
185
|
+
const resolveTarget = args.id || args.target;
|
|
186
|
+
if (!resolveTarget) {
|
|
187
|
+
(0, ui_1.error)('Usage: gurulu audiences delete <name-or-id> [--id ...]');
|
|
161
188
|
process.exit(1);
|
|
162
189
|
}
|
|
163
|
-
const id = await resolveId(
|
|
190
|
+
const id = await resolveId(resolveTarget, args.profile);
|
|
164
191
|
const qs = args.dryRun ? '?dryRun=1' : '';
|
|
165
192
|
if (args.dryRun) {
|
|
166
193
|
const body = await (0, api_client_1.cliApiJson)(`/api/cli/audiences/${encodeURIComponent(id)}${qs}`, { profile: args.profile, method: 'DELETE' });
|
|
@@ -17,6 +17,12 @@ export interface EventsArgs {
|
|
|
17
17
|
limit?: number;
|
|
18
18
|
json?: boolean;
|
|
19
19
|
profile?: string;
|
|
20
|
+
displayName?: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
category?: string;
|
|
23
|
+
properties?: string;
|
|
24
|
+
event?: string;
|
|
25
|
+
vertical?: string;
|
|
20
26
|
}
|
|
21
27
|
export declare function eventsCommand(args: EventsArgs): Promise<void>;
|
|
22
28
|
export declare function legacyEventsCommand(args: {
|
package/dist/commands/events.js
CHANGED
|
@@ -22,9 +22,17 @@ async function eventsCommand(args) {
|
|
|
22
22
|
return listCmd(args);
|
|
23
23
|
case 'tail':
|
|
24
24
|
return tailCmd(args);
|
|
25
|
+
case 'schema':
|
|
26
|
+
return schemaCmd(args);
|
|
27
|
+
case 'define':
|
|
28
|
+
return defineCmd(args);
|
|
29
|
+
case 'verify':
|
|
30
|
+
return verifyCmd(args);
|
|
31
|
+
case 'templates':
|
|
32
|
+
return templatesCmd(args);
|
|
25
33
|
default:
|
|
26
34
|
(0, ui_1.error)(`Unknown events action: ${action}`);
|
|
27
|
-
(0, ui_1.info)('Usage: gurulu events [list|tail]');
|
|
35
|
+
(0, ui_1.info)('Usage: gurulu events [list|tail|schema|define|verify|templates]');
|
|
28
36
|
process.exit(1);
|
|
29
37
|
}
|
|
30
38
|
}
|
|
@@ -112,6 +120,179 @@ async function tailCmd(args) {
|
|
|
112
120
|
process.stdout.write(formatFrame(text, !!args.json));
|
|
113
121
|
}
|
|
114
122
|
}
|
|
123
|
+
/* ββ schema: show property schema & implementation examples βββββββββ */
|
|
124
|
+
async function schemaCmd(args) {
|
|
125
|
+
if (!args.eventName) {
|
|
126
|
+
(0, ui_1.error)('Event name is required. Usage: gurulu events schema --event-name <name> --site <id>');
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
if (!args.site) {
|
|
130
|
+
(0, ui_1.error)('--site is required for schema lookup.');
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
const path = `/api/cli/events/schema/${encodeURIComponent(args.eventName)}?siteId=${args.site}`;
|
|
134
|
+
const data = await (0, api_client_1.cliApiJson)(path, { profile: args.profile });
|
|
135
|
+
if (args.json) {
|
|
136
|
+
process.stdout.write(JSON.stringify(data, null, 2) + '\n');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
console.log((0, ui_1.bold)(`\nEvent: ${data.eventName}`));
|
|
140
|
+
if (data.source)
|
|
141
|
+
console.log(`Source: ${data.source}`);
|
|
142
|
+
if (data.description)
|
|
143
|
+
console.log(`Description: ${data.description}`);
|
|
144
|
+
if (data.isRevenue)
|
|
145
|
+
console.log((0, ui_1.yellow)(`Revenue event: ${data.revenueNote || 'yes'}`));
|
|
146
|
+
if (data.propertySchema?.length) {
|
|
147
|
+
console.log((0, ui_1.bold)('\nProperties:'));
|
|
148
|
+
for (const p of data.propertySchema) {
|
|
149
|
+
const req = p.required ? (0, ui_1.red)('REQUIRED') : (0, ui_1.dim)('optional');
|
|
150
|
+
const fmt = p.format ? (0, ui_1.cyan)(` [${p.format}]`) : '';
|
|
151
|
+
const ex = p.example !== undefined ? (0, ui_1.dim)(` e.g. ${JSON.stringify(p.example)}`) : '';
|
|
152
|
+
console.log(` ${p.name}: ${(0, ui_1.cyan)(p.type)} (${req})${fmt} β ${p.description || ''}${ex}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (data.implementation && Object.keys(data.implementation).length) {
|
|
156
|
+
console.log((0, ui_1.bold)('\nImplementation:'));
|
|
157
|
+
for (const [platform, code] of Object.entries(data.implementation)) {
|
|
158
|
+
console.log(` ${(0, ui_1.cyan)(`[${platform}]`)}: ${code}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
console.log('');
|
|
162
|
+
}
|
|
163
|
+
/* ββ define: create a new custom event definition ββββββββββββββββββ */
|
|
164
|
+
async function defineCmd(args) {
|
|
165
|
+
if (!args.site) {
|
|
166
|
+
(0, ui_1.error)('--site is required.');
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
if (!args.eventName) {
|
|
170
|
+
(0, ui_1.error)('--event-name is required.');
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
if (!args.displayName) {
|
|
174
|
+
(0, ui_1.error)('--display-name is required.');
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
let propertySchema = [];
|
|
178
|
+
if (args.properties) {
|
|
179
|
+
try {
|
|
180
|
+
propertySchema = JSON.parse(args.properties);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
(0, ui_1.error)('Invalid JSON for --properties');
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const eventName = args.eventName.startsWith('$') ? args.eventName : `$${args.eventName}`;
|
|
188
|
+
const body = {
|
|
189
|
+
siteId: args.site,
|
|
190
|
+
eventName,
|
|
191
|
+
displayName: args.displayName,
|
|
192
|
+
description: args.description || '',
|
|
193
|
+
intentName: args.category || 'engagement',
|
|
194
|
+
fingerprint: { propertySchema },
|
|
195
|
+
};
|
|
196
|
+
const data = await (0, api_client_1.cliApiJson)('/api/cli/events/definitions', { profile: args.profile, method: 'POST', json: body });
|
|
197
|
+
if (args.json) {
|
|
198
|
+
process.stdout.write(JSON.stringify(data, null, 2) + '\n');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
(0, ui_1.success)(`Event ${eventName} defined successfully`);
|
|
202
|
+
(0, ui_1.info)(`Run: gurulu events schema --event-name ${eventName} --site ${args.site} to see implementation examples`);
|
|
203
|
+
}
|
|
204
|
+
/* ββ verify: check event health / quality ββββββββββββββββββββββββββ */
|
|
205
|
+
async function verifyCmd(args) {
|
|
206
|
+
if (!args.site) {
|
|
207
|
+
(0, ui_1.error)('--site is required.');
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
const qs = new URLSearchParams();
|
|
211
|
+
qs.set('siteId', args.site);
|
|
212
|
+
if (args.event)
|
|
213
|
+
qs.set('eventName', args.event);
|
|
214
|
+
const path = `/api/cli/events/health?${qs.toString()}`;
|
|
215
|
+
const data = await (0, api_client_1.cliApiJson)(path, { profile: args.profile });
|
|
216
|
+
if (args.json) {
|
|
217
|
+
process.stdout.write(JSON.stringify(data, null, 2) + '\n');
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
console.log((0, ui_1.bold)('\nEvent Health Report'));
|
|
221
|
+
console.log(`Site: ${args.site}`);
|
|
222
|
+
console.log('');
|
|
223
|
+
if (Array.isArray(data.events)) {
|
|
224
|
+
for (const ev of data.events) {
|
|
225
|
+
const statusIcon = ev.status === 'active'
|
|
226
|
+
? (0, ui_1.green)('β')
|
|
227
|
+
: ev.status === 'stale'
|
|
228
|
+
? (0, ui_1.yellow)('β')
|
|
229
|
+
: (0, ui_1.red)('β');
|
|
230
|
+
console.log(`${statusIcon} ${ev.event_name} β ${ev.total_count ?? 0} events (last: ${ev.last_seen ?? 'never'})`);
|
|
231
|
+
if (ev.missing_properties?.length) {
|
|
232
|
+
console.log((0, ui_1.yellow)(` Missing required properties: ${ev.missing_properties.join(', ')}`));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else if (data.total_events !== undefined) {
|
|
237
|
+
console.log(`Total events (24h): ${data.total_events}`);
|
|
238
|
+
console.log(`Unique event types: ${data.unique_types ?? 'N/A'}`);
|
|
239
|
+
}
|
|
240
|
+
console.log('');
|
|
241
|
+
}
|
|
242
|
+
/* ββ templates: list vertical event template catalogs βββββββββββββ */
|
|
243
|
+
async function templatesCmd(args) {
|
|
244
|
+
const qs = new URLSearchParams();
|
|
245
|
+
if (args.vertical)
|
|
246
|
+
qs.set('vertical', args.vertical);
|
|
247
|
+
const path = `/api/events/templates${qs.toString() ? `?${qs.toString()}` : ''}`;
|
|
248
|
+
const data = await (0, api_client_1.cliApiJson)(path, { profile: args.profile });
|
|
249
|
+
if (args.json) {
|
|
250
|
+
process.stdout.write(JSON.stringify(data, null, 2) + '\n');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (data.error) {
|
|
254
|
+
(0, ui_1.error)(data.error);
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
// List mode β no vertical specified
|
|
258
|
+
if (data.verticals) {
|
|
259
|
+
console.log((0, ui_1.bold)('\nAvailable Verticals\n'));
|
|
260
|
+
for (const v of data.verticals) {
|
|
261
|
+
console.log(` ${(0, ui_1.cyan)(v.id.padEnd(14))} ${v.name.padEnd(30)} ${(0, ui_1.dim)(`${v.eventCount} events`)}`);
|
|
262
|
+
}
|
|
263
|
+
console.log('');
|
|
264
|
+
(0, ui_1.info)('Usage: gurulu events templates --vertical <id>');
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
// Detail mode β specific vertical
|
|
268
|
+
console.log((0, ui_1.bold)(`\nVertical: ${data.vertical}`));
|
|
269
|
+
console.log(`Confidence: ${data.confidence}\n`);
|
|
270
|
+
if (data.events?.length) {
|
|
271
|
+
console.log((0, ui_1.bold)('Events:\n'));
|
|
272
|
+
for (const ev of data.events) {
|
|
273
|
+
console.log(` ${(0, ui_1.green)(ev.name)} ${(0, ui_1.dim)(`[${ev.category}]`)}`);
|
|
274
|
+
console.log(` ${(0, ui_1.dim)(ev.reasoning)}`);
|
|
275
|
+
if (ev.propertySchema?.length) {
|
|
276
|
+
for (const p of ev.propertySchema) {
|
|
277
|
+
const req = p.required ? (0, ui_1.red)('REQUIRED') : (0, ui_1.dim)('optional');
|
|
278
|
+
const fmt = p.format ? (0, ui_1.cyan)(` [${p.format}]`) : '';
|
|
279
|
+
const ex = p.example !== undefined ? (0, ui_1.dim)(` e.g. ${JSON.stringify(p.example)}`) : '';
|
|
280
|
+
console.log(` ${p.name}: ${(0, ui_1.cyan)(p.type)} (${req})${fmt}${ex}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
console.log('');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (data.funnels?.length) {
|
|
287
|
+
console.log((0, ui_1.bold)('Funnels:\n'));
|
|
288
|
+
for (const f of data.funnels) {
|
|
289
|
+
console.log(` ${(0, ui_1.yellow)(f.name)} ${(0, ui_1.dim)(`[${f.category}]`)}`);
|
|
290
|
+
console.log(` Steps: ${f.steps.join(' β ')}`);
|
|
291
|
+
console.log(` ${(0, ui_1.dim)(f.reasoning)}`);
|
|
292
|
+
console.log('');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
115
296
|
function formatFrame(raw, json) {
|
|
116
297
|
if (json)
|
|
117
298
|
return raw;
|
|
@@ -9,9 +9,13 @@ export interface ExperimentsArgs {
|
|
|
9
9
|
json?: boolean;
|
|
10
10
|
profile?: string;
|
|
11
11
|
fromFile?: string;
|
|
12
|
+
site?: string;
|
|
13
|
+
id?: string;
|
|
12
14
|
key?: string;
|
|
13
15
|
name?: string;
|
|
14
16
|
description?: string;
|
|
17
|
+
variants?: string;
|
|
18
|
+
goal?: string;
|
|
15
19
|
yes?: boolean;
|
|
16
20
|
dryRun?: boolean;
|
|
17
21
|
}
|