@gurulu/cli 1.0.0 → 1.0.4

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/dist/bin.js CHANGED
@@ -25055,8 +25055,332 @@ var doctorCmd = defineCommand({
25055
25055
  });
25056
25056
 
25057
25057
  // src/commands/init.ts
25058
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "node:fs";
25059
- import { dirname as dirname2 } from "node:path";
25058
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "node:fs";
25059
+ import { dirname as dirname3 } from "node:path";
25060
+
25061
+ // src/lib/detect.ts
25062
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
25063
+ import { dirname as dirname2, join as join4, parse } from "node:path";
25064
+ function readPackageJson(dir) {
25065
+ const p = join4(dir, "package.json");
25066
+ if (!existsSync4(p))
25067
+ return null;
25068
+ try {
25069
+ return JSON.parse(readFileSync4(p, "utf8"));
25070
+ } catch {
25071
+ return null;
25072
+ }
25073
+ }
25074
+ function hasDep(pkg, name) {
25075
+ if (!pkg)
25076
+ return false;
25077
+ return Boolean(pkg.dependencies?.[name] ?? pkg.devDependencies?.[name]);
25078
+ }
25079
+ function detectPackageManager(dir) {
25080
+ const root = parse(dir).root;
25081
+ let cur = dir;
25082
+ for (let i2 = 0;i2 < 6; i2++) {
25083
+ if (existsSync4(join4(cur, "bun.lock")) || existsSync4(join4(cur, "bun.lockb")))
25084
+ return "bun";
25085
+ if (existsSync4(join4(cur, "pnpm-lock.yaml")))
25086
+ return "pnpm";
25087
+ if (existsSync4(join4(cur, "yarn.lock")))
25088
+ return "yarn";
25089
+ if (existsSync4(join4(cur, "package-lock.json")))
25090
+ return "npm";
25091
+ if (cur === root)
25092
+ break;
25093
+ const parent = dirname2(cur);
25094
+ if (parent === cur)
25095
+ break;
25096
+ cur = parent;
25097
+ }
25098
+ return "npm";
25099
+ }
25100
+ function detectFramework(pkg, dir) {
25101
+ if (!pkg)
25102
+ return "unknown";
25103
+ if (hasDep(pkg, "next"))
25104
+ return "next";
25105
+ if (hasDep(pkg, "nuxt"))
25106
+ return "nuxt";
25107
+ if (hasDep(pkg, "@sveltejs/kit") || hasDep(pkg, "svelte"))
25108
+ return "svelte";
25109
+ if (hasDep(pkg, "astro"))
25110
+ return "astro";
25111
+ if (hasDep(pkg, "vite") && (hasDep(pkg, "vue") || hasDep(pkg, "react")))
25112
+ return "vite";
25113
+ if (hasDep(pkg, "vue"))
25114
+ return "vue";
25115
+ if (hasDep(pkg, "react"))
25116
+ return "react";
25117
+ if (hasDep(pkg, "hono"))
25118
+ return "hono";
25119
+ if (hasDep(pkg, "fastify"))
25120
+ return "fastify";
25121
+ if (hasDep(pkg, "express"))
25122
+ return "express";
25123
+ if (hasDep(pkg, "koa"))
25124
+ return "koa";
25125
+ if (existsSync4(join4(dir, "server.js")) || existsSync4(join4(dir, "server.ts")))
25126
+ return "node-server";
25127
+ return "unknown";
25128
+ }
25129
+ function frameworkRuntime(fw) {
25130
+ switch (fw) {
25131
+ case "next":
25132
+ case "react":
25133
+ case "vue":
25134
+ case "nuxt":
25135
+ case "svelte":
25136
+ case "astro":
25137
+ case "vite":
25138
+ return "browser";
25139
+ case "express":
25140
+ case "fastify":
25141
+ case "hono":
25142
+ case "koa":
25143
+ case "node-server":
25144
+ return "node";
25145
+ default:
25146
+ return "unknown";
25147
+ }
25148
+ }
25149
+ function detectProject(dir) {
25150
+ const pkg = readPackageJson(dir);
25151
+ const framework = detectFramework(pkg, dir);
25152
+ return {
25153
+ dir,
25154
+ hasPackageJson: pkg !== null,
25155
+ framework,
25156
+ runtime: frameworkRuntime(framework),
25157
+ packageManager: detectPackageManager(dir),
25158
+ packageJson: pkg
25159
+ };
25160
+ }
25161
+
25162
+ // src/lib/exec-install.ts
25163
+ import { spawn } from "node:child_process";
25164
+ async function execInstall(plan, opts) {
25165
+ const [bin, ...args] = plan.installCommand.split(/\s+/);
25166
+ if (!bin) {
25167
+ return {
25168
+ ok: false,
25169
+ exitCode: null,
25170
+ durationMs: 0,
25171
+ command: plan.installCommand,
25172
+ stderr: "empty install command"
25173
+ };
25174
+ }
25175
+ const started = Date.now();
25176
+ return new Promise((resolve) => {
25177
+ const child = spawn(bin, args, {
25178
+ cwd: opts.cwd,
25179
+ stdio: opts.silent ? ["ignore", "pipe", "pipe"] : ["inherit", "inherit", "pipe"],
25180
+ shell: process.platform === "win32"
25181
+ });
25182
+ let stderr = "";
25183
+ if (child.stderr) {
25184
+ child.stderr.on("data", (chunk) => {
25185
+ const text = chunk.toString("utf8");
25186
+ stderr += text;
25187
+ if (!opts.silent) {
25188
+ process.stderr.write(text);
25189
+ }
25190
+ });
25191
+ }
25192
+ const timeout = setTimeout(() => {
25193
+ child.kill("SIGTERM");
25194
+ }, opts.timeoutMs ?? 120000);
25195
+ child.on("error", (err) => {
25196
+ clearTimeout(timeout);
25197
+ resolve({
25198
+ ok: false,
25199
+ exitCode: null,
25200
+ durationMs: Date.now() - started,
25201
+ command: plan.installCommand,
25202
+ stderr: err.message
25203
+ });
25204
+ });
25205
+ child.on("close", (code) => {
25206
+ clearTimeout(timeout);
25207
+ resolve({
25208
+ ok: code === 0,
25209
+ exitCode: code,
25210
+ durationMs: Date.now() - started,
25211
+ command: plan.installCommand,
25212
+ ...stderr ? { stderr } : {}
25213
+ });
25214
+ });
25215
+ });
25216
+ }
25217
+
25218
+ // src/lib/install-plan.ts
25219
+ function installCmdFor(pm, pkg) {
25220
+ switch (pm) {
25221
+ case "bun":
25222
+ return `bun add ${pkg}`;
25223
+ case "pnpm":
25224
+ return `pnpm add ${pkg}`;
25225
+ case "yarn":
25226
+ return `yarn add ${pkg}`;
25227
+ default:
25228
+ return `npm install ${pkg}`;
25229
+ }
25230
+ }
25231
+ function sdkFor(framework) {
25232
+ switch (framework) {
25233
+ case "express":
25234
+ case "fastify":
25235
+ case "hono":
25236
+ case "koa":
25237
+ case "node-server":
25238
+ return "@gurulu/node";
25239
+ default:
25240
+ return "@gurulu/web";
25241
+ }
25242
+ }
25243
+ function snippetWeb(workspaceKey) {
25244
+ return `import gurulu from '@gurulu/web';
25245
+
25246
+ gurulu.init({
25247
+ workspaceKey: process.env.NEXT_PUBLIC_GURULU_WORKSPACE ?? '${workspaceKey}',
25248
+ endpoint: process.env.NEXT_PUBLIC_GURULU_ENDPOINT, // optional, defaults to https://ingest.gurulu.io
25249
+ });`;
25250
+ }
25251
+ function snippetNext(workspaceKey) {
25252
+ return `// src/app/gurulu-provider.tsx
25253
+ 'use client';
25254
+ import { useEffect } from 'react';
25255
+ import gurulu from '@gurulu/web';
25256
+
25257
+ export function GuruluProvider({ children }: { children: React.ReactNode }) {
25258
+ useEffect(() => {
25259
+ gurulu.init({
25260
+ workspaceKey: process.env.NEXT_PUBLIC_GURULU_WORKSPACE ?? '${workspaceKey}',
25261
+ endpoint: process.env.NEXT_PUBLIC_GURULU_ENDPOINT,
25262
+ });
25263
+ }, []);
25264
+ return <>{children}</>;
25265
+ }
25266
+
25267
+ // Then wrap your root in app/layout.tsx:
25268
+ // <GuruluProvider>{children}</GuruluProvider>`;
25269
+ }
25270
+ function snippetNode(workspaceKey) {
25271
+ return `import { createGurulu } from '@gurulu/node';
25272
+
25273
+ const gurulu = createGurulu({
25274
+ workspaceKey: process.env.GURULU_WORKSPACE ?? '${workspaceKey}',
25275
+ apiKey: process.env.GURULU_API_KEY,
25276
+ endpoint: process.env.GURULU_ENDPOINT, // optional, defaults to https://ingest.gurulu.io
25277
+ });
25278
+
25279
+ // In your handler:
25280
+ await gurulu.track('purchase_completed', {
25281
+ user_id: 'u_42',
25282
+ order_id: 'o_123',
25283
+ total: 49.99,
25284
+ });`;
25285
+ }
25286
+ function snippetExpress(workspaceKey) {
25287
+ return `import express from 'express';
25288
+ import { guruluMiddleware } from '@gurulu/node/middleware/express';
25289
+
25290
+ const app = express();
25291
+ app.use(guruluMiddleware({
25292
+ workspaceKey: process.env.GURULU_WORKSPACE ?? '${workspaceKey}',
25293
+ apiKey: process.env.GURULU_API_KEY,
25294
+ endpoint: process.env.GURULU_ENDPOINT,
25295
+ }));`;
25296
+ }
25297
+ function snippetHono(workspaceKey) {
25298
+ return `import { Hono } from 'hono';
25299
+ import { createGurulu } from '@gurulu/node';
25300
+
25301
+ const gurulu = createGurulu({
25302
+ workspaceKey: process.env.GURULU_WORKSPACE ?? '${workspaceKey}',
25303
+ apiKey: process.env.GURULU_API_KEY,
25304
+ endpoint: process.env.GURULU_ENDPOINT,
25305
+ });
25306
+
25307
+ const app = new Hono();
25308
+ app.post('/checkout/complete', async (c) => {
25309
+ await gurulu.track('purchase_completed', await c.req.json());
25310
+ return c.json({ ok: true });
25311
+ });`;
25312
+ }
25313
+ function placementHintFor(framework) {
25314
+ switch (framework) {
25315
+ case "next":
25316
+ return "Add the GuruluProvider to app/layout.tsx (App Router) or pages/_app.tsx (Pages Router) and wrap children.";
25317
+ case "nuxt":
25318
+ return "Add to plugins/gurulu.client.ts and register in nuxt.config.ts.";
25319
+ case "svelte":
25320
+ return "Add to src/routes/+layout.svelte inside onMount().";
25321
+ case "astro":
25322
+ return "Add to src/layouts/Base.astro inside a <script> tag (or use a hydrated component).";
25323
+ case "vite":
25324
+ case "react":
25325
+ case "vue":
25326
+ return "Add to src/main.ts (or your entry file) before mounting the app.";
25327
+ case "express":
25328
+ return "Register guruluMiddleware before your route handlers in your app entry file.";
25329
+ case "fastify":
25330
+ return "Register the Gurulu plugin via fastify.register() before route definitions.";
25331
+ case "hono":
25332
+ return "Initialize the SDK at the top of your app file; call track() inside route handlers.";
25333
+ case "koa":
25334
+ return "Add as Koa middleware (app.use) before your routes.";
25335
+ case "node-server":
25336
+ return "Initialize the SDK at startup; call track() from your handlers.";
25337
+ default:
25338
+ return "Initialize the SDK at your app entry point.";
25339
+ }
25340
+ }
25341
+ function initSnippetFor(framework, sdk, workspaceKey) {
25342
+ if (sdk === "@gurulu/node") {
25343
+ if (framework === "express")
25344
+ return snippetExpress(workspaceKey);
25345
+ if (framework === "hono")
25346
+ return snippetHono(workspaceKey);
25347
+ return snippetNode(workspaceKey);
25348
+ }
25349
+ if (framework === "next")
25350
+ return snippetNext(workspaceKey);
25351
+ return snippetWeb(workspaceKey);
25352
+ }
25353
+ function envKeysFor(sdk, framework) {
25354
+ if (sdk === "@gurulu/node") {
25355
+ return [
25356
+ { key: "GURULU_WORKSPACE", example: "pk_live_xxxxxxxxxxxx", required: true },
25357
+ { key: "GURULU_API_KEY", example: "sk_live_xxxxxxxxxxxx", required: true },
25358
+ {
25359
+ key: "GURULU_ENDPOINT",
25360
+ example: "https://ingest.gurulu.io",
25361
+ required: false
25362
+ }
25363
+ ];
25364
+ }
25365
+ const prefix = framework === "next" ? "NEXT_PUBLIC_GURULU" : "VITE_GURULU";
25366
+ return [
25367
+ { key: `${prefix}_WORKSPACE`, example: "pk_live_xxxxxxxxxxxx", required: false },
25368
+ { key: `${prefix}_ENDPOINT`, example: "https://ingest.gurulu.io", required: false }
25369
+ ];
25370
+ }
25371
+ function buildInstallPlan(detected, ctx = {}) {
25372
+ const sdk = sdkFor(detected.framework);
25373
+ const workspaceKey = ctx.writeKey ?? "pk_xxxxxxxxxxxx";
25374
+ return {
25375
+ sdk,
25376
+ packageManager: detected.packageManager,
25377
+ installCommand: installCmdFor(detected.packageManager, sdk),
25378
+ initSnippet: initSnippetFor(detected.framework, sdk, workspaceKey),
25379
+ envKeys: envKeysFor(sdk, detected.framework),
25380
+ placementHint: placementHintFor(detected.framework),
25381
+ framework: detected.framework
25382
+ };
25383
+ }
25060
25384
 
