@gurulu/cli 0.3.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -24
- package/dist/api-client.js +1 -1
- package/dist/commands/add-server.js +13 -6
- package/dist/commands/alerts.d.ts +5 -0
- package/dist/commands/alerts.js +43 -15
- package/dist/commands/audiences.d.ts +3 -0
- package/dist/commands/audiences.js +34 -7
- package/dist/commands/events.d.ts +6 -0
- package/dist/commands/events.js +182 -1
- package/dist/commands/experiments.d.ts +4 -0
- package/dist/commands/experiments.js +46 -15
- package/dist/commands/funnels.d.ts +17 -0
- package/dist/commands/funnels.js +203 -0
- package/dist/commands/goals.d.ts +18 -0
- package/dist/commands/goals.js +214 -0
- package/dist/commands/install.d.ts +8 -0
- package/dist/commands/install.js +74 -4
- package/dist/commands/sourcemap.d.ts +17 -5
- package/dist/commands/sourcemap.js +73 -6
- package/dist/commands/watch.d.ts +45 -0
- package/dist/commands/watch.js +258 -0
- package/dist/frameworks/detect.js +29 -7
- package/dist/index.js +158 -13
- package/package.json +1 -1
- package/scripts/gurulu-agentic-install.mjs +225 -0
- package/scripts/gurulu-scan.lib.cjs +539 -19
- package/scripts/patches/astro.patch.cjs +1 -0
- package/scripts/patches/auto-instrument/hono.cjs +381 -0
- package/scripts/patches/auto-instrument/index.cjs +2 -0
- package/scripts/patches/auto-instrument/nextjs-app-router.cjs +13 -4
- package/scripts/patches/express.patch.cjs +2 -2
- package/scripts/patches/fastify.patch.cjs +1 -0
- package/scripts/patches/nestjs.patch.cjs +1 -0
- package/scripts/patches/nextjs-app-router.patch.cjs +2 -2
- package/scripts/patches/nextjs-pages.patch.cjs +1 -0
- package/scripts/patches/remix.patch.cjs +1 -0
- package/scripts/patches/sveltekit.patch.cjs +1 -0
- package/scripts/patches/vite-react.patch.cjs +1 -0
- package/scripts/patches/vue.patch.cjs +1 -0
|
@@ -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>;
|
|
@@ -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;
|