@unifiedmemory/cli 1.0.0 → 1.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.
package/commands/init.js CHANGED
@@ -47,6 +47,25 @@ export async function init(options = {}) {
47
47
  console.log(' 3. Run `um status` to verify configuration\n');
48
48
  }
49
49
 
50
+ /**
51
+ * Validate that we have a real organization context (not fallback to user_id)
52
+ * @param {Object} authData - Auth data with user_id and org_id
53
+ * @returns {Object} { isValid, isPersonalContext, message }
54
+ */
55
+ function validateOrganizationContext(authData) {
56
+ const isPersonalContext = authData.org_id === authData.user_id;
57
+
58
+ if (isPersonalContext) {
59
+ return {
60
+ isValid: false,
61
+ isPersonalContext: true,
62
+ message: 'You are in personal account context. Organization-scoped operations require an organization.',
63
+ };
64
+ }
65
+
66
+ return { isValid: true, isPersonalContext: false, message: null };
67
+ }
68
+
50
69
  async function ensureAuthenticated(options) {
51
70
  // Try to load and refresh token if expired
52
71
  const stored = await loadAndRefreshToken(false); // Don't throw, allow new login
@@ -70,12 +89,6 @@ async function ensureAuthenticated(options) {
70
89
 
71
90
  // Need to login
72
91
  console.log(chalk.yellow('⚠ Authentication required\n'));
73
-
74
- if (options.apiKey) {
75
- return await loginWithApiKey(options.apiKey);
76
- }
77
-
78
- // For now, just use OAuth
79
92
  console.log(chalk.blue('Initiating OAuth login...'));
80
93
  const tokenData = await login();
81
94
 
@@ -100,29 +113,120 @@ async function ensureAuthenticated(options) {
100
113
  async function selectOrCreateProject(authData, options) {
101
114
  const apiUrl = authData.api_url || 'https://rose-asp-main-1c0b114.d2.zuplo.dev';
102
115
 
103
- // Fetch existing projects
104
- const projects = await fetchProjects(authData, apiUrl);
105
-
106
- console.log(chalk.cyan('\nšŸ“‹ Project setup:\n'));
107
- console.log(chalk.green(` 1. Create new project`));
108
- console.log(chalk.green(` 2. Select existing project (${projects.length} available)`));
109
-
110
- const { selection } = await inquirer.prompt([{
111
- type: 'input',
112
- name: 'selection',
113
- message: 'Choose option (1-2):',
114
- default: projects.length > 0 ? '2' : '1',
115
- validate: (input) => {
116
- const num = parseInt(input, 10);
117
- if (isNaN(num) || num < 1 || num > 2) {
118
- return 'Please enter 1 or 2';
116
+ // Validate organization context BEFORE making API calls
117
+ const contextValidation = validateOrganizationContext(authData);
118
+
119
+ if (!contextValidation.isValid && contextValidation.isPersonalContext) {
120
+ console.log(chalk.yellow('\nāš ļø ' + contextValidation.message));
121
+ console.log(chalk.gray('\nOrganization-scoped projects require an organization context.'));
122
+ console.log(chalk.cyan('\nRecommended actions:'));
123
+ console.log(chalk.cyan(' 1. Switch to an organization: um org switch'));
124
+ console.log(chalk.cyan(' 2. Create an organization at: https://unifiedmemory.ai'));
125
+ console.log(chalk.cyan(' 3. Re-run: um init'));
126
+ return null;
127
+ }
128
+
129
+ // Fetch existing projects with enhanced error handling
130
+ console.log(chalk.gray('Fetching projects...'));
131
+ const result = await fetchProjects(authData, apiUrl);
132
+
133
+ // Handle error responses
134
+ if (!result.success) {
135
+ console.log(chalk.red(`\nāŒ Failed to fetch projects: ${result.message}`));
136
+
137
+ if (result.errorType === 'UNAUTHORIZED') {
138
+ console.log(chalk.yellow('\nšŸ” Authentication Issue Detected\n'));
139
+ console.log(chalk.gray('Token appears to be invalid or expired.'));
140
+ console.log(chalk.cyan('\nšŸ”„ Attempting automatic re-authentication...\n'));
141
+
142
+ try {
143
+ // Trigger login flow
144
+ const tokenData = await login();
145
+
146
+ if (!tokenData) {
147
+ console.log(chalk.red('āŒ Re-authentication failed'));
148
+ console.log(chalk.cyan('\nPlease try:'));
149
+ console.log(chalk.cyan(' 1. Run: um login'));
150
+ console.log(chalk.cyan(' 2. Verify organization context: um org switch'));
151
+ return null;
152
+ }
153
+
154
+ // Update authData with fresh credentials
155
+ const savedToken = getToken();
156
+ authData.user_id = savedToken.decoded.sub;
157
+ authData.org_id = savedToken.selectedOrg?.id || authData.user_id;
158
+ authData.access_token = savedToken.idToken || savedToken.accessToken;
159
+ authData.expires_at = savedToken.decoded?.exp * 1000;
160
+
161
+ console.log(chalk.green('āœ“ Re-authentication successful!\n'));
162
+ console.log(chalk.gray('Retrying project fetch...\n'));
163
+
164
+ // Retry fetching projects with new token
165
+ const retryResult = await fetchProjects(authData, apiUrl);
166
+
167
+ if (!retryResult.success) {
168
+ // Still failing after retry
169
+ console.log(chalk.red(`\nāŒ Failed to fetch projects after re-authentication: ${retryResult.message}`));
170
+ console.log(chalk.yellow('\nThis may indicate:'));
171
+ console.log(chalk.gray(' - You don\'t have access to this organization'));
172
+ console.log(chalk.gray(' - The organization no longer exists'));
173
+ console.log(chalk.cyan('\nTry: um org switch'));
174
+ return null;
175
+ }
176
+
177
+ // Success! Update projects and continue
178
+ result.success = true;
179
+ result.projects = retryResult.projects;
180
+ console.log(chalk.green(`āœ“ Found ${retryResult.projects.length} project(s)\n`));
181
+
182
+ } catch (error) {
183
+ console.log(chalk.red(`\nāŒ Re-authentication error: ${error.message}`));
184
+ console.log(chalk.cyan('\nPlease run: um login'));
185
+ return null;
119
186
  }
120
- return true;
187
+ } else if (result.errorType === 'FORBIDDEN') {
188
+ console.log(chalk.yellow('\nšŸ”’ Access Denied\n'));
189
+ console.log(chalk.gray('You do not have permission to view projects in this organization.'));
190
+ console.log(chalk.cyan('\nContact your organization administrator for access.'));
191
+ return null;
192
+ } else if (result.errorType === 'NETWORK_ERROR') {
193
+ console.log(chalk.yellow('\n🌐 Network Error\n'));
194
+ console.log(chalk.gray('Could not connect to the API server.'));
195
+ console.log(chalk.cyan('\nCheck your internet connection and try again.'));
196
+ return null;
197
+ } else {
198
+ console.log(chalk.yellow('\nāš ļø Unexpected Error\n'));
199
+ console.log(chalk.gray(`Details: ${result.message}`));
200
+ console.log(chalk.cyan('\nTry: um login'));
201
+ return null;
202
+ }
203
+ }
204
+
205
+ let projects = result.projects;
206
+
207
+ console.log(chalk.cyan('\nšŸ“‹ Project setup\n'));
208
+
209
+ const choices = [
210
+ {
211
+ name: chalk.green('Create new project'),
212
+ value: 'create',
213
+ short: 'Create new project',
214
+ },
215
+ {
216
+ name: chalk.green(`Select existing project`) + chalk.gray(` (${projects.length} available)`),
217
+ value: 'select',
218
+ short: 'Select existing project',
121
219
  },
220
+ ];
221
+
222
+ const { action } = await inquirer.prompt([{
223
+ type: 'select',
224
+ name: 'action',
225
+ message: 'Choose an option:',
226
+ choices: choices,
227
+ default: projects.length > 0 ? 1 : 0,
122
228
  }]);
123
229
 
124
- const action = parseInt(selection, 10) === 1 ? 'create' : 'select';
125
-
126
230
  if (action === 'create') {
127
231
  const { name, description } = await inquirer.prompt([
128
232
  {
@@ -142,32 +246,40 @@ async function selectOrCreateProject(authData, options) {
142
246
  } else {
143
247
  if (projects.length === 0) {
144
248
  console.log(chalk.yellow('No projects found. Creating first project...'));
145
- return await selectOrCreateProject(authData, { ...options, action: 'create' });
249
+ const { name, description } = await inquirer.prompt([
250
+ {
251
+ type: 'input',
252
+ name: 'name',
253
+ message: 'Project name:',
254
+ validate: input => input.length > 0 || 'Name is required',
255
+ },
256
+ {
257
+ type: 'input',
258
+ name: 'description',
259
+ message: 'Project description (optional):',
260
+ },
261
+ ]);
262
+
263
+ return await createProject(authData, apiUrl, name, description);
146
264
  }
147
265
 
148
- // Display numbered list
149
- console.log(chalk.cyan('\nšŸ“‹ Available projects:\n'));
150
- projects.forEach((p, index) => {
151
- console.log(chalk.green(` ${index + 1}. ${p.display_name || p.name}`) + chalk.gray(` (${p.slug || p.id})`));
152
- });
153
-
154
- const { projectSelection } = await inquirer.prompt([{
155
- type: 'input',
156
- name: 'projectSelection',
157
- message: `Choose project (1-${projects.length}):`,
158
- default: '1',
159
- validate: (input) => {
160
- const num = parseInt(input, 10);
161
- if (isNaN(num) || num < 1 || num > projects.length) {
162
- return `Please enter a number between 1 and ${projects.length}`;
163
- }
164
- return true;
165
- },
266
+ // Display project list with arrow key selection
267
+ console.log(chalk.cyan('\nšŸ“‹ Available projects\n'));
268
+
269
+ const choices = projects.map(p => ({
270
+ name: chalk.green(p.display_name || p.name) + chalk.gray(` (${p.slug || p.id})`),
271
+ value: p,
272
+ short: p.display_name || p.name,
273
+ }));
274
+
275
+ const { project } = await inquirer.prompt([{
276
+ type: 'select',
277
+ name: 'project',
278
+ message: 'Choose a project:',
279
+ choices: choices,
280
+ pageSize: 10,
166
281
  }]);
167
282
 
168
- const selectedIndex = parseInt(projectSelection, 10) - 1;
169
- const project = projects[selectedIndex];
170
-
171
283
  return {
172
284
  project_id: project.id,
173
285
  project_name: project.display_name || project.name,
@@ -187,11 +299,47 @@ async function fetchProjects(authData, apiUrl) {
187
299
  },
188
300
  }
189
301
  );
190
- // API returns {items: [...], next_cursor, has_more}
191
- return response.data?.items || [];
302
+ return { success: true, projects: response.data?.items || [] };
192
303
  } catch (error) {
193
- console.error(chalk.red(`Failed to fetch projects: ${error.message}`));
194
- return [];
304
+ // Detect specific error types
305
+ if (error.response) {
306
+ const status = error.response.status;
307
+
308
+ if (status === 401) {
309
+ return {
310
+ success: false,
311
+ errorType: 'UNAUTHORIZED',
312
+ status: 401,
313
+ message: 'Authentication failed - token may be invalid or expired',
314
+ };
315
+ } else if (status === 403) {
316
+ return {
317
+ success: false,
318
+ errorType: 'FORBIDDEN',
319
+ status: 403,
320
+ message: 'Access denied - you may not have permission to view projects',
321
+ };
322
+ } else if (status >= 500) {
323
+ return {
324
+ success: false,
325
+ errorType: 'SERVER_ERROR',
326
+ status: status,
327
+ message: 'Server error - please try again later',
328
+ };
329
+ }
330
+ } else if (error.request) {
331
+ return {
332
+ success: false,
333
+ errorType: 'NETWORK_ERROR',
334
+ message: 'Network error - could not reach API server',
335
+ };
336
+ }
337
+
338
+ return {
339
+ success: false,
340
+ errorType: 'UNKNOWN',
341
+ message: error.message,
342
+ };
195
343
  }
196
344
  }
197
345
 
@@ -218,7 +366,27 @@ async function createProject(authData, apiUrl, name, description) {
218
366
  project_slug: project.slug,
219
367
  };
220
368
  } catch (error) {
221
- console.error(chalk.red(`Failed to create project: ${error.message}`));
369
+ if (error.response) {
370
+ const status = error.response.status;
371
+ console.error(chalk.red(`\nāŒ Failed to create project (HTTP ${status})`));
372
+
373
+ if (status === 401) {
374
+ console.log(chalk.yellow('šŸ” Authentication failed'));
375
+ console.log(chalk.gray('Your session may have expired.'));
376
+ console.log(chalk.cyan('Run: um login'));
377
+ } else if (status === 403) {
378
+ console.log(chalk.yellow('šŸ”’ Permission denied'));
379
+ console.log(chalk.gray('You do not have permission to create projects in this organization.'));
380
+ } else if (status === 409) {
381
+ console.log(chalk.yellow('āš ļø Project already exists'));
382
+ console.log(chalk.gray('A project with this name or slug already exists.'));
383
+ } else {
384
+ console.log(chalk.gray(`Details: ${error.message}`));
385
+ }
386
+ } else {
387
+ console.error(chalk.red(`\nāŒ Failed to create project: ${error.message}`));
388
+ }
389
+
222
390
  return null;
223
391
  }
224
392
  }
@@ -288,28 +456,37 @@ async function configureProviders(authData, projectData) {
288
456
 
289
457
  // NEW APPROACH: Configure local MCP server (no tokens in config files)
290
458
  for (const provider of detected) {
291
- const success = provider.configureMCP();
459
+ // Configure MCP server
460
+ const mcpSuccess = provider.configureMCP();
461
+
462
+ // Configure memory instructions
463
+ const instructionsResult = provider.configureMemoryInstructions?.();
292
464
 
293
- if (success) {
294
- console.log(chalk.green(`āœ“ Configured ${provider.name}`));
465
+ // Display results
466
+ if (mcpSuccess) {
467
+ console.log(chalk.green(`āœ“ Configured ${provider.name} MCP server`));
295
468
  } else {
296
- console.log(chalk.red(`āœ— Failed to configure ${provider.name}`));
469
+ console.log(chalk.red(`āœ— Failed to configure ${provider.name} MCP`));
470
+ }
471
+
472
+ if (instructionsResult) {
473
+ switch (instructionsResult.status) {
474
+ case 'created':
475
+ console.log(chalk.green(` āœ“ Created memory instructions`));
476
+ break;
477
+ case 'appended':
478
+ console.log(chalk.green(` āœ“ Appended memory instructions`));
479
+ break;
480
+ case 'skipped':
481
+ console.log(chalk.gray(` ℹ Memory instructions already present`));
482
+ break;
483
+ case 'error':
484
+ console.log(chalk.yellow(` ⚠ Could not write memory instructions: ${instructionsResult.error}`));
485
+ break;
486
+ }
297
487
  }
298
488
  }
299
489
 
300
490
  console.log(chalk.cyan('\nšŸ’” Important: Restart your AI assistant to load the new configuration'));
301
491
  console.log(chalk.gray(' The MCP server will automatically use your authentication and project context'));
302
492
  }
303
-
304
- async function loginWithApiKey(apiKey) {
305
- // TODO: Implement API key authentication
306
- // Exchange API key for JWT token
307
- console.log(chalk.yellow('API key authentication not yet implemented'));
308
- return null;
309
- }
310
-
311
- async function refreshToken(refreshToken) {
312
- // TODO: Implement token refresh
313
- console.log(chalk.yellow('Token refresh not yet implemented'));
314
- return null;
315
- }
package/commands/login.js CHANGED
@@ -6,10 +6,13 @@ import crypto from 'crypto';
6
6
  import inquirer from 'inquirer';
7
7
  import { config, validateConfig } from '../lib/config.js';
8
8
  import { saveToken, updateSelectedOrg } from '../lib/token-storage.js';
9
- import { getUserOrganizations, getOrganizationsFromToken, formatOrganization, getOrgScopedToken } from '../lib/clerk-api.js';
9
+ import { getUserOrganizations, getOrganizationsFromToken, getOrgScopedToken } from '../lib/clerk-api.js';
10
+ import { parseJWT } from '../lib/jwt-utils.js';
11
+ import { promptOrganizationSelection, displayOrganizationSelection } from '../lib/org-selection-ui.js';
10
12
 
11
13
  function generateRandomState() {
12
- return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
14
+ // Use cryptographically secure random bytes for CSRF protection
15
+ return crypto.randomBytes(32).toString('hex');
13
16
  }
14
17
 
15
18
  function base64URLEncode(buffer) {
@@ -29,77 +32,6 @@ function generatePKCE() {
29
32
  return { verifier, challenge };
30
33
  }
31
34
 
32
- function parseJWT(token) {
33
- try {
34
- const parts = token.split('.');
35
- if (parts.length !== 3) {
36
- return null;
37
- }
38
- const payload = Buffer.from(parts[1], 'base64').toString('utf8');
39
- return JSON.parse(payload);
40
- } catch (error) {
41
- return null;
42
- }
43
- }
44
-
45
- /**
46
- * Prompt user to select an organization context
47
- * @param {Array} memberships - Array of organization memberships
48
- * @returns {Promise<Object|null>} Selected organization data or null for personal context
49
- */
50
- async function selectOrganization(memberships) {
51
- console.log(chalk.blue('\nšŸ” Checking for organizations...'));
52
-
53
- if (memberships.length === 0) {
54
- console.log(chalk.gray('No organizations found. Using personal account context.'));
55
- return null;
56
- }
57
-
58
- console.log(chalk.green(`\nFound ${memberships.length} organization(s)!`));
59
-
60
- // Debug: Log raw memberships
61
- console.log(chalk.gray('\nRaw memberships data:'));
62
- console.log(JSON.stringify(memberships, null, 2));
63
-
64
- // Format organizations for display
65
- const formattedOrgs = memberships.map(formatOrganization);
66
-
67
- // Print numbered list
68
- console.log(chalk.cyan('\nšŸ“‹ Available contexts:\n'));
69
-
70
- formattedOrgs.forEach((org, index) => {
71
- console.log(chalk.green(` ${index + 1}. ${org.name}`) + chalk.gray(` (${org.slug})`) + chalk.yellow(` [${org.role}]`));
72
- });
73
-
74
- console.log(chalk.cyan(` ${formattedOrgs.length + 1}. Personal Account`) + chalk.gray(' (no organization)'));
75
-
76
- // Simple input prompt
77
- const answer = await inquirer.prompt([
78
- {
79
- type: 'input',
80
- name: 'selection',
81
- message: `Choose context (1-${formattedOrgs.length + 1}):`,
82
- default: '1',
83
- validate: (input) => {
84
- const num = parseInt(input, 10);
85
- if (isNaN(num) || num < 1 || num > formattedOrgs.length + 1) {
86
- return `Please enter a number between 1 and ${formattedOrgs.length + 1}`;
87
- }
88
- return true;
89
- },
90
- },
91
- ]);
92
-
93
- const selectedIndex = parseInt(answer.selection, 10) - 1;
94
-
95
- // If they selected beyond orgs list, that's personal account (null)
96
- if (selectedIndex >= formattedOrgs.length) {
97
- return null;
98
- }
99
-
100
- return formattedOrgs[selectedIndex];
101
- }
102
-
103
35
  export async function login() {
104
36
  validateConfig();
105
37
 
@@ -224,30 +156,10 @@ export async function login() {
224
156
  </html>
225
157
  `);
226
158
 
227
- // Parse JWT to show user info - try both access_token and id_token
159
+ // Parse JWT for user info - do not log tokens
228
160
  const tokenToParse = tokenData.id_token || tokenData.access_token;
229
- console.log(chalk.gray('Parsing token type:'), tokenData.id_token ? 'id_token' : 'access_token');
230
-
231
161
  const decoded = parseJWT(tokenToParse);
232
162
 
233
- if (!decoded) {
234
- console.log(chalk.yellow('āš ļø Could not parse JWT token'));
235
- console.log(chalk.gray('Token preview:'), tokenToParse ? tokenToParse.substring(0, 50) + '...' : 'null');
236
- } else {
237
- // Debug: Show JWT claims to see what's available
238
- console.log(chalk.gray('\nJWT Claims:'));
239
- console.log(chalk.gray(JSON.stringify(decoded, null, 2)));
240
- }
241
-
242
- // Debug: Show token previews to understand format
243
- console.log(chalk.gray('\nToken previews:'));
244
- if (tokenData.access_token) {
245
- console.log(chalk.gray(' Access token:'), tokenData.access_token.substring(0, 50) + '...');
246
- }
247
- if (tokenData.id_token) {
248
- console.log(chalk.gray(' ID token:'), tokenData.id_token.substring(0, 50) + '...');
249
- }
250
-
251
163
  // Save token (save both access_token and id_token if available)
252
164
  saveToken({
253
165
  accessToken: tokenData.access_token,
@@ -289,7 +201,9 @@ export async function login() {
289
201
  memberships = await getUserOrganizations(userId, sessionToken);
290
202
  }
291
203
 
292
- const selectedOrg = await selectOrganization(memberships);
204
+ console.log(chalk.blue('\nšŸ” Checking for organizations...'));
205
+ const selectedOrg = await promptOrganizationSelection(memberships);
206
+ displayOrganizationSelection(selectedOrg);
293
207
 
294
208
  if (selectedOrg) {
295
209
  // Get org-scoped JWT from Clerk
package/commands/org.js CHANGED
@@ -1,8 +1,8 @@
1
- import inquirer from 'inquirer';
2
1
  import chalk from 'chalk';
3
2
  import { updateSelectedOrg, getSelectedOrg } from '../lib/token-storage.js';
4
3
  import { loadAndRefreshToken } from '../lib/token-validation.js';
5
- import { getUserOrganizations, getOrganizationsFromToken, formatOrganization } from '../lib/clerk-api.js';
4
+ import { getUserOrganizations, getOrganizationsFromToken } from '../lib/clerk-api.js';
5
+ import { promptOrganizationSelection, displayOrganizationSelection } from '../lib/org-selection-ui.js';
6
6
 
7
7
  /**
8
8
  * Switch organization context
@@ -40,49 +40,20 @@ export async function switchOrg() {
40
40
  process.exit(0);
41
41
  }
42
42
 
43
- console.log(chalk.green(`\nFound ${memberships.length} organization(s)!`));
44
-
45
- // Format organizations for display
46
- const formattedOrgs = memberships.map(formatOrganization);
47
-
48
43
  // Get current selection
49
44
  const currentOrg = getSelectedOrg();
50
- const currentOrgId = currentOrg?.id;
51
-
52
- // Build choices for inquirer
53
- const choices = [
54
- {
55
- name: chalk.cyan('Personal Account') + chalk.gray(' (no organization)') + (currentOrgId ? '' : chalk.green(' ← current')),
56
- value: null,
57
- short: 'Personal Account',
58
- },
59
- new inquirer.Separator(chalk.gray('--- Organizations ---')),
60
- ...formattedOrgs.map(org => ({
61
- name: `${chalk.green(org.name)} ${chalk.gray(`(${org.slug})`)} ${chalk.yellow(`[${org.role}]`)}${org.id === currentOrgId ? chalk.green(' ← current') : ''}`,
62
- value: org,
63
- short: org.name,
64
- })),
65
- ];
66
45
 
67
46
  // Prompt user to select
68
- const answer = await inquirer.prompt([
69
- {
70
- type: 'list',
71
- name: 'organization',
72
- message: 'Select account context:',
73
- choices: choices,
74
- pageSize: 15,
75
- default: currentOrgId ? formattedOrgs.findIndex(org => org.id === currentOrgId) + 2 : 0, // +2 for personal + separator
76
- },
77
- ]);
47
+ const selectedOrg = await promptOrganizationSelection(memberships, currentOrg);
78
48
 
79
49
  // Update selected organization
80
- updateSelectedOrg(answer.organization);
50
+ updateSelectedOrg(selectedOrg);
81
51
 
82
- if (answer.organization) {
83
- console.log(chalk.green(`\nāœ… Switched to organization: ${chalk.bold(answer.organization.name)}`));
84
- console.log(chalk.gray(` Organization ID: ${answer.organization.id}`));
85
- console.log(chalk.gray(` Your role: ${answer.organization.role}`));
52
+ // Display result (with "Switched to" instead of "Selected")
53
+ if (selectedOrg) {
54
+ console.log(chalk.green(`\nāœ… Switched to organization: ${chalk.bold(selectedOrg.name)}`));
55
+ console.log(chalk.gray(` Organization ID: ${selectedOrg.id}`));
56
+ console.log(chalk.gray(` Your role: ${selectedOrg.role}`));
86
57
  } else {
87
58
  console.log(chalk.green('\nāœ… Switched to personal account context'));
88
59
  }
package/index.js CHANGED
@@ -11,13 +11,14 @@ import { record } from './commands/record.js';
11
11
  import { config } from './lib/config.js';
12
12
  import { getSelectedOrg } from './lib/token-storage.js';
13
13
  import { loadAndRefreshToken } from './lib/token-validation.js';
14
+ import { showWelcome } from './lib/welcome.js';
14
15
 
15
16
  const program = new Command();
16
17
 
17
18
  program
18
19
  .name('um')
19
20
  .description('UnifiedMemory CLI - AI code assistant integration')
20
- .version('1.0.0');
21
+ .version('1.1.0');
21
22
 
22
23
  // Unified command (primary)
23
24
  program
@@ -36,11 +37,19 @@ program
36
37
  }
37
38
  });
38
39
 
40
+ // Welcome command
41
+ program
42
+ .command('welcome')
43
+ .description('Show welcome message')
44
+ .action(() => {
45
+ showWelcome();
46
+ process.exit(0);
47
+ });
48
+
39
49
  // Individual commands (power users)
40
50
  program
41
51
  .command('login')
42
52
  .description('Login to UnifiedMemory')
43
- .option('--device', 'Use device flow for headless environments')
44
53
  .action(async (options) => {
45
54
  try {
46
55
  await login();
@@ -113,30 +122,6 @@ program
113
122
  }
114
123
  });
115
124
 
116
- // Project management command
117
- program
118
- .command('project')
119
- .description('Manage project configuration')
120
- .option('--create', 'Create new project')
121
- .option('--link <id>', 'Link existing project')
122
- .action(async (options) => {
123
- console.log(chalk.yellow('Project management not yet implemented'));
124
- console.log(chalk.gray('Use `um init` to configure a project'));
125
- process.exit(0);
126
- });
127
-
128
- // Configure command
129
- program
130
- .command('configure')
131
- .description('Configure AI code assistants')
132
- .option('--all', 'Configure all detected assistants')
133
- .option('--provider <name>', 'Configure specific provider')
134
- .action(async (options) => {
135
- console.log(chalk.yellow('Provider configuration not yet implemented'));
136
- console.log(chalk.gray('Use `um init` to auto-configure all providers'));
137
- process.exit(0);
138
- });
139
-
140
125
  // Organization management
141
126
  const orgCommand = program
142
127
  .command('org')
@@ -212,4 +197,10 @@ noteCommand
212
197
  }
213
198
  });
214
199
 
200
+ // Show welcome splash if no command provided
201
+ if (process.argv.length === 2) {
202
+ showWelcome();
203
+ program.help();
204
+ }
205
+
215
206
  program.parse();