25061
25385
  // src/commands/pull.ts
25062
25386
  import { writeFileSync as writeFileSync2 } from "node:fs";
@@ -25206,7 +25530,9 @@ var initCmd = defineCommand({
25206
25530
  args: {
25207
25531
  workspace: { type: "string", description: "Workspace ID (uuid)" },
25208
25532
  endpoint: { type: "string", description: "API endpoint", default: DEFAULT_ENDPOINT },
25209
- sdk: { type: "string", description: "SDK preference (web|node|react)", default: "web" },
25533
+ sdk: { type: "string", description: "SDK preference (web|node|auto)", default: "auto" },
25534
+ "write-key": { type: "string", description: "Workspace write key (pk_xxx) for init snippet" },
25535
+ "no-install": { type: "boolean", description: "Skip SDK install (config files only)" },
25210
25536
  "no-pull": { type: "boolean", description: "Skip first registry pull" },
25211
25537
  force: { type: "boolean", description: "Overwrite existing config" }
25212
25538
  },
@@ -25222,40 +25548,63 @@ var initCmd = defineCommand({
25222
25548
  console.error("[gurulu] --workspace <uuid> required (workspace ID from dashboard)");
25223
25549
  process.exit(1);
25224
25550
  }
25225
- const sdk = String(args.sdk ?? "web");
25226
- if (!["web", "node", "react"].includes(sdk)) {
25227
- console.error(`[gurulu] invalid --sdk: ${sdk} (use web|node|react)`);
25551
+ const sdkArg = String(args.sdk ?? "auto");
25552
+ if (!["web", "node", "auto"].includes(sdkArg)) {
25553
+ console.error(`[gurulu] invalid --sdk: ${sdkArg} (use web|node|auto)`);
25228
25554
  process.exit(1);
25229
25555
  }
25556
+ const detected = detectProject(cwd);
25557
+ const writeKey = String(args["write-key"] ?? "").trim() || "pk_xxxxxxxxxxxx";
25558
+ const plan = buildInstallPlan(detected, { writeKey, workspaceId });
25559
+ const effectiveSdkPref = sdkArg === "auto" ? plan.sdk === "@gurulu/node" ? "node" : "web" : sdkArg;
25230
25560
  const config = {
25231
25561
  workspace_id: workspaceId,
25232
25562
  endpoint: String(args.endpoint),
25233
- sdk_preference: sdk,
25563
+ sdk_preference: effectiveSdkPref,
25234
25564
  registry_path: ".gurulu/event-registry.json",
25235
25565
  generated_path: ".gurulu/generated.d.ts",
25236
25566
  manifest_lock_path: ".gurulu/manifest.lock",
25237
25567
  auto_pull_on_init: true
25238
25568
  };
25239
25569
  writeProjectConfig(config, cwd);
25240
- const dir = dirname2(projectConfigPath(cwd));
25241
- if (!existsSync4(dir))
25570
+ const dir = dirname3(projectConfigPath(cwd));
25571
+ if (!existsSync5(dir))
25242
25572
  mkdirSync2(dir, { recursive: true });
25243
- if (!existsSync4(projectRegistryPath(cwd))) {
25573
+ if (!existsSync5(projectRegistryPath(cwd))) {
25244
25574
  writeFileSync3(projectRegistryPath(cwd), `{}
25245
25575
  `, "utf-8");
25246
25576
  }
25247
- if (!existsSync4(projectGeneratedPath(cwd))) {
25577
+ if (!existsSync5(projectGeneratedPath(cwd))) {
25248
25578
  writeFileSync3(projectGeneratedPath(cwd), "// Run `gurulu pull` to populate typed events.\n", "utf-8");
25249
25579
  }
25250
- if (!existsSync4(projectManifestLockPath(cwd))) {
25580
+ if (!existsSync5(projectManifestLockPath(cwd))) {
25251
25581
  writeFileSync3(projectManifestLockPath(cwd), `0.0.0
25252
25582
  `, "utf-8");
25253
25583
  }
25254
25584
  console.log(`[gurulu] initialized ${projectConfigPath(cwd)}`);
25585
+ if (args["no-install"]) {
25586
+ console.log(`[gurulu] skipping SDK install (--no-install).`);
25587
+ console.log(` manual: ${plan.installCommand}`);
25588
+ } else if (!detected.hasPackageJson) {
25589
+ console.log(`[gurulu] no package.json found in ${cwd} — skipping SDK install.`);
25590
+ console.log(` use the script tag: <script src="https://cdn.gurulu.io/t.js" data-workspace="${writeKey}"></script>`);
25591
+ } else {
25592
+ console.log(`[gurulu] detected ${detected.framework} (${detected.packageManager}) — installing ${plan.sdk}…`);
25593
+ const result = await execInstall(plan, { cwd });
25594
+ if (result.ok) {
25595
+ console.log(`[gurulu] ${plan.sdk} installed (${(result.durationMs / 1000).toFixed(1)}s).`);
25596
+ } else {
25597
+ console.warn(`[gurulu] install failed (exit ${result.exitCode ?? "n/a"}) — run manually: ${plan.installCommand}`);
25598
+ }
25599
+ }
25600
+ console.log("");
25255
25601
  console.log("[gurulu] next steps:");
25256
25602
  console.log(" 1. gurulu login — authenticate (or store API key)");
25257
25603
  console.log(" 2. gurulu pull — fetch registry + code-gen");
25258
- console.log(" 3. import { GuruluEvents } — use generated types");
25604
+ console.log(` 3. ${plan.placementHint}`);
25605
+ console.log("");
25606
+ console.log("[gurulu] init snippet:");
25607
+ console.log(plan.initSnippet);
25259
25608
  if (!args["no-pull"]) {
25260
25609
  try {
25261
25610
  await runPull({ cwd });
@@ -25365,7 +25714,7 @@ var pushCmd = defineCommand({
25365
25714
  });
25366
25715
 
25367
25716
  // src/index.ts
25368
- var VERSION = "1.0.0";
25717
+ var VERSION = "1.0.4";
25369
25718
  var mcpCmd = defineCommand({
25370
25719
  meta: {
25371
25720
  name: "mcp",
@@ -13,6 +13,14 @@ export declare const initCmd: import("citty").CommandDef<{
13
13
  description: string;
14
14
  default: string;
15
15
  };
16
+ 'write-key': {
17
+ type: "string";
18
+ description: string;
19
+ };
20
+ 'no-install': {
21
+ type: "boolean";
22
+ description: string;
23
+ };
16
24
  'no-pull': {
17
25
  type: "boolean";
18
26
  description: string;
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA4BA,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;EA0FlB,CAAC"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAmCA,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgJlB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const VERSION = "1.0.0";
1
+ export declare const VERSION = "1.0.4";
2
2
  declare const mainCmd: import("citty").CommandDef<import("citty").ArgsDef>;
3
3
  export default mainCmd;
4
4
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -24632,8 +24632,332 @@ var doctorCmd = defineCommand({
24632
24632
  });
24633
24633
 
24634
24634
  // src/commands/init.ts
24635
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "node:fs";
24636
- import { dirname as dirname2 } from "node:path";
24635
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "node:fs";
24636
+ import { dirname as dirname3 } from "node:path";
24637
+
24638
+ // src/lib/detect.ts
24639
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
24640
+ import { dirname as dirname2, join as join4, parse } from "node:path";
24641
+ function readPackageJson(dir) {
24642
+ const p = join4(dir, "package.json");
24643
+ if (!existsSync4(p))
24644
+ return null;
24645
+ try {
24646
+ return JSON.parse(readFileSync4(p, "utf8"));
24647
+ } catch {
24648
+ return null;
24649
+ }
24650
+ }
24651
+ function hasDep(pkg, name) {
24652
+ if (!pkg)
24653
+ return false;
24654
+ return Boolean(pkg.dependencies?.[name] ?? pkg.devDependencies?.[name]);
24655
+ }
24656
+ function detectPackageManager(dir) {
24657
+ const root = parse(dir).root;
24658
+ let cur = dir;
24659
+ for (let i2 = 0;i2 < 6; i2++) {
24660
+ if (existsSync4(join4(cur, "bun.lock")) || existsSync4(join4(cur, "bun.lockb")))
24661
+ return "bun";
24662
+ if (existsSync4(join4(cur, "pnpm-lock.yaml")))
24663
+ return "pnpm";
24664
+ if (existsSync4(join4(cur, "yarn.lock")))
24665
+ return "yarn";
24666
+ if (existsSync4(join4(cur, "package-lock.json")))
24667
+ return "npm";
24668
+ if (cur === root)
24669
+ break;
24670
+ const parent = dirname2(cur);
24671
+ if (parent === cur)
24672
+ break;
24673
+ cur = parent;
24674
+ }
24675
+ return "npm";
24676
+ }
24677
+ function detectFramework(pkg, dir) {
24678
+ if (!pkg)
24679
+ return "unknown";
24680
+ if (hasDep(pkg, "next"))
24681
+ return "next";
24682
+ if (hasDep(pkg, "nuxt"))
24683
+ return "nuxt";
24684
+ if (hasDep(pkg, "@sveltejs/kit") || hasDep(pkg, "svelte"))
24685
+ return "svelte";
24686
+ if (hasDep(pkg, "astro"))
24687
+ return "astro";
24688
+ if (hasDep(pkg, "vite") && (hasDep(pkg, "vue") || hasDep(pkg, "react")))
24689
+ return "vite";
24690
+ if (hasDep(pkg, "vue"))
24691
+ return "vue";
24692
+ if (hasDep(pkg, "react"))
24693
+ return "react";
24694
+ if (hasDep(pkg, "hono"))
24695
+ return "hono";
24696
+ if (hasDep(pkg, "fastify"))
24697
+ return "fastify";
24698
+ if (hasDep(pkg, "express"))
24699
+ return "express";
24700
+ if (hasDep(pkg, "koa"))
24701
+ return "koa";
24702
+ if (existsSync4(join4(dir, "server.js")) || existsSync4(join4(dir, "server.ts")))
24703
+ return "node-server";
24704
+ return "unknown";
24705
+ }
24706
+ function frameworkRuntime(fw) {
24707
+ switch (fw) {
24708
+ case "next":
24709
+ case "react":
24710
+ case "vue":
24711
+ case "nuxt":
24712
+ case "svelte":
24713
+ case "astro":
24714
+ case "vite":
24715
+ return "browser";
24716
+ case "express":
24717
+ case "fastify":
24718
+ case "hono":
24719
+ case "koa":
24720
+ case "node-server":
24721
+ return "node";
24722
+ default:
24723
+ return "unknown";
24724
+ }
24725
+ }
24726
+ function detectProject(dir) {
24727
+ const pkg = readPackageJson(dir);
24728
+ const framework = detectFramework(pkg, dir);
24729
+ return {
24730
+ dir,
24731
+ hasPackageJson: pkg !== null,
24732
+ framework,
24733
+ runtime: frameworkRuntime(framework),
24734
+ packageManager: detectPackageManager(dir),
24735
+ packageJson: pkg
24736
+ };
24737
+ }
24738
+
24739
+ // src/lib/exec-install.ts
24740
+ import { spawn } from "node:child_process";
24741
+ async function execInstall(plan, opts) {
24742
+ const [bin, ...args] = plan.installCommand.split(/\s+/);
24743
+ if (!bin) {
24744
+ return {
24745
+ ok: false,
24746
+ exitCode: null,
24747
+ durationMs: 0,
24748
+ command: plan.installCommand,
24749
+ stderr: "empty install command"
24750
+ };
24751
+ }
24752
+ const started = Date.now();
24753
+ return new Promise((resolve) => {
24754
+ const child = spawn(bin, args, {
24755
+ cwd: opts.cwd,
24756
+ stdio: opts.silent ? ["ignore", "pipe", "pipe"] : ["inherit", "inherit", "pipe"],
24757
+ shell: process.platform === "win32"
24758
+ });
24759
+ let stderr = "";
24760
+ if (child.stderr) {
24761
+ child.stderr.on("data", (chunk) => {
24762
+ const text = chunk.toString("utf8");
24763
+ stderr += text;
24764
+ if (!opts.silent) {
24765
+ process.stderr.write(text);
24766
+ }
24767
+ });
24768
+ }
24769
+ const timeout = setTimeout(() => {
24770
+ child.kill("SIGTERM");
24771
+ }, opts.timeoutMs ?? 120000);
24772
+ child.on("error", (err) => {
24773
+ clearTimeout(timeout);
24774
+ resolve({
24775
+ ok: false,
24776
+ exitCode: null,
24777
+ durationMs: Date.now() - started,
24778
+ command: plan.installCommand,
24779
+ stderr: err.message
24780
+ });
24781
+ });
24782
+ child.on("close", (code) => {
24783
+ clearTimeout(timeout);
24784
+ resolve({
24785
+ ok: code === 0,
24786
+ exitCode: code,
24787
+ durationMs: Date.now() - started,
24788
+ command: plan.installCommand,
24789
+ ...stderr ? { stderr } : {}
24790
+ });
24791
+ });
24792
+ });
24793
+ }
24794
+
24795
+ // src/lib/install-plan.ts
24796
+ function installCmdFor(pm, pkg) {
24797
+ switch (pm) {
24798
+ case "bun":
24799
+ return `bun add ${pkg}`;
24800
+ case "pnpm":
24801
+ return `pnpm add ${pkg}`;
24802
+ case "yarn":
24803
+ return `yarn add ${pkg}`;
24804
+ default:
24805
+ return `npm install ${pkg}`;
24806
+ }
24807
+ }
24808
+ function sdkFor(framework) {
24809
+ switch (framework) {
24810
+ case "express":
24811
+ case "fastify":
24812
+ case "hono":
24813
+ case "koa":
24814
+ case "node-server":
24815
+ return "@gurulu/node";
24816
+ default:
24817
+ return "@gurulu/web";
24818
+ }
24819
+ }
24820
+ function snippetWeb(workspaceKey) {
24821
+ return `import gurulu from '@gurulu/web';
24822
+
24823
+ gurulu.init({
24824
+ workspaceKey: process.env.NEXT_PUBLIC_GURULU_WORKSPACE ?? '${workspaceKey}',
24825
+ endpoint: process.env.NEXT_PUBLIC_GURULU_ENDPOINT, // optional, defaults to https://ingest.gurulu.io
24826
+ });`;
24827
+ }
24828
+ function snippetNext(workspaceKey) {
24829
+ return `// src/app/gurulu-provider.tsx
24830
+ 'use client';
24831
+ import { useEffect } from 'react';
24832
+ import gurulu from '@gurulu/web';
24833
+
24834
+ export function GuruluProvider({ children }: { children: React.ReactNode }) {
24835
+ useEffect(() => {
24836
+ gurulu.init({
24837
+ workspaceKey: process.env.NEXT_PUBLIC_GURULU_WORKSPACE ?? '${workspaceKey}',
24838
+ endpoint: process.env.NEXT_PUBLIC_GURULU_ENDPOINT,
24839
+ });
24840
+ }, []);
24841
+ return <>{children}</>;
24842
+ }
24843
+
24844
+ // Then wrap your root in app/layout.tsx:
24845
+ // <GuruluProvider>{children}</GuruluProvider>`;
24846
+ }
24847
+ function snippetNode(workspaceKey) {
24848
+ return `import { createGurulu } from '@gurulu/node';
24849
+
24850
+ const gurulu = createGurulu({
24851
+ workspaceKey: process.env.GURULU_WORKSPACE ?? '${workspaceKey}',
24852
+ apiKey: process.env.GURULU_API_KEY,
24853
+ endpoint: process.env.GURULU_ENDPOINT, // optional, defaults to https://ingest.gurulu.io
24854
+ });
24855
+
24856
+ // In your handler:
24857
+ await gurulu.track('purchase_completed', {
24858
+ user_id: 'u_42',
24859
+ order_id: 'o_123',
24860
+ total: 49.99,
24861
+ });`;
24862
+ }
24863
+ function snippetExpress(workspaceKey) {
24864
+ return `import express from 'express';
24865
+ import { guruluMiddleware } from '@gurulu/node/middleware/express';
24866
+
24867
+ const app = express();
24868
+ app.use(guruluMiddleware({
24869
+ workspaceKey: process.env.GURULU_WORKSPACE ?? '${workspaceKey}',
24870
+ apiKey: process.env.GURULU_API_KEY,
24871
+ endpoint: process.env.GURULU_ENDPOINT,
24872
+ }));`;
24873
+ }
24874
+ function snippetHono(workspaceKey) {
24875
+ return `import { Hono } from 'hono';
24876
+ import { createGurulu } from '@gurulu/node';
24877
+
24878
+ const gurulu = createGurulu({
24879
+ workspaceKey: process.env.GURULU_WORKSPACE ?? '${workspaceKey}',
24880
+ apiKey: process.env.GURULU_API_KEY,
24881
+ endpoint: process.env.GURULU_ENDPOINT,
24882
+ });
24883
+
24884
+ const app = new Hono();
24885
+ app.post('/checkout/complete', async (c) => {
24886
+ await gurulu.track('purchase_completed', await c.req.json());
24887
+ return c.json({ ok: true });
24888
+ });`;
24889
+ }
24890
+ function placementHintFor(framework) {
24891
+ switch (framework) {
24892
+ case "next":
24893
+ return "Add the GuruluProvider to app/layout.tsx (App Router) or pages/_app.tsx (Pages Router) and wrap children.";
24894
+ case "nuxt":
24895
+ return "Add to plugins/gurulu.client.ts and register in nuxt.config.ts.";
24896
+ case "svelte":
24897
+ return "Add to src/routes/+layout.svelte inside onMount().";
24898
+ case "astro":
24899
+ return "Add to src/layouts/Base.astro inside a <script> tag (or use a hydrated component).";
24900
+ case "vite":
24901
+ case "react":
24902
+ case "vue":
24903
+ return "Add to src/main.ts (or your entry file) before mounting the app.";
24904
+ case "express":
24905
+ return "Register guruluMiddleware before your route handlers in your app entry file.";
24906
+ case "fastify":
24907
+ return "Register the Gurulu plugin via fastify.register() before route definitions.";
24908
+ case "hono":
24909
+ return "Initialize the SDK at the top of your app file; call track() inside route handlers.";
24910
+ case "koa":
24911
+ return "Add as Koa middleware (app.use) before your routes.";
24912
+ case "node-server":
24913
+ return "Initialize the SDK at startup; call track() from your handlers.";
24914
+ default:
24915
+ return "Initialize the SDK at your app entry point.";
24916
+ }
24917
+ }
24918
+ function initSnippetFor(framework, sdk, workspaceKey) {
24919
+ if (sdk === "@gurulu/node") {
24920
+ if (framework === "express")
24921
+ return snippetExpress(workspaceKey);
24922
+ if (framework === "hono")
24923
+ return snippetHono(workspaceKey);
24924
+ return snippetNode(workspaceKey);
24925
+ }
24926
+ if (framework === "next")
24927
+ return snippetNext(workspaceKey);
24928
+ return snippetWeb(workspaceKey);
24929
+ }
24930
+ function envKeysFor(sdk, framework) {
24931
+ if (sdk === "@gurulu/node") {
24932
+ return [
24933
+ { key: "GURULU_WORKSPACE", example: "pk_live_xxxxxxxxxxxx", required: true },
24934
+ { key: "GURULU_API_KEY", example: "sk_live_xxxxxxxxxxxx", required: true },
24935
+ {
24936
+ key: "GURULU_ENDPOINT",
24937
+ example: "https://ingest.gurulu.io",
24938
+ required: false
24939
+ }
24940
+ ];
24941
+ }
24942
+ const prefix = framework === "next" ? "NEXT_PUBLIC_GURULU" : "VITE_GURULU";
24943
+ return [
24944
+ { key: `${prefix}_WORKSPACE`, example: "pk_live_xxxxxxxxxxxx", required: false },
24945
+ { key: `${prefix}_ENDPOINT`, example: "https://ingest.gurulu.io", required: false }
24946
+ ];
24947
+ }
24948
+ function buildInstallPlan(detected, ctx = {}) {
24949
+ const sdk = sdkFor(detected.framework);
24950
+ const workspaceKey = ctx.writeKey ?? "pk_xxxxxxxxxxxx";
24951
+ return {
24952
+ sdk,
24953
+ packageManager: detected.packageManager,
24954
+ installCommand: installCmdFor(detected.packageManager, sdk),
24955
+ initSnippet: initSnippetFor(detected.framework, sdk, workspaceKey),
24956
+ envKeys: envKeysFor(sdk, detected.framework),
24957
+ placementHint: placementHintFor(detected.framework),
24958
+ framework: detected.framework
24959
+ };
24960
+ }
24637
24961
 
24638
24962
  // src/commands/pull.ts
24639
24963
  import { writeFileSync as writeFileSync2 } from "node:fs";
@@ -24783,7 +25107,9 @@ var initCmd = defineCommand({
24783
25107
  args: {
24784
25108
  workspace: { type: "string", description: "Workspace ID (uuid)" },
24785
25109
  endpoint: { type: "string", description: "API endpoint", default: DEFAULT_ENDPOINT },
24786
- sdk: { type: "string", description: "SDK preference (web|node|react)", default: "web" },
25110
+ sdk: { type: "string", description: "SDK preference (web|node|auto)", default: "auto" },
25111
+ "write-key": { type: "string", description: "Workspace write key (pk_xxx) for init snippet" },
25112
+ "no-install": { type: "boolean", description: "Skip SDK install (config files only)" },
24787
25113
  "no-pull": { type: "boolean", description: "Skip first registry pull" },
24788
25114
  force: { type: "boolean", description: "Overwrite existing config" }
24789
25115
  },
@@ -24799,40 +25125,63 @@ var initCmd = defineCommand({
24799
25125
  console.error("[gurulu] --workspace <uuid> required (workspace ID from dashboard)");
24800
25126
  process.exit(1);
24801
25127
  }
24802
- const sdk = String(args.sdk ?? "web");
24803
- if (!["web", "node", "react"].includes(sdk)) {
24804
- console.error(`[gurulu] invalid --sdk: ${sdk} (use web|node|react)`);
25128
+ const sdkArg = String(args.sdk ?? "auto");
25129
+ if (!["web", "node", "auto"].includes(sdkArg)) {
25130
+ console.error(`[gurulu] invalid --sdk: ${sdkArg} (use web|node|auto)`);
24805
25131
  process.exit(1);
24806
25132
  }
25133
+ const detected = detectProject(cwd);
25134
+ const writeKey = String(args["write-key"] ?? "").trim() || "pk_xxxxxxxxxxxx";
25135
+ const plan = buildInstallPlan(detected, { writeKey, workspaceId });
25136
+ const effectiveSdkPref = sdkArg === "auto" ? plan.sdk === "@gurulu/node" ? "node" : "web" : sdkArg;
24807
25137
  const config = {
24808
25138
  workspace_id: workspaceId,
24809
25139
  endpoint: String(args.endpoint),
24810
- sdk_preference: sdk,
25140
+ sdk_preference: effectiveSdkPref,
24811
25141
  registry_path: ".gurulu/event-registry.json",
24812
25142
  generated_path: ".gurulu/generated.d.ts",
24813
25143
  manifest_lock_path: ".gurulu/manifest.lock",
24814
25144
  auto_pull_on_init: true
24815
25145
  };
24816
25146
  writeProjectConfig(config, cwd);
24817
- const dir = dirname2(projectConfigPath(cwd));
24818
- if (!existsSync4(dir))
25147
+ const dir = dirname3(projectConfigPath(cwd));
25148
+ if (!existsSync5(dir))
24819
25149
  mkdirSync2(dir, { recursive: true });
24820
- if (!existsSync4(projectRegistryPath(cwd))) {
25150
+ if (!existsSync5(projectRegistryPath(cwd))) {
24821
25151
  writeFileSync3(projectRegistryPath(cwd), `{}
24822
25152
  `, "utf-8");
24823
25153
  }
24824
- if (!existsSync4(projectGeneratedPath(cwd))) {
25154
+ if (!existsSync5(projectGeneratedPath(cwd))) {
24825
25155
  writeFileSync3(projectGeneratedPath(cwd), "// Run `gurulu pull` to populate typed events.\n", "utf-8");
24826
25156
  }
24827
- if (!existsSync4(projectManifestLockPath(cwd))) {
25157
+ if (!existsSync5(projectManifestLockPath(cwd))) {
24828
25158
  writeFileSync3(projectManifestLockPath(cwd), `0.0.0
24829
25159
  `, "utf-8");
24830
25160
  }
24831
25161
  console.log(`[gurulu] initialized ${projectConfigPath(cwd)}`);
25162
+ if (args["no-install"]) {
25163
+ console.log(`[gurulu] skipping SDK install (--no-install).`);
25164
+ console.log(` manual: ${plan.installCommand}`);
25165
+ } else if (!detected.hasPackageJson) {
25166
+ console.log(`[gurulu] no package.json found in ${cwd} — skipping SDK install.`);
25167
+ console.log(` use the script tag: <script src="https://cdn.gurulu.io/t.js" data-workspace="${writeKey}"></script>`);
25168
+ } else {
25169
+ console.log(`[gurulu] detected ${detected.framework} (${detected.packageManager}) — installing ${plan.sdk}…`);
25170
+ const result = await execInstall(plan, { cwd });
25171
+ if (result.ok) {
25172
+ console.log(`[gurulu] ${plan.sdk} installed (${(result.durationMs / 1000).toFixed(1)}s).`);
25173
+ } else {
25174
+ console.warn(`[gurulu] install failed (exit ${result.exitCode ?? "n/a"}) — run manually: ${plan.installCommand}`);
25175
+ }
25176
+ }
25177
+ console.log("");
24832
25178
  console.log("[gurulu] next steps:");
24833
25179
  console.log(" 1. gurulu login — authenticate (or store API key)");
24834
25180
  console.log(" 2. gurulu pull — fetch registry + code-gen");
24835
- console.log(" 3. import { GuruluEvents } — use generated types");
25181
+ console.log(` 3. ${plan.placementHint}`);
25182
+ console.log("");
25183
+ console.log("[gurulu] init snippet:");
25184
+ console.log(plan.initSnippet);
24836
25185
  if (!args["no-pull"]) {
24837
25186
  try {
24838
25187
  await runPull({ cwd });
@@ -24942,7 +25291,7 @@ var pushCmd = defineCommand({
24942
25291
  });
24943
25292
 
24944
25293
  // src/index.ts
24945
- var VERSION = "1.0.0";
25294
+ var VERSION = "1.0.4";
24946
25295
  var mcpCmd = defineCommand({
24947
25296
  meta: {
24948
25297
  name: "mcp",
@@ -0,0 +1,27 @@
1
+ export type Framework = 'next' | 'react' | 'vue' | 'nuxt' | 'svelte' | 'astro' | 'vite' | 'express' | 'fastify' | 'hono' | 'koa' | 'node-server' | 'unknown';
2
+ export type Runtime = 'browser' | 'node' | 'unknown';
3
+ export type PackageManager = 'bun' | 'pnpm' | 'yarn' | 'npm';
4
+ export interface DetectedProject {
5
+ dir: string;
6
+ hasPackageJson: boolean;
7
+ framework: Framework;
8
+ runtime: Runtime;
9
+ packageManager: PackageManager;
10
+ packageJson: PackageJsonShape | null;
11
+ }
12
+ interface PackageJsonShape {
13
+ name?: string;
14
+ type?: 'module' | 'commonjs';
15
+ dependencies?: Record<string, string>;
16
+ devDependencies?: Record<string, string>;
17
+ }
18
+ /**
19
+ * Walk up the directory tree (max 6 levels) looking for a lockfile.
20
+ * Monorepo workspaces typically have the lockfile at the repo root.
21
+ */
22
+ export declare function detectPackageManager(dir: string): PackageManager;
23
+ export declare function detectFramework(pkg: PackageJsonShape | null, dir: string): Framework;
24
+ export declare function frameworkRuntime(fw: Framework): Runtime;
25
+ export declare function detectProject(dir: string): DetectedProject;
26
+ export {};
27
+ //# sourceMappingURL=detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../src/lib/detect.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,OAAO,GACP,KAAK,GACL,MAAM,GACN,QAAQ,GACR,OAAO,GACP,MAAM,GACN,SAAS,GACT,SAAS,GACT,MAAM,GACN,KAAK,GACL,aAAa,GACb,SAAS,CAAC;AAEd,MAAM,MAAM,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;AAErD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAE7D,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,EAAE,SAAS,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,cAAc,CAAC;IAC/B,WAAW,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACtC;AAED,UAAU,gBAAgB;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAiBD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAchE;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,gBAAgB,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAkBpF;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,CAmBvD;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAW1D"}
@@ -0,0 +1,106 @@
1
+ // src/lib/detect.ts
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { dirname, join, parse } from "node:path";
4
+ function readPackageJson(dir) {
5
+ const p = join(dir, "package.json");
6
+ if (!existsSync(p))
7
+ return null;
8
+ try {
9
+ return JSON.parse(readFileSync(p, "utf8"));
10
+ } catch {
11
+ return null;
12
+ }
13
+ }
14
+ function hasDep(pkg, name) {
15
+ if (!pkg)
16
+ return false;
17
+ return Boolean(pkg.dependencies?.[name] ?? pkg.devDependencies?.[name]);
18
+ }
19
+ function detectPackageManager(dir) {
20
+ const root = parse(dir).root;
21
+ let cur = dir;
22
+ for (let i = 0;i < 6; i++) {
23
+ if (existsSync(join(cur, "bun.lock")) || existsSync(join(cur, "bun.lockb")))
24
+ return "bun";
25
+ if (existsSync(join(cur, "pnpm-lock.yaml")))
26
+ return "pnpm";
27
+ if (existsSync(join(cur, "yarn.lock")))
28
+ return "yarn";
29
+ if (existsSync(join(cur, "package-lock.json")))
30
+ return "npm";
31
+ if (cur === root)
32
+ break;
33
+ const parent = dirname(cur);
34
+ if (parent === cur)
35
+ break;
36
+ cur = parent;
37
+ }
38
+ return "npm";
39
+ }
40
+ function detectFramework(pkg, dir) {
41
+ if (!pkg)
42
+ return "unknown";
43
+ if (hasDep(pkg, "next"))
44
+ return "next";
45
+ if (hasDep(pkg, "nuxt"))
46
+ return "nuxt";
47
+ if (hasDep(pkg, "@sveltejs/kit") || hasDep(pkg, "svelte"))
48
+ return "svelte";
49
+ if (hasDep(pkg, "astro"))
50
+ return "astro";
51
+ if (hasDep(pkg, "vite") && (hasDep(pkg, "vue") || hasDep(pkg, "react")))
52
+ return "vite";
53
+ if (hasDep(pkg, "vue"))
54
+ return "vue";
55
+ if (hasDep(pkg, "react"))
56
+ return "react";
57
+ if (hasDep(pkg, "hono"))
58
+ return "hono";
59
+ if (hasDep(pkg, "fastify"))
60
+ return "fastify";
61
+ if (hasDep(pkg, "express"))
62
+ return "express";
63
+ if (hasDep(pkg, "koa"))
64
+ return "koa";
65
+ if (existsSync(join(dir, "server.js")) || existsSync(join(dir, "server.ts")))
66
+ return "node-server";
67
+ return "unknown";
68
+ }
69
+ function frameworkRuntime(fw) {
70
+ switch (fw) {
71
+ case "next":
72
+ case "react":
73
+ case "vue":
74
+ case "nuxt":
75
+ case "svelte":
76
+ case "astro":
77
+ case "vite":
78
+ return "browser";
79
+ case "express":
80
+ case "fastify":
81
+ case "hono":
82
+ case "koa":
83
+ case "node-server":
84
+ return "node";
85
+ default:
86
+ return "unknown";
87
+ }
88
+ }
89
+ function detectProject(dir) {
90
+ const pkg = readPackageJson(dir);
91
+ const framework = detectFramework(pkg, dir);
92
+ return {
93
+ dir,
94
+ hasPackageJson: pkg !== null,
95
+ framework,
96
+ runtime: frameworkRuntime(framework),
97
+ packageManager: detectPackageManager(dir),
98
+ packageJson: pkg
99
+ };
100
+ }
101
+ export {
102
+ frameworkRuntime,
103
+ detectProject,
104
+ detectPackageManager,
105
+ detectFramework
106
+ };
@@ -0,0 +1,21 @@
1
+ import type { InstallPlan } from './install-plan.ts';
2
+ export interface ExecInstallOptions {
3
+ cwd: string;
4
+ /** Suppress stdout/stderr forwarding (still captured in result). */
5
+ silent?: boolean;
6
+ /** Hard timeout in ms (default 120s). */
7
+ timeoutMs?: number;
8
+ }
9
+ export interface ExecInstallResult {
10
+ ok: boolean;
11
+ exitCode: number | null;
12
+ durationMs: number;
13
+ command: string;
14
+ stderr?: string;
15
+ }
16
+ /**
17
+ * Spawn the package manager install command for the planned SDK.
18
+ * Streams output to the parent process (unless `silent: true`).
19
+ */
20
+ export declare function execInstall(plan: InstallPlan, opts: ExecInstallOptions): Promise<ExecInstallResult>;
21
+ //# sourceMappingURL=exec-install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec-install.d.ts","sourceRoot":"","sources":["../../src/lib/exec-install.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,oEAAoE;IACpE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,iBAAiB,CAAC,CA6D5B"}
@@ -0,0 +1,25 @@
1
+ import type { DetectedProject, Framework, PackageManager } from './detect.ts';
2
+ export type SdkPackage = '@gurulu/web' | '@gurulu/node';
3
+ export interface InstallPlanContext {
4
+ /** Workspace key (pk_xxx) — script tag + init() içine inject. */
5
+ writeKey?: string;
6
+ /** Workspace UUID — CLI/MCP referansı. */
7
+ workspaceId?: string;
8
+ /** Server-side projeler için API key (sk_xxx). */
9
+ apiKey?: string;
10
+ }
11
+ export interface InstallPlan {
12
+ sdk: SdkPackage;
13
+ packageManager: PackageManager;
14
+ installCommand: string;
15
+ initSnippet: string;
16
+ envKeys: Array<{
17
+ key: string;
18
+ example: string;
19
+ required: boolean;
20
+ }>;
21
+ placementHint: string;
22
+ framework: Framework;
23
+ }
24
+ export declare function buildInstallPlan(detected: DetectedProject, ctx?: InstallPlanContext): InstallPlan;
25
+ //# sourceMappingURL=install-plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-plan.d.ts","sourceRoot":"","sources":["../../src/lib/install-plan.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE9E,MAAM,MAAM,UAAU,GAAG,aAAa,GAAG,cAAc,CAAC;AAExD,MAAM,WAAW,kBAAkB;IACjC,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,UAAU,CAAC;IAChB,cAAc,EAAE,cAAc,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACpE,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,SAAS,CAAC;CACtB;AAwKD,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,eAAe,EACzB,GAAG,GAAE,kBAAuB,GAC3B,WAAW,CAYb"}
@@ -0,0 +1,169 @@
1
+ // src/lib/install-plan.ts
2
+ function installCmdFor(pm, pkg) {
3
+ switch (pm) {
4
+ case "bun":
5
+ return `bun add ${pkg}`;
6
+ case "pnpm":
7
+ return `pnpm add ${pkg}`;
8
+ case "yarn":
9
+ return `yarn add ${pkg}`;
10
+ default:
11
+ return `npm install ${pkg}`;
12
+ }
13
+ }
14
+ function sdkFor(framework) {
15
+ switch (framework) {
16
+ case "express":
17
+ case "fastify":
18
+ case "hono":
19
+ case "koa":
20
+ case "node-server":
21
+ return "@gurulu/node";
22
+ default:
23
+ return "@gurulu/web";
24
+ }
25
+ }
26
+ function snippetWeb(workspaceKey) {
27
+ return `import gurulu from '@gurulu/web';
28
+
29
+ gurulu.init({
30
+ workspaceKey: process.env.NEXT_PUBLIC_GURULU_WORKSPACE ?? '${workspaceKey}',
31
+ endpoint: process.env.NEXT_PUBLIC_GURULU_ENDPOINT, // optional, defaults to https://ingest.gurulu.io
32
+ });`;
33
+ }
34
+ function snippetNext(workspaceKey) {
35
+ return `// src/app/gurulu-provider.tsx
36
+ 'use client';
37
+ import { useEffect } from 'react';
38
+ import gurulu from '@gurulu/web';
39
+
40
+ export function GuruluProvider({ children }: { children: React.ReactNode }) {
41
+ useEffect(() => {
42
+ gurulu.init({
43
+ workspaceKey: process.env.NEXT_PUBLIC_GURULU_WORKSPACE ?? '${workspaceKey}',
44
+ endpoint: process.env.NEXT_PUBLIC_GURULU_ENDPOINT,
45
+ });
46
+ }, []);
47
+ return <>{children}</>;
48
+ }
49
+
50
+ // Then wrap your root in app/layout.tsx:
51
+ // <GuruluProvider>{children}</GuruluProvider>`;
52
+ }
53
+ function snippetNode(workspaceKey) {
54
+ return `import { createGurulu } from '@gurulu/node';
55
+
56
+ const gurulu = createGurulu({
57
+ workspaceKey: process.env.GURULU_WORKSPACE ?? '${workspaceKey}',
58
+ apiKey: process.env.GURULU_API_KEY,
59
+ endpoint: process.env.GURULU_ENDPOINT, // optional, defaults to https://ingest.gurulu.io
60
+ });
61
+
62
+ // In your handler:
63
+ await gurulu.track('purchase_completed', {
64
+ user_id: 'u_42',
65
+ order_id: 'o_123',
66
+ total: 49.99,
67
+ });`;
68
+ }
69
+ function snippetExpress(workspaceKey) {
70
+ return `import express from 'express';
71
+ import { guruluMiddleware } from '@gurulu/node/middleware/express';
72
+
73
+ const app = express();
74
+ app.use(guruluMiddleware({
75
+ workspaceKey: process.env.GURULU_WORKSPACE ?? '${workspaceKey}',
76
+ apiKey: process.env.GURULU_API_KEY,
77
+ endpoint: process.env.GURULU_ENDPOINT,
78
+ }));`;
79
+ }
80
+ function snippetHono(workspaceKey) {
81
+ return `import { Hono } from 'hono';
82
+ import { createGurulu } from '@gurulu/node';
83
+
84
+ const gurulu = createGurulu({
85
+ workspaceKey: process.env.GURULU_WORKSPACE ?? '${workspaceKey}',
86
+ apiKey: process.env.GURULU_API_KEY,
87
+ endpoint: process.env.GURULU_ENDPOINT,
88
+ });
89
+
90
+ const app = new Hono();
91
+ app.post('/checkout/complete', async (c) => {
92
+ await gurulu.track('purchase_completed', await c.req.json());
93
+ return c.json({ ok: true });
94
+ });`;
95
+ }
96
+ function placementHintFor(framework) {
97
+ switch (framework) {
98
+ case "next":
99
+ return "Add the GuruluProvider to app/layout.tsx (App Router) or pages/_app.tsx (Pages Router) and wrap children.";
100
+ case "nuxt":
101
+ return "Add to plugins/gurulu.client.ts and register in nuxt.config.ts.";
102
+ case "svelte":
103
+ return "Add to src/routes/+layout.svelte inside onMount().";
104
+ case "astro":
105
+ return "Add to src/layouts/Base.astro inside a <script> tag (or use a hydrated component).";
106
+ case "vite":
107
+ case "react":
108
+ case "vue":
109
+ return "Add to src/main.ts (or your entry file) before mounting the app.";
110
+ case "express":
111
+ return "Register guruluMiddleware before your route handlers in your app entry file.";
112
+ case "fastify":
113
+ return "Register the Gurulu plugin via fastify.register() before route definitions.";
114
+ case "hono":
115
+ return "Initialize the SDK at the top of your app file; call track() inside route handlers.";
116
+ case "koa":
117
+ return "Add as Koa middleware (app.use) before your routes.";
118
+ case "node-server":
119
+ return "Initialize the SDK at startup; call track() from your handlers.";
120
+ default:
121
+ return "Initialize the SDK at your app entry point.";
122
+ }
123
+ }
124
+ function initSnippetFor(framework, sdk, workspaceKey) {
125
+ if (sdk === "@gurulu/node") {
126
+ if (framework === "express")
127
+ return snippetExpress(workspaceKey);
128
+ if (framework === "hono")
129
+ return snippetHono(workspaceKey);
130
+ return snippetNode(workspaceKey);
131
+ }
132
+ if (framework === "next")
133
+ return snippetNext(workspaceKey);
134
+ return snippetWeb(workspaceKey);
135
+ }
136
+ function envKeysFor(sdk, framework) {
137
+ if (sdk === "@gurulu/node") {
138
+ return [
139
+ { key: "GURULU_WORKSPACE", example: "pk_live_xxxxxxxxxxxx", required: true },
140
+ { key: "GURULU_API_KEY", example: "sk_live_xxxxxxxxxxxx", required: true },
141
+ {
142
+ key: "GURULU_ENDPOINT",
143
+ example: "https://ingest.gurulu.io",
144
+ required: false
145
+ }
146
+ ];
147
+ }
148
+ const prefix = framework === "next" ? "NEXT_PUBLIC_GURULU" : "VITE_GURULU";
149
+ return [
150
+ { key: `${prefix}_WORKSPACE`, example: "pk_live_xxxxxxxxxxxx", required: false },
151
+ { key: `${prefix}_ENDPOINT`, example: "https://ingest.gurulu.io", required: false }
152
+ ];
153
+ }
154
+ function buildInstallPlan(detected, ctx = {}) {
155
+ const sdk = sdkFor(detected.framework);
156
+ const workspaceKey = ctx.writeKey ?? "pk_xxxxxxxxxxxx";
157
+ return {
158
+ sdk,
159
+ packageManager: detected.packageManager,
160
+ installCommand: installCmdFor(detected.packageManager, sdk),
161
+ initSnippet: initSnippetFor(detected.framework, sdk, workspaceKey),
162
+ envKeys: envKeysFor(sdk, detected.framework),
163
+ placementHint: placementHintFor(detected.framework),
164
+ framework: detected.framework
165
+ };
166
+ }
167
+ export {
168
+ buildInstallPlan
169
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gurulu/cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.4",
4
4
  "private": false,
5
5
  "license": "BUSL-1.1",
6
6
  "publishConfig": {
@@ -26,6 +26,16 @@
26
26
  "import": "./dist/index.js",
27
27
  "default": "./dist/index.js"
28
28
  },
29
+ "./detect": {
30
+ "types": "./dist/lib/detect.d.ts",
31
+ "import": "./dist/lib/detect.js",
32
+ "default": "./dist/lib/detect.js"
33
+ },
34
+ "./install-plan": {
35
+ "types": "./dist/lib/install-plan.d.ts",
36
+ "import": "./dist/lib/install-plan.js",
37
+ "default": "./dist/lib/install-plan.js"
38
+ },
29
39
  "./package.json": "./package.json"
30
40
  },
31
41
  "files": [
@@ -34,8 +44,9 @@
34
44
  "LICENSE"
35
45
  ],
36
46
  "scripts": {
37
- "build": "rm -rf dist && bun run build:js && bun run build:types && bun run build:bin",
47
+ "build": "rm -rf dist && bun run build:js && bun run build:lib && bun run build:types && bun run build:bin",
38
48
  "build:js": "bun build ./src/index.ts --outdir ./dist --target node --format=esm",
49
+ "build:lib": "bun build ./src/lib/detect.ts ./src/lib/install-plan.ts --outdir ./dist/lib --target node --format=esm",
39
50
  "build:bin": "bun build ./src/bin.ts --outdir ./dist --target node --format=esm && chmod +x dist/bin.js",
40
51
  "build:types": "tsc -p tsconfig.build.json",
41
52
  "typecheck": "tsc --noEmit",