@productbrain/cli 0.1.0-beta.29 → 0.1.0-beta.32

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 (106) hide show
  1. package/README.md +62 -178
  2. package/dist/__tests__/constants.test.d.ts +2 -0
  3. package/dist/__tests__/constants.test.d.ts.map +1 -0
  4. package/dist/__tests__/constants.test.js +94 -0
  5. package/dist/__tests__/constants.test.js.map +1 -0
  6. package/dist/__tests__/errors.test.d.ts +2 -0
  7. package/dist/__tests__/errors.test.d.ts.map +1 -0
  8. package/dist/__tests__/errors.test.js +117 -0
  9. package/dist/__tests__/errors.test.js.map +1 -0
  10. package/dist/__tests__/glossary.test.d.ts +2 -0
  11. package/dist/__tests__/glossary.test.d.ts.map +1 -0
  12. package/dist/__tests__/glossary.test.js +32 -0
  13. package/dist/__tests__/glossary.test.js.map +1 -0
  14. package/dist/__tests__/login.test.d.ts +2 -0
  15. package/dist/__tests__/login.test.d.ts.map +1 -0
  16. package/dist/__tests__/login.test.js +168 -0
  17. package/dist/__tests__/login.test.js.map +1 -0
  18. package/dist/__tests__/profiles.test.d.ts +2 -0
  19. package/dist/__tests__/profiles.test.d.ts.map +1 -0
  20. package/dist/__tests__/profiles.test.js +168 -0
  21. package/dist/__tests__/profiles.test.js.map +1 -0
  22. package/dist/__tests__/setup.test.d.ts +2 -0
  23. package/dist/__tests__/setup.test.d.ts.map +1 -0
  24. package/dist/__tests__/setup.test.js +170 -0
  25. package/dist/__tests__/setup.test.js.map +1 -0
  26. package/dist/commands/capture.d.ts.map +1 -1
  27. package/dist/commands/capture.js +23 -2
  28. package/dist/commands/capture.js.map +1 -1
  29. package/dist/commands/doctor.d.ts +18 -0
  30. package/dist/commands/doctor.d.ts.map +1 -0
  31. package/dist/commands/doctor.js +211 -0
  32. package/dist/commands/doctor.js.map +1 -0
  33. package/dist/commands/doctor.test.d.ts +7 -0
  34. package/dist/commands/doctor.test.d.ts.map +1 -0
  35. package/dist/commands/doctor.test.js +265 -0
  36. package/dist/commands/doctor.test.js.map +1 -0
  37. package/dist/commands/login.d.ts +4 -0
  38. package/dist/commands/login.d.ts.map +1 -1
  39. package/dist/commands/login.js +53 -27
  40. package/dist/commands/login.js.map +1 -1
  41. package/dist/commands/profile.d.ts +24 -0
  42. package/dist/commands/profile.d.ts.map +1 -0
  43. package/dist/commands/profile.js +82 -0
  44. package/dist/commands/profile.js.map +1 -0
  45. package/dist/commands/promote.d.ts.map +1 -1
  46. package/dist/commands/promote.js +3 -2
  47. package/dist/commands/promote.js.map +1 -1
  48. package/dist/commands/setup.d.ts +16 -0
  49. package/dist/commands/setup.d.ts.map +1 -0
  50. package/dist/commands/setup.js +213 -0
  51. package/dist/commands/setup.js.map +1 -0
  52. package/dist/formatters/promote.d.ts +1 -0
  53. package/dist/formatters/promote.d.ts.map +1 -1
  54. package/dist/formatters/promote.js +1 -0
  55. package/dist/formatters/promote.js.map +1 -1
  56. package/dist/index.js +251 -284
  57. package/dist/index.js.map +1 -1
  58. package/dist/lib/activation.d.ts +28 -0
  59. package/dist/lib/activation.d.ts.map +1 -0
  60. package/dist/lib/activation.js +57 -0
  61. package/dist/lib/activation.js.map +1 -0
  62. package/dist/lib/activation.test.d.ts +6 -0
  63. package/dist/lib/activation.test.d.ts.map +1 -0
  64. package/dist/lib/activation.test.js +121 -0
  65. package/dist/lib/activation.test.js.map +1 -0
  66. package/dist/lib/client.d.ts +19 -2
  67. package/dist/lib/client.d.ts.map +1 -1
  68. package/dist/lib/client.js +71 -11
  69. package/dist/lib/client.js.map +1 -1
  70. package/dist/lib/config.d.ts +9 -3
  71. package/dist/lib/config.d.ts.map +1 -1
  72. package/dist/lib/config.js +54 -15
  73. package/dist/lib/config.js.map +1 -1
  74. package/dist/lib/constants.d.ts +21 -0
  75. package/dist/lib/constants.d.ts.map +1 -0
  76. package/dist/lib/constants.js +39 -0
  77. package/dist/lib/constants.js.map +1 -0
  78. package/dist/lib/errors.d.ts +57 -0
  79. package/dist/lib/errors.d.ts.map +1 -0
  80. package/dist/lib/errors.js +65 -0
  81. package/dist/lib/errors.js.map +1 -0
  82. package/dist/lib/glossary.d.ts +19 -0
  83. package/dist/lib/glossary.d.ts.map +1 -0
  84. package/dist/lib/glossary.js +53 -0
  85. package/dist/lib/glossary.js.map +1 -0
  86. package/dist/lib/profiles.d.ts +34 -0
  87. package/dist/lib/profiles.d.ts.map +1 -0
  88. package/dist/lib/profiles.js +173 -0
  89. package/dist/lib/profiles.js.map +1 -0
  90. package/dist/lib/runner.d.ts +2 -0
  91. package/dist/lib/runner.d.ts.map +1 -1
  92. package/dist/lib/runner.js +33 -4
  93. package/dist/lib/runner.js.map +1 -1
  94. package/dist/lib/style.d.ts +65 -0
  95. package/dist/lib/style.d.ts.map +1 -0
  96. package/dist/lib/style.js +108 -0
  97. package/dist/lib/style.js.map +1 -0
  98. package/dist/lib/style.test.d.ts +7 -0
  99. package/dist/lib/style.test.d.ts.map +1 -0
  100. package/dist/lib/style.test.js +195 -0
  101. package/dist/lib/style.test.js.map +1 -0
  102. package/dist/lib/telemetry.d.ts +15 -0
  103. package/dist/lib/telemetry.d.ts.map +1 -0
  104. package/dist/lib/telemetry.js +29 -0
  105. package/dist/lib/telemetry.js.map +1 -0
  106. package/package.json +1 -1
