@gurulu/cli 1.0.0 → 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.
package/dist/bin.js CHANGED
@@ -25055,8 +25055,324 @@ 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 { createGurulu } from '@gurulu/web';
25245
+
25246
+ const gurulu = createGurulu();
25247
+ gurulu.init({ workspace: '${workspaceKey}' });`;
25248
+ }
25249
+ function snippetNext(workspaceKey) {
25250
+ return `// app/layout.tsx (or pages/_app.tsx)
25251
+ 'use client';
25252
+ import { useEffect } from 'react';
25253
+ import { createGurulu } from '@gurulu/web';
25254
+
25255
+ const gurulu = createGurulu();
25256
+
25257
+ export function GuruluProvider({ children }: { children: React.ReactNode }) {
25258
+ useEffect(() => {
25259
+ gurulu.init({ workspace: '${workspaceKey}' });
25260
+ }, []);
25261
+ return <>{children}</>;
25262
+ }`;
25263
+ }
25264
+ function snippetNode(workspaceKey) {
25265
+ return `import { createGurulu } from '@gurulu/node';
25266
+
25267
+ const gurulu = createGurulu({
25268
+ workspace: '${workspaceKey}',
25269
+ apiKey: process.env.GURULU_API_KEY,
25270
+ });
25271
+
25272
+ // In your handler:
25273
+ await gurulu.track('purchase.completed', {
25274
+ user_id: 'u_42',
25275
+ order_id: 'o_123',
25276
+ total: 49.99,
25277
+ });`;
25278
+ }
25279
+ function snippetExpress(workspaceKey) {
25280
+ return `import express from 'express';
25281
+ import { guruluMiddleware } from '@gurulu/node/middleware/express';
25282
+
25283
+ const app = express();
25284
+ app.use(guruluMiddleware({
25285
+ workspace: '${workspaceKey}',
25286
+ apiKey: process.env.GURULU_API_KEY,
25287
+ }));`;
25288
+ }
25289
+ function snippetHono(workspaceKey) {
25290
+ return `import { Hono } from 'hono';
25291
+ import { createGurulu } from '@gurulu/node';
25292
+
25293
+ const gurulu = createGurulu({
25294
+ workspace: '${workspaceKey}',
25295
+ apiKey: process.env.GURULU_API_KEY,
25296
+ });
25297
+
25298
+ const app = new Hono();
25299
+ app.post('/checkout/complete', async (c) => {
25300
+ await gurulu.track('purchase.completed', await c.req.json());
25301
+ return c.json({ ok: true });
25302
+ });`;
25303
+ }
25304
+ function placementHintFor(framework) {
25305
+ switch (framework) {
25306
+ case "next":
25307
+ return "Add the GuruluProvider to app/layout.tsx (App Router) or pages/_app.tsx (Pages Router) and wrap children.";
25308
+ case "nuxt":
25309
+ return "Add to plugins/gurulu.client.ts and register in nuxt.config.ts.";
25310
+ case "svelte":
25311
+ return "Add to src/routes/+layout.svelte inside onMount().";
25312
+ case "astro":
25313
+ return "Add to src/layouts/Base.astro inside a <script> tag (or use a hydrated component).";
25314
+ case "vite":
25315
+ case "react":
25316
+ case "vue":
25317
+ return "Add to src/main.ts (or your entry file) before mounting the app.";
25318
+ case "express":
25319
+ return "Register guruluMiddleware before your route handlers in your app entry file.";
25320
+ case "fastify":
25321
+ return "Register the Gurulu plugin via fastify.register() before route definitions.";
25322
+ case "hono":
25323
+ return "Initialize the SDK at the top of your app file; call track() inside route handlers.";
25324
+ case "koa":
25325
+ return "Add as Koa middleware (app.use) before your routes.";
25326
+ case "node-server":
25327
+ return "Initialize the SDK at startup; call track() from your handlers.";
25328
+ default:
25329
+ return "Initialize the SDK at your app entry point.";
25330
+ }
25331
+ }
25332
+ function initSnippetFor(framework, sdk, workspaceKey) {
25333
+ if (sdk === "@gurulu/node") {
25334
+ if (framework === "express")
25335
+ return snippetExpress(workspaceKey);
25336
+ if (framework === "hono")
25337
+ return snippetHono(workspaceKey);
25338
+ return snippetNode(workspaceKey);
25339
+ }
25340
+ if (framework === "next")
25341
+ return snippetNext(workspaceKey);
25342
+ return snippetWeb(workspaceKey);
25343
+ }
25344
+ function envKeysFor(sdk, framework) {
25345
+ if (sdk === "@gurulu/node") {
25346
+ return [
25347
+ {
25348
+ key: "GURULU_API_KEY",
25349
+ example: "sk_live_xxxxxxxxxxxx",
25350
+ required: true
25351
+ }
25352
+ ];
25353
+ }
25354
+ const envKey = framework === "next" ? "NEXT_PUBLIC_GURULU_WORKSPACE" : "VITE_GURULU_WORKSPACE";
25355
+ return [
25356
+ {
25357
+ key: envKey,
25358
+ example: "pk_live_xxxxxxxxxxxx",
25359
+ required: false
25360
+ }
25361
+ ];
25362
+ }
25363
+ function buildInstallPlan(detected, ctx = {}) {
25364
+ const sdk = sdkFor(detected.framework);
25365
+ const workspaceKey = ctx.writeKey ?? "pk_xxxxxxxxxxxx";
25366
+ return {
25367
+ sdk,
25368
+ packageManager: detected.packageManager,
25369
+ installCommand: installCmdFor(detected.packageManager, sdk),
25370
+ initSnippet: initSnippetFor(detected.framework, sdk, workspaceKey),
25371
+ envKeys: envKeysFor(sdk, detected.framework),
25372
+ placementHint: placementHintFor(detected.framework),
25373
+ framework: detected.framework
25374
+ };
25375
+ }
25060
25376
 
25061
25377
  // src/commands/pull.ts
25062
25378
  import { writeFileSync as writeFileSync2 } from "node:fs";
@@ -25206,7 +25522,9 @@ var initCmd = defineCommand({
25206
25522
  args: {
25207
25523
  workspace: { type: "string", description: "Workspace ID (uuid)" },
25208
25524
  endpoint: { type: "string", description: "API endpoint", default: DEFAULT_ENDPOINT },
25209
- sdk: { type: "string", description: "SDK preference (web|node|react)", default: "web" },
25525
+ sdk: { type: "string", description: "SDK preference (web|node|auto)", default: "auto" },
25526
+ "write-key": { type: "string", description: "Workspace write key (pk_xxx) for init snippet" },
25527
+ "no-install": { type: "boolean", description: "Skip SDK install (config files only)" },
25210
25528
  "no-pull": { type: "boolean", description: "Skip first registry pull" },
25211
25529
  force: { type: "boolean", description: "Overwrite existing config" }
25212
25530
  },
@@ -25222,40 +25540,63 @@ var initCmd = defineCommand({
25222
25540
  console.error("[gurulu] --workspace <uuid> required (workspace ID from dashboard)");
25223
25541
  process.exit(1);
25224
25542
  }
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)`);
25543
+ const sdkArg = String(args.sdk ?? "auto");
25544
+ if (!["web", "node", "auto"].includes(sdkArg)) {
25545
+ console.error(`[gurulu] invalid --sdk: ${sdkArg} (use web|node|auto)`);
25228
25546
  process.exit(1);
25229
25547
  }
