@gurulu/cli 0.4.0 → 0.4.2
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/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 +57 -1
- 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 +275 -3
- package/scripts/gurulu-scan.lib.cjs +539 -19
- package/scripts/patches/auto-instrument/ast-helper.cjs +158 -10
- package/scripts/patches/auto-instrument/astro.cjs +12 -6
- package/scripts/patches/auto-instrument/express.cjs +23 -8
- package/scripts/patches/auto-instrument/fastify.cjs +7 -3
- package/scripts/patches/auto-instrument/hono.cjs +392 -0
- package/scripts/patches/auto-instrument/index.cjs +2 -0
- package/scripts/patches/auto-instrument/nestjs.cjs +7 -3
- package/scripts/patches/auto-instrument/nextjs-app-router.cjs +40 -13
- package/scripts/patches/auto-instrument/nextjs-pages.cjs +23 -10
- package/scripts/patches/auto-instrument/remix.cjs +7 -3
- package/scripts/patches/auto-instrument/sdk-helper-map.cjs +241 -0
- package/scripts/patches/auto-instrument/sveltekit.cjs +7 -3
- package/scripts/patches/auto-instrument/vue.cjs +7 -3
- package/scripts/patches/index.cjs +6 -0
|
@@ -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
|
}
|
|
@@ -36,7 +36,8 @@ async function experimentsCommand(args) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
async function listCmd(args) {
|
|
39
|
-
const
|
|
39
|
+
const qs = args.site ? `?siteId=${encodeURIComponent(args.site)}` : '';
|
|
40
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/experiments${qs}`, {
|
|
40
41
|
profile: args.profile,
|
|
41
42
|
});
|
|
42
43
|
if (args.json) {
|
|
@@ -109,14 +110,31 @@ async function createCmd(args) {
|
|
|
109
110
|
let payload = {};
|
|
110
111
|
if (args.fromFile)
|
|
111
112
|
payload = (0, from_file_1.loadFromFile)(args.fromFile);
|
|
113
|
+
if (args.site)
|
|
114
|
+
payload.siteId = args.site;
|
|
112
115
|
if (args.key)
|
|
113
116
|
payload.key = args.key;
|
|
114
117
|
if (args.name)
|
|
115
118
|
payload.name = args.name;
|
|
116
119
|
if (args.description !== undefined)
|
|
117
120
|
payload.description = args.description;
|
|
118
|
-
if (
|
|
119
|
-
|
|
121
|
+
if (args.goal)
|
|
122
|
+
payload.goalId = args.goal;
|
|
123
|
+
if (args.variants) {
|
|
124
|
+
try {
|
|
125
|
+
payload.variants = JSON.parse(args.variants);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
(0, ui_1.error)('--variants must be valid JSON');
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (!payload.name || !Array.isArray(payload.variants)) {
|
|
133
|
+
(0, ui_1.error)('name and variants[] are required (pass --name --variants \'[...]\' or --from-file).');
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
if (!payload.siteId) {
|
|
137
|
+
(0, ui_1.error)('site is required (pass --site)');
|
|
120
138
|
process.exit(1);
|
|
121
139
|
}
|
|
122
140
|
if (args.dryRun) {
|
|
@@ -146,8 +164,9 @@ async function createCmd(args) {
|
|
|
146
164
|
(0, ui_1.success)(`Created experiment ${body.experiment?.key ?? ''}`);
|
|
147
165
|
}
|
|
148
166
|
async function updateCmd(args) {
|
|
149
|
-
|
|
150
|
-
|
|
167
|
+
const resolveTarget = args.id || args.target;
|
|
168
|
+
if (!resolveTarget) {
|
|
169
|
+
(0, ui_1.error)('Usage: gurulu experiments update <key> [--id ...] [--name ...] [--variants ...]');
|
|
151
170
|
process.exit(1);
|
|
152
171
|
}
|
|
153
172
|
let payload = {};
|
|
@@ -157,39 +176,51 @@ async function updateCmd(args) {
|
|
|
157
176
|
payload.name = args.name;
|
|
158
177
|
if (args.description !== undefined)
|
|
159
178
|
payload.description = args.description;
|
|
179
|
+
if (args.goal)
|
|
180
|
+
payload.goalId = args.goal;
|
|
181
|
+
if (args.variants) {
|
|
182
|
+
try {
|
|
183
|
+
payload.variants = JSON.parse(args.variants);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
(0, ui_1.error)('--variants must be valid JSON');
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
160
190
|
if (args.dryRun) {
|
|
161
|
-
const body = await (0, api_client_1.cliApiJson)(`/api/cli/experiments/${encodeURIComponent(
|
|
191
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/experiments/${encodeURIComponent(resolveTarget)}?dryRun=1`, { profile: args.profile, method: 'PATCH', json: payload });
|
|
162
192
|
(0, dry_run_1.printDryRun)(body, args.json);
|
|
163
193
|
return;
|
|
164
194
|
}
|
|
165
|
-
const ok = await (0, confirm_1.promptConfirm)(`Update experiment '${
|
|
195
|
+
const ok = await (0, confirm_1.promptConfirm)(`Update experiment '${resolveTarget}'?`, {
|
|
166
196
|
yes: args.yes,
|
|
167
197
|
defaultYes: true,
|
|
168
198
|
});
|
|
169
199
|
if (!ok)
|
|
170
200
|
return (0, ui_1.info)('Aborted.');
|
|
171
|
-
const body = await (0, api_client_1.cliApiJson)(`/api/cli/experiments/${encodeURIComponent(
|
|
201
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/experiments/${encodeURIComponent(resolveTarget)}`, { profile: args.profile, method: 'PATCH', json: payload });
|
|
172
202
|
if (args.json) {
|
|
173
203
|
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
174
204
|
return;
|
|
175
205
|
}
|
|
176
|
-
(0, ui_1.success)(`Updated experiment ${
|
|
206
|
+
(0, ui_1.success)(`Updated experiment ${resolveTarget}`);
|
|
177
207
|
}
|
|
178
208
|
async function deleteCmd(args) {
|
|
179
|
-
|
|
180
|
-
|
|
209
|
+
const resolveTarget = args.id || args.target;
|
|
210
|
+
if (!resolveTarget) {
|
|
211
|
+
(0, ui_1.error)('Usage: gurulu experiments delete <key> [--id ...]');
|
|
181
212
|
process.exit(1);
|
|
182
213
|
}
|
|
183
214
|
if (args.dryRun) {
|
|
184
|
-
const body = await (0, api_client_1.cliApiJson)(`/api/cli/experiments/${encodeURIComponent(
|
|
215
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/experiments/${encodeURIComponent(resolveTarget)}?dryRun=1`, { profile: args.profile, method: 'DELETE' });
|
|
185
216
|
(0, dry_run_1.printDryRun)(body, args.json);
|
|
186
217
|
return;
|
|
187
218
|
}
|
|
188
|
-
const ok = await (0, confirm_1.promptConfirm)(`About to delete experiment '${
|
|
219
|
+
const ok = await (0, confirm_1.promptConfirm)(`About to delete experiment '${resolveTarget}'. Continue?`, { yes: args.yes, defaultYes: false });
|
|
189
220
|
if (!ok)
|
|
190
221
|
return (0, ui_1.info)('Aborted.');
|
|
191
|
-
await (0, api_client_1.cliApiJson)(`/api/cli/experiments/${encodeURIComponent(
|
|
192
|
-
(0, ui_1.success)(`Deleted experiment ${
|
|
222
|
+
await (0, api_client_1.cliApiJson)(`/api/cli/experiments/${encodeURIComponent(resolveTarget)}`, { profile: args.profile, method: 'DELETE' });
|
|
223
|
+
(0, ui_1.success)(`Deleted experiment ${resolveTarget}`);
|
|
193
224
|
}
|
|
194
225
|
async function startCmd(args) {
|
|
195
226
|
if (!args.target) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `gurulu funnels list|show|create|update|delete` — manage conversion funnels.
|
|
3
|
+
*/
|
|
4
|
+
export interface FunnelsArgs {
|
|
5
|
+
action?: string;
|
|
6
|
+
target?: string;
|
|
7
|
+
json?: boolean;
|
|
8
|
+
profile?: string;
|
|
9
|
+
fromFile?: string;
|
|
10
|
+
site?: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
steps?: string;
|
|
14
|
+
yes?: boolean;
|
|
15
|
+
dryRun?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function funnelsCommand(args: FunnelsArgs): Promise<void>;
|