@@ -0,0 +1,168 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ // ---------------------------------------------------------------------------
3
+ // Mocks
4
+ // ---------------------------------------------------------------------------
5
+ const validateKeyMock = vi.fn();
6
+ vi.mock('../lib/client.js', () => ({
7
+ validateKey: (...args) => validateKeyMock(...args),
8
+ }));
9
+ vi.mock('../lib/config.js', () => ({
10
+ HOME_CONFIG_DIR: '/tmp/test-pb-config',
11
+ HOME_ENV_PATH: '/tmp/test-pb-config/.env',
12
+ }));
13
+ const mkdirSyncMock = vi.fn();
14
+ const writeFileSyncMock = vi.fn();
15
+ vi.mock('fs', () => ({
16
+ mkdirSync: (...args) => mkdirSyncMock(...args),
17
+ writeFileSync: (...args) => writeFileSyncMock(...args),
18
+ existsSync: vi.fn(() => false),
19
+ readFileSync: vi.fn(() => ''),
20
+ }));
21
+ // Mock readline — simulate user pasting a key
22
+ let readlineAnswer = '';
23
+ vi.mock('readline', () => ({
24
+ createInterface: () => ({
25
+ question: (_prompt, cb) => cb(readlineAnswer),
26
+ close: vi.fn(),
27
+ }),
28
+ }));
29
+ // Capture console/process output
30
+ const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
31
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
32
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
33
+ const stdoutWriteSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
34
+ // Track process.exit calls without throwing (so try/catch in login.ts doesn't catch it)
35
+ let exitCode;
36
+ vi.spyOn(process, 'exit').mockImplementation((code) => {
37
+ exitCode = code;
38
+ // Return undefined as never — the function won't actually continue
39
+ // but the test can check exitCode after the call
40
+ return undefined;
41
+ });
42
+ // ---------------------------------------------------------------------------
43
+ // Tests
44
+ // ---------------------------------------------------------------------------
45
+ describe('pb login — key validation', () => {
46
+ beforeEach(() => {
47
+ vi.clearAllMocks();
48
+ readlineAnswer = '';
49
+ exitCode = undefined;
50
+ delete process.env.CONVEX_SITE_URL;
51
+ });
52
+ it('saves key on successful validation', async () => {
53
+ readlineAnswer = 'pb_sk_valid_key_123';
54
+ validateKeyMock.mockResolvedValue({ valid: true, workspaceId: 'ws-123' });
55
+ const { runLogin } = await import('../commands/login.js');
56
+ await runLogin();
57
+ expect(validateKeyMock).toHaveBeenCalledWith('pb_sk_valid_key_123', 'https://gateway.productbrain.io');
58
+ expect(writeFileSyncMock).toHaveBeenCalledTimes(1);
59
+ expect(writeFileSyncMock.mock.calls[0][1]).toContain('PRODUCTBRAIN_API_KEY=pb_sk_valid_key_123');
60
+ expect(stdoutWriteSpy).toHaveBeenCalledWith(' connected.\n\n');
61
+ expect(exitCode).toBeUndefined();
62
+ });
63
+ it('rejects invalid key (401) without saving', async () => {
64
+ readlineAnswer = 'pb_sk_bad_key';
65
+ validateKeyMock.mockResolvedValue({ valid: false, error: 'Invalid API key.' });
66
+ const { runLogin } = await import('../commands/login.js');
67
+ await runLogin();
68
+ expect(writeFileSyncMock).not.toHaveBeenCalled();
69
+ expect(exitCode).toBe(1);
70
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Invalid key'));
71
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('work.productbrain.io'));
72
+ });
73
+ it('saves key on network timeout with warning', async () => {
74
+ readlineAnswer = 'pb_sk_offline_key';
75
+ validateKeyMock.mockRejectedValue(new Error('fetch failed'));
76
+ const { runLogin } = await import('../commands/login.js');
77
+ await runLogin();
78
+ expect(writeFileSyncMock).toHaveBeenCalledTimes(1);
79
+ expect(writeFileSyncMock.mock.calls[0][1]).toContain('PRODUCTBRAIN_API_KEY=pb_sk_offline_key');
80
+ expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('Could not reach server'));
81
+ expect(exitCode).toBeUndefined();
82
+ });
83
+ it('exits on empty key', async () => {
84
+ readlineAnswer = '';
85
+ const { runLogin } = await import('../commands/login.js');
86
+ await runLogin();
87
+ expect(exitCode).toBe(1);
88
+ expect(validateKeyMock).not.toHaveBeenCalled();
89
+ expect(writeFileSyncMock).not.toHaveBeenCalled();
90
+ });
91
+ it('exits on key without pb_sk_ prefix', async () => {
92
+ readlineAnswer = 'sk_not_a_pb_key';
93
+ const { runLogin } = await import('../commands/login.js');
94
+ await runLogin();
95
+ expect(exitCode).toBe(1);
96
+ expect(validateKeyMock).not.toHaveBeenCalled();
97
+ expect(writeFileSyncMock).not.toHaveBeenCalled();
98
+ });
99
+ it('shows signup URL in error message', async () => {
100
+ readlineAnswer = 'pb_sk_bad';
101
+ validateKeyMock.mockResolvedValue({ valid: false, error: 'Invalid API key.' });
102
+ const { runLogin } = await import('../commands/login.js');
103
+ await runLogin();
104
+ // Signup URL should appear in the error output
105
+ const allErrorCalls = consoleErrorSpy.mock.calls.flat().join(' ');
106
+ expect(allErrorCalls).toContain('work.productbrain.io');
107
+ });
108
+ });
109
+ describe('validateKey()', () => {
110
+ const originalFetch = globalThis.fetch;
111
+ beforeEach(() => {
112
+ vi.clearAllMocks();
113
+ });
114
+ afterEach(() => {
115
+ globalThis.fetch = originalFetch;
116
+ });
117
+ it('returns valid on 200 with workspace data', async () => {
118
+ globalThis.fetch = vi.fn().mockResolvedValue({
119
+ ok: true,
120
+ status: 200,
121
+ json: () => Promise.resolve({ data: { _id: 'ws-abc' } }),
122
+ });
123
+ const mod = await vi.importActual('../lib/client.js');
124
+ const result = await mod.validateKey('pb_sk_test', 'https://test.convex.site');
125
+ expect(result).toEqual({ valid: true, workspaceId: 'ws-abc' });
126
+ });
127
+ it('returns invalid on 401', async () => {
128
+ globalThis.fetch = vi.fn().mockResolvedValue({
129
+ ok: false,
130
+ status: 401,
131
+ json: () => Promise.resolve({ error: 'Unauthorized' }),
132
+ });
133
+ const mod = await vi.importActual('../lib/client.js');
134
+ const result = await mod.validateKey('pb_sk_bad', 'https://test.convex.site');
135
+ expect(result).toEqual({ valid: false, error: 'Invalid API key.' });
136
+ });
137
+ it('returns invalid on 403', async () => {
138
+ globalThis.fetch = vi.fn().mockResolvedValue({
139
+ ok: false,
140
+ status: 403,
141
+ json: () => Promise.resolve({ error: 'Forbidden' }),
142
+ });
143
+ const mod = await vi.importActual('../lib/client.js');
144
+ const result = await mod.validateKey('pb_sk_bad', 'https://test.convex.site');
145
+ expect(result).toEqual({ valid: false, error: 'Invalid API key.' });
146
+ });
147
+ it('returns invalid on server error with message', async () => {
148
+ globalThis.fetch = vi.fn().mockResolvedValue({
149
+ ok: false,
150
+ status: 500,
151
+ json: () => Promise.resolve({ error: 'Internal server error' }),
152
+ });
153
+ const mod = await vi.importActual('../lib/client.js');
154
+ const result = await mod.validateKey('pb_sk_test', 'https://test.convex.site');
155
+ expect(result).toEqual({ valid: false, error: 'Internal server error' });
156
+ });
157
+ it('throws on network error (fetch failed)', async () => {
158
+ globalThis.fetch = vi.fn().mockRejectedValue(new Error('fetch failed'));
159
+ const mod = await vi.importActual('../lib/client.js');
160
+ await expect(mod.validateKey('pb_sk_test', 'https://test.convex.site')).rejects.toThrow('fetch failed');
161
+ });
162
+ it('throws on timeout (abort)', async () => {
163
+ globalThis.fetch = vi.fn().mockImplementation(() => new Promise((_, reject) => setTimeout(() => reject(new Error('AbortError')), 100)));
164
+ const mod = await vi.importActual('../lib/client.js');
165
+ await expect(mod.validateKey('pb_sk_test', 'https://test.convex.site', 50)).rejects.toThrow();
166
+ });
167
+ });
168
+ //# sourceMappingURL=login.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.test.js","sourceRoot":"","sources":["../../src/__tests__/login.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=profiles.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profiles.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/profiles.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Profile system tests — WP-302 Slice 2.
3
+ * Tests: CRUD round-trip, auto-migration, resolution order,
4
+ * cannot-delete-active, session close on switch.
5
+ */
6
+ import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest';
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'fs';
8
+ import { resolve } from 'path';
9
+ // vi.hoisted runs before mock hoisting — safe to compute test paths here
10
+ const { TEST_HOME, TEST_CONFIG_DIR, TEST_PROFILES_DIR, TEST_ACTIVE_PATH, TEST_LEGACY_PATH } = vi.hoisted(() => {
11
+ const os = require('os');
12
+ const path = require('path');
13
+ const home = path.resolve(os.tmpdir(), `pb-test-profiles-${process.pid}`);
14
+ return {
15
+ TEST_HOME: home,
16
+ TEST_CONFIG_DIR: path.resolve(home, '.config', 'productbrain'),
17
+ TEST_PROFILES_DIR: path.resolve(home, '.config', 'productbrain', 'profiles'),
18
+ TEST_ACTIVE_PATH: path.resolve(home, '.config', 'productbrain', 'active-profile'),
19
+ TEST_LEGACY_PATH: path.resolve(home, '.config', 'productbrain', '.env'),
20
+ };
21
+ });
22
+ vi.mock('os', async (importOriginal) => {
23
+ const actual = await importOriginal();
24
+ return { ...actual, homedir: () => TEST_HOME };
25
+ });
26
+ // Mock session to track close-on-switch
27
+ const mockClearSession = vi.fn();
28
+ let mockSessionState = null;
29
+ vi.mock('../lib/session.js', () => ({
30
+ readSession: () => mockSessionState,
31
+ writeSession: vi.fn(),
32
+ clearSession: mockClearSession,
33
+ addCapturedEntry: vi.fn(),
34
+ }));
35
+ // Import after mocks
36
+ import { listProfiles, getActiveProfile, createProfile, useProfile, deleteProfile, resolveProfileConfig, } from '../lib/profiles.js';
37
+ describe('profiles', () => {
38
+ beforeEach(() => {
39
+ // Clean slate
40
+ if (existsSync(TEST_CONFIG_DIR)) {
41
+ rmSync(TEST_CONFIG_DIR, { recursive: true, force: true });
42
+ }
43
+ mkdirSync(TEST_CONFIG_DIR, { recursive: true });
44
+ delete process.env.PB_PROFILE;
45
+ mockSessionState = null;
46
+ vi.clearAllMocks();
47
+ });
48
+ afterEach(() => {
49
+ if (existsSync(TEST_HOME)) {
50
+ rmSync(TEST_HOME, { recursive: true, force: true });
51
+ }
52
+ });
53
+ describe('CRUD round-trip', () => {
54
+ it('creates, lists, and deletes a profile', () => {
55
+ createProfile('staging', 'pb_sk_test_staging');
56
+ const profiles = listProfiles();
57
+ expect(profiles).toContain('staging');
58
+ // First profile becomes active
59
+ expect(getActiveProfile()).toBe('staging');
60
+ // Create a second profile so we can delete the first
61
+ createProfile('dev', 'pb_sk_test_dev');
62
+ useProfile('dev');
63
+ deleteProfile('staging');
64
+ expect(listProfiles()).not.toContain('staging');
65
+ });
66
+ it('rejects duplicate profile names', () => {
67
+ createProfile('myprofile', 'pb_sk_test');
68
+ expect(() => createProfile('myprofile', 'pb_sk_test2')).toThrow(/already exists/);
69
+ });
70
+ it('rejects invalid profile names', () => {
71
+ expect(() => createProfile('My Profile!', 'pb_sk_test')).toThrow(/Invalid profile name/);
72
+ expect(() => createProfile('', 'pb_sk_test')).toThrow(/Invalid profile name/);
73
+ expect(() => createProfile('-bad', 'pb_sk_test')).toThrow(/Invalid profile name/);
74
+ });
75
+ });
76
+ describe('auto-migration from legacy .env', () => {
77
+ it('migrates legacy .env to default profile on first resolveProfileConfig()', () => {
78
+ // Set up legacy .env (no profiles dir yet)
79
+ writeFileSync(TEST_LEGACY_PATH, 'PRODUCTBRAIN_API_KEY=pb_sk_legacy_key\nCONVEX_SITE_URL=https://test.convex.site\n');
80
+ const config = resolveProfileConfig();
81
+ expect(config).not.toBeNull();
82
+ expect(config.apiKey).toBe('pb_sk_legacy_key');
83
+ // Should have created the default profile
84
+ expect(existsSync(resolve(TEST_PROFILES_DIR, 'default.env'))).toBe(true);
85
+ expect(readFileSync(TEST_ACTIVE_PATH, 'utf8').trim()).toBe('default');
86
+ });
87
+ it('does not re-migrate if profiles dir already exists', () => {
88
+ mkdirSync(TEST_PROFILES_DIR, { recursive: true });
89
+ writeFileSync(TEST_LEGACY_PATH, 'PRODUCTBRAIN_API_KEY=pb_sk_legacy_key\n');
90
+ // Profiles dir exists, so no migration
91
+ const config = resolveProfileConfig();
92
+ // Falls through to legacy .env directly
93
+ expect(config).not.toBeNull();
94
+ expect(config.apiKey).toBe('pb_sk_legacy_key');
95
+ // Should NOT have created a default profile
96
+ expect(existsSync(resolve(TEST_PROFILES_DIR, 'default.env'))).toBe(false);
97
+ });
98
+ });
99
+ describe('resolution order', () => {
100
+ it('PB_PROFILE env var wins over active-profile file', () => {
101
+ createProfile('prod', 'pb_sk_prod_key');
102
+ createProfile('staging', 'pb_sk_staging_key');
103
+ useProfile('prod');
104
+ process.env.PB_PROFILE = 'staging';
105
+ const config = resolveProfileConfig();
106
+ expect(config.apiKey).toBe('pb_sk_staging_key');
107
+ });
108
+ it('active-profile file wins over default', () => {
109
+ createProfile('default', 'pb_sk_default_key');
110
+ createProfile('custom', 'pb_sk_custom_key');
111
+ useProfile('custom');
112
+ const config = resolveProfileConfig();
113
+ expect(config.apiKey).toBe('pb_sk_custom_key');
114
+ });
115
+ it('falls back to default profile when no active-profile file', () => {
116
+ mkdirSync(TEST_PROFILES_DIR, { recursive: true });
117
+ writeFileSync(resolve(TEST_PROFILES_DIR, 'default.env'), 'PRODUCTBRAIN_API_KEY=pb_sk_fallback\n', { mode: 0o600 });
118
+ const config = resolveProfileConfig();
119
+ expect(config.apiKey).toBe('pb_sk_fallback');
120
+ });
121
+ });
122
+ describe('cannot delete active profile', () => {
123
+ it('throws PROFILE_IS_ACTIVE error', () => {
124
+ createProfile('active-one', 'pb_sk_test');
125
+ createProfile('other', 'pb_sk_test2');
126
+ useProfile('active-one');
127
+ expect(() => deleteProfile('active-one')).toThrow(/Cannot delete the active profile/);
128
+ });
129
+ });
130
+ describe('cannot delete last profile', () => {
131
+ it('throws PROFILE_IS_LAST error', () => {
132
+ createProfile('only-one', 'pb_sk_test');
133
+ createProfile('other', 'pb_sk_test2');
134
+ useProfile('other');
135
+ deleteProfile('only-one');
136
+ // Now 'other' is the last profile — PROFILE_IS_LAST fires before PROFILE_IS_ACTIVE
137
+ expect(() => deleteProfile('other')).toThrow(/Cannot delete the last profile/);
138
+ });
139
+ });
140
+ describe('profile use', () => {
141
+ it('switches active profile', () => {
142
+ createProfile('first', 'pb_sk_first');
143
+ createProfile('second', 'pb_sk_second');
144
+ useProfile('second');
145
+ expect(getActiveProfile()).toBe('second');
146
+ useProfile('first');
147
+ expect(getActiveProfile()).toBe('first');
148
+ });
149
+ it('errors on non-existent profile', () => {
150
+ expect(() => useProfile('nonexistent')).toThrow(/does not exist/);
151
+ });
152
+ });
153
+ describe('profile file format', () => {
154
+ it('stores apiKey and optional siteUrl', () => {
155
+ createProfile('with-url', 'pb_sk_test', 'https://custom.convex.site');
156
+ const content = readFileSync(resolve(TEST_PROFILES_DIR, 'with-url.env'), 'utf8');
157
+ expect(content).toContain('PRODUCTBRAIN_API_KEY=pb_sk_test');
158
+ expect(content).toContain('CONVEX_SITE_URL=https://custom.convex.site');
159
+ });
160
+ it('omits siteUrl when not provided', () => {
161
+ createProfile('no-url', 'pb_sk_test');
162
+ const content = readFileSync(resolve(TEST_PROFILES_DIR, 'no-url.env'), 'utf8');
163
+ expect(content).toContain('PRODUCTBRAIN_API_KEY=pb_sk_test');
164
+ expect(content).not.toContain('CONVEX_SITE_URL');
165
+ });
166
+ });
167
+ });
168
+ //# sourceMappingURL=profiles.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profiles.test.js","sourceRoot":"","sources":["../../src/__tests__/profiles.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=setup.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/setup.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,170 @@
1
+ /**
2
+ * pb setup — guided onboarding tests.
3
+ * WP-301 Slice 3. Verifies:
4
+ * - setup with valid existing config skips login
5
+ * - setup tracks telemetry events in order
6
+ * - setup with no config triggers login prompt
7
+ * - first capture flow starts session and creates entry
8
+ */
9
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
10
+ // Track telemetry calls in order
11
+ const telemetryEvents = [];
12
+ vi.mock('../lib/telemetry.js', () => ({
13
+ trackEvent: (event) => {
14
+ telemetryEvents.push(event);
15
+ },
16
+ }));
17
+ const mcpCallMock = vi.fn();
18
+ const mcpCallWithSessionMock = vi.fn();
19
+ vi.mock('../lib/client.js', () => ({
20
+ mcpCall: (...args) => mcpCallMock(...args),
21
+ mcpCallWithSession: (...args) => mcpCallWithSessionMock(...args),
22
+ }));
23
+ let mockConfig = {
24
+ apiKey: 'pb_sk_test_key_1234',
25
+ siteUrl: 'https://test.convex.site',
26
+ };
27
+ vi.mock('../lib/config.js', () => ({
28
+ getConfig: () => {
29
+ if (!mockConfig)
30
+ throw new Error('No API key.');
31
+ return mockConfig;
32
+ },
33
+ getConfigOrGuide: vi.fn(() => mockConfig
34
+ ? Promise.resolve(mockConfig)
35
+ : Promise.reject(new Error('No API key.'))),
36
+ HOME_CONFIG_DIR: '/tmp/test-config',
37
+ HOME_ENV_PATH: '/tmp/test-config/.env',
38
+ }));
39
+ let mockSession = null;
40
+ vi.mock('../lib/session.js', () => ({
41
+ readSession: () => mockSession,
42
+ writeSession: vi.fn(),
43
+ clearSession: vi.fn(),
44
+ addCapturedEntry: vi.fn(),
45
+ }));
46
+ const runLoginMock = vi.fn();
47
+ vi.mock('../commands/login.js', () => ({
48
+ runLogin: () => runLoginMock(),
49
+ }));
50
+ // Mock readline — supply answers in sequence
51
+ let readlineAnswers = [];
52
+ let answerIndex = 0;
53
+ vi.mock('readline', () => ({
54
+ createInterface: () => ({
55
+ question: (_prompt, cb) => {
56
+ const answer = readlineAnswers[answerIndex] ?? '';
57
+ answerIndex++;
58
+ cb(answer);
59
+ },
60
+ close: vi.fn(),
61
+ }),
62
+ }));
63
+ import { runSetup } from '../commands/setup.js';
64
+ describe('runSetup', () => {
65
+ beforeEach(() => {
66
+ vi.clearAllMocks();
67
+ telemetryEvents.length = 0;
68
+ readlineAnswers = [];
69
+ answerIndex = 0;
70
+ mockConfig = { apiKey: 'pb_sk_test_key_1234', siteUrl: 'https://test.convex.site' };
71
+ mockSession = null;
72
+ });
73
+ it('skips login when valid config exists and user declines capture', async () => {
74
+ // User answers: "n" to "Skip to first capture?"
75
+ readlineAnswers = ['n'];
76
+ mcpCallMock.mockResolvedValueOnce({ _id: 'ws-1', keyId: 'key-1', name: 'Test Workspace' }); // resolveWorkspace
77
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
78
+ await runSetup();
79
+ // Should NOT call runLogin
80
+ expect(runLoginMock).not.toHaveBeenCalled();
81
+ // Should show masked key
82
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('pb_sk_'));
83
+ // Should show workspace name
84
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Test Workspace'));
85
+ logSpy.mockRestore();
86
+ });
87
+ it('tracks telemetry events in correct order when user completes full flow with existing config', async () => {
88
+ // User answers: "y" to skip to capture, "y" to capture, then capture text
89
+ readlineAnswers = ['y', 'y', 'My first insight'];
90
+ mcpCallMock
91
+ .mockResolvedValueOnce({ _id: 'ws-1', keyId: 'key-1', name: 'Test Workspace' }) // resolveWorkspace (step 1)
92
+ .mockResolvedValueOnce({ _id: 'ws-1', keyId: 'key-1', name: 'Test Workspace' }) // resolveWorkspace (capture)
93
+ .mockResolvedValueOnce({ sessionId: 'sess-1', workspaceName: 'Test Workspace', initiatedBy: 'cli', toolsScope: 'readwrite' }); // startSession
94
+ mcpCallWithSessionMock
95
+ .mockResolvedValueOnce({ docId: 'doc-1', entryId: 'INS-1' }); // createEntry
96
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
97
+ await runSetup();
98
+ expect(telemetryEvents).toEqual([
99
+ 'setup_started',
100
+ 'workspace_bound',
101
+ 'first_capture_prompted',
102
+ 'setup_completed',
103
+ ]);
104
+ logSpy.mockRestore();
105
+ });
106
+ it('triggers login prompt when no valid config exists', async () => {
107
+ mockConfig = null;
108
+ // User answers: "n" to "Do you have an account?"
109
+ readlineAnswers = ['n'];
110
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
111
+ await runSetup();
112
+ // Should show signup guidance
113
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('productbrain.io'));
114
+ // Should NOT call runLogin since user said no account
115
+ expect(runLoginMock).not.toHaveBeenCalled();
116
+ logSpy.mockRestore();
117
+ });
118
+ it('calls runLogin when user has account but no config', async () => {
119
+ mockConfig = null;
120
+ // User answers: "y" to "have account?", then login runs,
121
+ // then "n" to capture prompt (via post-login rl)
122
+ readlineAnswers = ['y', 'n'];
123
+ // After login, config becomes valid
124
+ runLoginMock.mockImplementation(() => {
125
+ mockConfig = { apiKey: 'pb_sk_new_key', siteUrl: 'https://test.convex.site' };
126
+ });
127
+ mcpCallMock.mockResolvedValueOnce({ _id: 'ws-1', keyId: 'key-1', name: 'Test Workspace' }); // resolveWorkspace
128
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
129
+ await runSetup();
130
+ expect(runLoginMock).toHaveBeenCalledOnce();
131
+ expect(telemetryEvents).toContain('setup_started');
132
+ expect(telemetryEvents).toContain('key_validated');
133
+ logSpy.mockRestore();
134
+ });
135
+ it('handles first capture with session creation', async () => {
136
+ // Already configured, user wants to capture
137
+ readlineAnswers = ['y', 'y', 'DEC: Use trunk-based dev'];
138
+ mcpCallMock
139
+ .mockResolvedValueOnce({ _id: 'ws-1', keyId: 'key-1', name: 'Test Workspace' }) // resolveWorkspace (verify)
140
+ .mockResolvedValueOnce({ _id: 'ws-1', keyId: 'key-1', name: 'Test Workspace' }) // resolveWorkspace (capture)
141
+ .mockResolvedValueOnce({ sessionId: 'sess-setup', workspaceName: 'Test Workspace', initiatedBy: 'cli', toolsScope: 'readwrite' }); // startSession
142
+ mcpCallWithSessionMock
143
+ .mockResolvedValueOnce({ docId: 'doc-1', entryId: 'DEC-100' }); // createEntry
144
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
145
+ await runSetup();
146
+ // Should have called createEntry with the capture text
147
+ expect(mcpCallWithSessionMock).toHaveBeenCalledWith('chain.createEntry', expect.objectContaining({
148
+ name: 'DEC: Use trunk-based dev',
149
+ collectionSlug: 'insights',
150
+ }));
151
+ // Should show the entry ID
152
+ expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('DEC-100'));
153
+ logSpy.mockRestore();
154
+ });
155
+ it('skips capture gracefully when user enters empty text', async () => {
156
+ readlineAnswers = ['y', 'y', ''];
157
+ mcpCallMock
158
+ .mockResolvedValueOnce({ _id: 'ws-1', keyId: 'key-1', name: 'Test Workspace' }) // resolveWorkspace (verify)
159
+ .mockResolvedValueOnce({ _id: 'ws-1', keyId: 'key-1', name: 'Test Workspace' }) // resolveWorkspace (capture)
160
+ .mockResolvedValueOnce({ sessionId: 'sess-setup', workspaceName: 'Test Workspace', initiatedBy: 'cli', toolsScope: 'readwrite' }); // startSession
161
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
162
+ await runSetup();
163
+ // Should NOT call createEntry
164
+ expect(mcpCallWithSessionMock).not.toHaveBeenCalled();
165
+ // Should still complete setup
166
+ expect(telemetryEvents).toContain('setup_completed');
167
+ logSpy.mockRestore();
168
+ });
169
+ });
170
+ //# sourceMappingURL=setup.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.test.js","sourceRoot":"","sources":["../../src/__tests__/setup.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/commands/capture.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAgDH,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wFAAwF;IACxF,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA4ED,wBAAsB,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAmIvE"}
1
+ {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/commands/capture.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAkDH,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wFAAwF;IACxF,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA4ED,wBAAsB,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAqJvE"}
@@ -11,6 +11,8 @@ import { mcpCallWithSession } from '../lib/client.js';
11
11
  import { readSession, addCapturedEntry } from '../lib/session.js';
12
12
  import { isJsonMode } from '../lib/runner.js';
13
13
  import { formatCaptureReceipt } from '../formatters/capture.js';
14
+ import { checkActivation } from '../lib/activation.js';
15
+ import { progress } from '../lib/style.js';
14
16
  /**
15
17
  * Infer an ISO date (YYYY-MM-DD) from free-text values.
16
18
  *
@@ -113,7 +115,7 @@ export async function runCapture(options) {
113
115
  }
114
116
  else {
115
117
  if (!json)
116
- process.stdout.write('Classifying...\n');
118
+ progress('Classifying...');
117
119
  classification = await mcpCallWithSession('chain.resolveCollection', {
118
120
  entryName,
119
121
  entryDescription,
@@ -126,7 +128,7 @@ export async function runCapture(options) {
126
128
  }
127
129
  // Phase 2: Create entry as draft
128
130
  if (!json)
129
- process.stdout.write(`Capturing to ${collectionSlug}...\n`);
131
+ progress(`Capturing to ${collectionSlug}...`);
130
132
  const data = { description: entryDescription };
131
133
  // Only set date when we can infer a source date from the capture itself.
132
134
  const datedCollections = ['tensions', 'decisions', 'insights', 'standards', 'business-rules'];
@@ -147,6 +149,25 @@ export async function runCapture(options) {
147
149
  });
148
150
  // Track in session state
149
151
  addCapturedEntry(result.entryId);
152
+ // WP-301 S4: Activation ceremony — show contextual retrieval after first capture
153
+ if (!json) {
154
+ try {
155
+ const activation = await checkActivation(result.entryId);
156
+ if (activation?.isFirst && activation.relatedEntries.length > 0) {
157
+ process.stdout.write('\n Your first capture is on the Chain. Here\'s how it connects:\n');
158
+ for (const rel of activation.relatedEntries.slice(0, 3)) {
159
+ process.stdout.write(` \u2192 ${rel.entryId}: ${rel.name} (${rel.relation})\n`);
160
+ }
161
+ process.stdout.write('\n');
162
+ }
163
+ else if (activation?.isFirst) {
164
+ process.stdout.write('\n Your first capture is on the Chain! Run `pb orient -b` to see your workspace.\n\n');
165
+ }
166
+ }
167
+ catch {
168
+ // Activation is additive — never blocks capture
169
+ }
170
+ }
150
171
  // BET-272 S3: Advisory quality hints — from createEntryWithClassification result (BR-144)
151
172
  const qualityHints = result.qualityHints ?? [];
152
173
  // Phase 3: Link to existing entry if --link provided (TEN-705)