25548
+ const detected = detectProject(cwd);
25549
+ const writeKey = String(args["write-key"] ?? "").trim() || "pk_xxxxxxxxxxxx";
25550
+ const plan = buildInstallPlan(detected, { writeKey, workspaceId });
25551
+ const effectiveSdkPref = sdkArg === "auto" ? plan.sdk === "@gurulu/node" ? "node" : "web" : sdkArg;
25230
25552
  const config = {
25231
25553
  workspace_id: workspaceId,
25232
25554
  endpoint: String(args.endpoint),
25233
- sdk_preference: sdk,
25555
+ sdk_preference: effectiveSdkPref,
25234
25556
  registry_path: ".gurulu/event-registry.json",
25235
25557
  generated_path: ".gurulu/generated.d.ts",
25236
25558
  manifest_lock_path: ".gurulu/manifest.lock",
25237
25559
  auto_pull_on_init: true
25238
25560
  };
25239
25561
  writeProjectConfig(config, cwd);
25240
- const dir = dirname2(projectConfigPath(cwd));
25241
- if (!existsSync4(dir))
25562
+ const dir = dirname3(projectConfigPath(cwd));
25563
+ if (!existsSync5(dir))
25242
25564
  mkdirSync2(dir, { recursive: true });
25243
- if (!existsSync4(projectRegistryPath(cwd))) {
25565
+ if (!existsSync5(projectRegistryPath(cwd))) {
25244
25566
  writeFileSync3(projectRegistryPath(cwd), `{}
25245
25567
  `, "utf-8");
25246
25568
  }
25247
- if (!existsSync4(projectGeneratedPath(cwd))) {
25569
+ if (!existsSync5(projectGeneratedPath(cwd))) {
25248
25570
  writeFileSync3(projectGeneratedPath(cwd), "// Run `gurulu pull` to populate typed events.\n", "utf-8");
25249
25571
  }
25250
- if (!existsSync4(projectManifestLockPath(cwd))) {
25572
+ if (!existsSync5(projectManifestLockPath(cwd))) {
25251
25573
  writeFileSync3(projectManifestLockPath(cwd), `0.0.0
25252
25574
  `, "utf-8");
25253
25575
  }
25254
25576
  console.log(`[gurulu] initialized ${projectConfigPath(cwd)}`);
25577
+ if (args["no-install"]) {
25578
+ console.log(`[gurulu] skipping SDK install (--no-install).`);
25579
+ console.log(` manual: ${plan.installCommand}`);
25580
+ } else if (!detected.hasPackageJson) {
25581
+ console.log(`[gurulu] no package.json found in ${cwd} — skipping SDK install.`);
25582
+ console.log(` use the script tag: <script src="https://cdn.gurulu.io/t.js" data-workspace="${writeKey}"></script>`);
25583
+ } else {
25584
+ console.log(`[gurulu] detected ${detected.framework} (${detected.packageManager}) — installing ${plan.sdk}…`);
25585
+ const result = await execInstall(plan, { cwd });
25586
+ if (result.ok) {
25587
+ console.log(`[gurulu] ${plan.sdk} installed (${(result.durationMs / 1000).toFixed(1)}s).`);
25588
+ } else {
25589
+ console.warn(`[gurulu] install failed (exit ${result.exitCode ?? "n/a"}) — run manually: ${plan.installCommand}`);
25590
+ }
25591
+ }
25592
+ console.log("");
25255
25593
  console.log("[gurulu] next steps:");
25256
25594
  console.log(" 1. gurulu login — authenticate (or store API key)");
25257
25595
  console.log(" 2. gurulu pull — fetch registry + code-gen");
25258
- console.log(" 3. import { GuruluEvents } — use generated types");
25596
+ console.log(` 3. ${plan.placementHint}`);
25597
+ console.log("");
25598
+ console.log("[gurulu] init snippet:");
25599
+ console.log(plan.initSnippet);
25259
25600
  if (!args["no-pull"]) {
25260
25601
  try {
25261
25602
  await runPull({ cwd });
@@ -25365,7 +25706,7 @@ var pushCmd = defineCommand({
25365
25706
  });
25366
25707
 
25367
25708
  // src/index.ts
25368
- var VERSION = "1.0.0";
25709
+ var VERSION = "1.0.1";
25369
25710
  var mcpCmd = defineCommand({
25370
25711
  meta: {
25371
25712
  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.1";
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,324 @@ 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 { createGurulu } from '@gurulu/web';
24822
+
24823
+ const gurulu = createGurulu();
24824
+ gurulu.init({ workspace: '${workspaceKey}' });`;
24825
+ }
24826
+ function snippetNext(workspaceKey) {
24827
+ return `// app/layout.tsx (or pages/_app.tsx)
24828
+ 'use client';
24829
+ import { useEffect } from 'react';
24830
+ import { createGurulu } from '@gurulu/web';
24831
+
24832
+ const gurulu = createGurulu();
24833
+
24834
+ export function GuruluProvider({ children }: { children: React.ReactNode }) {
24835
+ useEffect(() => {
24836
+ gurulu.init({ workspace: '${workspaceKey}' });
24837
+ }, []);
24838
+ return <>{children}</>;
24839
+ }`;
24840
+ }
24841
+ function snippetNode(workspaceKey) {
24842
+ return `import { createGurulu } from '@gurulu/node';
24843
+
24844
+ const gurulu = createGurulu({
24845
+ workspace: '${workspaceKey}',
24846
+ apiKey: process.env.GURULU_API_KEY,
24847
+ });
24848
+
24849
+ // In your handler:
24850
+ await gurulu.track('purchase.completed', {
24851
+ user_id: 'u_42',
24852
+ order_id: 'o_123',
24853
+ total: 49.99,
24854
+ });`;
24855
+ }
24856
+ function snippetExpress(workspaceKey) {
24857
+ return `import express from 'express';
24858
+ import { guruluMiddleware } from '@gurulu/node/middleware/express';
24859
+
24860
+ const app = express();
24861
+ app.use(guruluMiddleware({
24862
+ workspace: '${workspaceKey}',
24863
+ apiKey: process.env.GURULU_API_KEY,
24864
+ }));`;
24865
+ }
24866
+ function snippetHono(workspaceKey) {
24867
+ return `import { Hono } from 'hono';
24868
+ import { createGurulu } from '@gurulu/node';
24869
+
24870
+ const gurulu = createGurulu({
24871
+ workspace: '${workspaceKey}',
24872
+ apiKey: process.env.GURULU_API_KEY,
24873
+ });
24874
+
24875
+ const app = new Hono();
24876
+ app.post('/checkout/complete', async (c) => {
24877
+ await gurulu.track('purchase.completed', await c.req.json());
24878
+ return c.json({ ok: true });
24879
+ });`;
24880
+ }
24881
+ function placementHintFor(framework) {
24882
+ switch (framework) {
24883
+ case "next":
24884
+ return "Add the GuruluProvider to app/layout.tsx (App Router) or pages/_app.tsx (Pages Router) and wrap children.";
24885
+ case "nuxt":
24886
+ return "Add to plugins/gurulu.client.ts and register in nuxt.config.ts.";
24887
+ case "svelte":
24888
+ return "Add to src/routes/+layout.svelte inside onMount().";
24889
+ case "astro":
24890
+ return "Add to src/layouts/Base.astro inside a <script> tag (or use a hydrated component).";
24891
+ case "vite":
24892
+ case "react":
24893
+ case "vue":
24894
+ return "Add to src/main.ts (or your entry file) before mounting the app.";
24895
+ case "express":
24896
+ return "Register guruluMiddleware before your route handlers in your app entry file.";
24897
+ case "fastify":
24898
+ return "Register the Gurulu plugin via fastify.register() before route definitions.";
24899
+ case "hono":
24900
+ return "Initialize the SDK at the top of your app file; call track() inside route handlers.";
24901
+ case "koa":
24902
+ return "Add as Koa middleware (app.use) before your routes.";
24903
+ case "node-server":
24904
+ return "Initialize the SDK at startup; call track() from your handlers.";
24905
+ default:
24906
+ return "Initialize the SDK at your app entry point.";
24907
+ }
24908
+ }
24909
+ function initSnippetFor(framework, sdk, workspaceKey) {
24910
+ if (sdk === "@gurulu/node") {
24911
+ if (framework === "express")
24912
+ return snippetExpress(workspaceKey);
24913
+ if (framework === "hono")
24914
+ return snippetHono(workspaceKey);
24915
+ return snippetNode(workspaceKey);
24916
+ }
24917
+ if (framework === "next")
24918
+ return snippetNext(workspaceKey);
24919
+ return snippetWeb(workspaceKey);
24920
+ }
24921
+ function envKeysFor(sdk, framework) {
24922
+ if (sdk === "@gurulu/node") {
24923
+ return [
24924
+ {
24925
+ key: "GURULU_API_KEY",
24926
+ example: "sk_live_xxxxxxxxxxxx",
24927
+ required: true
24928
+ }
24929
+ ];
24930
+ }
24931
+ const envKey = framework === "next" ? "NEXT_PUBLIC_GURULU_WORKSPACE" : "VITE_GURULU_WORKSPACE";
24932
+ return [
24933
+ {
24934
+ key: envKey,
24935
+ example: "pk_live_xxxxxxxxxxxx",
24936
+ required: false
24937
+ }
24938
+ ];
24939
+ }
24940
+ function buildInstallPlan(detected, ctx = {}) {
24941
+ const sdk = sdkFor(detected.framework);
24942
+ const workspaceKey = ctx.writeKey ?? "pk_xxxxxxxxxxxx";
24943
+ return {
24944
+ sdk,
24945
+ packageManager: detected.packageManager,
24946
+ installCommand: installCmdFor(detected.packageManager, sdk),
24947
+ initSnippet: initSnippetFor(detected.framework, sdk, workspaceKey),
24948
+ envKeys: envKeysFor(sdk, detected.framework),
24949
+ placementHint: placementHintFor(detected.framework),
24950
+ framework: detected.framework
24951
+ };
24952
+ }
24637
24953
 
24638
24954
  // src/commands/pull.ts
24639
24955
  import { writeFileSync as writeFileSync2 } from "node:fs";
@@ -24783,7 +25099,9 @@ var initCmd = defineCommand({
24783
25099
  args: {
24784
25100
  workspace: { type: "string", description: "Workspace ID (uuid)" },
24785
25101
  endpoint: { type: "string", description: "API endpoint", default: DEFAULT_ENDPOINT },
24786
- sdk: { type: "string", description: "SDK preference (web|node|react)", default: "web" },
25102
+ sdk: { type: "string", description: "SDK preference (web|node|auto)", default: "auto" },
25103
+ "write-key": { type: "string", description: "Workspace write key (pk_xxx) for init snippet" },
25104
+ "no-install": { type: "boolean", description: "Skip SDK install (config files only)" },
24787
25105
  "no-pull": { type: "boolean", description: "Skip first registry pull" },
24788
25106
  force: { type: "boolean", description: "Overwrite existing config" }
24789
25107
  },
@@ -24799,40 +25117,63 @@ var initCmd = defineCommand({
24799
25117
  console.error("[gurulu] --workspace <uuid> required (workspace ID from dashboard)");
24800
25118
  process.exit(1);
24801
25119
  }
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)`);
25120
+ const sdkArg = String(args.sdk ?? "auto");
25121
+ if (!["web", "node", "auto"].includes(sdkArg)) {
25122
+ console.error(`[gurulu] invalid --sdk: ${sdkArg} (use web|node|auto)`);
24805
25123
  process.exit(1);
24806
25124
  }
25125
+ const detected = detectProject(cwd);
25126
+ const writeKey = String(args["write-key"] ?? "").trim() || "pk_xxxxxxxxxxxx";
25127
+ const plan = buildInstallPlan(detected, { writeKey, workspaceId });
25128
+ const effectiveSdkPref = sdkArg === "auto" ? plan.sdk === "@gurulu/node" ? "node" : "web" : sdkArg;
24807
25129
  const config = {
24808
25130
  workspace_id: workspaceId,
24809
25131
  endpoint: String(args.endpoint),
24810
- sdk_preference: sdk,
25132
+ sdk_preference: effectiveSdkPref,
24811
25133
  registry_path: ".gurulu/event-registry.json",
24812
25134
  generated_path: ".gurulu/generated.d.ts",
24813
25135
  manifest_lock_path: ".gurulu/manifest.lock",
24814
25136
  auto_pull_on_init: true
24815
25137
  };
24816
25138
  writeProjectConfig(config, cwd);
24817
- const dir = dirname2(projectConfigPath(cwd));
24818
- if (!existsSync4(dir))
25139
+ const dir = dirname3(projectConfigPath(cwd));
25140
+ if (!existsSync5(dir))
24819
25141
  mkdirSync2(dir, { recursive: true });
24820
- if (!existsSync4(projectRegistryPath(cwd))) {
25142
+ if (!existsSync5(projectRegistryPath(cwd))) {
24821
25143
  writeFileSync3(projectRegistryPath(cwd), `{}
24822
25144
  `, "utf-8");
24823
25145
  }
24824
- if (!existsSync4(projectGeneratedPath(cwd))) {
25146
+ if (!existsSync5(projectGeneratedPath(cwd))) {
24825
25147
  writeFileSync3(projectGeneratedPath(cwd), "// Run `gurulu pull` to populate typed events.\n", "utf-8");
24826
25148
  }
24827
- if (!existsSync4(projectManifestLockPath(cwd))) {
25149
+ if (!existsSync5(projectManifestLockPath(cwd))) {
24828
25150
  writeFileSync3(projectManifestLockPath(cwd), `0.0.0
24829
25151
  `, "utf-8");
24830
25152
  }
24831
25153
  console.log(`[gurulu] initialized ${projectConfigPath(cwd)}`);
25154
+ if (args["no-install"]) {
25155
+ console.log(`[gurulu] skipping SDK install (--no-install).`);
25156
+ console.log(` manual: ${plan.installCommand}`);
25157
+ } else if (!detected.hasPackageJson) {
25158
+ console.log(`[gurulu] no package.json found in ${cwd} — skipping SDK install.`);
25159
+ console.log(` use the script tag: <script src="https://cdn.gurulu.io/t.js" data-workspace="${writeKey}"></script>`);
25160
+ } else {
25161
+ console.log(`[gurulu] detected ${detected.framework} (${detected.packageManager}) — installing ${plan.sdk}…`);
25162
+ const result = await execInstall(plan, { cwd });
25163
+ if (result.ok) {
25164
+ console.log(`[gurulu] ${plan.sdk} installed (${(result.durationMs / 1000).toFixed(1)}s).`);
25165
+ } else {
25166
+ console.warn(`[gurulu] install failed (exit ${result.exitCode ?? "n/a"}) — run manually: ${plan.installCommand}`);
25167
+ }
25168
+ }
25169
+ console.log("");
24832
25170
  console.log("[gurulu] next steps:");
24833
25171
  console.log(" 1. gurulu login — authenticate (or store API key)");
24834
25172
  console.log(" 2. gurulu pull — fetch registry + code-gen");
24835
- console.log(" 3. import { GuruluEvents } — use generated types");
25173
+ console.log(` 3. ${plan.placementHint}`);
25174
+ console.log("");
25175
+ console.log("[gurulu] init snippet:");
25176
+ console.log(plan.initSnippet);
24836
25177
  if (!args["no-pull"]) {
24837
25178
  try {
24838
25179
  await runPull({ cwd });
@@ -24942,7 +25283,7 @@ var pushCmd = defineCommand({
24942
25283
  });
24943
25284
 
24944
25285
  // src/index.ts
24945
- var VERSION = "1.0.0";
25286
+ var VERSION = "1.0.1";
24946
25287
  var mcpCmd = defineCommand({
24947
25288
  meta: {
24948
25289
  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;AA2JD,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,eAAe,EACzB,GAAG,GAAE,kBAAuB,GAC3B,WAAW,CAYb"}
@@ -0,0 +1,161 @@
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 { createGurulu } from '@gurulu/web';
28
+
29
+ const gurulu = createGurulu();
30
+ gurulu.init({ workspace: '${workspaceKey}' });`;
31
+ }
32
+ function snippetNext(workspaceKey) {
33
+ return `// app/layout.tsx (or pages/_app.tsx)
34
+ 'use client';
35
+ import { useEffect } from 'react';
36
+ import { createGurulu } from '@gurulu/web';
37
+
38
+ const gurulu = createGurulu();
39
+
40
+ export function GuruluProvider({ children }: { children: React.ReactNode }) {
41
+ useEffect(() => {
42
+ gurulu.init({ workspace: '${workspaceKey}' });
43
+ }, []);
44
+ return <>{children}</>;
45
+ }`;
46
+ }
47
+ function snippetNode(workspaceKey) {
48
+ return `import { createGurulu } from '@gurulu/node';
49
+
50
+ const gurulu = createGurulu({
51
+ workspace: '${workspaceKey}',
52
+ apiKey: process.env.GURULU_API_KEY,
53
+ });
54
+
55
+ // In your handler:
56
+ await gurulu.track('purchase.completed', {
57
+ user_id: 'u_42',
58
+ order_id: 'o_123',
59
+ total: 49.99,
60
+ });`;
61
+ }
62
+ function snippetExpress(workspaceKey) {
63
+ return `import express from 'express';
64
+ import { guruluMiddleware } from '@gurulu/node/middleware/express';
65
+
66
+ const app = express();
67
+ app.use(guruluMiddleware({
68
+ workspace: '${workspaceKey}',
69
+ apiKey: process.env.GURULU_API_KEY,
70
+ }));`;
71
+ }
72
+ function snippetHono(workspaceKey) {
73
+ return `import { Hono } from 'hono';
74
+ import { createGurulu } from '@gurulu/node';
75
+
76
+ const gurulu = createGurulu({
77
+ workspace: '${workspaceKey}',
78
+ apiKey: process.env.GURULU_API_KEY,
79
+ });
80
+
81
+ const app = new Hono();
82
+ app.post('/checkout/complete', async (c) => {
83
+ await gurulu.track('purchase.completed', await c.req.json());
84
+ return c.json({ ok: true });
85
+ });`;
86
+ }
87
+ function placementHintFor(framework) {
88
+ switch (framework) {
89
+ case "next":
90
+ return "Add the GuruluProvider to app/layout.tsx (App Router) or pages/_app.tsx (Pages Router) and wrap children.";
91
+ case "nuxt":
92
+ return "Add to plugins/gurulu.client.ts and register in nuxt.config.ts.";
93
+ case "svelte":
94
+ return "Add to src/routes/+layout.svelte inside onMount().";
95
+ case "astro":
96
+ return "Add to src/layouts/Base.astro inside a <script> tag (or use a hydrated component).";
97
+ case "vite":
98
+ case "react":
99
+ case "vue":
100
+ return "Add to src/main.ts (or your entry file) before mounting the app.";
101
+ case "express":
102
+ return "Register guruluMiddleware before your route handlers in your app entry file.";
103
+ case "fastify":
104
+ return "Register the Gurulu plugin via fastify.register() before route definitions.";
105
+ case "hono":
106
+ return "Initialize the SDK at the top of your app file; call track() inside route handlers.";
107
+ case "koa":
108
+ return "Add as Koa middleware (app.use) before your routes.";
109
+ case "node-server":
110
+ return "Initialize the SDK at startup; call track() from your handlers.";
111
+ default:
112
+ return "Initialize the SDK at your app entry point.";
113
+ }
114
+ }
115
+ function initSnippetFor(framework, sdk, workspaceKey) {
116
+ if (sdk === "@gurulu/node") {
117
+ if (framework === "express")
118
+ return snippetExpress(workspaceKey);
119
+ if (framework === "hono")
120
+ return snippetHono(workspaceKey);
121
+ return snippetNode(workspaceKey);
122
+ }
123
+ if (framework === "next")
124
+ return snippetNext(workspaceKey);
125
+ return snippetWeb(workspaceKey);
126
+ }
127
+ function envKeysFor(sdk, framework) {
128
+ if (sdk === "@gurulu/node") {
129
+ return [
130
+ {
131
+ key: "GURULU_API_KEY",
132
+ example: "sk_live_xxxxxxxxxxxx",
133
+ required: true
134
+ }
135
+ ];
136
+ }
137
+ const envKey = framework === "next" ? "NEXT_PUBLIC_GURULU_WORKSPACE" : "VITE_GURULU_WORKSPACE";
138
+ return [
139
+ {
140
+ key: envKey,
141
+ example: "pk_live_xxxxxxxxxxxx",
142
+ required: false
143
+ }
144
+ ];
145
+ }
146
+ function buildInstallPlan(detected, ctx = {}) {
147
+ const sdk = sdkFor(detected.framework);
148
+ const workspaceKey = ctx.writeKey ?? "pk_xxxxxxxxxxxx";
149
+ return {
150
+ sdk,
151
+ packageManager: detected.packageManager,
152
+ installCommand: installCmdFor(detected.packageManager, sdk),
153
+ initSnippet: initSnippetFor(detected.framework, sdk, workspaceKey),
154
+ envKeys: envKeysFor(sdk, detected.framework),
155
+ placementHint: placementHintFor(detected.framework),
156
+ framework: detected.framework
157
+ };
158
+ }
159
+ export {
160
+ buildInstallPlan
161
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gurulu/cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
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",