@gurulu/cli 0.4.7 → 1.0.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/LICENSE +92 -0
- package/README.md +35 -106
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +25410 -0
- package/dist/commands/auth.d.ts +23 -20
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/doctor.d.ts +20 -6
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/init.d.ts +25 -11
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/pull.d.ts +13 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/push.d.ts +40 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/validate.d.ts +36 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24985 -876
- package/dist/lib/api.d.ts +139 -0
- package/dist/lib/api.d.ts.map +1 -0
- package/dist/lib/codegen.d.ts +4 -0
- package/dist/lib/codegen.d.ts.map +1 -0
- package/dist/lib/config.d.ts +43 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/package.json +40 -20
- package/bin/gurulu.js +0 -2
- package/dist/api-client.d.ts +0 -33
- package/dist/api-client.js +0 -175
- package/dist/commands/add-server.d.ts +0 -9
- package/dist/commands/add-server.js +0 -162
- package/dist/commands/alerts.d.ts +0 -27
- package/dist/commands/alerts.js +0 -309
- package/dist/commands/api-keys.d.ts +0 -20
- package/dist/commands/api-keys.js +0 -130
- package/dist/commands/attribution.d.ts +0 -22
- package/dist/commands/attribution.js +0 -111
- package/dist/commands/audiences.d.ts +0 -23
- package/dist/commands/audiences.js +0 -243
- package/dist/commands/audit.d.ts +0 -20
- package/dist/commands/audit.js +0 -130
- package/dist/commands/auth.js +0 -249
- package/dist/commands/chat.d.ts +0 -19
- package/dist/commands/chat.js +0 -118
- package/dist/commands/config.d.ts +0 -10
- package/dist/commands/config.js +0 -92
- package/dist/commands/consent.d.ts +0 -27
- package/dist/commands/consent.js +0 -233
- package/dist/commands/conversion-paths.d.ts +0 -19
- package/dist/commands/conversion-paths.js +0 -55
- package/dist/commands/db.d.ts +0 -25
- package/dist/commands/db.js +0 -330
- package/dist/commands/destinations.d.ts +0 -20
- package/dist/commands/destinations.js +0 -191
- package/dist/commands/doctor.js +0 -360
- package/dist/commands/errors.d.ts +0 -27
- package/dist/commands/errors.js +0 -121
- package/dist/commands/events.d.ts +0 -33
- package/dist/commands/events.js +0 -371
- package/dist/commands/experiments.d.ts +0 -22
- package/dist/commands/experiments.js +0 -264
- package/dist/commands/funnels.d.ts +0 -17
- package/dist/commands/funnels.js +0 -203
- package/dist/commands/goals.d.ts +0 -18
- package/dist/commands/goals.js +0 -214
- package/dist/commands/heatmap.d.ts +0 -27
- package/dist/commands/heatmap.js +0 -112
- package/dist/commands/identity.d.ts +0 -29
- package/dist/commands/identity.js +0 -328
- package/dist/commands/init.js +0 -215
- package/dist/commands/insights.d.ts +0 -10
- package/dist/commands/insights.js +0 -77
- package/dist/commands/install.d.ts +0 -259
- package/dist/commands/install.js +0 -1590
- package/dist/commands/login.d.ts +0 -20
- package/dist/commands/login.js +0 -170
- package/dist/commands/logout.d.ts +0 -10
- package/dist/commands/logout.js +0 -41
- package/dist/commands/playground.d.ts +0 -11
- package/dist/commands/playground.js +0 -47
- package/dist/commands/releases.d.ts +0 -17
- package/dist/commands/releases.js +0 -54
- package/dist/commands/replay.d.ts +0 -18
- package/dist/commands/replay.js +0 -64
- package/dist/commands/secrets.d.ts +0 -19
- package/dist/commands/secrets.js +0 -145
- package/dist/commands/setup.d.ts +0 -21
- package/dist/commands/setup.js +0 -67
- package/dist/commands/sites.d.ts +0 -18
- package/dist/commands/sites.js +0 -139
- package/dist/commands/skad.d.ts +0 -18
- package/dist/commands/skad.js +0 -53
- package/dist/commands/sourcemap.d.ts +0 -33
- package/dist/commands/sourcemap.js +0 -204
- package/dist/commands/status.d.ts +0 -7
- package/dist/commands/status.js +0 -136
- package/dist/commands/upgrade.d.ts +0 -21
- package/dist/commands/upgrade.js +0 -183
- package/dist/commands/warehouse.d.ts +0 -20
- package/dist/commands/warehouse.js +0 -65
- package/dist/commands/warehouses.d.ts +0 -17
- package/dist/commands/warehouses.js +0 -182
- package/dist/commands/watch.d.ts +0 -45
- package/dist/commands/watch.js +0 -258
- package/dist/commands/whoami.d.ts +0 -9
- package/dist/commands/whoami.js +0 -50
- package/dist/config.d.ts +0 -75
- package/dist/config.js +0 -329
- package/dist/frameworks/detect.d.ts +0 -8
- package/dist/frameworks/detect.js +0 -458
- package/dist/install-intent-proposal.d.ts +0 -99
- package/dist/install-intent-proposal.js +0 -202
- package/dist/utils/api.d.ts +0 -20
- package/dist/utils/api.js +0 -47
- package/dist/utils/config.d.ts +0 -13
- package/dist/utils/config.js +0 -30
- package/dist/utils/confirm.d.ts +0 -17
- package/dist/utils/confirm.js +0 -40
- package/dist/utils/dry-run.d.ts +0 -20
- package/dist/utils/dry-run.js +0 -67
- package/dist/utils/from-file.d.ts +0 -9
- package/dist/utils/from-file.js +0 -72
- package/dist/utils/redact.d.ts +0 -14
- package/dist/utils/redact.js +0 -48
- package/dist/utils/ui.d.ts +0 -14
- package/dist/utils/ui.js +0 -59
- package/scripts/.gitkeep +0 -0
- package/scripts/README-gurulu-agentic-install.md +0 -114
- package/scripts/README-gurulu-scan.md +0 -98
- package/scripts/audit-cli-scopes.mjs +0 -204
- package/scripts/backfill-tenant-id.mjs +0 -172
- package/scripts/backfill-tenant-links.ts +0 -252
- package/scripts/backup-clickhouse.sh +0 -27
- package/scripts/backup-postgres.sh +0 -19
- package/scripts/bootstrap-runtime-schema.mjs +0 -87
- package/scripts/bootstrap-stripe.mjs +0 -158
- package/scripts/gurulu-agentic-install.lib.cjs +0 -762
- package/scripts/gurulu-agentic-install.mjs +0 -623
- package/scripts/gurulu-scan.lib.cjs +0 -1509
- package/scripts/gurulu-scan.mjs +0 -91
- package/scripts/gurulu-verify-install.lib.cjs +0 -334
- package/scripts/gurulu-verify-install.mjs +0 -59
- package/scripts/init-ssl.sh +0 -26
- package/scripts/migrate-flow-graph-enums.sh +0 -86
- package/scripts/monitor-disk.sh +0 -24
- package/scripts/patches/astro.patch.cjs +0 -74
- package/scripts/patches/auto-instrument/ast-helper.cjs +0 -480
- package/scripts/patches/auto-instrument/astro.cjs +0 -273
- package/scripts/patches/auto-instrument/express.cjs +0 -383
- package/scripts/patches/auto-instrument/fastify.cjs +0 -262
- package/scripts/patches/auto-instrument/hono.cjs +0 -392
- package/scripts/patches/auto-instrument/index.cjs +0 -80
- package/scripts/patches/auto-instrument/nestjs.cjs +0 -286
- package/scripts/patches/auto-instrument/nextjs-app-router.cjs +0 -345
- package/scripts/patches/auto-instrument/nextjs-pages.cjs +0 -361
- package/scripts/patches/auto-instrument/remix.cjs +0 -168
- package/scripts/patches/auto-instrument/sdk-helper-map.cjs +0 -241
- package/scripts/patches/auto-instrument/singleton-helper.cjs +0 -193
- package/scripts/patches/auto-instrument/sveltekit.cjs +0 -161
- package/scripts/patches/auto-instrument/vite-react.cjs +0 -37
- package/scripts/patches/auto-instrument/vue.cjs +0 -196
- package/scripts/patches/express.patch.cjs +0 -99
- package/scripts/patches/fastify.patch.cjs +0 -108
- package/scripts/patches/index.cjs +0 -300
- package/scripts/patches/nestjs.patch.cjs +0 -112
- package/scripts/patches/nextjs-app-router.patch.cjs +0 -97
- package/scripts/patches/nextjs-pages.patch.cjs +0 -97
- package/scripts/patches/remix.patch.cjs +0 -75
- package/scripts/patches/sveltekit.patch.cjs +0 -72
- package/scripts/patches/vite-react.patch.cjs +0 -73
- package/scripts/patches/vue.patch.cjs +0 -82
- package/scripts/renew-ssl.sh +0 -14
- package/scripts/resolve-migration.sh +0 -23
- package/scripts/seed-cli-dev-keys.mjs +0 -130
- package/scripts/seed-test-data.mjs +0 -391
- package/scripts/spike-browserless.ts +0 -65
- package/scripts/tenant-pivot-consistency-check.mjs +0 -205
- package/scripts/tenant-pivot-phase-3-cleanup.lib.cjs +0 -258
- package/scripts/tenant-pivot-phase-3-cleanup.mjs +0 -98
- package/scripts/test-identity-resolution.ts +0 -804
- package/scripts/validate-gurulu-schemas.mjs +0 -79
|
@@ -1,623 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// gurulu-agentic-install.mjs — Phase 14 B3 CLI wrapper.
|
|
3
|
-
//
|
|
4
|
-
// Reads a `gurulu-scan` JSON output and produces the 10 `.gurulu/` artifacts
|
|
5
|
-
// that the runtime loader (Phase 13 B1) consumes. Pairs with:
|
|
6
|
-
// scripts/gurulu-scan.mjs (Phase 13 B2 — scanner)
|
|
7
|
-
// scripts/gurulu-agentic-install.lib.cjs (generator logic)
|
|
8
|
-
//
|
|
9
|
-
// Usage:
|
|
10
|
-
// node scripts/gurulu-agentic-install.mjs <scan-output.json> \
|
|
11
|
-
// --site-id <id> --tenant-id <id> [--output .gurulu/] [--dry-run] [--quiet]
|
|
12
|
-
//
|
|
13
|
-
// Pass `-` as the scan path to read JSON from stdin.
|
|
14
|
-
|
|
15
|
-
import { createRequire } from 'node:module';
|
|
16
|
-
import { readFileSync, statSync, existsSync } from 'node:fs';
|
|
17
|
-
import path from 'node:path';
|
|
18
|
-
import { fileURLToPath } from 'node:url';
|
|
19
|
-
import { posix as posixPath } from 'node:path';
|
|
20
|
-
|
|
21
|
-
const require = createRequire(import.meta.url);
|
|
22
|
-
const lib = require('./gurulu-agentic-install.lib.cjs');
|
|
23
|
-
const patches = require('./patches/index.cjs');
|
|
24
|
-
|
|
25
|
-
export const {
|
|
26
|
-
INSTALL_AGENT_VERSION,
|
|
27
|
-
generateArtifacts,
|
|
28
|
-
generateInstallPlan,
|
|
29
|
-
generateCoreConfig,
|
|
30
|
-
generateWebConfig,
|
|
31
|
-
generateAppConfig,
|
|
32
|
-
generateServerMap,
|
|
33
|
-
generateDbMap,
|
|
34
|
-
generateFlowSeeds,
|
|
35
|
-
generateMilestoneRules,
|
|
36
|
-
generateCorrelationMap,
|
|
37
|
-
generateConnectors,
|
|
38
|
-
deriveBusinessSurfaces,
|
|
39
|
-
deriveMilestones,
|
|
40
|
-
inferEventName,
|
|
41
|
-
validateArtifacts,
|
|
42
|
-
writeArtifacts,
|
|
43
|
-
} = lib;
|
|
44
|
-
|
|
45
|
-
// ---------------------------------------------------------------------------
|
|
46
|
-
// CLI entry
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
|
|
49
|
-
function parseArgs(argv) {
|
|
50
|
-
const args = { _: [] };
|
|
51
|
-
for (let i = 0; i < argv.length; i++) {
|
|
52
|
-
const a = argv[i];
|
|
53
|
-
if (a === '--help' || a === '-h') {
|
|
54
|
-
args.help = true;
|
|
55
|
-
} else if (a === '--quiet' || a === '-q') {
|
|
56
|
-
args.quiet = true;
|
|
57
|
-
} else if (a === '--dry-run') {
|
|
58
|
-
args.dryRun = true;
|
|
59
|
-
} else if (a === '--apply') {
|
|
60
|
-
args.apply = true;
|
|
61
|
-
} else if (a === '--rollback') {
|
|
62
|
-
args.rollback = true;
|
|
63
|
-
} else if (a === '--auto-instrument') {
|
|
64
|
-
args.autoInstrument = true;
|
|
65
|
-
} else if (a.startsWith('--')) {
|
|
66
|
-
const key = a.slice(2);
|
|
67
|
-
args[key] = argv[++i];
|
|
68
|
-
} else {
|
|
69
|
-
args._.push(a);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return args;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function printHelp(out = process.stdout) {
|
|
76
|
-
out.write(
|
|
77
|
-
[
|
|
78
|
-
'Usage: node scripts/gurulu-agentic-install.mjs <scan-output.json> \\',
|
|
79
|
-
' --site-id <id> --tenant-id <id> [--output .gurulu/] [--dry-run] [--quiet]',
|
|
80
|
-
'',
|
|
81
|
-
'Reads a gurulu-scan JSON document (pass `-` for stdin) and generates the',
|
|
82
|
-
'10 .gurulu/ artifacts ingested by the Phase 13 B1 runtime loader.',
|
|
83
|
-
'',
|
|
84
|
-
'Options:',
|
|
85
|
-
' --site-id <id> Stable site identifier (required)',
|
|
86
|
-
' --tenant-id <id> Tenant identifier (required)',
|
|
87
|
-
' --publishable-key <key> Stripe-style gpk_live_/gpk_test_ key (optional)',
|
|
88
|
-
' --output <path> Output directory (default: .gurulu)',
|
|
89
|
-
' --domains <list> Comma-separated host list for install-plan.domains',
|
|
90
|
-
' --dry-run Validate + report without writing files (default for patch mode)',
|
|
91
|
-
' --apply (patch mode) write patches + create backup',
|
|
92
|
-
' --rollback (patch mode) restore most recent backup',
|
|
93
|
-
' --framework <f> auto|nextjs-app|nextjs-pages|vite-react|express',
|
|
94
|
-
' --auto-instrument (Phase 18.7) Also write gurulu.track() calls into route handlers',
|
|
95
|
-
' --intent-result <path> JSON file with InstallIntent output (feeds --auto-instrument)',
|
|
96
|
-
' --token <key> CLI auth token for LLM property extraction API',
|
|
97
|
-
' --api-url <url> Base URL for the Gurulu API (default: https://gurulu.io)',
|
|
98
|
-
' --quiet Suppress non-error output',
|
|
99
|
-
' -h, --help Show this help',
|
|
100
|
-
'',
|
|
101
|
-
].join('\n'),
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async function readScanInput(scanFile) {
|
|
106
|
-
if (scanFile === '-') {
|
|
107
|
-
// stdin
|
|
108
|
-
const chunks = [];
|
|
109
|
-
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
110
|
-
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
111
|
-
}
|
|
112
|
-
return JSON.parse(readFileSync(scanFile, 'utf8'));
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Return a placeholder JS expression for a property that lacks an example
|
|
117
|
-
* value. The placeholder includes a TODO comment so developers know to fill
|
|
118
|
-
* in the real expression from their route handler context.
|
|
119
|
-
*/
|
|
120
|
-
function getPropertyPlaceholder(type, name) {
|
|
121
|
-
switch (type) {
|
|
122
|
-
case 'string': return `'' /* TODO: ${name} */`;
|
|
123
|
-
case 'number': return `0 /* TODO: ${name} */`;
|
|
124
|
-
case 'boolean': return `false /* TODO: ${name} */`;
|
|
125
|
-
default: return `undefined /* TODO: ${name} */`;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// ---------------------------------------------------------------------------
|
|
130
|
-
// Route resolution: map "POST /api/checkout" → actual file path on disk.
|
|
131
|
-
// Reuses the candidate patterns from the auto-instrument modules.
|
|
132
|
-
// ---------------------------------------------------------------------------
|
|
133
|
-
|
|
134
|
-
function parseRouteString(routeStr) {
|
|
135
|
-
if (!routeStr || typeof routeStr !== 'string') return null;
|
|
136
|
-
const m = routeStr.trim().match(/^([A-Z]+)\s+(\/.*)$/);
|
|
137
|
-
if (!m) return null;
|
|
138
|
-
return { method: m[1].toUpperCase(), urlPath: m[2].replace(/\/+$/, '').split('?')[0] };
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function resolveRouteFile(routeStr, framework, repoRoot) {
|
|
142
|
-
const parsed = parseRouteString(routeStr);
|
|
143
|
-
if (!parsed) return null;
|
|
144
|
-
const { urlPath } = parsed;
|
|
145
|
-
const segments = urlPath.split('/').filter(Boolean);
|
|
146
|
-
|
|
147
|
-
// Build candidate file paths based on framework.
|
|
148
|
-
const candidates = [];
|
|
149
|
-
|
|
150
|
-
// Next.js App Router patterns.
|
|
151
|
-
if (!framework || /next/i.test(framework)) {
|
|
152
|
-
const exts = ['ts', 'tsx', 'js', 'jsx'];
|
|
153
|
-
for (const ext of exts) {
|
|
154
|
-
candidates.push(posixPath.join('src', 'app', ...segments, `route.${ext}`));
|
|
155
|
-
candidates.push(posixPath.join('app', ...segments, `route.${ext}`));
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Next.js Pages API patterns.
|
|
160
|
-
if (!framework || /next.*pages/i.test(framework)) {
|
|
161
|
-
const exts = ['ts', 'tsx', 'js', 'jsx'];
|
|
162
|
-
for (const ext of exts) {
|
|
163
|
-
candidates.push(posixPath.join('src', 'pages', ...segments, `index.${ext}`));
|
|
164
|
-
candidates.push(posixPath.join('pages', ...segments, `index.${ext}`));
|
|
165
|
-
candidates.push(posixPath.join('src', 'pages', ...segments.slice(0, -1), `${segments[segments.length - 1]}.${ext}`));
|
|
166
|
-
candidates.push(posixPath.join('pages', ...segments.slice(0, -1), `${segments[segments.length - 1]}.${ext}`));
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Express / Fastify / generic patterns.
|
|
171
|
-
if (!framework || /express|fastify|nest|generic/i.test(framework)) {
|
|
172
|
-
const roots = ['src/routes', 'routes', 'src/api', 'api', 'src/controllers', 'controllers', 'src', 'app'];
|
|
173
|
-
const exts = ['ts', 'js', 'mjs', 'cjs'];
|
|
174
|
-
for (const root of roots) {
|
|
175
|
-
for (const ext of exts) {
|
|
176
|
-
// e.g. src/routes/checkout.ts
|
|
177
|
-
candidates.push(posixPath.join(root, ...segments.slice(1), `index.${ext}`));
|
|
178
|
-
if (segments.length > 1) {
|
|
179
|
-
candidates.push(posixPath.join(root, ...segments.slice(1, -1), `${segments[segments.length - 1]}.${ext}`));
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// SvelteKit patterns.
|
|
186
|
-
if (!framework || /svelte/i.test(framework)) {
|
|
187
|
-
const exts = ['ts', 'js'];
|
|
188
|
-
for (const ext of exts) {
|
|
189
|
-
candidates.push(posixPath.join('src', 'routes', ...segments, `+server.${ext}`));
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Remix patterns.
|
|
194
|
-
if (!framework || /remix/i.test(framework)) {
|
|
195
|
-
const exts = ['ts', 'tsx', 'js', 'jsx'];
|
|
196
|
-
for (const ext of exts) {
|
|
197
|
-
candidates.push(posixPath.join('app', 'routes', segments.join('.') + `.${ext}`));
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Find first existing candidate.
|
|
202
|
-
for (const rel of candidates) {
|
|
203
|
-
const abs = path.join(repoRoot, rel);
|
|
204
|
-
if (existsSync(abs)) return abs;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return null;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// ---------------------------------------------------------------------------
|
|
211
|
-
// LLM property extraction via the server API endpoint.
|
|
212
|
-
// ---------------------------------------------------------------------------
|
|
213
|
-
|
|
214
|
-
async function tryLlmExtraction(events, { repoRoot, framework, ingestUrl, token, siteId, log }) {
|
|
215
|
-
let llmHits = 0;
|
|
216
|
-
for (const event of events) {
|
|
217
|
-
// Skip if already has extracted properties.
|
|
218
|
-
if (event.extractedProperties && event.extractedProperties.length > 0) continue;
|
|
219
|
-
|
|
220
|
-
const routeStr = event.source && event.source.route;
|
|
221
|
-
if (!routeStr) continue;
|
|
222
|
-
|
|
223
|
-
const routeFile = resolveRouteFile(routeStr, framework, repoRoot);
|
|
224
|
-
if (!routeFile) continue;
|
|
225
|
-
|
|
226
|
-
let handlerSource;
|
|
227
|
-
try {
|
|
228
|
-
handlerSource = readFileSync(routeFile, 'utf-8');
|
|
229
|
-
} catch {
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Cap at 4KB to keep LLM calls reasonable.
|
|
234
|
-
const clipped = handlerSource.slice(0, 4096);
|
|
235
|
-
|
|
236
|
-
try {
|
|
237
|
-
const res = await fetch(`${ingestUrl}/api/cli/install/extract-properties`, {
|
|
238
|
-
method: 'POST',
|
|
239
|
-
headers: {
|
|
240
|
-
Authorization: `Bearer ${token}`,
|
|
241
|
-
'Content-Type': 'application/json',
|
|
242
|
-
},
|
|
243
|
-
body: JSON.stringify({
|
|
244
|
-
siteId,
|
|
245
|
-
eventName: event.name,
|
|
246
|
-
handlerSource: clipped,
|
|
247
|
-
framework,
|
|
248
|
-
}),
|
|
249
|
-
});
|
|
250
|
-
if (res.ok) {
|
|
251
|
-
const data = await res.json();
|
|
252
|
-
if (data.properties && data.properties.length > 0 && data.confidence >= 0.7) {
|
|
253
|
-
event.extractedProperties = data.properties;
|
|
254
|
-
llmHits++;
|
|
255
|
-
log(` LLM extracted ${data.properties.length} property(ies) for ${event.name} (confidence: ${data.confidence.toFixed(2)})`);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
} catch {
|
|
259
|
-
// LLM extraction failed — fall through to placeholder.
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
return llmHits;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
async function runPatchMode(repoRoot, args) {
|
|
266
|
-
const log = args.quiet ? () => {} : (m) => process.stdout.write(m + '\n');
|
|
267
|
-
if (args.rollback) {
|
|
268
|
-
const res = patches.rollback(repoRoot);
|
|
269
|
-
if (!res.rolledBack) {
|
|
270
|
-
process.stderr.write(`Rollback failed: ${res.reason}\n`);
|
|
271
|
-
process.exit(3);
|
|
272
|
-
}
|
|
273
|
-
log(`Rolled back ${res.files.length} files`);
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
if (!args['site-id'] || !args['tenant-id']) {
|
|
277
|
-
process.stderr.write('Error: --site-id and --tenant-id are required for patch mode\n');
|
|
278
|
-
process.exit(1);
|
|
279
|
-
}
|
|
280
|
-
const framework = args.framework || 'auto';
|
|
281
|
-
const { patcher, detection, error, unsupportedFramework } = patches.resolvePatcher(repoRoot, framework);
|
|
282
|
-
if (error) {
|
|
283
|
-
process.stderr.write(`Error: ${error}\n`);
|
|
284
|
-
// Sprint E1.6 — exit 6 specifically for mobile / not-yet-supported
|
|
285
|
-
// frameworks so the wrapping CLI can surface docs links instead of a
|
|
286
|
-
// generic install failure.
|
|
287
|
-
process.exit(unsupportedFramework ? 6 : 1);
|
|
288
|
-
}
|
|
289
|
-
if (!patcher) {
|
|
290
|
-
process.stderr.write('No supported framework detected in ' + repoRoot + '\n');
|
|
291
|
-
process.exit(4);
|
|
292
|
-
}
|
|
293
|
-
const injection = patches.buildInjection({
|
|
294
|
-
siteId: args['site-id'],
|
|
295
|
-
tenantId: args['tenant-id'],
|
|
296
|
-
publishableKey: args['publishable-key'] || '',
|
|
297
|
-
scriptSrc: args['script-src'] || 'https://gurulu.io/t.js',
|
|
298
|
-
});
|
|
299
|
-
const plan = patcher.plan({ repoRoot, injection });
|
|
300
|
-
log(`Framework: ${patcher.name}`);
|
|
301
|
-
log(`Detected files: ${(detection?.files || []).join(', ') || '(none)'}`);
|
|
302
|
-
if (plan.changes.length === 0) {
|
|
303
|
-
log('No changes to apply (already installed or nothing to patch)');
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
// Default behavior is dry-run unless --apply is passed.
|
|
307
|
-
const shouldApply = !!args.apply;
|
|
308
|
-
if (!shouldApply) {
|
|
309
|
-
for (const change of plan.changes) {
|
|
310
|
-
process.stdout.write(patches.unifiedDiff(change.relPath, change.before, change.after));
|
|
311
|
-
}
|
|
312
|
-
// Phase 18.7 B6 — include auto-instrument diff in dry-run output so
|
|
313
|
-
// users can eyeball what will be written before --apply.
|
|
314
|
-
if (args.autoInstrument && args['intent-result']) {
|
|
315
|
-
try {
|
|
316
|
-
const intent = JSON.parse(readFileSync(args['intent-result'], 'utf8'));
|
|
317
|
-
const acceptedEvents = (intent && intent.accepted && intent.accepted.events) || [];
|
|
318
|
-
const frameworkName = (patcher && patcher.name) || framework;
|
|
319
|
-
|
|
320
|
-
// Phase 20 — Try LLM-based property extraction first.
|
|
321
|
-
const ingestUrl = args['ingest-url'] || args['api-url'] || 'https://gurulu.io';
|
|
322
|
-
const token = args['token'] || args['api-key'] || '';
|
|
323
|
-
const siteId = args['site-id'] || '';
|
|
324
|
-
if (token) {
|
|
325
|
-
try {
|
|
326
|
-
const hits = await tryLlmExtraction(acceptedEvents, {
|
|
327
|
-
repoRoot,
|
|
328
|
-
framework: frameworkName,
|
|
329
|
-
ingestUrl,
|
|
330
|
-
token,
|
|
331
|
-
siteId,
|
|
332
|
-
log,
|
|
333
|
-
});
|
|
334
|
-
if (hits > 0) log(`LLM property extraction: ${hits} event(s) enriched`);
|
|
335
|
-
} catch (err) {
|
|
336
|
-
log(`LLM property extraction failed (using placeholders): ${err.message || err}`);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Placeholder fallback for events that LLM didn't cover.
|
|
341
|
-
for (const event of acceptedEvents) {
|
|
342
|
-
if (event.extractedProperties && event.extractedProperties.length > 0) continue;
|
|
343
|
-
if (!event.propertySchema || !event.propertySchema.length) continue;
|
|
344
|
-
event.extractedProperties = event.propertySchema
|
|
345
|
-
.filter((p) => p.required)
|
|
346
|
-
.map((p) => ({
|
|
347
|
-
name: p.name,
|
|
348
|
-
type: p.type || 'unknown',
|
|
349
|
-
source: p.example !== undefined && p.example !== null
|
|
350
|
-
? JSON.stringify(p.example)
|
|
351
|
-
: getPropertyPlaceholder(p.type, p.name),
|
|
352
|
-
}));
|
|
353
|
-
}
|
|
354
|
-
const aiResult = patches.autoInstrumentDispatch(
|
|
355
|
-
frameworkName,
|
|
356
|
-
{ repoRoot },
|
|
357
|
-
acceptedEvents,
|
|
358
|
-
);
|
|
359
|
-
for (const change of aiResult.changes || []) {
|
|
360
|
-
process.stdout.write(
|
|
361
|
-
patches.unifiedDiff(change.relPath, change.before, change.after),
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
log(
|
|
365
|
-
`Auto-instrument dry-run: ${aiResult.filesModified} file(s) would change, ` +
|
|
366
|
-
`${aiResult.eventsInstrumented} event(s), ${aiResult.eventsSkipped} skipped.`,
|
|
367
|
-
);
|
|
368
|
-
} catch (err) {
|
|
369
|
-
process.stderr.write(
|
|
370
|
-
`Auto-instrument dry-run failed: ${err.message || err}\n`,
|
|
371
|
-
);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
log(`Dry-run: ${plan.changes.length} file(s) would change. Re-run with --apply to write.`);
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
const result = await patches.applyPlan(repoRoot, patcher, plan);
|
|
378
|
-
log(`Applied ${result.files.length} file change(s). Patch log: .gurulu/patch-log.json`);
|
|
379
|
-
|
|
380
|
-
// -------------------------------------------------------------------
|
|
381
|
-
// Phase 18.7 B6 — Auto-instrumentation (opt-in).
|
|
382
|
-
// When --auto-instrument is set AND --intent-result is provided, load
|
|
383
|
-
// the accepted events from the Phase 18.6 intent proposal, dispatch
|
|
384
|
-
// to the matching auto-instrument module, and append the resulting
|
|
385
|
-
// route-handler changes to the existing patch-log.
|
|
386
|
-
//
|
|
387
|
-
// Sprint D / D2 — auto-instrument failures (collision, apply-throw, or a
|
|
388
|
-
// missing intent-result file) now roll back the script-tag patch we just
|
|
389
|
-
// applied. Otherwise the user is left half-installed: a tracker tag in
|
|
390
|
-
// their HTML/layout but no route-handler instrumentation, and no signal
|
|
391
|
-
// that the partial state needs cleanup. We invoke `patches.rollback()` and
|
|
392
|
-
// emit `INSTALL_ROLLED_BACK` on stdout so install.ts (and any wrapping
|
|
393
|
-
// agent) can surface the rollback to the user.
|
|
394
|
-
// -------------------------------------------------------------------
|
|
395
|
-
let autoInstrumentFailureReason = null;
|
|
396
|
-
function rollbackOnFailure(reason) {
|
|
397
|
-
if (autoInstrumentFailureReason) return; // only roll back once
|
|
398
|
-
autoInstrumentFailureReason = reason;
|
|
399
|
-
try {
|
|
400
|
-
const rb = patches.rollback(repoRoot);
|
|
401
|
-
if (rb && rb.rolledBack) {
|
|
402
|
-
log(`Auto-instrument failed (${reason}); rolled back ${rb.files.length} script-tag change(s).`);
|
|
403
|
-
process.stdout.write(
|
|
404
|
-
'INSTALL_ROLLED_BACK ' +
|
|
405
|
-
JSON.stringify({ stage: 'auto-instrument', reason, files: rb.files }) +
|
|
406
|
-
'\n',
|
|
407
|
-
);
|
|
408
|
-
} else {
|
|
409
|
-
process.stderr.write(
|
|
410
|
-
`Auto-instrument failed (${reason}); rollback unavailable: ${rb && rb.reason}\n`,
|
|
411
|
-
);
|
|
412
|
-
}
|
|
413
|
-
} catch (err) {
|
|
414
|
-
process.stderr.write(
|
|
415
|
-
`Auto-instrument failed (${reason}); rollback threw: ${err.stack || err.message || err}\n`,
|
|
416
|
-
);
|
|
417
|
-
}
|
|
418
|
-
// Sprint E1.1 — exit code 5 signals "patches rolled back" so install.ts
|
|
419
|
-
// can skip the npm-install / .env-merge / ingest-ping steps and surface
|
|
420
|
-
// the rollback to the user. The wrapping CLI converts this into a
|
|
421
|
-
// partially-installed state with `summary.rolledBack=true`.
|
|
422
|
-
process.exit(5);
|
|
423
|
-
}
|
|
424
|
-
if (args.autoInstrument) {
|
|
425
|
-
const intentPath = args['intent-result'];
|
|
426
|
-
if (!intentPath) {
|
|
427
|
-
// Sprint D / D2 — explicit `--auto-instrument` without `--intent-result`
|
|
428
|
-
// is a user error, not "skip silently". The script-tag patch we just
|
|
429
|
-
// applied promised the agent that route handlers would be wired, so
|
|
430
|
-
// unwind that partial state.
|
|
431
|
-
rollbackOnFailure('missing-intent-result');
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
let intent;
|
|
435
|
-
try {
|
|
436
|
-
intent = JSON.parse(readFileSync(intentPath, 'utf8'));
|
|
437
|
-
} catch (err) {
|
|
438
|
-
process.stderr.write(`Auto-instrument: failed to read ${intentPath}: ${err.message}\n`);
|
|
439
|
-
rollbackOnFailure('intent-result-unreadable');
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
const acceptedEvents = (intent && intent.accepted && intent.accepted.events) || [];
|
|
443
|
-
if (acceptedEvents.length === 0) {
|
|
444
|
-
log('Auto-instrument: no accepted events to instrument');
|
|
445
|
-
// Empty intent file is NOT a failure — the agent legitimately had
|
|
446
|
-
// nothing to instrument. Leave the script-tag patch on disk.
|
|
447
|
-
return;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
const frameworkName = (patcher && patcher.name) || framework;
|
|
451
|
-
|
|
452
|
-
// Phase 20 — Try LLM-based property extraction first.
|
|
453
|
-
const ingestUrl = args['ingest-url'] || args['api-url'] || 'https://gurulu.io';
|
|
454
|
-
const token = args['token'] || args['api-key'] || '';
|
|
455
|
-
const siteId = args['site-id'] || '';
|
|
456
|
-
if (token) {
|
|
457
|
-
try {
|
|
458
|
-
const hits = await tryLlmExtraction(acceptedEvents, {
|
|
459
|
-
repoRoot,
|
|
460
|
-
framework: frameworkName,
|
|
461
|
-
ingestUrl,
|
|
462
|
-
token,
|
|
463
|
-
siteId,
|
|
464
|
-
log,
|
|
465
|
-
});
|
|
466
|
-
if (hits > 0) log(`LLM property extraction: ${hits} event(s) enriched`);
|
|
467
|
-
} catch (err) {
|
|
468
|
-
log(`LLM property extraction failed (using placeholders): ${err.message || err}`);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// Placeholder fallback: enrich events with extractedProperties from
|
|
473
|
-
// propertySchema for any events the LLM didn't cover.
|
|
474
|
-
for (const event of acceptedEvents) {
|
|
475
|
-
if (event.extractedProperties && event.extractedProperties.length > 0) continue;
|
|
476
|
-
if (!event.propertySchema || !event.propertySchema.length) continue;
|
|
477
|
-
event.extractedProperties = event.propertySchema
|
|
478
|
-
.filter((p) => p.required)
|
|
479
|
-
.map((p) => ({
|
|
480
|
-
name: p.name,
|
|
481
|
-
type: p.type || 'unknown',
|
|
482
|
-
source: p.example !== undefined && p.example !== null
|
|
483
|
-
? JSON.stringify(p.example)
|
|
484
|
-
: getPropertyPlaceholder(p.type, p.name),
|
|
485
|
-
}));
|
|
486
|
-
}
|
|
487
|
-
const aiResult = patches.autoInstrumentDispatch(
|
|
488
|
-
frameworkName,
|
|
489
|
-
{ repoRoot },
|
|
490
|
-
acceptedEvents,
|
|
491
|
-
);
|
|
492
|
-
if (aiResult.collision) {
|
|
493
|
-
process.stderr.write(
|
|
494
|
-
`Auto-instrument: singleton helper collision; aborting without changes.\n`,
|
|
495
|
-
);
|
|
496
|
-
// Sprint D / D2 — collision mid-flow leaves the user with a script tag
|
|
497
|
-
// injected and no helper file. Roll back the script-tag patch so the
|
|
498
|
-
// agent can decide whether to retry after resolving the collision.
|
|
499
|
-
rollbackOnFailure('singleton-helper-collision');
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
if (!aiResult.changes || aiResult.changes.length === 0) {
|
|
503
|
-
log(`Auto-instrument: 0 file(s) changed (${aiResult.eventsSkipped} event(s) skipped)`);
|
|
504
|
-
for (const note of aiResult.notes || []) log(` note: ${note}`);
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
try {
|
|
508
|
-
const aiApply = await patches.applyAutoInstrumentPlan(repoRoot, {
|
|
509
|
-
framework: frameworkName,
|
|
510
|
-
changes: aiResult.changes,
|
|
511
|
-
});
|
|
512
|
-
log(
|
|
513
|
-
`Auto-instrument: ${aiResult.filesModified} file(s) instrumented, ` +
|
|
514
|
-
`${aiResult.eventsInstrumented} event(s), ${aiResult.eventsSkipped} skipped`,
|
|
515
|
-
);
|
|
516
|
-
log(`Auto-instrument: wrote ${aiApply.files.length} change(s) to patch-log`);
|
|
517
|
-
// Emit a machine-readable summary on stdout so the CLI can capture it.
|
|
518
|
-
process.stdout.write(
|
|
519
|
-
'AUTO_INSTRUMENT_RESULT ' +
|
|
520
|
-
JSON.stringify({
|
|
521
|
-
filesModified: aiResult.filesModified,
|
|
522
|
-
eventsInstrumented: aiResult.eventsInstrumented,
|
|
523
|
-
eventsSkipped: aiResult.eventsSkipped,
|
|
524
|
-
notes: aiResult.notes || [],
|
|
525
|
-
}) +
|
|
526
|
-
'\n',
|
|
527
|
-
);
|
|
528
|
-
} catch (err) {
|
|
529
|
-
process.stderr.write(`Auto-instrument: apply failed: ${err.stack || err.message || err}\n`);
|
|
530
|
-
// Sprint D / D2 — applyAutoInstrumentPlan threw mid-write. The patch
|
|
531
|
-
// log is partially written; let the rollback restore both script-tag
|
|
532
|
-
// and any auto-instrument files we already touched (rollback walks the
|
|
533
|
-
// full file list in patch-log.json).
|
|
534
|
-
rollbackOnFailure('apply-auto-instrument-threw');
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
async function main() {
|
|
540
|
-
const args = parseArgs(process.argv.slice(2));
|
|
541
|
-
if (args.help) {
|
|
542
|
-
printHelp();
|
|
543
|
-
process.exit(0);
|
|
544
|
-
}
|
|
545
|
-
const input = args._[0];
|
|
546
|
-
if (!input) {
|
|
547
|
-
process.stderr.write('Error: input path is required (scan json or repo root)\n');
|
|
548
|
-
printHelp(process.stderr);
|
|
549
|
-
process.exit(1);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// Patch-mode: when input is a directory we run the code editor pipeline.
|
|
553
|
-
const isDirectory = input !== '-' && existsSync(input) && statSync(input).isDirectory();
|
|
554
|
-
if (isDirectory) {
|
|
555
|
-
await runPatchMode(input, args);
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
const scanFile = input;
|
|
560
|
-
if (!args['site-id'] || !args['tenant-id']) {
|
|
561
|
-
process.stderr.write('Error: --site-id and --tenant-id are required\n');
|
|
562
|
-
process.exit(1);
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
const outputPath = args.output || '.gurulu';
|
|
566
|
-
const log = args.quiet ? () => {} : (m) => process.stderr.write(m + '\n');
|
|
567
|
-
|
|
568
|
-
let scanOutput;
|
|
569
|
-
try {
|
|
570
|
-
scanOutput = await readScanInput(scanFile);
|
|
571
|
-
} catch (err) {
|
|
572
|
-
process.stderr.write(`Error: failed to read scan input: ${err.message}\n`);
|
|
573
|
-
process.exit(1);
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
const domains = typeof args.domains === 'string'
|
|
577
|
-
? args.domains.split(',').map((d) => d.trim()).filter(Boolean)
|
|
578
|
-
: undefined;
|
|
579
|
-
|
|
580
|
-
const result = generateArtifacts(scanOutput, {
|
|
581
|
-
siteId: args['site-id'],
|
|
582
|
-
tenantId: args['tenant-id'],
|
|
583
|
-
outputPath,
|
|
584
|
-
...(domains ? { domains } : {}),
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
const validation = validateArtifacts(result.artifacts);
|
|
588
|
-
if (!validation.ok) {
|
|
589
|
-
process.stderr.write('Validation failed — not writing any files:\n');
|
|
590
|
-
for (const err of validation.errors) {
|
|
591
|
-
process.stderr.write(` ${err.file}: ${err.message}\n`);
|
|
592
|
-
}
|
|
593
|
-
process.exit(2);
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
const filenames = Object.keys(result.artifacts);
|
|
597
|
-
if (args.dryRun) {
|
|
598
|
-
log(`Dry-run — would write ${filenames.length} files to ${outputPath}/:`);
|
|
599
|
-
for (const f of filenames) log(` ${outputPath}/${f}`);
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
await writeArtifacts(result.artifacts, outputPath);
|
|
604
|
-
log(`Wrote ${filenames.length} artifacts to ${outputPath}/`);
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
const invokedDirectly =
|
|
608
|
-
typeof process !== 'undefined' &&
|
|
609
|
-
process.argv[1] &&
|
|
610
|
-
(() => {
|
|
611
|
-
try {
|
|
612
|
-
return fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
|
|
613
|
-
} catch {
|
|
614
|
-
return false;
|
|
615
|
-
}
|
|
616
|
-
})();
|
|
617
|
-
|
|
618
|
-
if (invokedDirectly) {
|
|
619
|
-
main().catch((err) => {
|
|
620
|
-
process.stderr.write(`[agentic-install] ERROR: ${err.stack || err.message || err}\n`);
|
|
621
|
-
process.exit(1);
|
|
622
|
-
});
|
|
623
|
-
}
|