@productbrain/cli 0.1.0-beta.28 → 0.1.0-beta.30
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/README.md +2 -0
- package/dist/__tests__/constants.test.d.ts +2 -0
- package/dist/__tests__/constants.test.d.ts.map +1 -0
- package/dist/__tests__/constants.test.js +94 -0
- package/dist/__tests__/constants.test.js.map +1 -0
- package/dist/__tests__/login.test.d.ts +2 -0
- package/dist/__tests__/login.test.d.ts.map +1 -0
- package/dist/__tests__/login.test.js +168 -0
- package/dist/__tests__/login.test.js.map +1 -0
- package/dist/__tests__/setup.test.d.ts +2 -0
- package/dist/__tests__/setup.test.d.ts.map +1 -0
- package/dist/__tests__/setup.test.js +170 -0
- package/dist/__tests__/setup.test.js.map +1 -0
- package/dist/commands/capture.d.ts.map +1 -1
- package/dist/commands/capture.js +20 -0
- package/dist/commands/capture.js.map +1 -1
- package/dist/commands/collections.d.ts +6 -0
- package/dist/commands/collections.d.ts.map +1 -1
- package/dist/commands/collections.js +14 -0
- package/dist/commands/collections.js.map +1 -1
- package/dist/commands/doctor.d.ts +11 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +124 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/doctor.test.d.ts +6 -0
- package/dist/commands/doctor.test.d.ts.map +1 -0
- package/dist/commands/doctor.test.js +102 -0
- package/dist/commands/doctor.test.js.map +1 -0
- package/dist/commands/login.d.ts +4 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +53 -27
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/setup.d.ts +16 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +213 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/generators/__tests__/surface-profiles.test.d.ts +2 -0
- package/dist/generators/__tests__/surface-profiles.test.d.ts.map +1 -0
- package/dist/generators/__tests__/surface-profiles.test.js +89 -0
- package/dist/generators/__tests__/surface-profiles.test.js.map +1 -0
- package/dist/generators/handshake-diff.test.js +1 -2
- package/dist/generators/handshake-diff.test.js.map +1 -1
- package/dist/index.js +39 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/activation.d.ts +28 -0
- package/dist/lib/activation.d.ts.map +1 -0
- package/dist/lib/activation.js +57 -0
- package/dist/lib/activation.js.map +1 -0
- package/dist/lib/activation.test.d.ts +6 -0
- package/dist/lib/activation.test.d.ts.map +1 -0
- package/dist/lib/activation.test.js +121 -0
- package/dist/lib/activation.test.js.map +1 -0
- package/dist/lib/client.d.ts +17 -0
- package/dist/lib/client.d.ts.map +1 -1
- package/dist/lib/client.js +41 -0
- package/dist/lib/client.js.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +5 -3
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/constants.d.ts +21 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +39 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/telemetry.d.ts +15 -0
- package/dist/lib/telemetry.d.ts.map +1 -0
- package/dist/lib/telemetry.js +29 -0
- package/dist/lib/telemetry.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pb doctor — health check for CLI configuration and connectivity.
|
|
3
|
+
*
|
|
4
|
+
* Runs a series of diagnostic checks (config file, API key, environment,
|
|
5
|
+
* server reachability, Node version, CLI version) and prints a summary.
|
|
6
|
+
* Does NOT require an active session — this is a diagnostic tool.
|
|
7
|
+
*
|
|
8
|
+
* WP-301 Slice 2.
|
|
9
|
+
*/
|
|
10
|
+
export declare function runDoctor(): Promise<void>;
|
|
11
|
+
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgHH,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CA2B/C"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pb doctor — health check for CLI configuration and connectivity.
|
|
3
|
+
*
|
|
4
|
+
* Runs a series of diagnostic checks (config file, API key, environment,
|
|
5
|
+
* server reachability, Node version, CLI version) and prints a summary.
|
|
6
|
+
* Does NOT require an active session — this is a diagnostic tool.
|
|
7
|
+
*
|
|
8
|
+
* WP-301 Slice 2.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync } from 'node:fs';
|
|
11
|
+
import { readFileSync } from 'node:fs';
|
|
12
|
+
import { dirname, join } from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { HOME_ENV_PATH } from '../lib/config.js';
|
|
15
|
+
import { DEFAULT_SITE_URL } from '../lib/constants.js';
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
// ANSI color helpers — degrade gracefully when NO_COLOR is set or not a TTY.
|
|
18
|
+
const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
19
|
+
const green = (s) => (useColor ? `\x1b[32m${s}\x1b[0m` : s);
|
|
20
|
+
const red = (s) => (useColor ? `\x1b[31m${s}\x1b[0m` : s);
|
|
21
|
+
const yellow = (s) => (useColor ? `\x1b[33m${s}\x1b[0m` : s);
|
|
22
|
+
function formatCheck(result) {
|
|
23
|
+
const icon = result.status === 'pass'
|
|
24
|
+
? green('\u2713')
|
|
25
|
+
: result.status === 'fail'
|
|
26
|
+
? red('\u2717')
|
|
27
|
+
: yellow('\u26A0');
|
|
28
|
+
const paddedLabel = result.label.padEnd(16);
|
|
29
|
+
return `${icon} ${paddedLabel}${result.detail}`;
|
|
30
|
+
}
|
|
31
|
+
/** Check 1: Config file existence */
|
|
32
|
+
function checkConfigFile() {
|
|
33
|
+
const exists = existsSync(HOME_ENV_PATH);
|
|
34
|
+
return {
|
|
35
|
+
status: exists ? 'pass' : 'fail',
|
|
36
|
+
label: 'Config file',
|
|
37
|
+
detail: exists ? HOME_ENV_PATH : `Not found: ${HOME_ENV_PATH}`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Check 2: API key presence and format */
|
|
41
|
+
function checkApiKey() {
|
|
42
|
+
const apiKey = process.env.PRODUCTBRAIN_API_KEY ?? '';
|
|
43
|
+
if (!apiKey) {
|
|
44
|
+
return { status: 'fail', label: 'API key', detail: 'Not set. Run: pb login' };
|
|
45
|
+
}
|
|
46
|
+
if (!apiKey.startsWith('pb_sk_')) {
|
|
47
|
+
return {
|
|
48
|
+
status: 'fail',
|
|
49
|
+
label: 'API key',
|
|
50
|
+
detail: `Invalid format (expected pb_sk_...). Run: pb login`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const masked = apiKey.slice(0, 8) + '****';
|
|
54
|
+
return { status: 'pass', label: 'API key', detail: masked };
|
|
55
|
+
}
|
|
56
|
+
/** Check 3: Environment and resolved URLs */
|
|
57
|
+
function checkEnvironment() {
|
|
58
|
+
const env = process.env.PB_ENV || 'production (default)';
|
|
59
|
+
const siteUrl = (process.env.CONVEX_SITE_URL ?? DEFAULT_SITE_URL).replace(/\/$/, '');
|
|
60
|
+
return {
|
|
61
|
+
status: 'pass',
|
|
62
|
+
label: 'Environment',
|
|
63
|
+
detail: `${env} \u2192 ${siteUrl}`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** Check 4: Server reachability via mcpCall */
|
|
67
|
+
async function checkReachability() {
|
|
68
|
+
try {
|
|
69
|
+
// Dynamic import to avoid module-load side effects when testing
|
|
70
|
+
const { mcpCall } = await import('../lib/client.js');
|
|
71
|
+
const start = Date.now();
|
|
72
|
+
await mcpCall('chain.getOrientView', {});
|
|
73
|
+
const elapsed = Date.now() - start;
|
|
74
|
+
return {
|
|
75
|
+
status: 'pass',
|
|
76
|
+
label: 'Reachability',
|
|
77
|
+
detail: `OK (${elapsed}ms)`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
82
|
+
// Truncate long error messages for readability
|
|
83
|
+
const short = msg.length > 80 ? msg.slice(0, 77) + '...' : msg;
|
|
84
|
+
return { status: 'fail', label: 'Reachability', detail: short };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** Check 5: Node.js version */
|
|
88
|
+
function checkNodeVersion() {
|
|
89
|
+
return { status: 'pass', label: 'Node', detail: process.version };
|
|
90
|
+
}
|
|
91
|
+
/** Check 6: CLI version from package.json */
|
|
92
|
+
function checkCliVersion() {
|
|
93
|
+
try {
|
|
94
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf8'));
|
|
95
|
+
return { status: 'pass', label: 'CLI', detail: pkg.version };
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return { status: 'warn', label: 'CLI', detail: 'Unable to read package.json' };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export async function runDoctor() {
|
|
102
|
+
const results = [];
|
|
103
|
+
// Synchronous checks
|
|
104
|
+
results.push(checkConfigFile());
|
|
105
|
+
results.push(checkApiKey());
|
|
106
|
+
results.push(checkEnvironment());
|
|
107
|
+
// Async check — server reachability
|
|
108
|
+
results.push(await checkReachability());
|
|
109
|
+
// More synchronous checks
|
|
110
|
+
results.push(checkNodeVersion());
|
|
111
|
+
results.push(checkCliVersion());
|
|
112
|
+
// Print results
|
|
113
|
+
console.log('');
|
|
114
|
+
for (const r of results) {
|
|
115
|
+
console.log(formatCheck(r));
|
|
116
|
+
}
|
|
117
|
+
console.log('');
|
|
118
|
+
// Exit with non-zero if any check failed
|
|
119
|
+
const hasFail = results.some((r) => r.status === 'fail');
|
|
120
|
+
if (hasFail) {
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.test.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for pb doctor — health check command.
|
|
3
|
+
* WP-301 Slice 2.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
// We test runDoctor by capturing its console output and process.exit calls.
|
|
7
|
+
// Mock dependencies before importing the module under test.
|
|
8
|
+
// Mock config — controls HOME_ENV_PATH export and env state
|
|
9
|
+
const mockHomeEnvPath = '/mock/.config/productbrain/.env';
|
|
10
|
+
vi.mock('../lib/config.js', () => ({
|
|
11
|
+
HOME_ENV_PATH: mockHomeEnvPath,
|
|
12
|
+
}));
|
|
13
|
+
// Mock client — controls reachability
|
|
14
|
+
const mockMcpCall = vi.fn();
|
|
15
|
+
vi.mock('../lib/client.js', () => ({
|
|
16
|
+
mcpCall: (...args) => mockMcpCall(...args),
|
|
17
|
+
}));
|
|
18
|
+
// Mock existsSync for config file check
|
|
19
|
+
vi.mock('node:fs', async () => {
|
|
20
|
+
const actual = await vi.importActual('node:fs');
|
|
21
|
+
return {
|
|
22
|
+
...actual,
|
|
23
|
+
existsSync: vi.fn((path) => {
|
|
24
|
+
if (path === mockHomeEnvPath)
|
|
25
|
+
return mockConfigFileExists;
|
|
26
|
+
return actual.existsSync(path);
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
let mockConfigFileExists = true;
|
|
31
|
+
describe('runDoctor', () => {
|
|
32
|
+
let consoleLogSpy;
|
|
33
|
+
let processExitSpy;
|
|
34
|
+
const originalEnv = { ...process.env };
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
37
|
+
processExitSpy = vi.spyOn(process, 'exit').mockImplementation((() => { }));
|
|
38
|
+
mockConfigFileExists = true;
|
|
39
|
+
// Set valid env defaults
|
|
40
|
+
process.env.PRODUCTBRAIN_API_KEY = 'pb_sk_test1234abcd';
|
|
41
|
+
process.env.CONVEX_SITE_URL = 'https://trustworthy-kangaroo-277.convex.site';
|
|
42
|
+
delete process.env.PB_ENV;
|
|
43
|
+
});
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
vi.restoreAllMocks();
|
|
46
|
+
// Restore original env
|
|
47
|
+
process.env = { ...originalEnv };
|
|
48
|
+
});
|
|
49
|
+
it('all checks pass with valid config and reachable server', async () => {
|
|
50
|
+
mockMcpCall.mockResolvedValueOnce({ ok: true });
|
|
51
|
+
const { runDoctor } = await import('./doctor.js');
|
|
52
|
+
await runDoctor();
|
|
53
|
+
const output = consoleLogSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
54
|
+
// All six checks should show pass symbol
|
|
55
|
+
expect(output).toContain('Config file');
|
|
56
|
+
expect(output).toContain(mockHomeEnvPath);
|
|
57
|
+
expect(output).toContain('API key');
|
|
58
|
+
expect(output).toContain('pb_sk_te****');
|
|
59
|
+
expect(output).toContain('Environment');
|
|
60
|
+
expect(output).toContain('production (default)');
|
|
61
|
+
expect(output).toContain('Reachability');
|
|
62
|
+
expect(output).toContain('OK (');
|
|
63
|
+
expect(output).toContain('Node');
|
|
64
|
+
expect(output).toContain(process.version);
|
|
65
|
+
expect(output).toContain('CLI');
|
|
66
|
+
// Should NOT exit with failure
|
|
67
|
+
expect(processExitSpy).not.toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
it('reports missing API key', async () => {
|
|
70
|
+
delete process.env.PRODUCTBRAIN_API_KEY;
|
|
71
|
+
mockMcpCall.mockRejectedValueOnce(new Error('No API key'));
|
|
72
|
+
const { runDoctor } = await import('./doctor.js');
|
|
73
|
+
await runDoctor();
|
|
74
|
+
const output = consoleLogSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
75
|
+
expect(output).toContain('Not set');
|
|
76
|
+
expect(output).toContain('pb login');
|
|
77
|
+
// Should exit 1 because API key check failed
|
|
78
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
79
|
+
});
|
|
80
|
+
it('reports unreachable server', async () => {
|
|
81
|
+
mockMcpCall.mockRejectedValueOnce(new Error('fetch failed'));
|
|
82
|
+
const { runDoctor } = await import('./doctor.js');
|
|
83
|
+
await runDoctor();
|
|
84
|
+
const output = consoleLogSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
85
|
+
expect(output).toContain('Reachability');
|
|
86
|
+
expect(output).toContain('fetch failed');
|
|
87
|
+
// Should exit 1 because reachability failed
|
|
88
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
89
|
+
});
|
|
90
|
+
it('reports missing config file', async () => {
|
|
91
|
+
mockConfigFileExists = false;
|
|
92
|
+
mockMcpCall.mockResolvedValueOnce({ ok: true });
|
|
93
|
+
const { runDoctor } = await import('./doctor.js');
|
|
94
|
+
await runDoctor();
|
|
95
|
+
const output = consoleLogSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
96
|
+
expect(output).toContain('Not found');
|
|
97
|
+
expect(output).toContain(mockHomeEnvPath);
|
|
98
|
+
// Should exit 1 because config file check failed
|
|
99
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
//# sourceMappingURL=doctor.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.test.js","sourceRoot":"","sources":["../../src/commands/doctor.test.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/login.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* pb login — save API key to ~/.config/productbrain/.env so pb works from any directory.
|
|
3
|
+
*
|
|
4
|
+
* Validates the key against the server before saving. On auth failure, shows
|
|
5
|
+
* error + signup URL and does NOT save. On network/timeout, warns but saves
|
|
6
|
+
* (don't block offline users).
|
|
3
7
|
*/
|
|
4
8
|
export declare function runLogin(): Promise<void>;
|
|
5
9
|
//# sourceMappingURL=login.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAiCH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAyD9C"}
|
package/dist/commands/login.js
CHANGED
|
@@ -1,53 +1,79 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* pb login — save API key to ~/.config/productbrain/.env so pb works from any directory.
|
|
3
|
+
*
|
|
4
|
+
* Validates the key against the server before saving. On auth failure, shows
|
|
5
|
+
* error + signup URL and does NOT save. On network/timeout, warns but saves
|
|
6
|
+
* (don't block offline users).
|
|
3
7
|
*/
|
|
4
8
|
import { createInterface } from 'readline';
|
|
5
9
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
6
|
-
import { HOME_CONFIG_DIR, HOME_ENV_PATH
|
|
7
|
-
|
|
10
|
+
import { HOME_CONFIG_DIR, HOME_ENV_PATH } from '../lib/config.js';
|
|
11
|
+
import { DEFAULT_SITE_URL, resolveAppUrl } from '../lib/constants.js';
|
|
12
|
+
import { validateKey } from '../lib/client.js';
|
|
13
|
+
const SIGNUP_URL = resolveAppUrl();
|
|
8
14
|
function question(rl, prompt) {
|
|
9
15
|
return new Promise((resolve) => {
|
|
10
16
|
rl.question(prompt, (answer) => resolve((answer ?? '').trim()));
|
|
11
17
|
});
|
|
12
18
|
}
|
|
13
|
-
|
|
14
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
15
|
-
console.log('Product Brain — sign in with your API key');
|
|
16
|
-
console.log(' Get a key: Product Brain app → Settings → API Keys\n');
|
|
17
|
-
let apiKey = await question(rl, 'Paste your API key (pb_sk_...): ');
|
|
18
|
-
if (!apiKey) {
|
|
19
|
-
console.error('No key entered. Exiting.');
|
|
20
|
-
rl.close();
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
if (!apiKey.startsWith('pb_sk_')) {
|
|
24
|
-
console.error('Key must start with pb_sk_. Get one from Product Brain → Settings → API Keys.');
|
|
25
|
-
rl.close();
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
const siteUrl = await question(rl, `API base URL (Enter for default): `);
|
|
29
|
-
rl.close();
|
|
30
|
-
const url = siteUrl || DEFAULT_SITE_URL;
|
|
19
|
+
function saveKey(apiKey, siteUrl) {
|
|
31
20
|
const content = [
|
|
32
21
|
`# Product Brain CLI — saved by "pb login"`,
|
|
33
22
|
`# Edit or delete this file to change or remove your key.`,
|
|
34
23
|
``,
|
|
35
24
|
`PRODUCTBRAIN_API_KEY=${apiKey}`,
|
|
36
|
-
`CONVEX_SITE_URL=${
|
|
25
|
+
`CONVEX_SITE_URL=${siteUrl}`,
|
|
37
26
|
``,
|
|
38
27
|
].join('\n');
|
|
39
28
|
mkdirSync(HOME_CONFIG_DIR, { recursive: true });
|
|
40
29
|
writeFileSync(HOME_ENV_PATH, content, { mode: 0o600 });
|
|
41
30
|
process.env.PRODUCTBRAIN_API_KEY = apiKey;
|
|
42
|
-
process.env.CONVEX_SITE_URL =
|
|
43
|
-
|
|
44
|
-
|
|
31
|
+
process.env.CONVEX_SITE_URL = siteUrl;
|
|
32
|
+
}
|
|
33
|
+
export async function runLogin() {
|
|
34
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
35
|
+
console.log('Product Brain — sign in with your API key');
|
|
36
|
+
console.log(` Get a key: ${SIGNUP_URL} → Settings → API Keys\n`);
|
|
37
|
+
const apiKey = await question(rl, 'Paste your API key (pb_sk_...): ');
|
|
38
|
+
rl.close();
|
|
39
|
+
if (!apiKey) {
|
|
40
|
+
console.error('No key entered. Exiting.');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (!apiKey.startsWith('pb_sk_')) {
|
|
45
|
+
console.error(`Key must start with pb_sk_. Get one from ${SIGNUP_URL} → Settings → API Keys.`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// Use CONVEX_SITE_URL env var if set (advanced users), otherwise default
|
|
50
|
+
const siteUrl = (process.env.CONVEX_SITE_URL || DEFAULT_SITE_URL).replace(/\/$/, '');
|
|
51
|
+
// Validate key against the server before saving
|
|
52
|
+
process.stdout.write('Validating key...');
|
|
45
53
|
try {
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
const result = await validateKey(apiKey, siteUrl);
|
|
55
|
+
if (result.valid) {
|
|
56
|
+
process.stdout.write(' connected.\n\n');
|
|
57
|
+
saveKey(apiKey, siteUrl);
|
|
58
|
+
console.log(`Saved to ${HOME_ENV_PATH}`);
|
|
59
|
+
console.log('You can run pb from any directory now. Try: pb orient -b\n');
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
process.stdout.write(' failed.\n\n');
|
|
63
|
+
console.error(`Invalid key: ${result.error}`);
|
|
64
|
+
console.error(`Get a valid key: ${SIGNUP_URL} → Settings → API Keys.`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
48
68
|
}
|
|
49
69
|
catch {
|
|
50
|
-
//
|
|
70
|
+
// Network error or timeout — save anyway, don't block offline users
|
|
71
|
+
process.stdout.write(' could not reach server.\n\n');
|
|
72
|
+
console.warn('Could not reach server — key saved but not verified.\n' +
|
|
73
|
+
'Run `pb doctor` to check connectivity later.\n');
|
|
74
|
+
saveKey(apiKey, siteUrl);
|
|
75
|
+
console.log(`Saved to ${HOME_ENV_PATH}`);
|
|
76
|
+
console.log('You can run pb from any directory now. Try: pb orient -b\n');
|
|
51
77
|
}
|
|
52
78
|
}
|
|
53
79
|
//# sourceMappingURL=login.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pb setup — guided first-time onboarding: npm install → first capture.
|
|
3
|
+
* WP-301 Slice 3. Each step emits telemetry (STD-155 event names).
|
|
4
|
+
*
|
|
5
|
+
* Flow:
|
|
6
|
+
* 0. Check existing config → offer health check or continue to login
|
|
7
|
+
* 1. Account check → signup URL or login inline
|
|
8
|
+
* 2. Workspace binding confirmation
|
|
9
|
+
* 3. Guided first capture (starts a session, captures via MCP)
|
|
10
|
+
* 4. Summary with next-step hint
|
|
11
|
+
*
|
|
12
|
+
* Uses readline for interactive prompts (same pattern as login.ts).
|
|
13
|
+
* No new npm dependencies.
|
|
14
|
+
*/
|
|
15
|
+
export declare function runSetup(): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAyCH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAQ9C"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pb setup — guided first-time onboarding: npm install → first capture.
|
|
3
|
+
* WP-301 Slice 3. Each step emits telemetry (STD-155 event names).
|
|
4
|
+
*
|
|
5
|
+
* Flow:
|
|
6
|
+
* 0. Check existing config → offer health check or continue to login
|
|
7
|
+
* 1. Account check → signup URL or login inline
|
|
8
|
+
* 2. Workspace binding confirmation
|
|
9
|
+
* 3. Guided first capture (starts a session, captures via MCP)
|
|
10
|
+
* 4. Summary with next-step hint
|
|
11
|
+
*
|
|
12
|
+
* Uses readline for interactive prompts (same pattern as login.ts).
|
|
13
|
+
* No new npm dependencies.
|
|
14
|
+
*/
|
|
15
|
+
import { createInterface } from 'readline';
|
|
16
|
+
import { getConfig } from '../lib/config.js';
|
|
17
|
+
import { mcpCall, mcpCallWithSession } from '../lib/client.js';
|
|
18
|
+
import { readSession, writeSession } from '../lib/session.js';
|
|
19
|
+
import { trackEvent } from '../lib/telemetry.js';
|
|
20
|
+
import { runLogin } from './login.js';
|
|
21
|
+
/** Prompt helper — same pattern as login.ts. */
|
|
22
|
+
function question(rl, prompt) {
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
rl.question(prompt, (answer) => resolve((answer ?? '').trim()));
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/** Mask an API key for display: show prefix + last 4 chars. */
|
|
28
|
+
function maskKey(key) {
|
|
29
|
+
if (key.length <= 10)
|
|
30
|
+
return key.slice(0, 6) + '****';
|
|
31
|
+
return key.slice(0, 6) + '...' + key.slice(-4);
|
|
32
|
+
}
|
|
33
|
+
export async function runSetup() {
|
|
34
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
35
|
+
try {
|
|
36
|
+
await runSetupFlow(rl);
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
rl.close();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function runSetupFlow(rl) {
|
|
43
|
+
trackEvent('setup_started');
|
|
44
|
+
console.log('');
|
|
45
|
+
console.log('Product Brain — Setup');
|
|
46
|
+
console.log('=====================');
|
|
47
|
+
console.log('');
|
|
48
|
+
// Step 1: Check existing config
|
|
49
|
+
let hasValidConfig = false;
|
|
50
|
+
try {
|
|
51
|
+
const config = getConfig();
|
|
52
|
+
hasValidConfig = true;
|
|
53
|
+
console.log(`Already configured: ${maskKey(config.apiKey)}`);
|
|
54
|
+
console.log(`API endpoint: ${config.siteUrl}`);
|
|
55
|
+
console.log('');
|
|
56
|
+
// Verify workspace connectivity
|
|
57
|
+
try {
|
|
58
|
+
const workspace = await mcpCall('resolveWorkspace', {});
|
|
59
|
+
if (workspace?.name) {
|
|
60
|
+
console.log(`Connected to workspace: ${workspace.name}`);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
console.log('Workspace connection verified.');
|
|
64
|
+
}
|
|
65
|
+
trackEvent('workspace_bound');
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
console.log('Warning: Could not verify workspace connection. Your key may be invalid.');
|
|
69
|
+
console.log('Run `pb login` to re-enter your API key.');
|
|
70
|
+
}
|
|
71
|
+
console.log('');
|
|
72
|
+
const runCheck = await question(rl, 'Skip to first capture? (y/n): ');
|
|
73
|
+
if (runCheck.toLowerCase() !== 'y' && runCheck.toLowerCase() !== 'yes') {
|
|
74
|
+
console.log('');
|
|
75
|
+
console.log('Setup complete. Try: pb orient -b');
|
|
76
|
+
trackEvent('setup_completed');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// No valid config — continue to login
|
|
82
|
+
}
|
|
83
|
+
// Step 2: Account check + login (only if no valid config)
|
|
84
|
+
if (!hasValidConfig) {
|
|
85
|
+
console.log('To get started, you need a Product Brain account and API key.');
|
|
86
|
+
console.log('');
|
|
87
|
+
const hasAccount = await question(rl, 'Do you have a Product Brain account? (y/n): ');
|
|
88
|
+
if (hasAccount.toLowerCase() !== 'y' && hasAccount.toLowerCase() !== 'yes') {
|
|
89
|
+
console.log('');
|
|
90
|
+
console.log('Create an account at: https://productbrain.io');
|
|
91
|
+
console.log('Then come back and run: pb setup');
|
|
92
|
+
console.log('');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Run login flow — this handles key prompt, validation, and saving
|
|
96
|
+
console.log('');
|
|
97
|
+
rl.close(); // Close our rl so login.ts can create its own
|
|
98
|
+
await runLogin();
|
|
99
|
+
trackEvent('key_validated');
|
|
100
|
+
// Re-open rl for remaining prompts
|
|
101
|
+
const newRl = createInterface({ input: process.stdin, output: process.stdout });
|
|
102
|
+
try {
|
|
103
|
+
await runSetupPostLogin(newRl);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
newRl.close();
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// If we already had config, go straight to first capture
|
|
111
|
+
await runFirstCapture(rl);
|
|
112
|
+
}
|
|
113
|
+
async function runSetupPostLogin(rl) {
|
|
114
|
+
// Step 3: Workspace binding — the API key already binds to a workspace
|
|
115
|
+
console.log('');
|
|
116
|
+
try {
|
|
117
|
+
const workspace = await mcpCall('resolveWorkspace', {});
|
|
118
|
+
if (workspace?.name) {
|
|
119
|
+
console.log(`Connected to workspace: ${workspace.name}`);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
console.log('Workspace connection verified.');
|
|
123
|
+
}
|
|
124
|
+
trackEvent('workspace_bound');
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
console.log(`Warning: Could not verify workspace: ${err instanceof Error ? err.message : String(err)}`);
|
|
128
|
+
console.log('You can still try commands. Run `pb orient -b` to test.');
|
|
129
|
+
}
|
|
130
|
+
// Step 4: First capture
|
|
131
|
+
await runFirstCapture(rl);
|
|
132
|
+
}
|
|
133
|
+
async function runFirstCapture(rl) {
|
|
134
|
+
console.log('');
|
|
135
|
+
const wantCapture = await question(rl, 'Ready to capture your first piece of knowledge? (y/n): ');
|
|
136
|
+
trackEvent('first_capture_prompted');
|
|
137
|
+
if (wantCapture.toLowerCase() !== 'y' && wantCapture.toLowerCase() !== 'yes') {
|
|
138
|
+
console.log('');
|
|
139
|
+
console.log('Setup complete! Try: pb orient -b');
|
|
140
|
+
trackEvent('setup_completed');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Start a session if none exists
|
|
144
|
+
let session = readSession();
|
|
145
|
+
if (!session) {
|
|
146
|
+
try {
|
|
147
|
+
const workspace = await mcpCall('resolveWorkspace', {});
|
|
148
|
+
if (!workspace?.keyId) {
|
|
149
|
+
console.log('Your API key is read-only. You need a readwrite key for captures.');
|
|
150
|
+
console.log('Generate one: Product Brain app > Settings > API Keys.');
|
|
151
|
+
trackEvent('setup_completed');
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const result = await mcpCall('agent.startSession', {
|
|
155
|
+
workspaceId: workspace._id,
|
|
156
|
+
apiKeyId: workspace.keyId,
|
|
157
|
+
clientKind: 'cli',
|
|
158
|
+
});
|
|
159
|
+
session = {
|
|
160
|
+
sessionId: result.sessionId,
|
|
161
|
+
workspaceId: workspace._id,
|
|
162
|
+
workspaceName: result.workspaceName,
|
|
163
|
+
startedAt: new Date().toISOString(),
|
|
164
|
+
entriesCaptured: [],
|
|
165
|
+
};
|
|
166
|
+
writeSession(session);
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
console.log(`Could not start session: ${err instanceof Error ? err.message : String(err)}`);
|
|
170
|
+
console.log('You can start a session manually: pb session start');
|
|
171
|
+
trackEvent('setup_completed');
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Prompt for capture text
|
|
176
|
+
console.log('');
|
|
177
|
+
console.log('Type a short insight, decision, or tension:');
|
|
178
|
+
const captureText = await question(rl, '> ');
|
|
179
|
+
if (!captureText) {
|
|
180
|
+
console.log('No text entered. You can capture later: pb capture "your insight here"');
|
|
181
|
+
console.log('');
|
|
182
|
+
console.log('Setup complete! Try: pb orient -b');
|
|
183
|
+
trackEvent('setup_completed');
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// Capture via MCP — use mcpCallWithSession which reads the session file
|
|
187
|
+
try {
|
|
188
|
+
const result = await mcpCallWithSession('chain.createEntry', {
|
|
189
|
+
collectionSlug: 'insights',
|
|
190
|
+
name: captureText,
|
|
191
|
+
status: 'draft',
|
|
192
|
+
data: { description: captureText },
|
|
193
|
+
sessionId: session.sessionId,
|
|
194
|
+
createdBy: `agent:${session.sessionId}`,
|
|
195
|
+
});
|
|
196
|
+
console.log('');
|
|
197
|
+
console.log(`Captured: ${result.entryId} (draft)`);
|
|
198
|
+
console.log('Your knowledge is on the Chain. Review it in Product Brain.');
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
console.log(`Capture failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
202
|
+
console.log('You can try again: pb capture "your insight here"');
|
|
203
|
+
}
|
|
204
|
+
// Step 5: Summary
|
|
205
|
+
console.log('');
|
|
206
|
+
console.log('Setup complete! Next steps:');
|
|
207
|
+
console.log(' pb orient -b See your workspace at a glance');
|
|
208
|
+
console.log(' pb capture "..." Capture more knowledge');
|
|
209
|
+
console.log(' pb session close End your session when done');
|
|
210
|
+
console.log('');
|
|
211
|
+
trackEvent('setup_completed');
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=setup.js.map
|