@gurulu/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +66 -0
  2. package/bin/gurulu.js +2 -0
  3. package/dist/api-client.d.ts +27 -0
  4. package/dist/api-client.js +150 -0
  5. package/dist/commands/add-server.d.ts +9 -0
  6. package/dist/commands/add-server.js +155 -0
  7. package/dist/commands/alerts.d.ts +22 -0
  8. package/dist/commands/alerts.js +281 -0
  9. package/dist/commands/api-keys.d.ts +20 -0
  10. package/dist/commands/api-keys.js +130 -0
  11. package/dist/commands/audiences.d.ts +16 -0
  12. package/dist/commands/audiences.js +180 -0
  13. package/dist/commands/audit.d.ts +20 -0
  14. package/dist/commands/audit.js +130 -0
  15. package/dist/commands/auth.d.ts +20 -0
  16. package/dist/commands/auth.js +214 -0
  17. package/dist/commands/chat.d.ts +18 -0
  18. package/dist/commands/chat.js +117 -0
  19. package/dist/commands/config.d.ts +10 -0
  20. package/dist/commands/config.js +92 -0
  21. package/dist/commands/db.d.ts +25 -0
  22. package/dist/commands/db.js +322 -0
  23. package/dist/commands/destinations.d.ts +20 -0
  24. package/dist/commands/destinations.js +191 -0
  25. package/dist/commands/doctor.d.ts +7 -0
  26. package/dist/commands/doctor.js +318 -0
  27. package/dist/commands/events.d.ts +27 -0
  28. package/dist/commands/events.js +147 -0
  29. package/dist/commands/experiments.d.ts +18 -0
  30. package/dist/commands/experiments.js +233 -0
  31. package/dist/commands/identity.d.ts +13 -0
  32. package/dist/commands/identity.js +107 -0
  33. package/dist/commands/init.d.ts +11 -0
  34. package/dist/commands/init.js +215 -0
  35. package/dist/commands/insights.d.ts +10 -0
  36. package/dist/commands/insights.js +65 -0
  37. package/dist/commands/install.d.ts +233 -0
  38. package/dist/commands/install.js +920 -0
  39. package/dist/commands/login.d.ts +20 -0
  40. package/dist/commands/login.js +170 -0
  41. package/dist/commands/logout.d.ts +10 -0
  42. package/dist/commands/logout.js +41 -0
  43. package/dist/commands/playground.d.ts +11 -0
  44. package/dist/commands/playground.js +47 -0
  45. package/dist/commands/sites.d.ts +18 -0
  46. package/dist/commands/sites.js +139 -0
  47. package/dist/commands/sourcemap.d.ts +21 -0
  48. package/dist/commands/sourcemap.js +137 -0
  49. package/dist/commands/status.d.ts +7 -0
  50. package/dist/commands/status.js +136 -0
  51. package/dist/commands/warehouse.d.ts +20 -0
  52. package/dist/commands/warehouse.js +65 -0
  53. package/dist/commands/warehouses.d.ts +17 -0
  54. package/dist/commands/warehouses.js +182 -0
  55. package/dist/commands/whoami.d.ts +9 -0
  56. package/dist/commands/whoami.js +47 -0
  57. package/dist/config.d.ts +75 -0
  58. package/dist/config.js +329 -0
  59. package/dist/frameworks/detect.d.ts +8 -0
  60. package/dist/frameworks/detect.js +362 -0
  61. package/dist/index.d.ts +1 -0
  62. package/dist/index.js +429 -0
  63. package/dist/install-intent-proposal.d.ts +99 -0
  64. package/dist/install-intent-proposal.js +202 -0
  65. package/dist/utils/api.d.ts +20 -0
  66. package/dist/utils/api.js +47 -0
  67. package/dist/utils/config.d.ts +13 -0
  68. package/dist/utils/config.js +30 -0
  69. package/dist/utils/confirm.d.ts +17 -0
  70. package/dist/utils/confirm.js +40 -0
  71. package/dist/utils/dry-run.d.ts +20 -0
  72. package/dist/utils/dry-run.js +67 -0
  73. package/dist/utils/from-file.d.ts +9 -0
  74. package/dist/utils/from-file.js +72 -0
  75. package/dist/utils/ui.d.ts +14 -0
  76. package/dist/utils/ui.js +59 -0
  77. package/package.json +26 -0
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # @gurulu/cli
2
+
3
+ CLI wizard for setting up Gurulu.io analytics in any project.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ npx @gurulu/cli init
9
+ ```
10
+
11
+ ## Commands
12
+
13
+ | Command | Description |
14
+ |---------|-------------|
15
+ | `gurulu init` | Set up Gurulu analytics (auto-detects framework) |
16
+ | `gurulu login` | Authenticate with your Personal API Key |
17
+ | `gurulu events` | List detected events from your site |
18
+ | `gurulu status` | Quick health check |
19
+ | `gurulu doctor` | Comprehensive diagnostics |
20
+ | `gurulu add-server` | Add server-side SDK (@gurulu/node) |
21
+
22
+ ## Supported Frameworks
23
+
24
+ - Next.js (App Router & Pages Router)
25
+ - React (Vite & CRA)
26
+ - Vue 3
27
+ - Nuxt 3
28
+ - Svelte & SvelteKit
29
+ - Astro
30
+ - Express
31
+ - NestJS
32
+ - Plain HTML
33
+
34
+ ## Authentication
35
+
36
+ ```bash
37
+ gurulu login --api-key pak_live_xxxxx
38
+ ```
39
+
40
+ Or set the `GURULU_API_KEY` environment variable.
41
+
42
+ ## Non-Interactive Mode
43
+
44
+ All commands support `--no-interactive` for CI/CD:
45
+
46
+ ```bash
47
+ gurulu init --site-id abc123 --token tok_xxx --no-interactive
48
+ ```
49
+
50
+ ## JSON Output
51
+
52
+ Use `--json` flag for machine-readable output:
53
+
54
+ ```bash
55
+ gurulu events --json
56
+ gurulu status --json
57
+ gurulu doctor --json
58
+ ```
59
+
60
+ ## Development
61
+
62
+ ```bash
63
+ npm install
64
+ npm run build
65
+ node bin/gurulu.js --help
66
+ ```
package/bin/gurulu.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../dist/index.js');
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Phase 18.5 W2 — Thin fetch wrapper used by all authenticated CLI
3
+ * commands. Handles auth header injection, user-agent, and the common
4
+ * 401/402/429/5xx error presentations.
5
+ */
6
+ import { type ActiveProfile } from './config';
7
+ export declare function userAgent(): string;
8
+ export interface CliApiOptions extends RequestInit {
9
+ profile?: string;
10
+ skipAuth?: boolean;
11
+ /** Used internally by tests — inject a pre-resolved profile. */
12
+ preloadedProfile?: ActiveProfile;
13
+ /** Disable process.exit for tests. */
14
+ noExitOnError?: boolean;
15
+ }
16
+ export declare class CliApiError extends Error {
17
+ status: number;
18
+ code: string;
19
+ details: unknown;
20
+ constructor(status: number, code: string, message: string, details?: unknown);
21
+ }
22
+ export declare function cliApi(path: string, init?: CliApiOptions): Promise<Response>;
23
+ /** Helper: POST JSON and return parsed body, with rich error reporting. */
24
+ export declare function cliApiJson<T = unknown>(path: string, init?: CliApiOptions & {
25
+ method?: string;
26
+ json?: unknown;
27
+ }): Promise<T>;
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ /**
3
+ * Phase 18.5 W2 — Thin fetch wrapper used by all authenticated CLI
4
+ * commands. Handles auth header injection, user-agent, and the common
5
+ * 401/402/429/5xx error presentations.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.CliApiError = void 0;
42
+ exports.userAgent = userAgent;
43
+ exports.cliApi = cliApi;
44
+ exports.cliApiJson = cliApiJson;
45
+ const os = __importStar(require("os"));
46
+ const config_1 = require("./config");
47
+ let cachedVersion = null;
48
+ function cliVersion() {
49
+ if (cachedVersion)
50
+ return cachedVersion;
51
+ try {
52
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
53
+ const pkg = require('../package.json');
54
+ cachedVersion = pkg.version || '0.0.0';
55
+ }
56
+ catch {
57
+ cachedVersion = '0.0.0';
58
+ }
59
+ return cachedVersion;
60
+ }
61
+ function userAgent() {
62
+ return `gurulu-cli/${cliVersion()} (node/${process.versions.node}; ${os.platform()})`;
63
+ }
64
+ class CliApiError extends Error {
65
+ status;
66
+ code;
67
+ details;
68
+ constructor(status, code, message, details) {
69
+ super(message);
70
+ this.status = status;
71
+ this.code = code;
72
+ this.details = details;
73
+ }
74
+ }
75
+ exports.CliApiError = CliApiError;
76
+ function fatal(msg, opts) {
77
+ process.stderr.write(`${msg}\n`);
78
+ if (!opts.noExitOnError)
79
+ process.exit(1);
80
+ throw new CliApiError(0, 'fatal', msg);
81
+ }
82
+ async function cliApi(path, init = {}) {
83
+ let profile = init.preloadedProfile || null;
84
+ if (!profile && !init.skipAuth) {
85
+ try {
86
+ profile = await (0, config_1.loadActiveProfile)({ profile: init.profile });
87
+ }
88
+ catch (err) {
89
+ fatal(`Not logged in. Run \`gurulu login\` first. (${err.message})`, init);
90
+ }
91
+ }
92
+ const base = profile?.api_base || process.env.GURULU_API_BASE || 'https://gurulu.io';
93
+ const url = path.startsWith('http') ? path : `${base}${path}`;
94
+ const headers = new Headers(init.headers || {});
95
+ if (!headers.has('user-agent'))
96
+ headers.set('user-agent', userAgent());
97
+ if (!init.skipAuth && profile) {
98
+ headers.set('authorization', `Bearer ${profile.secret_key}`);
99
+ }
100
+ if (init.body && !headers.has('content-type')) {
101
+ headers.set('content-type', 'application/json');
102
+ }
103
+ let res;
104
+ try {
105
+ res = await globalThis.fetch(url, { ...init, headers });
106
+ }
107
+ catch (err) {
108
+ fatal(`Gurulu is temporarily unavailable (network error): ${err.message}`, init);
109
+ }
110
+ if (res.status === 401) {
111
+ fatal('Your session is invalid. Run `gurulu login` again.', init);
112
+ }
113
+ if (res.status === 402) {
114
+ let msg = 'Quota exceeded. Upgrade at https://gurulu.io/settings/billing';
115
+ try {
116
+ const body = await res.clone().json();
117
+ if (body?.message)
118
+ msg = `Quota exceeded: ${body.message}`;
119
+ if (body?.upgradeUrl)
120
+ msg += `\n → ${body.upgradeUrl}`;
121
+ }
122
+ catch {
123
+ /* ignore */
124
+ }
125
+ fatal(msg, init);
126
+ }
127
+ if (res.status === 429) {
128
+ const retry = res.headers.get('retry-after') || '';
129
+ fatal(`Rate limited by Gurulu${retry ? ` — try again in ${retry}s` : ''}. Try again shortly.`, init);
130
+ }
131
+ if (res.status >= 500) {
132
+ fatal(`Gurulu is temporarily unavailable (HTTP ${res.status}).`, init);
133
+ }
134
+ return res;
135
+ }
136
+ /** Helper: POST JSON and return parsed body, with rich error reporting. */
137
+ async function cliApiJson(path, init = {}) {
138
+ const { json, ...rest } = init;
139
+ const res = await cliApi(path, {
140
+ ...rest,
141
+ method: init.method || (json ? 'POST' : 'GET'),
142
+ body: json !== undefined ? JSON.stringify(json) : rest.body,
143
+ });
144
+ const text = await res.text();
145
+ const parsed = text ? JSON.parse(text) : {};
146
+ if (!res.ok) {
147
+ throw new CliApiError(res.status, parsed?.error || 'http_error', parsed?.message || `HTTP ${res.status}`, parsed);
148
+ }
149
+ return parsed;
150
+ }
@@ -0,0 +1,9 @@
1
+ interface AddServerArgs {
2
+ site?: string;
3
+ noInteractive?: boolean;
4
+ dryRun?: boolean;
5
+ json?: boolean;
6
+ profile?: string;
7
+ }
8
+ export declare function addServerCommand(args: AddServerArgs): Promise<void>;
9
+ export {};
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.addServerCommand = addServerCommand;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const child_process_1 = require("child_process");
10
+ const config_1 = require("../config");
11
+ const api_client_1 = require("../api-client");
12
+ const ui_1 = require("../utils/ui");
13
+ async function addServerCommand(args) {
14
+ let profile;
15
+ try {
16
+ profile = await (0, config_1.loadActiveProfile)({ profile: args.profile });
17
+ }
18
+ catch {
19
+ profile = null;
20
+ }
21
+ const siteId = args.site;
22
+ const projectDir = process.cwd();
23
+ if (args.json) {
24
+ return addServerJSON(siteId, profile, projectDir, args);
25
+ }
26
+ (0, ui_1.banner)();
27
+ console.log((0, ui_1.bold)(' Add Server SDK'));
28
+ console.log('');
29
+ // Check for package.json
30
+ const pkgPath = path_1.default.join(projectDir, 'package.json');
31
+ if (!fs_1.default.existsSync(pkgPath)) {
32
+ (0, ui_1.error)('No package.json found in current directory.');
33
+ process.exit(1);
34
+ }
35
+ // Check if already installed
36
+ const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf-8'));
37
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
38
+ if (allDeps['@gurulu/node']) {
39
+ (0, ui_1.info)('@gurulu/node is already installed.');
40
+ }
41
+ if (!siteId) {
42
+ (0, ui_1.error)('No site selected. Run "gurulu login" first or use --site <id>.');
43
+ process.exit(1);
44
+ }
45
+ // Get server API key
46
+ let serverApiKey = process.env.GURULU_SERVER_API_KEY;
47
+ if (!serverApiKey && profile) {
48
+ try {
49
+ const data = await (0, api_client_1.cliApiJson)(`/api/cli/sites/${encodeURIComponent(siteId)}`, { preloadedProfile: profile });
50
+ serverApiKey = data.site?.publishableKey || '';
51
+ }
52
+ catch (err) {
53
+ (0, ui_1.warn)(`Could not fetch server credentials: ${err.message}`);
54
+ }
55
+ }
56
+ if (!serverApiKey && !args.noInteractive) {
57
+ serverApiKey = await (0, ui_1.prompt)(' Server API Key: ');
58
+ }
59
+ if (!serverApiKey) {
60
+ (0, ui_1.error)('Server API key required. Set GURULU_SERVER_API_KEY or run "gurulu login" first.');
61
+ process.exit(1);
62
+ }
63
+ if (args.dryRun) {
64
+ (0, ui_1.info)('Dry run - would perform the following:');
65
+ (0, ui_1.step)('Install @gurulu/node package');
66
+ (0, ui_1.step)('Create server SDK config file');
67
+ (0, ui_1.step)('Add GURULU_SERVER_API_KEY to .env');
68
+ return;
69
+ }
70
+ // Step 1: Install package
71
+ (0, ui_1.step)('Installing @gurulu/node...');
72
+ try {
73
+ // Detect package manager
74
+ let pm = 'npm install';
75
+ if (fs_1.default.existsSync(path_1.default.join(projectDir, 'pnpm-lock.yaml'))) {
76
+ pm = 'pnpm add';
77
+ }
78
+ else if (fs_1.default.existsSync(path_1.default.join(projectDir, 'yarn.lock'))) {
79
+ pm = 'yarn add';
80
+ }
81
+ else if (fs_1.default.existsSync(path_1.default.join(projectDir, 'bun.lockb'))) {
82
+ pm = 'bun add';
83
+ }
84
+ (0, child_process_1.execSync)(`${pm} @gurulu/node`, { cwd: projectDir, stdio: 'pipe' });
85
+ (0, ui_1.success)('Installed @gurulu/node');
86
+ }
87
+ catch (err) {
88
+ (0, ui_1.warn)(`Could not auto-install. Run manually: npm install @gurulu/node`);
89
+ }
90
+ // Step 2: Create server config file
91
+ const configFilePath = path_1.default.join(projectDir, 'src', 'lib', 'gurulu-server.ts');
92
+ const configCode = `import { GuruluNode } from '@gurulu/node';
93
+
94
+ export const gurulu = new GuruluNode({
95
+ siteId: process.env.GURULU_SITE_ID || '${siteId}',
96
+ apiKey: process.env.GURULU_SERVER_API_KEY || '',
97
+ });
98
+ `;
99
+ if (!fs_1.default.existsSync(configFilePath)) {
100
+ const configDir = path_1.default.dirname(configFilePath);
101
+ fs_1.default.mkdirSync(configDir, { recursive: true });
102
+ fs_1.default.writeFileSync(configFilePath, configCode);
103
+ (0, ui_1.success)('Created src/lib/gurulu-server.ts');
104
+ }
105
+ else {
106
+ (0, ui_1.info)('src/lib/gurulu-server.ts already exists, skipping.');
107
+ }
108
+ // Step 3: Update .env
109
+ const envFile = path_1.default.join(projectDir, '.env');
110
+ const envLines = [
111
+ `GURULU_SITE_ID=${siteId}`,
112
+ `GURULU_SERVER_API_KEY=${serverApiKey}`,
113
+ ];
114
+ let existingEnv = '';
115
+ if (fs_1.default.existsSync(envFile)) {
116
+ existingEnv = fs_1.default.readFileSync(envFile, 'utf-8');
117
+ }
118
+ const newLines = envLines.filter(line => {
119
+ const key = line.split('=')[0];
120
+ return !existingEnv.includes(key);
121
+ });
122
+ if (newLines.length > 0) {
123
+ const separator = existingEnv && !existingEnv.endsWith('\n') ? '\n' : '';
124
+ const header = existingEnv.includes('GURULU') ? '' : '# Gurulu.io Server SDK\n';
125
+ fs_1.default.appendFileSync(envFile, `${separator}${header}${newLines.join('\n')}\n`);
126
+ (0, ui_1.success)('Updated .env with server credentials');
127
+ }
128
+ else {
129
+ (0, ui_1.info)('.env already has Gurulu server credentials');
130
+ }
131
+ // Done
132
+ console.log('');
133
+ console.log((0, ui_1.bold)(' Next steps:'));
134
+ console.log('');
135
+ (0, ui_1.step)('Import gurulu from src/lib/gurulu-server.ts in your API routes');
136
+ (0, ui_1.step)(`Use ${(0, ui_1.cyan)('gurulu.track(event, properties)')} to send server-side events`);
137
+ (0, ui_1.step)(`Use ${(0, ui_1.cyan)('gurulu.identify(userId, traits)')} to identify users`);
138
+ (0, ui_1.step)(`Run ${(0, ui_1.cyan)('gurulu doctor')} to verify the setup`);
139
+ console.log('');
140
+ (0, ui_1.success)('Server SDK setup complete!');
141
+ console.log('');
142
+ }
143
+ async function addServerJSON(siteId, profile, projectDir, args) {
144
+ const result = {
145
+ siteId: siteId || null,
146
+ authenticated: !!profile,
147
+ dryRun: !!args.dryRun,
148
+ steps: [
149
+ { action: 'install', package: '@gurulu/node' },
150
+ { action: 'create', file: 'src/lib/gurulu-server.ts' },
151
+ { action: 'update', file: '.env', keys: ['GURULU_SITE_ID', 'GURULU_SERVER_API_KEY'] },
152
+ ],
153
+ };
154
+ console.log(JSON.stringify(result, null, 2));
155
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Phase 19.5 W2 B4 — `gurulu alerts list|show|channels list`.
3
+ * Phase 20 W2 B3 — `create|update|delete` + channels create/update/delete.
4
+ */
5
+ export interface AlertsArgs {
6
+ action?: string;
7
+ sub?: string;
8
+ target?: string;
9
+ severity?: string;
10
+ acknowledged?: string;
11
+ limit?: number;
12
+ json?: boolean;
13
+ profile?: string;
14
+ fromFile?: string;
15
+ name?: string;
16
+ type?: string;
17
+ metric?: string;
18
+ note?: string;
19
+ yes?: boolean;
20
+ dryRun?: boolean;
21
+ }
22
+ export declare function alertsCommand(args: AlertsArgs): Promise<void>;