@gurulu/cli 0.4.0 → 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/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 +225 -0
- package/scripts/gurulu-scan.lib.cjs +539 -19
- 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
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `gurulu funnels list|show|create|update|delete` — manage conversion funnels.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.funnelsCommand = funnelsCommand;
|
|
7
|
+
const api_client_1 = require("../api-client");
|
|
8
|
+
const ui_1 = require("../utils/ui");
|
|
9
|
+
const confirm_1 = require("../utils/confirm");
|
|
10
|
+
const dry_run_1 = require("../utils/dry-run");
|
|
11
|
+
const from_file_1 = require("../utils/from-file");
|
|
12
|
+
async function funnelsCommand(args) {
|
|
13
|
+
const action = args.action || 'list';
|
|
14
|
+
switch (action) {
|
|
15
|
+
case 'list':
|
|
16
|
+
return listCmd(args);
|
|
17
|
+
case 'show':
|
|
18
|
+
return showCmd(args);
|
|
19
|
+
case 'create':
|
|
20
|
+
return createCmd(args);
|
|
21
|
+
case 'update':
|
|
22
|
+
return updateCmd(args);
|
|
23
|
+
case 'delete':
|
|
24
|
+
return deleteCmd(args);
|
|
25
|
+
default:
|
|
26
|
+
(0, ui_1.error)(`Unknown funnels action: ${action}`);
|
|
27
|
+
(0, ui_1.info)('Usage: gurulu funnels [list|show|create|update|delete]');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function parseSteps(stepsStr) {
|
|
32
|
+
return stepsStr
|
|
33
|
+
.split(',')
|
|
34
|
+
.map((s) => s.trim())
|
|
35
|
+
.filter(Boolean)
|
|
36
|
+
.map((event_name) => ({ event_name }));
|
|
37
|
+
}
|
|
38
|
+
async function listCmd(args) {
|
|
39
|
+
const qs = args.site ? `?siteId=${encodeURIComponent(args.site)}` : '';
|
|
40
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/funnels${qs}`, {
|
|
41
|
+
profile: args.profile,
|
|
42
|
+
});
|
|
43
|
+
const funnels = body.funnels || [];
|
|
44
|
+
if (args.json) {
|
|
45
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (funnels.length === 0) {
|
|
49
|
+
(0, ui_1.info)('No funnels yet.');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
process.stdout.write(['NAME', 'ID', 'STEPS', 'STATUS', 'UPDATED'].join('\t') + '\n');
|
|
53
|
+
for (const f of funnels) {
|
|
54
|
+
const stepCount = Array.isArray(f.steps) ? f.steps.length : 0;
|
|
55
|
+
process.stdout.write([
|
|
56
|
+
f.name,
|
|
57
|
+
f.id,
|
|
58
|
+
String(stepCount),
|
|
59
|
+
f.status,
|
|
60
|
+
String(f.updatedAt || '-').slice(0, 19),
|
|
61
|
+
].join('\t') + '\n');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function showCmd(args) {
|
|
65
|
+
if (!args.target) {
|
|
66
|
+
(0, ui_1.error)('Usage: gurulu funnels show <id>');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/funnels/${encodeURIComponent(args.target)}`, { profile: args.profile });
|
|
70
|
+
if (args.json) {
|
|
71
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const f = body.funnel;
|
|
75
|
+
const steps = Array.isArray(f.steps) ? f.steps : [];
|
|
76
|
+
process.stdout.write(`Name: ${f.name}\n`);
|
|
77
|
+
process.stdout.write(`ID: ${f.id}\n`);
|
|
78
|
+
process.stdout.write(`Site: ${f.siteId}\n`);
|
|
79
|
+
process.stdout.write(`Description: ${f.description || '-'}\n`);
|
|
80
|
+
process.stdout.write(`Status: ${f.status}\n`);
|
|
81
|
+
process.stdout.write(`Steps (${steps.length}):\n`);
|
|
82
|
+
for (let i = 0; i < steps.length; i++) {
|
|
83
|
+
const s = steps[i];
|
|
84
|
+
const label = s.label || s.event_name || s.eventName || '-';
|
|
85
|
+
process.stdout.write(` ${i + 1}. ${label}\n`);
|
|
86
|
+
}
|
|
87
|
+
if (f.cachedMetrics) {
|
|
88
|
+
const m = f.cachedMetrics;
|
|
89
|
+
process.stdout.write(`Overall rate: ${m.overallRate ?? '-'}\n`);
|
|
90
|
+
process.stdout.write(`Computed at: ${m.computedAt || f.lastComputedAt || '-'}\n`);
|
|
91
|
+
}
|
|
92
|
+
process.stdout.write(`Updated: ${f.updatedAt}\n`);
|
|
93
|
+
}
|
|
94
|
+
async function createCmd(args) {
|
|
95
|
+
let payload = {};
|
|
96
|
+
if (args.fromFile) {
|
|
97
|
+
payload = (0, from_file_1.loadFromFile)(args.fromFile);
|
|
98
|
+
}
|
|
99
|
+
if (args.name)
|
|
100
|
+
payload.name = args.name;
|
|
101
|
+
if (args.site)
|
|
102
|
+
payload.siteId = args.site;
|
|
103
|
+
if (args.steps)
|
|
104
|
+
payload.steps = parseSteps(args.steps);
|
|
105
|
+
if (!payload.name) {
|
|
106
|
+
(0, ui_1.error)('name is required (pass --name or --from-file)');
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
if (!payload.siteId) {
|
|
110
|
+
(0, ui_1.error)('site is required (pass --site)');
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
if (!Array.isArray(payload.steps) || payload.steps.length < 2) {
|
|
114
|
+
(0, ui_1.error)('At least 2 steps are required (pass --steps as comma-separated event names)');
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
const qs = args.dryRun ? '?dryRun=1' : '';
|
|
118
|
+
if (args.dryRun) {
|
|
119
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/funnels${qs}`, {
|
|
120
|
+
profile: args.profile,
|
|
121
|
+
method: 'POST',
|
|
122
|
+
json: payload,
|
|
123
|
+
});
|
|
124
|
+
(0, dry_run_1.printDryRun)(body, args.json);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const ok = await (0, confirm_1.promptConfirm)(`Create funnel '${payload.name}'?`, {
|
|
128
|
+
yes: args.yes,
|
|
129
|
+
defaultYes: true,
|
|
130
|
+
});
|
|
131
|
+
if (!ok) {
|
|
132
|
+
(0, ui_1.info)('Aborted.');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const body = await (0, api_client_1.cliApiJson)('/api/cli/funnels', {
|
|
136
|
+
profile: args.profile,
|
|
137
|
+
method: 'POST',
|
|
138
|
+
json: payload,
|
|
139
|
+
});
|
|
140
|
+
if (args.json) {
|
|
141
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
(0, ui_1.success)(`Created funnel ${body.funnel?.id ?? ''}`);
|
|
145
|
+
}
|
|
146
|
+
async function updateCmd(args) {
|
|
147
|
+
if (!args.target) {
|
|
148
|
+
(0, ui_1.error)('Usage: gurulu funnels update <id> [--name ...] [--steps ...]');
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
let payload = {};
|
|
152
|
+
if (args.fromFile) {
|
|
153
|
+
payload = (0, from_file_1.loadFromFile)(args.fromFile);
|
|
154
|
+
}
|
|
155
|
+
if (args.name)
|
|
156
|
+
payload.name = args.name;
|
|
157
|
+
if (args.description !== undefined)
|
|
158
|
+
payload.description = args.description;
|
|
159
|
+
if (args.steps)
|
|
160
|
+
payload.steps = parseSteps(args.steps);
|
|
161
|
+
const qs = args.dryRun ? '?dryRun=1' : '';
|
|
162
|
+
if (args.dryRun) {
|
|
163
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/funnels/${encodeURIComponent(args.target)}${qs}`, { profile: args.profile, method: 'PUT', json: payload });
|
|
164
|
+
(0, dry_run_1.printDryRun)(body, args.json);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const ok = await (0, confirm_1.promptConfirm)(`Update funnel '${args.target}'?`, {
|
|
168
|
+
yes: args.yes,
|
|
169
|
+
defaultYes: true,
|
|
170
|
+
});
|
|
171
|
+
if (!ok) {
|
|
172
|
+
(0, ui_1.info)('Aborted.');
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/funnels/${encodeURIComponent(args.target)}`, { profile: args.profile, method: 'PUT', json: payload });
|
|
176
|
+
if (args.json) {
|
|
177
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
(0, ui_1.success)(`Updated funnel ${args.target}`);
|
|
181
|
+
}
|
|
182
|
+
async function deleteCmd(args) {
|
|
183
|
+
if (!args.target) {
|
|
184
|
+
(0, ui_1.error)('Usage: gurulu funnels delete <id>');
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
const qs = args.dryRun ? '?dryRun=1' : '';
|
|
188
|
+
if (args.dryRun) {
|
|
189
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/funnels/${encodeURIComponent(args.target)}${qs}`, { profile: args.profile, method: 'DELETE' });
|
|
190
|
+
(0, dry_run_1.printDryRun)(body, args.json);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const ok = await (0, confirm_1.promptConfirm)(`About to delete funnel '${args.target}'. Continue?`, { yes: args.yes, defaultYes: false });
|
|
194
|
+
if (!ok) {
|
|
195
|
+
(0, ui_1.info)('Aborted.');
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
await (0, api_client_1.cliApiJson)(`/api/cli/funnels/${encodeURIComponent(args.target)}`, {
|
|
199
|
+
profile: args.profile,
|
|
200
|
+
method: 'DELETE',
|
|
201
|
+
});
|
|
202
|
+
(0, ui_1.success)(`Deleted funnel ${args.target}`);
|
|
203
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `gurulu goals list|show|create|update|delete` — manage conversion goals.
|
|
3
|
+
*/
|
|
4
|
+
export interface GoalsArgs {
|
|
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
|
+
event?: string;
|
|
14
|
+
propertiesFilter?: string;
|
|
15
|
+
yes?: boolean;
|
|
16
|
+
dryRun?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function goalsCommand(args: GoalsArgs): Promise<void>;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `gurulu goals list|show|create|update|delete` — manage conversion goals.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.goalsCommand = goalsCommand;
|
|
7
|
+
const api_client_1 = require("../api-client");
|
|
8
|
+
const ui_1 = require("../utils/ui");
|
|
9
|
+
const confirm_1 = require("../utils/confirm");
|
|
10
|
+
const dry_run_1 = require("../utils/dry-run");
|
|
11
|
+
const from_file_1 = require("../utils/from-file");
|
|
12
|
+
async function goalsCommand(args) {
|
|
13
|
+
const action = args.action || 'list';
|
|
14
|
+
switch (action) {
|
|
15
|
+
case 'list':
|
|
16
|
+
return listCmd(args);
|
|
17
|
+
case 'show':
|
|
18
|
+
return showCmd(args);
|
|
19
|
+
case 'create':
|
|
20
|
+
return createCmd(args);
|
|
21
|
+
case 'update':
|
|
22
|
+
return updateCmd(args);
|
|
23
|
+
case 'delete':
|
|
24
|
+
return deleteCmd(args);
|
|
25
|
+
default:
|
|
26
|
+
(0, ui_1.error)(`Unknown goals action: ${action}`);
|
|
27
|
+
(0, ui_1.info)('Usage: gurulu goals [list|show|create|update|delete]');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function listCmd(args) {
|
|
32
|
+
const qs = args.site ? `?siteId=${encodeURIComponent(args.site)}` : '';
|
|
33
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/goals${qs}`, {
|
|
34
|
+
profile: args.profile,
|
|
35
|
+
});
|
|
36
|
+
const goals = body.goals || [];
|
|
37
|
+
if (args.json) {
|
|
38
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (goals.length === 0) {
|
|
42
|
+
(0, ui_1.info)('No goals yet.');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
process.stdout.write(['NAME', 'ID', 'EVENT', 'STATUS', 'CONVERSIONS', 'UPDATED'].join('\t') + '\n');
|
|
46
|
+
for (const g of goals) {
|
|
47
|
+
const eventName = g.eventPattern?.event_name || '-';
|
|
48
|
+
process.stdout.write([
|
|
49
|
+
g.name,
|
|
50
|
+
g.id,
|
|
51
|
+
String(eventName),
|
|
52
|
+
g.status,
|
|
53
|
+
String(g.totalConversions ?? 0),
|
|
54
|
+
String(g.updatedAt || '-').slice(0, 19),
|
|
55
|
+
].join('\t') + '\n');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function showCmd(args) {
|
|
59
|
+
if (!args.target) {
|
|
60
|
+
(0, ui_1.error)('Usage: gurulu goals show <id>');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/goals/${encodeURIComponent(args.target)}`, { profile: args.profile });
|
|
64
|
+
if (args.json) {
|
|
65
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const g = body.goal;
|
|
69
|
+
const eventName = g.eventPattern?.event_name || '-';
|
|
70
|
+
const propsFilter = g.eventPattern?.properties_filter || null;
|
|
71
|
+
process.stdout.write(`Name: ${g.name}\n`);
|
|
72
|
+
process.stdout.write(`ID: ${g.id}\n`);
|
|
73
|
+
process.stdout.write(`Site: ${g.siteId}\n`);
|
|
74
|
+
process.stdout.write(`Description: ${g.description || '-'}\n`);
|
|
75
|
+
process.stdout.write(`Event: ${eventName}\n`);
|
|
76
|
+
process.stdout.write(`Properties filter: ${propsFilter ? JSON.stringify(propsFilter) : '-'}\n`);
|
|
77
|
+
process.stdout.write(`Status: ${g.status}\n`);
|
|
78
|
+
process.stdout.write(`Conversions: ${g.totalConversions}\n`);
|
|
79
|
+
process.stdout.write(`Last converted: ${g.lastConvertedAt || '-'}\n`);
|
|
80
|
+
process.stdout.write(`Updated: ${g.updatedAt}\n`);
|
|
81
|
+
}
|
|
82
|
+
async function createCmd(args) {
|
|
83
|
+
let payload = {};
|
|
84
|
+
if (args.fromFile) {
|
|
85
|
+
payload = (0, from_file_1.loadFromFile)(args.fromFile);
|
|
86
|
+
}
|
|
87
|
+
if (args.name)
|
|
88
|
+
payload.name = args.name;
|
|
89
|
+
if (args.site)
|
|
90
|
+
payload.siteId = args.site;
|
|
91
|
+
// Build eventPattern from --event and --properties-filter
|
|
92
|
+
const eventPattern = payload.eventPattern || {};
|
|
93
|
+
if (args.event)
|
|
94
|
+
eventPattern.event_name = args.event;
|
|
95
|
+
if (args.propertiesFilter) {
|
|
96
|
+
try {
|
|
97
|
+
eventPattern.properties_filter = JSON.parse(args.propertiesFilter);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
(0, ui_1.error)('--properties-filter must be valid JSON');
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (Object.keys(eventPattern).length > 0)
|
|
105
|
+
payload.eventPattern = eventPattern;
|
|
106
|
+
if (!payload.name) {
|
|
107
|
+
(0, ui_1.error)('name is required (pass --name or --from-file)');
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
if (!payload.siteId) {
|
|
111
|
+
(0, ui_1.error)('site is required (pass --site)');
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
const qs = args.dryRun ? '?dryRun=1' : '';
|
|
115
|
+
if (args.dryRun) {
|
|
116
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/goals${qs}`, {
|
|
117
|
+
profile: args.profile,
|
|
118
|
+
method: 'POST',
|
|
119
|
+
json: payload,
|
|
120
|
+
});
|
|
121
|
+
(0, dry_run_1.printDryRun)(body, args.json);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const ok = await (0, confirm_1.promptConfirm)(`Create goal '${payload.name}'?`, {
|
|
125
|
+
yes: args.yes,
|
|
126
|
+
defaultYes: true,
|
|
127
|
+
});
|
|
128
|
+
if (!ok) {
|
|
129
|
+
(0, ui_1.info)('Aborted.');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const body = await (0, api_client_1.cliApiJson)('/api/cli/goals', {
|
|
133
|
+
profile: args.profile,
|
|
134
|
+
method: 'POST',
|
|
135
|
+
json: payload,
|
|
136
|
+
});
|
|
137
|
+
if (args.json) {
|
|
138
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
(0, ui_1.success)(`Created goal ${body.goal?.id ?? ''}`);
|
|
142
|
+
}
|
|
143
|
+
async function updateCmd(args) {
|
|
144
|
+
if (!args.target) {
|
|
145
|
+
(0, ui_1.error)('Usage: gurulu goals update <id> [--name ...] [--event ...]');
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
let payload = {};
|
|
149
|
+
if (args.fromFile) {
|
|
150
|
+
payload = (0, from_file_1.loadFromFile)(args.fromFile);
|
|
151
|
+
}
|
|
152
|
+
if (args.name)
|
|
153
|
+
payload.name = args.name;
|
|
154
|
+
if (args.description !== undefined)
|
|
155
|
+
payload.description = args.description;
|
|
156
|
+
// Build eventPattern if --event or --properties-filter provided
|
|
157
|
+
if (args.event || args.propertiesFilter) {
|
|
158
|
+
const eventPattern = payload.eventPattern || {};
|
|
159
|
+
if (args.event)
|
|
160
|
+
eventPattern.event_name = args.event;
|
|
161
|
+
if (args.propertiesFilter) {
|
|
162
|
+
try {
|
|
163
|
+
eventPattern.properties_filter = JSON.parse(args.propertiesFilter);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
(0, ui_1.error)('--properties-filter must be valid JSON');
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
payload.eventPattern = eventPattern;
|
|
171
|
+
}
|
|
172
|
+
const qs = args.dryRun ? '?dryRun=1' : '';
|
|
173
|
+
if (args.dryRun) {
|
|
174
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/goals/${encodeURIComponent(args.target)}${qs}`, { profile: args.profile, method: 'PUT', json: payload });
|
|
175
|
+
(0, dry_run_1.printDryRun)(body, args.json);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const ok = await (0, confirm_1.promptConfirm)(`Update goal '${args.target}'?`, {
|
|
179
|
+
yes: args.yes,
|
|
180
|
+
defaultYes: true,
|
|
181
|
+
});
|
|
182
|
+
if (!ok) {
|
|
183
|
+
(0, ui_1.info)('Aborted.');
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/goals/${encodeURIComponent(args.target)}`, { profile: args.profile, method: 'PUT', json: payload });
|
|
187
|
+
if (args.json) {
|
|
188
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
(0, ui_1.success)(`Updated goal ${args.target}`);
|
|
192
|
+
}
|
|
193
|
+
async function deleteCmd(args) {
|
|
194
|
+
if (!args.target) {
|
|
195
|
+
(0, ui_1.error)('Usage: gurulu goals delete <id>');
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
const qs = args.dryRun ? '?dryRun=1' : '';
|
|
199
|
+
if (args.dryRun) {
|
|
200
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/goals/${encodeURIComponent(args.target)}${qs}`, { profile: args.profile, method: 'DELETE' });
|
|
201
|
+
(0, dry_run_1.printDryRun)(body, args.json);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const ok = await (0, confirm_1.promptConfirm)(`About to delete goal '${args.target}'. Continue?`, { yes: args.yes, defaultYes: false });
|
|
205
|
+
if (!ok) {
|
|
206
|
+
(0, ui_1.info)('Aborted.');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
await (0, api_client_1.cliApiJson)(`/api/cli/goals/${encodeURIComponent(args.target)}`, {
|
|
210
|
+
profile: args.profile,
|
|
211
|
+
method: 'DELETE',
|
|
212
|
+
});
|
|
213
|
+
(0, ui_1.success)(`Deleted goal ${args.target}`);
|
|
214
|
+
}
|
|
@@ -38,6 +38,14 @@ export interface InstallArgs {
|
|
|
38
38
|
autoProperties?: boolean;
|
|
39
39
|
/** Internal: path to the serialized intent JSON forwarded to the agentic-install script. */
|
|
40
40
|
intentResultPath?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Internal: API token (active profile secret key) forwarded to the
|
|
43
|
+
* agentic-install script as `--token`. The script's `tryLlmExtraction`
|
|
44
|
+
* needs this to call the LLM property-extraction endpoint; without it
|
|
45
|
+
* extraction silently 401s and every injected `gurulu.track(...)` ends
|
|
46
|
+
* up with `// TODO: <prop>` placeholders. See Sprint A fix A2.
|
|
47
|
+
*/
|
|
48
|
+
authToken?: string;
|
|
41
49
|
}
|
|
42
50
|
export interface IntentPreSeedResult {
|
|
43
51
|
ok: boolean;
|
package/dist/commands/install.js
CHANGED
|
@@ -323,6 +323,11 @@ async function runInstallFlow(args, deps, scriptsDir) {
|
|
|
323
323
|
if (args.framework) {
|
|
324
324
|
planArgs.push('--framework', args.framework);
|
|
325
325
|
}
|
|
326
|
+
// Sprint A fix A2 — forward the active profile's secret key so the planner
|
|
327
|
+
// can call the LLM property-extraction endpoint while building the diff.
|
|
328
|
+
if (args.authToken) {
|
|
329
|
+
planArgs.push('--token', args.authToken);
|
|
330
|
+
}
|
|
326
331
|
// Phase 18.7 B — include auto-instrument diff in the dry-run plan so the
|
|
327
332
|
// consent prompt / preview shows both script-tag + track-call changes.
|
|
328
333
|
if (args.autoInstrument) {
|
|
@@ -366,6 +371,14 @@ async function runInstallFlow(args, deps, scriptsDir) {
|
|
|
366
371
|
if (args.framework) {
|
|
367
372
|
applyArgs.push('--framework', args.framework);
|
|
368
373
|
}
|
|
374
|
+
// Sprint A fix A2 — forward the active profile's secret key so the
|
|
375
|
+
// agentic-install script's `tryLlmExtraction` can authenticate against
|
|
376
|
+
// the LLM property-extraction endpoint. Without it the request 401s and
|
|
377
|
+
// every injected `gurulu.track(...)` falls back to `// TODO: <prop>`
|
|
378
|
+
// placeholders, breaking the auto-instrument feature.
|
|
379
|
+
if (args.authToken) {
|
|
380
|
+
applyArgs.push('--token', args.authToken);
|
|
381
|
+
}
|
|
369
382
|
// Phase 18.7 B — forward auto-instrument flag + intent result path.
|
|
370
383
|
if (args.autoInstrument) {
|
|
371
384
|
applyArgs.push('--auto-instrument');
|
|
@@ -571,6 +584,32 @@ async function runInstallFlow(args, deps, scriptsDir) {
|
|
|
571
584
|
console.log((0, ui_1.dim)(' │ ') + 'Docs: https://gurulu.io/docs/quick-start');
|
|
572
585
|
console.log((0, ui_1.dim)(' └─────────────────────────────────────────────'));
|
|
573
586
|
console.log('');
|
|
587
|
+
// Goals & funnels management
|
|
588
|
+
log(deps, 'info', `${(0, ui_1.cyan)('Goals & Funnels')}`);
|
|
589
|
+
log(deps, 'info', ` ${(0, ui_1.cyan)('gurulu goals list')} ${(0, ui_1.dim)('List all conversion goals')}`);
|
|
590
|
+
log(deps, 'info', ` ${(0, ui_1.cyan)('gurulu goals create')} ${(0, ui_1.dim)('Create a new goal interactively')}`);
|
|
591
|
+
log(deps, 'info', ` ${(0, ui_1.cyan)('gurulu funnels create')} ${(0, ui_1.dim)('Define a multi-step funnel')}`);
|
|
592
|
+
console.log('');
|
|
593
|
+
// Server-side setup
|
|
594
|
+
log(deps, 'info', `${(0, ui_1.cyan)('Server-Side Tracking')}`);
|
|
595
|
+
log(deps, 'info', ` ${(0, ui_1.dim)('Add server-side event tracking with:')} ${(0, ui_1.cyan)('gurulu add-server')}`);
|
|
596
|
+
log(deps, 'info', ` ${(0, ui_1.dim)('Or manually:')}`);
|
|
597
|
+
log(deps, 'info', ` ${(0, ui_1.cyan)('npm install @gurulu/node')}`);
|
|
598
|
+
log(deps, 'info', ` ${(0, ui_1.dim)('import { Gurulu } from \'@gurulu/node\';')}`);
|
|
599
|
+
log(deps, 'info', ` ${(0, ui_1.dim)('const gurulu = new Gurulu({ siteId, serverApiKey });')}`);
|
|
600
|
+
log(deps, 'info', ` ${(0, ui_1.dim)('gurulu.track(\'order_completed\', { userId, revenue });')}`);
|
|
601
|
+
console.log('');
|
|
602
|
+
// DataLayer Bridge for GTM
|
|
603
|
+
log(deps, 'info', `${(0, ui_1.cyan)('DataLayer Bridge')} ${(0, ui_1.dim)('(for GTM users)')}`);
|
|
604
|
+
log(deps, 'info', ` ${(0, ui_1.dim)('Auto-capture all Google Tag Manager events:')}`);
|
|
605
|
+
log(deps, 'info', ` ${(0, ui_1.cyan)('window.gurulu.loadDataLayerBridge()')}`);
|
|
606
|
+
console.log('');
|
|
607
|
+
// identify() guidance
|
|
608
|
+
log(deps, 'info', `${(0, ui_1.cyan)('User Identification')}`);
|
|
609
|
+
log(deps, 'info', ` ${(0, ui_1.dim)('Web:')} ${(0, ui_1.cyan)('window.gurulu.identify(\'user_123\', { email, plan })')}`);
|
|
610
|
+
log(deps, 'info', ` ${(0, ui_1.dim)('Server:')} ${(0, ui_1.cyan)('gurulu.identify(\'user_123\', { email, plan })')}`);
|
|
611
|
+
log(deps, 'info', ` ${(0, ui_1.dim)('Call on login/signup to link events to known users.')}`);
|
|
612
|
+
console.log('');
|
|
574
613
|
}
|
|
575
614
|
return summary;
|
|
576
615
|
}
|
|
@@ -821,6 +860,9 @@ async function runAuthenticatedInstallFlow(args, authDeps, installDeps, scriptsD
|
|
|
821
860
|
...filled,
|
|
822
861
|
autoInstrument: autoInstrumentEnabled,
|
|
823
862
|
intentResultPath,
|
|
863
|
+
// Sprint A fix A2 — propagate the active profile secret key so the core
|
|
864
|
+
// flow can forward it to the agentic-install script as `--token`.
|
|
865
|
+
authToken: authDeps.profile.secret_key,
|
|
824
866
|
};
|
|
825
867
|
// Run the core flow.
|
|
826
868
|
const summary = await runInstallFlow(runArgs, installDeps, scriptsDir);
|
|
@@ -901,6 +943,13 @@ async function runAuthenticatedInstallFlow(args, authDeps, installDeps, scriptsD
|
|
|
901
943
|
async function installCommand(args) {
|
|
902
944
|
const scriptsDir = resolveScriptsDir();
|
|
903
945
|
const deps = createDefaultDeps(scriptsDir);
|
|
946
|
+
// Sprint A fix A3 — make `--verify` opt-in (default false). The verifier
|
|
947
|
+
// (`scripts/gurulu-verify-install.lib.cjs:312`) hard-`require`s
|
|
948
|
+
// `playwright-core`, which is NOT a runtime dependency of @gurulu/cli, so a
|
|
949
|
+
// default-on verify aborts every fresh install with "Verify threw". Treat
|
|
950
|
+
// verify as strictly opt-in: only `args.verify === true` runs it. We also
|
|
951
|
+
// patch the CLI option default in `src/index.ts` so the flag matches.
|
|
952
|
+
args = { ...args, verify: args.verify === true };
|
|
904
953
|
// Legacy unauthenticated path: --site-id passed explicitly and no active profile.
|
|
905
954
|
const profile = await (0, config_1.getActiveProfile)({ profile: args.profile });
|
|
906
955
|
const legacyMode = !!args.siteId && !profile;
|
|
@@ -914,7 +963,14 @@ async function installCommand(args) {
|
|
|
914
963
|
if (!tenantId)
|
|
915
964
|
tenantId = await deps.prompt(' Tenant ID: ');
|
|
916
965
|
}
|
|
917
|
-
|
|
966
|
+
// Sprint A fix A2 — legacy callers can supply a token via env so that
|
|
967
|
+
// auto-instrument property extraction still works without a profile.
|
|
968
|
+
const filled = {
|
|
969
|
+
...args,
|
|
970
|
+
siteId,
|
|
971
|
+
tenantId,
|
|
972
|
+
authToken: args.authToken || process.env.GURULU_API_KEY || process.env.GURULU_SECRET_KEY,
|
|
973
|
+
};
|
|
918
974
|
const summary = await runInstallFlow(filled, deps, scriptsDir);
|
|
919
975
|
if (summary.errors.length > 0)
|
|
920
976
|
process.exit(1);
|
|
@@ -1,21 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `gurulu sourcemap upload` — Upload source maps for error deobfuscation.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* `/api/errors/sourcemaps` with Bearer auth. The server stores them on
|
|
6
|
-
* disk for later stack trace deobfuscation (follow-up task).
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
4
|
+
* Web (default):
|
|
9
5
|
* gurulu sourcemap upload --release v1.0.0 --dir .next/static/
|
|
6
|
+
*
|
|
7
|
+
* Native (C3 — iOS dSYM / Android ProGuard):
|
|
8
|
+
* gurulu sourcemap upload --platform=ios --version=1.4.2 --bundle-id=com.example.app --file=app.xcarchive/dSYMs/MyApp.dSYM.zip
|
|
9
|
+
* gurulu sourcemap upload --platform=android --version=1.4.2 --bundle-id=com.example.app --file=app/build/outputs/mapping/release/mapping.txt
|
|
10
|
+
*
|
|
11
|
+
* The native variant POSTs a single `file` to `/api/sourcemap/native`, which
|
|
12
|
+
* persists it under {SOURCEMAP_STORAGE_PATH}/native/{siteId}/{platform}/{release}/
|
|
13
|
+
* and registers a `NativeSymbolFile` row.
|
|
10
14
|
*/
|
|
11
15
|
import { loadActiveProfile } from '../config';
|
|
12
16
|
export interface SourcemapArgs {
|
|
13
17
|
action?: string;
|
|
14
18
|
release?: string;
|
|
19
|
+
/** Alias of --release for native uploads. */
|
|
20
|
+
version?: string;
|
|
15
21
|
dir?: string;
|
|
22
|
+
/** Native uploads: path to the dSYM zip or `mapping.txt`. */
|
|
23
|
+
file?: string;
|
|
24
|
+
/** Native uploads: iOS bundleId or Android applicationId. */
|
|
25
|
+
bundleId?: string;
|
|
26
|
+
appId?: string;
|
|
16
27
|
site?: string;
|
|
17
28
|
profile?: string;
|
|
18
29
|
json?: boolean;
|
|
30
|
+
platform?: 'web' | 'server' | 'ios' | 'android';
|
|
19
31
|
}
|
|
20
32
|
export declare function sourcemapCommand(args: SourcemapArgs): Promise<void>;
|
|
21
33
|
export declare const _loadActiveProfile: typeof loadActiveProfile;
|