@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.
Files changed (38) hide show
  1. package/dist/commands/add-server.js +13 -6
  2. package/dist/commands/alerts.d.ts +5 -0
  3. package/dist/commands/alerts.js +43 -15
  4. package/dist/commands/audiences.d.ts +3 -0
  5. package/dist/commands/audiences.js +34 -7
  6. package/dist/commands/events.d.ts +6 -0
  7. package/dist/commands/events.js +182 -1
  8. package/dist/commands/experiments.d.ts +4 -0
  9. package/dist/commands/experiments.js +46 -15
  10. package/dist/commands/funnels.d.ts +17 -0
  11. package/dist/commands/funnels.js +203 -0
  12. package/dist/commands/goals.d.ts +18 -0
  13. package/dist/commands/goals.js +214 -0
  14. package/dist/commands/install.d.ts +8 -0
  15. package/dist/commands/install.js +57 -1
  16. package/dist/commands/sourcemap.d.ts +17 -5
  17. package/dist/commands/sourcemap.js +73 -6
  18. package/dist/commands/watch.d.ts +45 -0
  19. package/dist/commands/watch.js +258 -0
  20. package/dist/frameworks/detect.js +29 -7
  21. package/dist/index.js +158 -13
  22. package/package.json +1 -1
  23. package/scripts/gurulu-agentic-install.mjs +275 -3
  24. package/scripts/gurulu-scan.lib.cjs +539 -19
  25. package/scripts/patches/auto-instrument/ast-helper.cjs +158 -10
  26. package/scripts/patches/auto-instrument/astro.cjs +12 -6
  27. package/scripts/patches/auto-instrument/express.cjs +23 -8
  28. package/scripts/patches/auto-instrument/fastify.cjs +7 -3
  29. package/scripts/patches/auto-instrument/hono.cjs +392 -0
  30. package/scripts/patches/auto-instrument/index.cjs +2 -0
  31. package/scripts/patches/auto-instrument/nestjs.cjs +7 -3
  32. package/scripts/patches/auto-instrument/nextjs-app-router.cjs +40 -13
  33. package/scripts/patches/auto-instrument/nextjs-pages.cjs +23 -10
  34. package/scripts/patches/auto-instrument/remix.cjs +7 -3
  35. package/scripts/patches/auto-instrument/sdk-helper-map.cjs +241 -0
  36. package/scripts/patches/auto-instrument/sveltekit.cjs +7 -3
  37. package/scripts/patches/auto-instrument/vue.cjs +7 -3
  38. package/scripts/patches/index.cjs +6 -0
@@ -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;
@@ -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
- const filled = { ...args, siteId, tenantId };
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
- * Reads all `.map` files from the specified directory and POSTs them to
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;