@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.
Files changed (39) hide show
  1. package/README.md +61 -24
  2. package/dist/api-client.js +1 -1
  3. package/dist/commands/add-server.js +13 -6
  4. package/dist/commands/alerts.d.ts +5 -0
  5. package/dist/commands/alerts.js +43 -15
  6. package/dist/commands/audiences.d.ts +3 -0
  7. package/dist/commands/audiences.js +34 -7
  8. package/dist/commands/events.d.ts +6 -0
  9. package/dist/commands/events.js +182 -1
  10. package/dist/commands/experiments.d.ts +4 -0
  11. package/dist/commands/experiments.js +46 -15
  12. package/dist/commands/funnels.d.ts +17 -0
  13. package/dist/commands/funnels.js +203 -0
  14. package/dist/commands/goals.d.ts +18 -0
  15. package/dist/commands/goals.js +214 -0
  16. package/dist/commands/install.d.ts +8 -0
  17. package/dist/commands/install.js +74 -4
  18. package/dist/commands/sourcemap.d.ts +17 -5
  19. package/dist/commands/sourcemap.js +73 -6
  20. package/dist/commands/watch.d.ts +45 -0
  21. package/dist/commands/watch.js +258 -0
  22. package/dist/frameworks/detect.js +29 -7
  23. package/dist/index.js +158 -13
  24. package/package.json +1 -1
  25. package/scripts/gurulu-agentic-install.mjs +225 -0
  26. package/scripts/gurulu-scan.lib.cjs +539 -19
  27. package/scripts/patches/astro.patch.cjs +1 -0
  28. package/scripts/patches/auto-instrument/hono.cjs +381 -0
  29. package/scripts/patches/auto-instrument/index.cjs +2 -0
  30. package/scripts/patches/auto-instrument/nextjs-app-router.cjs +13 -4
  31. package/scripts/patches/express.patch.cjs +2 -2
  32. package/scripts/patches/fastify.patch.cjs +1 -0
  33. package/scripts/patches/nestjs.patch.cjs +1 -0
  34. package/scripts/patches/nextjs-app-router.patch.cjs +2 -2
  35. package/scripts/patches/nextjs-pages.patch.cjs +1 -0
  36. package/scripts/patches/remix.patch.cjs +1 -0
  37. package/scripts/patches/sveltekit.patch.cjs +1 -0
  38. package/scripts/patches/vite-react.patch.cjs +1 -0
  39. package/scripts/patches/vue.patch.cjs +1 -0
@@ -36,7 +36,8 @@ async function experimentsCommand(args) {
36
36
  }
37
37
  }
38
38
  async function listCmd(args) {
39
- const body = await (0, api_client_1.cliApiJson)('/api/cli/experiments', {
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 (!payload.key || !payload.name || !Array.isArray(payload.variants)) {
119
- (0, ui_1.error)('key, name, and variants[] are required.');
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
- if (!args.target) {
150
- (0, ui_1.error)('Usage: gurulu experiments update <key>');
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(args.target)}?dryRun=1`, { profile: args.profile, method: 'PATCH', json: payload });
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 '${args.target}'?`, {
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(args.target)}`, { profile: args.profile, method: 'PATCH', json: payload });
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 ${args.target}`);
206
+ (0, ui_1.success)(`Updated experiment ${resolveTarget}`);
177
207
  }
178
208
  async function deleteCmd(args) {
179
- if (!args.target) {
180
- (0, ui_1.error)('Usage: gurulu experiments delete <key>');
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(args.target)}?dryRun=1`, { profile: args.profile, method: 'DELETE' });
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 '${args.target}'. Continue?`, { yes: args.yes, defaultYes: false });
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(args.target)}`, { profile: args.profile, method: 'DELETE' });
192
- (0, ui_1.success)(`Deleted experiment ${args.target}`);
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>;
@@ -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;