@gurulu/cli 0.1.0
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 +66 -0
- package/bin/gurulu.js +2 -0
- package/dist/api-client.d.ts +27 -0
- package/dist/api-client.js +150 -0
- package/dist/commands/add-server.d.ts +9 -0
- package/dist/commands/add-server.js +155 -0
- package/dist/commands/alerts.d.ts +22 -0
- package/dist/commands/alerts.js +281 -0
- package/dist/commands/api-keys.d.ts +20 -0
- package/dist/commands/api-keys.js +130 -0
- package/dist/commands/audiences.d.ts +16 -0
- package/dist/commands/audiences.js +180 -0
- package/dist/commands/audit.d.ts +20 -0
- package/dist/commands/audit.js +130 -0
- package/dist/commands/auth.d.ts +20 -0
- package/dist/commands/auth.js +214 -0
- package/dist/commands/chat.d.ts +18 -0
- package/dist/commands/chat.js +117 -0
- package/dist/commands/config.d.ts +10 -0
- package/dist/commands/config.js +92 -0
- package/dist/commands/db.d.ts +25 -0
- package/dist/commands/db.js +322 -0
- package/dist/commands/destinations.d.ts +20 -0
- package/dist/commands/destinations.js +191 -0
- package/dist/commands/doctor.d.ts +7 -0
- package/dist/commands/doctor.js +318 -0
- package/dist/commands/events.d.ts +27 -0
- package/dist/commands/events.js +147 -0
- package/dist/commands/experiments.d.ts +18 -0
- package/dist/commands/experiments.js +233 -0
- package/dist/commands/identity.d.ts +13 -0
- package/dist/commands/identity.js +107 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.js +215 -0
- package/dist/commands/insights.d.ts +10 -0
- package/dist/commands/insights.js +65 -0
- package/dist/commands/install.d.ts +233 -0
- package/dist/commands/install.js +920 -0
- package/dist/commands/login.d.ts +20 -0
- package/dist/commands/login.js +170 -0
- package/dist/commands/logout.d.ts +10 -0
- package/dist/commands/logout.js +41 -0
- package/dist/commands/playground.d.ts +11 -0
- package/dist/commands/playground.js +47 -0
- package/dist/commands/sites.d.ts +18 -0
- package/dist/commands/sites.js +139 -0
- package/dist/commands/sourcemap.d.ts +21 -0
- package/dist/commands/sourcemap.js +137 -0
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.js +136 -0
- package/dist/commands/warehouse.d.ts +20 -0
- package/dist/commands/warehouse.js +65 -0
- package/dist/commands/warehouses.d.ts +17 -0
- package/dist/commands/warehouses.js +182 -0
- package/dist/commands/whoami.d.ts +9 -0
- package/dist/commands/whoami.js +47 -0
- package/dist/config.d.ts +75 -0
- package/dist/config.js +329 -0
- package/dist/frameworks/detect.d.ts +8 -0
- package/dist/frameworks/detect.js +362 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +429 -0
- package/dist/install-intent-proposal.d.ts +99 -0
- package/dist/install-intent-proposal.js +202 -0
- package/dist/utils/api.d.ts +20 -0
- package/dist/utils/api.js +47 -0
- package/dist/utils/config.d.ts +13 -0
- package/dist/utils/config.js +30 -0
- package/dist/utils/confirm.d.ts +17 -0
- package/dist/utils/confirm.js +40 -0
- package/dist/utils/dry-run.d.ts +20 -0
- package/dist/utils/dry-run.js +67 -0
- package/dist/utils/from-file.d.ts +9 -0
- package/dist/utils/from-file.js +72 -0
- package/dist/utils/ui.d.ts +14 -0
- package/dist/utils/ui.js +59 -0
- package/package.json +26 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Phase 19.5 W2 B7 — `gurulu experiments list|show|results`.
|
|
4
|
+
* Phase 20 W2 B4 — `create|update|delete|start|stop`.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.experimentsCommand = experimentsCommand;
|
|
8
|
+
const api_client_1 = require("../api-client");
|
|
9
|
+
const ui_1 = require("../utils/ui");
|
|
10
|
+
const confirm_1 = require("../utils/confirm");
|
|
11
|
+
const dry_run_1 = require("../utils/dry-run");
|
|
12
|
+
const from_file_1 = require("../utils/from-file");
|
|
13
|
+
async function experimentsCommand(args) {
|
|
14
|
+
const action = args.action || 'list';
|
|
15
|
+
switch (action) {
|
|
16
|
+
case 'list':
|
|
17
|
+
return listCmd(args);
|
|
18
|
+
case 'show':
|
|
19
|
+
return showCmd(args);
|
|
20
|
+
case 'results':
|
|
21
|
+
return resultsCmd(args);
|
|
22
|
+
case 'create':
|
|
23
|
+
return createCmd(args);
|
|
24
|
+
case 'update':
|
|
25
|
+
return updateCmd(args);
|
|
26
|
+
case 'delete':
|
|
27
|
+
return deleteCmd(args);
|
|
28
|
+
case 'start':
|
|
29
|
+
return startCmd(args);
|
|
30
|
+
case 'stop':
|
|
31
|
+
return stopCmd(args);
|
|
32
|
+
default:
|
|
33
|
+
(0, ui_1.error)(`Unknown experiments action: ${action}`);
|
|
34
|
+
(0, ui_1.info)('Usage: gurulu experiments [list|show|results|create|update|delete|start|stop]');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function listCmd(args) {
|
|
39
|
+
const body = await (0, api_client_1.cliApiJson)('/api/cli/experiments', {
|
|
40
|
+
profile: args.profile,
|
|
41
|
+
});
|
|
42
|
+
if (args.json) {
|
|
43
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const rows = body.experiments || [];
|
|
47
|
+
if (rows.length === 0) {
|
|
48
|
+
(0, ui_1.info)('No experiments yet.');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
process.stdout.write(['KEY', 'NAME', 'ACTIVE', 'ASSIGNMENTS', 'STARTED'].join('\t') + '\n');
|
|
52
|
+
for (const e of rows) {
|
|
53
|
+
process.stdout.write([
|
|
54
|
+
e.key,
|
|
55
|
+
e.name,
|
|
56
|
+
e.isActive ? 'yes' : 'no',
|
|
57
|
+
String(e.assignmentsCount ?? 0),
|
|
58
|
+
String(e.startedAt || '-'),
|
|
59
|
+
].join('\t') + '\n');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function showCmd(args) {
|
|
63
|
+
if (!args.target) {
|
|
64
|
+
(0, ui_1.error)('Usage: gurulu experiments show <key>');
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/experiments/${encodeURIComponent(args.target)}`, { profile: args.profile });
|
|
68
|
+
if (args.json) {
|
|
69
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const e = body.experiment;
|
|
73
|
+
process.stdout.write(`Key: ${e.key}\n`);
|
|
74
|
+
process.stdout.write(`Name: ${e.name}\n`);
|
|
75
|
+
process.stdout.write(`Active: ${e.isActive ? 'yes' : 'no'}\n`);
|
|
76
|
+
process.stdout.write(`Assignments: ${e.assignmentsCount}\n`);
|
|
77
|
+
process.stdout.write(`Variants: ${JSON.stringify(e.variants)}\n`);
|
|
78
|
+
process.stdout.write(`Started: ${e.startedAt || '-'}\n`);
|
|
79
|
+
process.stdout.write(`Ended: ${e.endedAt || '-'}\n`);
|
|
80
|
+
}
|
|
81
|
+
async function resultsCmd(args) {
|
|
82
|
+
if (!args.target) {
|
|
83
|
+
(0, ui_1.error)('Usage: gurulu experiments results <key>');
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
const qs = args.conversionEvent
|
|
87
|
+
? `?conversionEvent=${encodeURIComponent(args.conversionEvent)}`
|
|
88
|
+
: '';
|
|
89
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/experiments/${encodeURIComponent(args.target)}/results${qs}`, { profile: args.profile });
|
|
90
|
+
if (args.json) {
|
|
91
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
process.stdout.write(`Experiment: ${body.experimentKey || body.experimentId}\n`);
|
|
95
|
+
process.stdout.write(`Conversion: ${body.conversionEvent}\n`);
|
|
96
|
+
process.stdout.write(['VARIANT', 'ASSIGNS', 'CONV', 'RATE', 'CI_LO', 'CI_HI'].join('\t') + '\n');
|
|
97
|
+
for (const r of body.results || []) {
|
|
98
|
+
process.stdout.write([
|
|
99
|
+
r.variantId,
|
|
100
|
+
String(r.assignments),
|
|
101
|
+
String(r.conversions),
|
|
102
|
+
r.conversionRate.toFixed(3),
|
|
103
|
+
r.ciLower.toFixed(3),
|
|
104
|
+
r.ciUpper.toFixed(3),
|
|
105
|
+
].join('\t') + '\n');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function createCmd(args) {
|
|
109
|
+
let payload = {};
|
|
110
|
+
if (args.fromFile)
|
|
111
|
+
payload = (0, from_file_1.loadFromFile)(args.fromFile);
|
|
112
|
+
if (args.key)
|
|
113
|
+
payload.key = args.key;
|
|
114
|
+
if (args.name)
|
|
115
|
+
payload.name = args.name;
|
|
116
|
+
if (args.description !== undefined)
|
|
117
|
+
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.');
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
if (args.dryRun) {
|
|
123
|
+
const body = await (0, api_client_1.cliApiJson)('/api/cli/experiments?dryRun=1', {
|
|
124
|
+
profile: args.profile,
|
|
125
|
+
method: 'POST',
|
|
126
|
+
json: payload,
|
|
127
|
+
});
|
|
128
|
+
(0, dry_run_1.printDryRun)(body, args.json);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const ok = await (0, confirm_1.promptConfirm)(`Create experiment '${payload.key}'?`, {
|
|
132
|
+
yes: args.yes,
|
|
133
|
+
defaultYes: true,
|
|
134
|
+
});
|
|
135
|
+
if (!ok)
|
|
136
|
+
return (0, ui_1.info)('Aborted.');
|
|
137
|
+
const body = await (0, api_client_1.cliApiJson)('/api/cli/experiments', {
|
|
138
|
+
profile: args.profile,
|
|
139
|
+
method: 'POST',
|
|
140
|
+
json: payload,
|
|
141
|
+
});
|
|
142
|
+
if (args.json) {
|
|
143
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
(0, ui_1.success)(`Created experiment ${body.experiment?.key ?? ''}`);
|
|
147
|
+
}
|
|
148
|
+
async function updateCmd(args) {
|
|
149
|
+
if (!args.target) {
|
|
150
|
+
(0, ui_1.error)('Usage: gurulu experiments update <key>');
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
let payload = {};
|
|
154
|
+
if (args.fromFile)
|
|
155
|
+
payload = (0, from_file_1.loadFromFile)(args.fromFile);
|
|
156
|
+
if (args.name)
|
|
157
|
+
payload.name = args.name;
|
|
158
|
+
if (args.description !== undefined)
|
|
159
|
+
payload.description = args.description;
|
|
160
|
+
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 });
|
|
162
|
+
(0, dry_run_1.printDryRun)(body, args.json);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const ok = await (0, confirm_1.promptConfirm)(`Update experiment '${args.target}'?`, {
|
|
166
|
+
yes: args.yes,
|
|
167
|
+
defaultYes: true,
|
|
168
|
+
});
|
|
169
|
+
if (!ok)
|
|
170
|
+
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 });
|
|
172
|
+
if (args.json) {
|
|
173
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
(0, ui_1.success)(`Updated experiment ${args.target}`);
|
|
177
|
+
}
|
|
178
|
+
async function deleteCmd(args) {
|
|
179
|
+
if (!args.target) {
|
|
180
|
+
(0, ui_1.error)('Usage: gurulu experiments delete <key>');
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
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' });
|
|
185
|
+
(0, dry_run_1.printDryRun)(body, args.json);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const ok = await (0, confirm_1.promptConfirm)(`About to delete experiment '${args.target}'. Continue?`, { yes: args.yes, defaultYes: false });
|
|
189
|
+
if (!ok)
|
|
190
|
+
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}`);
|
|
193
|
+
}
|
|
194
|
+
async function startCmd(args) {
|
|
195
|
+
if (!args.target) {
|
|
196
|
+
(0, ui_1.error)('Usage: gurulu experiments start <key>');
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
const ok = await (0, confirm_1.promptConfirm)(`Start experiment '${args.target}'?`, {
|
|
200
|
+
yes: args.yes,
|
|
201
|
+
defaultYes: true,
|
|
202
|
+
});
|
|
203
|
+
if (!ok)
|
|
204
|
+
return (0, ui_1.info)('Aborted.');
|
|
205
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/experiments/${encodeURIComponent(args.target)}/start${args.dryRun ? '?dryRun=1' : ''}`, { profile: args.profile, method: 'POST', json: {} });
|
|
206
|
+
if (args.dryRun)
|
|
207
|
+
return (0, dry_run_1.printDryRun)(body, args.json);
|
|
208
|
+
if (args.json) {
|
|
209
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
(0, ui_1.success)(`Started experiment ${args.target}`);
|
|
213
|
+
}
|
|
214
|
+
async function stopCmd(args) {
|
|
215
|
+
if (!args.target) {
|
|
216
|
+
(0, ui_1.error)('Usage: gurulu experiments stop <key>');
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
const ok = await (0, confirm_1.promptConfirm)(`Stop experiment '${args.target}'?`, {
|
|
220
|
+
yes: args.yes,
|
|
221
|
+
defaultYes: true,
|
|
222
|
+
});
|
|
223
|
+
if (!ok)
|
|
224
|
+
return (0, ui_1.info)('Aborted.');
|
|
225
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/experiments/${encodeURIComponent(args.target)}/stop${args.dryRun ? '?dryRun=1' : ''}`, { profile: args.profile, method: 'POST', json: {} });
|
|
226
|
+
if (args.dryRun)
|
|
227
|
+
return (0, dry_run_1.printDryRun)(body, args.json);
|
|
228
|
+
if (args.json) {
|
|
229
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
(0, ui_1.success)(`Stopped experiment ${args.target}`);
|
|
233
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 19.5 W2 B8 — `gurulu identity decay|transfers|cdc-sources`.
|
|
3
|
+
*/
|
|
4
|
+
export interface IdentityArgs {
|
|
5
|
+
action?: string;
|
|
6
|
+
sub?: string;
|
|
7
|
+
direction?: string;
|
|
8
|
+
status?: string;
|
|
9
|
+
limit?: number;
|
|
10
|
+
json?: boolean;
|
|
11
|
+
profile?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function identityCommand(args: IdentityArgs): Promise<void>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Phase 19.5 W2 B8 — `gurulu identity decay|transfers|cdc-sources`.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.identityCommand = identityCommand;
|
|
7
|
+
const api_client_1 = require("../api-client");
|
|
8
|
+
const ui_1 = require("../utils/ui");
|
|
9
|
+
async function identityCommand(args) {
|
|
10
|
+
const action = args.action || '';
|
|
11
|
+
switch (action) {
|
|
12
|
+
case 'decay':
|
|
13
|
+
return decayCmd(args);
|
|
14
|
+
case 'transfers':
|
|
15
|
+
return transfersCmd(args);
|
|
16
|
+
case 'cdc-sources':
|
|
17
|
+
return cdcSourcesCmd(args);
|
|
18
|
+
default:
|
|
19
|
+
(0, ui_1.error)(`Unknown identity action: ${action}`);
|
|
20
|
+
(0, ui_1.info)('Usage: gurulu identity [decay stats|transfers list|cdc-sources list]');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function decayCmd(args) {
|
|
25
|
+
// `gurulu identity decay stats` is the only supported form for Phase 19.5.
|
|
26
|
+
if (args.sub && args.sub !== 'stats') {
|
|
27
|
+
(0, ui_1.error)(`Unknown identity decay sub: ${args.sub}`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
const body = await (0, api_client_1.cliApiJson)('/api/cli/identity/decay/stats', {
|
|
31
|
+
profile: args.profile,
|
|
32
|
+
});
|
|
33
|
+
if (args.json) {
|
|
34
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
process.stdout.write(`Retired claims: ${body.totalRetiredClaims}\n`);
|
|
38
|
+
process.stdout.write(`Retired tags: ${body.totalRetiredTags}\n`);
|
|
39
|
+
process.stdout.write(`Recent jobs: ${(body.recentRuns || []).length}\n`);
|
|
40
|
+
process.stdout.write(`Histogram:\n`);
|
|
41
|
+
for (const [k, v] of Object.entries(body.decayHistogram || {})) {
|
|
42
|
+
process.stdout.write(` ${k}: ${v}\n`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function transfersCmd(args) {
|
|
46
|
+
if (args.sub && args.sub !== 'list') {
|
|
47
|
+
(0, ui_1.error)(`Unknown identity transfers sub: ${args.sub}`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
const qs = new URLSearchParams();
|
|
51
|
+
if (args.direction)
|
|
52
|
+
qs.set('direction', args.direction);
|
|
53
|
+
if (args.status)
|
|
54
|
+
qs.set('status', args.status);
|
|
55
|
+
if (args.limit)
|
|
56
|
+
qs.set('limit', String(args.limit));
|
|
57
|
+
const path = `/api/cli/identity/transfers${qs.toString() ? `?${qs.toString()}` : ''}`;
|
|
58
|
+
const body = await (0, api_client_1.cliApiJson)(path, { profile: args.profile });
|
|
59
|
+
if (args.json) {
|
|
60
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const rows = body.transfers || [];
|
|
64
|
+
if (rows.length === 0) {
|
|
65
|
+
(0, ui_1.info)('No transfers.');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
process.stdout.write(['ID', 'SOURCE', 'TARGET', 'STATUS', 'INITIATED'].join('\t') + '\n');
|
|
69
|
+
for (const t of rows) {
|
|
70
|
+
process.stdout.write([
|
|
71
|
+
t.id,
|
|
72
|
+
t.sourceTenantId,
|
|
73
|
+
t.targetTenantId,
|
|
74
|
+
t.status,
|
|
75
|
+
String(t.initiatedAt || '-'),
|
|
76
|
+
].join('\t') + '\n');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function cdcSourcesCmd(args) {
|
|
80
|
+
if (args.sub && args.sub !== 'list') {
|
|
81
|
+
(0, ui_1.error)(`Unknown identity cdc-sources sub: ${args.sub}`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
const body = await (0, api_client_1.cliApiJson)('/api/cli/identity/cdc-sources', {
|
|
85
|
+
profile: args.profile,
|
|
86
|
+
});
|
|
87
|
+
if (args.json) {
|
|
88
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const rows = body.sources || [];
|
|
92
|
+
if (rows.length === 0) {
|
|
93
|
+
(0, ui_1.info)('No CDC sources configured.');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
process.stdout.write(['ID', 'NAME', 'TYPE', 'TABLE', 'ACTIVE', 'LAST_POLL'].join('\t') + '\n');
|
|
97
|
+
for (const s of rows) {
|
|
98
|
+
process.stdout.write([
|
|
99
|
+
s.id,
|
|
100
|
+
s.name,
|
|
101
|
+
s.sourceType,
|
|
102
|
+
s.tableName,
|
|
103
|
+
s.isActive ? 'yes' : 'no',
|
|
104
|
+
String(s.lastPollAt || '-'),
|
|
105
|
+
].join('\t') + '\n');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.initCommand = initCommand;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const config_1 = require("../config");
|
|
10
|
+
const api_client_1 = require("../api-client");
|
|
11
|
+
const detect_1 = require("../frameworks/detect");
|
|
12
|
+
const ui_1 = require("../utils/ui");
|
|
13
|
+
async function initCommand(args) {
|
|
14
|
+
const projectDir = process.cwd();
|
|
15
|
+
if (args.json) {
|
|
16
|
+
return initJSON(args, projectDir);
|
|
17
|
+
}
|
|
18
|
+
(0, ui_1.banner)();
|
|
19
|
+
// Step 1: Detect framework
|
|
20
|
+
let framework = args.framework || (0, detect_1.detectFramework)(projectDir);
|
|
21
|
+
(0, ui_1.info)(`Detected framework: ${(0, ui_1.bold)((0, detect_1.getFrameworkDisplayName)(framework))}`);
|
|
22
|
+
if (!args.framework && !args.noInteractive && framework === 'unknown') {
|
|
23
|
+
console.log('');
|
|
24
|
+
(0, ui_1.warn)('Could not auto-detect framework.');
|
|
25
|
+
const frameworks = ['nextjs-app', 'nextjs-pages', 'react-vite', 'react-cra', 'vue3', 'nuxt3', 'svelte', 'sveltekit', 'astro', 'express', 'nestjs', 'react-native', 'ios-swift', 'android-kotlin', 'flutter', 'html'];
|
|
26
|
+
const idx = await (0, ui_1.promptSelect)(' Select your framework: ', frameworks.map(f => (0, detect_1.getFrameworkDisplayName)(f)));
|
|
27
|
+
framework = frameworks[idx];
|
|
28
|
+
(0, ui_1.info)(`Using: ${(0, ui_1.bold)((0, detect_1.getFrameworkDisplayName)(framework))}`);
|
|
29
|
+
}
|
|
30
|
+
// Step 2: Get site credentials
|
|
31
|
+
let siteId = args.siteId || process.env.GURULU_SITE_ID;
|
|
32
|
+
let token = args.token || process.env.GURULU_TOKEN;
|
|
33
|
+
if (!siteId || !token) {
|
|
34
|
+
let profile;
|
|
35
|
+
try {
|
|
36
|
+
profile = await (0, config_1.loadActiveProfile)({ profile: args.profile });
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
profile = null;
|
|
40
|
+
}
|
|
41
|
+
if (profile && !siteId) {
|
|
42
|
+
try {
|
|
43
|
+
const data = await (0, api_client_1.cliApiJson)('/api/cli/sites', { preloadedProfile: profile });
|
|
44
|
+
const sites = data.sites || [];
|
|
45
|
+
if (sites.length === 0) {
|
|
46
|
+
(0, ui_1.error)('No sites found. Create a site at https://gurulu.io first.');
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
if (sites.length === 1 || args.noInteractive) {
|
|
50
|
+
siteId = sites[0].id;
|
|
51
|
+
(0, ui_1.info)(`Using site: ${(0, ui_1.bold)(sites[0].domain)}`);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
console.log('');
|
|
55
|
+
const idx = await (0, ui_1.promptSelect)(' Select a site: ', sites.map(s => `${s.domain} (${s.id})`));
|
|
56
|
+
siteId = sites[idx].id;
|
|
57
|
+
}
|
|
58
|
+
// Fetch site details to get publishable key as token
|
|
59
|
+
if (!token) {
|
|
60
|
+
(0, ui_1.step)('Fetching site credentials...');
|
|
61
|
+
const siteData = await (0, api_client_1.cliApiJson)(`/api/cli/sites/${encodeURIComponent(siteId)}`, { preloadedProfile: profile });
|
|
62
|
+
token = siteData.site?.publishableKey || '';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
if (!args.noInteractive) {
|
|
67
|
+
(0, ui_1.warn)('Could not fetch sites. Enter credentials manually.');
|
|
68
|
+
if (!siteId)
|
|
69
|
+
siteId = await (0, ui_1.prompt)(' Site ID: ');
|
|
70
|
+
if (!token)
|
|
71
|
+
token = await (0, ui_1.prompt)(' Site Token: ');
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
(0, ui_1.error)(`Failed to fetch credentials: ${err.message}`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else if (!args.noInteractive) {
|
|
80
|
+
if (!profile) {
|
|
81
|
+
(0, ui_1.warn)('Not authenticated. Run "gurulu login" first, or provide --site-id and --token.');
|
|
82
|
+
console.log('');
|
|
83
|
+
}
|
|
84
|
+
if (!siteId)
|
|
85
|
+
siteId = await (0, ui_1.prompt)(' Site ID: ');
|
|
86
|
+
if (!token)
|
|
87
|
+
token = await (0, ui_1.prompt)(' Site Token: ');
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
(0, ui_1.error)('Site ID and token required in non-interactive mode. Use --site-id and --token, or run "gurulu login" first.');
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (!siteId || !token) {
|
|
95
|
+
(0, ui_1.error)('Site ID and token are required.');
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
// Step 3: Generate setup code
|
|
99
|
+
const snippet = (0, detect_1.getSetupSnippet)(framework, siteId, token);
|
|
100
|
+
console.log('');
|
|
101
|
+
if (args.dryRun) {
|
|
102
|
+
(0, ui_1.info)('Dry run - showing what would be done:');
|
|
103
|
+
console.log('');
|
|
104
|
+
if (snippet.file) {
|
|
105
|
+
console.log(` ${(0, ui_1.dim)('File:')} ${(0, ui_1.cyan)(snippet.file)}`);
|
|
106
|
+
}
|
|
107
|
+
console.log(` ${(0, ui_1.dim)('Instruction:')} ${snippet.instruction}`);
|
|
108
|
+
console.log('');
|
|
109
|
+
console.log((0, ui_1.dim)(' --- Code ---'));
|
|
110
|
+
snippet.code.split('\n').forEach(line => {
|
|
111
|
+
console.log(` ${(0, ui_1.dim)(line)}`);
|
|
112
|
+
});
|
|
113
|
+
console.log((0, ui_1.dim)(' --- End ---'));
|
|
114
|
+
console.log('');
|
|
115
|
+
if (framework !== 'html') {
|
|
116
|
+
console.log(` ${(0, ui_1.dim)('.env.local would include:')}`);
|
|
117
|
+
console.log(` NEXT_PUBLIC_GURULU_SITE_ID=${siteId}`);
|
|
118
|
+
console.log(` NEXT_PUBLIC_GURULU_TOKEN=${token}`);
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Step 4: Write setup code
|
|
123
|
+
if (snippet.file) {
|
|
124
|
+
const filePath = path_1.default.join(projectDir, snippet.file);
|
|
125
|
+
const fileDir = path_1.default.dirname(filePath);
|
|
126
|
+
if (fs_1.default.existsSync(filePath)) {
|
|
127
|
+
(0, ui_1.warn)(`File already exists: ${snippet.file}`);
|
|
128
|
+
if (!args.noInteractive) {
|
|
129
|
+
const answer = await (0, ui_1.prompt)(' Overwrite? (y/N): ');
|
|
130
|
+
if (answer.toLowerCase() !== 'y') {
|
|
131
|
+
(0, ui_1.info)('Skipping file creation.');
|
|
132
|
+
printNextSteps(snippet, siteId, token, framework);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
(0, ui_1.info)('Skipping existing file in non-interactive mode.');
|
|
138
|
+
printNextSteps(snippet, siteId, token, framework);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
fs_1.default.mkdirSync(fileDir, { recursive: true });
|
|
143
|
+
fs_1.default.writeFileSync(filePath, snippet.code);
|
|
144
|
+
(0, ui_1.success)(`Created ${snippet.file}`);
|
|
145
|
+
}
|
|
146
|
+
// Step 5: Update .env.local (for frontend frameworks)
|
|
147
|
+
const frontendFrameworks = ['nextjs-app', 'nextjs-pages', 'react-vite', 'react-cra', 'vue3', 'nuxt3', 'svelte', 'sveltekit', 'astro'];
|
|
148
|
+
if (frontendFrameworks.includes(framework)) {
|
|
149
|
+
const envFile = path_1.default.join(projectDir, '.env.local');
|
|
150
|
+
const envPrefix = framework.startsWith('nuxt') ? 'NUXT_PUBLIC' : framework.startsWith('next') ? 'NEXT_PUBLIC' : 'VITE';
|
|
151
|
+
const envLines = [
|
|
152
|
+
`${envPrefix}_GURULU_SITE_ID=${siteId}`,
|
|
153
|
+
`${envPrefix}_GURULU_TOKEN=${token}`,
|
|
154
|
+
];
|
|
155
|
+
let existingEnv = '';
|
|
156
|
+
if (fs_1.default.existsSync(envFile)) {
|
|
157
|
+
existingEnv = fs_1.default.readFileSync(envFile, 'utf-8');
|
|
158
|
+
}
|
|
159
|
+
const newLines = envLines.filter(line => {
|
|
160
|
+
const key = line.split('=')[0];
|
|
161
|
+
return !existingEnv.includes(key);
|
|
162
|
+
});
|
|
163
|
+
if (newLines.length > 0) {
|
|
164
|
+
const separator = existingEnv && !existingEnv.endsWith('\n') ? '\n' : '';
|
|
165
|
+
const header = existingEnv ? '' : '# Gurulu.io Analytics\n';
|
|
166
|
+
fs_1.default.appendFileSync(envFile, `${separator}${header}${newLines.join('\n')}\n`);
|
|
167
|
+
(0, ui_1.success)('Updated .env.local with site credentials');
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
(0, ui_1.info)('.env.local already has Gurulu credentials');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Step 6: Print next steps
|
|
174
|
+
printNextSteps(snippet, siteId, token, framework);
|
|
175
|
+
}
|
|
176
|
+
function printNextSteps(snippet, siteId, token, framework) {
|
|
177
|
+
console.log('');
|
|
178
|
+
console.log((0, ui_1.bold)(' Next steps:'));
|
|
179
|
+
console.log('');
|
|
180
|
+
(0, ui_1.step)(snippet.instruction);
|
|
181
|
+
const mobileFrameworks = ['react-native', 'ios-swift', 'android-kotlin', 'flutter'];
|
|
182
|
+
if (framework === 'html' || mobileFrameworks.includes(framework)) {
|
|
183
|
+
console.log('');
|
|
184
|
+
console.log((0, ui_1.dim)(' Copy this snippet:'));
|
|
185
|
+
console.log('');
|
|
186
|
+
snippet.code.split('\n').forEach(line => {
|
|
187
|
+
console.log(` ${line}`);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
console.log('');
|
|
191
|
+
(0, ui_1.step)(`Run ${(0, ui_1.cyan)('gurulu status')} to verify the connection`);
|
|
192
|
+
(0, ui_1.step)(`Run ${(0, ui_1.cyan)('gurulu events')} to see detected events`);
|
|
193
|
+
(0, ui_1.step)(`Visit ${(0, ui_1.cyan)('https://gurulu.io')} to view your dashboard`);
|
|
194
|
+
console.log('');
|
|
195
|
+
(0, ui_1.success)('Setup complete!');
|
|
196
|
+
console.log('');
|
|
197
|
+
}
|
|
198
|
+
async function initJSON(args, projectDir) {
|
|
199
|
+
const framework = args.framework || (0, detect_1.detectFramework)(projectDir);
|
|
200
|
+
const siteId = args.siteId || process.env.GURULU_SITE_ID || '';
|
|
201
|
+
const token = args.token || process.env.GURULU_TOKEN || '';
|
|
202
|
+
const snippet = (0, detect_1.getSetupSnippet)(framework, siteId, token);
|
|
203
|
+
const output = {
|
|
204
|
+
framework,
|
|
205
|
+
frameworkName: (0, detect_1.getFrameworkDisplayName)(framework),
|
|
206
|
+
siteId,
|
|
207
|
+
snippet: {
|
|
208
|
+
file: snippet.file,
|
|
209
|
+
code: snippet.code,
|
|
210
|
+
instruction: snippet.instruction,
|
|
211
|
+
},
|
|
212
|
+
dryRun: !!args.dryRun,
|
|
213
|
+
};
|
|
214
|
+
console.log(JSON.stringify(output, null, 2));
|
|
215
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 19.5 W2 B5 — `gurulu insights today|history|weekly`.
|
|
3
|
+
*/
|
|
4
|
+
export interface InsightsArgs {
|
|
5
|
+
action?: string;
|
|
6
|
+
days?: number;
|
|
7
|
+
json?: boolean;
|
|
8
|
+
profile?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function insightsCommand(args: InsightsArgs): Promise<void>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Phase 19.5 W2 B5 — `gurulu insights today|history|weekly`.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.insightsCommand = insightsCommand;
|
|
7
|
+
const api_client_1 = require("../api-client");
|
|
8
|
+
const ui_1 = require("../utils/ui");
|
|
9
|
+
async function insightsCommand(args) {
|
|
10
|
+
const action = args.action || 'today';
|
|
11
|
+
switch (action) {
|
|
12
|
+
case 'today':
|
|
13
|
+
return todayCmd(args);
|
|
14
|
+
case 'history':
|
|
15
|
+
case 'weekly':
|
|
16
|
+
return historyCmd(args);
|
|
17
|
+
default:
|
|
18
|
+
(0, ui_1.error)(`Unknown insights action: ${action}`);
|
|
19
|
+
(0, ui_1.info)('Usage: gurulu insights [today|history|weekly]');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function todayCmd(args) {
|
|
24
|
+
try {
|
|
25
|
+
const body = await (0, api_client_1.cliApiJson)('/api/cli/insights', {
|
|
26
|
+
profile: args.profile,
|
|
27
|
+
});
|
|
28
|
+
if (args.json) {
|
|
29
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const i = body.insight;
|
|
33
|
+
process.stdout.write(`Date: ${String(i.date).slice(0, 10)}\n`);
|
|
34
|
+
process.stdout.write(`Summary: ${i.summary || '-'}\n`);
|
|
35
|
+
if (Array.isArray(i.highlights)) {
|
|
36
|
+
process.stdout.write(`Highlights:\n`);
|
|
37
|
+
for (const h of i.highlights)
|
|
38
|
+
process.stdout.write(` - ${h}\n`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
if (err?.status === 404) {
|
|
43
|
+
(0, ui_1.info)('No daily insight available yet.');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function historyCmd(args) {
|
|
50
|
+
const days = args.days ?? 7;
|
|
51
|
+
const body = await (0, api_client_1.cliApiJson)(`/api/cli/insights/history?days=${days}`, { profile: args.profile });
|
|
52
|
+
if (args.json) {
|
|
53
|
+
process.stdout.write(JSON.stringify(body, null, 2) + '\n');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const rows = body.insights || [];
|
|
57
|
+
if (rows.length === 0) {
|
|
58
|
+
(0, ui_1.info)('No insights in the selected window.');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
process.stdout.write(['DATE', 'SUMMARY'].join('\t') + '\n');
|
|
62
|
+
for (const r of rows) {
|
|
63
|
+
process.stdout.write([String(r.date).slice(0, 10), String(r.summary || '-').slice(0, 80)].join('\t') + '\n');
|
|
64
|
+
}
|
|
65
|
+
}
|