@gurulu/cli 0.4.7 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/LICENSE +92 -0
  2. package/README.md +35 -106
  3. package/dist/bin.d.ts +3 -0
  4. package/dist/bin.d.ts.map +1 -0
  5. package/dist/bin.js +25751 -0
  6. package/dist/commands/auth.d.ts +23 -20
  7. package/dist/commands/auth.d.ts.map +1 -0
  8. package/dist/commands/doctor.d.ts +20 -6
  9. package/dist/commands/doctor.d.ts.map +1 -0
  10. package/dist/commands/init.d.ts +33 -11
  11. package/dist/commands/init.d.ts.map +1 -0
  12. package/dist/commands/pull.d.ts +13 -0
  13. package/dist/commands/pull.d.ts.map +1 -0
  14. package/dist/commands/push.d.ts +40 -0
  15. package/dist/commands/push.d.ts.map +1 -0
  16. package/dist/commands/validate.d.ts +36 -0
  17. package/dist/commands/validate.d.ts.map +1 -0
  18. package/dist/index.d.ts +4 -1
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +25326 -876
  21. package/dist/lib/api.d.ts +139 -0
  22. package/dist/lib/api.d.ts.map +1 -0
  23. package/dist/lib/codegen.d.ts +4 -0
  24. package/dist/lib/codegen.d.ts.map +1 -0
  25. package/dist/lib/config.d.ts +43 -0
  26. package/dist/lib/config.d.ts.map +1 -0
  27. package/dist/lib/detect.d.ts +27 -0
  28. package/dist/lib/detect.d.ts.map +1 -0
  29. package/dist/lib/detect.js +106 -0
  30. package/dist/lib/exec-install.d.ts +21 -0
  31. package/dist/lib/exec-install.d.ts.map +1 -0
  32. package/dist/lib/install-plan.d.ts +25 -0
  33. package/dist/lib/install-plan.d.ts.map +1 -0
  34. package/dist/lib/install-plan.js +161 -0
  35. package/package.json +51 -20
  36. package/bin/gurulu.js +0 -2
  37. package/dist/api-client.d.ts +0 -33
  38. package/dist/api-client.js +0 -175
  39. package/dist/commands/add-server.d.ts +0 -9
  40. package/dist/commands/add-server.js +0 -162
  41. package/dist/commands/alerts.d.ts +0 -27
  42. package/dist/commands/alerts.js +0 -309
  43. package/dist/commands/api-keys.d.ts +0 -20
  44. package/dist/commands/api-keys.js +0 -130
  45. package/dist/commands/attribution.d.ts +0 -22
  46. package/dist/commands/attribution.js +0 -111
  47. package/dist/commands/audiences.d.ts +0 -23
  48. package/dist/commands/audiences.js +0 -243
  49. package/dist/commands/audit.d.ts +0 -20
  50. package/dist/commands/audit.js +0 -130
  51. package/dist/commands/auth.js +0 -249
  52. package/dist/commands/chat.d.ts +0 -19
  53. package/dist/commands/chat.js +0 -118
  54. package/dist/commands/config.d.ts +0 -10
  55. package/dist/commands/config.js +0 -92
  56. package/dist/commands/consent.d.ts +0 -27
  57. package/dist/commands/consent.js +0 -233
  58. package/dist/commands/conversion-paths.d.ts +0 -19
  59. package/dist/commands/conversion-paths.js +0 -55
  60. package/dist/commands/db.d.ts +0 -25
  61. package/dist/commands/db.js +0 -330
  62. package/dist/commands/destinations.d.ts +0 -20
  63. package/dist/commands/destinations.js +0 -191
  64. package/dist/commands/doctor.js +0 -360
  65. package/dist/commands/errors.d.ts +0 -27
  66. package/dist/commands/errors.js +0 -121
  67. package/dist/commands/events.d.ts +0 -33
  68. package/dist/commands/events.js +0 -371
  69. package/dist/commands/experiments.d.ts +0 -22
  70. package/dist/commands/experiments.js +0 -264
  71. package/dist/commands/funnels.d.ts +0 -17
  72. package/dist/commands/funnels.js +0 -203
  73. package/dist/commands/goals.d.ts +0 -18
  74. package/dist/commands/goals.js +0 -214
  75. package/dist/commands/heatmap.d.ts +0 -27
  76. package/dist/commands/heatmap.js +0 -112
  77. package/dist/commands/identity.d.ts +0 -29
  78. package/dist/commands/identity.js +0 -328
  79. package/dist/commands/init.js +0 -215
  80. package/dist/commands/insights.d.ts +0 -10
  81. package/dist/commands/insights.js +0 -77
  82. package/dist/commands/install.d.ts +0 -259
  83. package/dist/commands/install.js +0 -1590
  84. package/dist/commands/login.d.ts +0 -20
  85. package/dist/commands/login.js +0 -170
  86. package/dist/commands/logout.d.ts +0 -10
  87. package/dist/commands/logout.js +0 -41
  88. package/dist/commands/playground.d.ts +0 -11
  89. package/dist/commands/playground.js +0 -47
  90. package/dist/commands/releases.d.ts +0 -17
  91. package/dist/commands/releases.js +0 -54
  92. package/dist/commands/replay.d.ts +0 -18
  93. package/dist/commands/replay.js +0 -64
  94. package/dist/commands/secrets.d.ts +0 -19
  95. package/dist/commands/secrets.js +0 -145
  96. package/dist/commands/setup.d.ts +0 -21
  97. package/dist/commands/setup.js +0 -67
  98. package/dist/commands/sites.d.ts +0 -18
  99. package/dist/commands/sites.js +0 -139
  100. package/dist/commands/skad.d.ts +0 -18
  101. package/dist/commands/skad.js +0 -53
  102. package/dist/commands/sourcemap.d.ts +0 -33
  103. package/dist/commands/sourcemap.js +0 -204
  104. package/dist/commands/status.d.ts +0 -7
  105. package/dist/commands/status.js +0 -136
  106. package/dist/commands/upgrade.d.ts +0 -21
  107. package/dist/commands/upgrade.js +0 -183
  108. package/dist/commands/warehouse.d.ts +0 -20
  109. package/dist/commands/warehouse.js +0 -65
  110. package/dist/commands/warehouses.d.ts +0 -17
  111. package/dist/commands/warehouses.js +0 -182
  112. package/dist/commands/watch.d.ts +0 -45
  113. package/dist/commands/watch.js +0 -258
  114. package/dist/commands/whoami.d.ts +0 -9
  115. package/dist/commands/whoami.js +0 -50
  116. package/dist/config.d.ts +0 -75
  117. package/dist/config.js +0 -329
  118. package/dist/frameworks/detect.d.ts +0 -8
  119. package/dist/frameworks/detect.js +0 -458
  120. package/dist/install-intent-proposal.d.ts +0 -99
  121. package/dist/install-intent-proposal.js +0 -202
  122. package/dist/utils/api.d.ts +0 -20
  123. package/dist/utils/api.js +0 -47
  124. package/dist/utils/config.d.ts +0 -13
  125. package/dist/utils/config.js +0 -30
  126. package/dist/utils/confirm.d.ts +0 -17
  127. package/dist/utils/confirm.js +0 -40
  128. package/dist/utils/dry-run.d.ts +0 -20
  129. package/dist/utils/dry-run.js +0 -67
  130. package/dist/utils/from-file.d.ts +0 -9
  131. package/dist/utils/from-file.js +0 -72
  132. package/dist/utils/redact.d.ts +0 -14
  133. package/dist/utils/redact.js +0 -48
  134. package/dist/utils/ui.d.ts +0 -14
  135. package/dist/utils/ui.js +0 -59
  136. package/scripts/.gitkeep +0 -0
  137. package/scripts/README-gurulu-agentic-install.md +0 -114
  138. package/scripts/README-gurulu-scan.md +0 -98
  139. package/scripts/audit-cli-scopes.mjs +0 -204
  140. package/scripts/backfill-tenant-id.mjs +0 -172
  141. package/scripts/backfill-tenant-links.ts +0 -252
  142. package/scripts/backup-clickhouse.sh +0 -27
  143. package/scripts/backup-postgres.sh +0 -19
  144. package/scripts/bootstrap-runtime-schema.mjs +0 -87
  145. package/scripts/bootstrap-stripe.mjs +0 -158
  146. package/scripts/gurulu-agentic-install.lib.cjs +0 -762
  147. package/scripts/gurulu-agentic-install.mjs +0 -623
  148. package/scripts/gurulu-scan.lib.cjs +0 -1509
  149. package/scripts/gurulu-scan.mjs +0 -91
  150. package/scripts/gurulu-verify-install.lib.cjs +0 -334
  151. package/scripts/gurulu-verify-install.mjs +0 -59
  152. package/scripts/init-ssl.sh +0 -26
  153. package/scripts/migrate-flow-graph-enums.sh +0 -86
  154. package/scripts/monitor-disk.sh +0 -24
  155. package/scripts/patches/astro.patch.cjs +0 -74
  156. package/scripts/patches/auto-instrument/ast-helper.cjs +0 -480
  157. package/scripts/patches/auto-instrument/astro.cjs +0 -273
  158. package/scripts/patches/auto-instrument/express.cjs +0 -383
  159. package/scripts/patches/auto-instrument/fastify.cjs +0 -262
  160. package/scripts/patches/auto-instrument/hono.cjs +0 -392
  161. package/scripts/patches/auto-instrument/index.cjs +0 -80
  162. package/scripts/patches/auto-instrument/nestjs.cjs +0 -286
  163. package/scripts/patches/auto-instrument/nextjs-app-router.cjs +0 -345
  164. package/scripts/patches/auto-instrument/nextjs-pages.cjs +0 -361
  165. package/scripts/patches/auto-instrument/remix.cjs +0 -168
  166. package/scripts/patches/auto-instrument/sdk-helper-map.cjs +0 -241
  167. package/scripts/patches/auto-instrument/singleton-helper.cjs +0 -193
  168. package/scripts/patches/auto-instrument/sveltekit.cjs +0 -161
  169. package/scripts/patches/auto-instrument/vite-react.cjs +0 -37
  170. package/scripts/patches/auto-instrument/vue.cjs +0 -196
  171. package/scripts/patches/express.patch.cjs +0 -99
  172. package/scripts/patches/fastify.patch.cjs +0 -108
  173. package/scripts/patches/index.cjs +0 -300
  174. package/scripts/patches/nestjs.patch.cjs +0 -112
  175. package/scripts/patches/nextjs-app-router.patch.cjs +0 -97
  176. package/scripts/patches/nextjs-pages.patch.cjs +0 -97
  177. package/scripts/patches/remix.patch.cjs +0 -75
  178. package/scripts/patches/sveltekit.patch.cjs +0 -72
  179. package/scripts/patches/vite-react.patch.cjs +0 -73
  180. package/scripts/patches/vue.patch.cjs +0 -82
  181. package/scripts/renew-ssl.sh +0 -14
  182. package/scripts/resolve-migration.sh +0 -23
  183. package/scripts/seed-cli-dev-keys.mjs +0 -130
  184. package/scripts/seed-test-data.mjs +0 -391
  185. package/scripts/spike-browserless.ts +0 -65
  186. package/scripts/tenant-pivot-consistency-check.mjs +0 -205
  187. package/scripts/tenant-pivot-phase-3-cleanup.lib.cjs +0 -258
  188. package/scripts/tenant-pivot-phase-3-cleanup.mjs +0 -98
  189. package/scripts/test-identity-resolution.ts +0 -804
  190. 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
- }