@unifiedmemory/cli 1.0.1 → 1.2.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/.env.example +27 -6
- package/CHANGELOG.md +264 -0
- package/README.md +64 -3
- package/commands/init.js +370 -71
- package/commands/login.js +9 -95
- package/commands/org.js +9 -38
- package/index.js +17 -26
- package/lib/config.js +42 -24
- package/lib/jwt-utils.js +63 -0
- package/lib/mcp-server.js +1 -1
- package/lib/memory-instructions.js +72 -0
- package/lib/org-selection-ui.js +104 -0
- package/lib/provider-detector.js +91 -79
- package/lib/token-refresh.js +1 -18
- package/lib/token-storage.js +15 -2
- package/lib/welcome.js +40 -0
- package/package.json +6 -4
- package/HOOK_SETUP.md +0 -338
- package/lib/hooks.js +0 -43
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
|
|
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
|
|
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(
|
|
50
|
+
updateSelectedOrg(selectedOrg);
|
|
81
51
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
console.log(chalk.
|
|
85
|
-
console.log(chalk.gray(`
|
|
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.
|
|
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();
|
package/lib/config.js
CHANGED
|
@@ -1,39 +1,57 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
1
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import { dirname } from 'path';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { config as dotenvConfig } from 'dotenv';
|
|
5
4
|
|
|
6
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
6
|
const __dirname = dirname(__filename);
|
|
8
7
|
|
|
9
|
-
// Load environment variables from .env file
|
|
10
|
-
|
|
11
|
-
if (fs.existsSync(envPath)) {
|
|
12
|
-
const envContent = fs.readFileSync(envPath, 'utf8');
|
|
13
|
-
envContent.split('\n').forEach(line => {
|
|
14
|
-
const [key, ...valueParts] = line.split('=');
|
|
15
|
-
if (key && valueParts.length > 0) {
|
|
16
|
-
const value = valueParts.join('=').trim();
|
|
17
|
-
if (!process.env[key]) {
|
|
18
|
-
process.env[key] = value;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
}
|
|
8
|
+
// Load environment variables from .env file using dotenv
|
|
9
|
+
dotenvConfig({ path: join(__dirname, '..', '.env') });
|
|
23
10
|
|
|
24
11
|
export const config = {
|
|
25
|
-
//
|
|
26
|
-
clerkClientId: process.env.CLERK_CLIENT_ID
|
|
12
|
+
// Required: Clerk OAuth configuration
|
|
13
|
+
clerkClientId: process.env.CLERK_CLIENT_ID,
|
|
27
14
|
clerkClientSecret: process.env.CLERK_CLIENT_SECRET, // Optional for PKCE flow
|
|
28
|
-
clerkDomain: process.env.CLERK_DOMAIN
|
|
29
|
-
|
|
15
|
+
clerkDomain: process.env.CLERK_DOMAIN,
|
|
16
|
+
|
|
17
|
+
// Required: API configuration
|
|
18
|
+
apiEndpoint: process.env.API_ENDPOINT,
|
|
19
|
+
|
|
20
|
+
// Optional: OAuth flow configuration (non-sensitive defaults OK)
|
|
30
21
|
redirectUri: process.env.REDIRECT_URI || 'http://localhost:3333/callback',
|
|
31
22
|
port: parseInt(process.env.PORT || '3333', 10)
|
|
32
23
|
};
|
|
33
24
|
|
|
34
|
-
// Validation function
|
|
25
|
+
// Validation function - ensures required configuration is present
|
|
35
26
|
export function validateConfig() {
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
const missing = [];
|
|
28
|
+
|
|
29
|
+
if (!config.clerkClientId) {
|
|
30
|
+
missing.push('CLERK_CLIENT_ID');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!config.clerkDomain) {
|
|
34
|
+
missing.push('CLERK_DOMAIN');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!config.apiEndpoint) {
|
|
38
|
+
missing.push('API_ENDPOINT');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (missing.length > 0) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Missing required environment variables: ${missing.join(', ')}\n\n` +
|
|
44
|
+
`Please create a .env file in the project root with these values.\n` +
|
|
45
|
+
`See .env.example for a template.`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Validate URL format for apiEndpoint
|
|
50
|
+
try {
|
|
51
|
+
new URL(config.apiEndpoint);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
throw new Error(`API_ENDPOINT must be a valid URL (got: ${config.apiEndpoint})`);
|
|
54
|
+
}
|
|
55
|
+
|
|
38
56
|
return true;
|
|
39
57
|
}
|
package/lib/jwt-utils.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT Utility Functions
|
|
3
|
+
*
|
|
4
|
+
* Centralized JWT parsing, validation, and expiration checking.
|
|
5
|
+
* Extracted from duplicate implementations in login.js and token-refresh.js.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parse a JWT token and return its payload
|
|
10
|
+
* @param {string} token - The JWT token to parse
|
|
11
|
+
* @returns {Object|null} Decoded JWT payload or null if invalid
|
|
12
|
+
*/
|
|
13
|
+
export function parseJWT(token) {
|
|
14
|
+
try {
|
|
15
|
+
const parts = token.split('.');
|
|
16
|
+
if (parts.length !== 3) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const payload = Buffer.from(parts[1], 'base64').toString('utf8');
|
|
20
|
+
return JSON.parse(payload);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if a JWT token is expired
|
|
28
|
+
* @param {Object} decoded - Decoded JWT payload with exp claim
|
|
29
|
+
* @param {number} bufferMs - Optional expiration buffer in milliseconds (default: 0)
|
|
30
|
+
* @returns {boolean} True if token is expired (including buffer)
|
|
31
|
+
*/
|
|
32
|
+
export function isJWTExpired(decoded, bufferMs = 0) {
|
|
33
|
+
if (!decoded?.exp) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
const expirationTime = decoded.exp * 1000;
|
|
37
|
+
return Date.now() >= (expirationTime - bufferMs);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Validate JWT structure has required fields
|
|
42
|
+
* @param {Object} decoded - Decoded JWT payload
|
|
43
|
+
* @returns {boolean} True if JWT has valid structure (sub and exp claims)
|
|
44
|
+
*/
|
|
45
|
+
export function validateJWTStructure(decoded) {
|
|
46
|
+
return decoded &&
|
|
47
|
+
typeof decoded === 'object' &&
|
|
48
|
+
decoded.sub &&
|
|
49
|
+
decoded.exp;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get time until JWT expiration
|
|
54
|
+
* @param {Object} decoded - Decoded JWT payload with exp claim
|
|
55
|
+
* @returns {number} Milliseconds until expiration (negative if already expired)
|
|
56
|
+
*/
|
|
57
|
+
export function getTimeUntilExpiration(decoded) {
|
|
58
|
+
if (!decoded?.exp) {
|
|
59
|
+
return -1;
|
|
60
|
+
}
|
|
61
|
+
const expirationTime = decoded.exp * 1000;
|
|
62
|
+
return expirationTime - Date.now();
|
|
63
|
+
}
|
package/lib/mcp-server.js
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Unique marker to detect if instructions already present
|
|
2
|
+
export const MEMORY_INSTRUCTIONS_MARKER = '<!-- UNIFIEDMEMORY_INSTRUCTIONS_V1 -->';
|
|
3
|
+
|
|
4
|
+
export function getMemoryInstructions() {
|
|
5
|
+
return `${MEMORY_INSTRUCTIONS_MARKER}
|
|
6
|
+
|
|
7
|
+
# UnifiedMemory Automatic Integration
|
|
8
|
+
|
|
9
|
+
When working in this project, automatically integrate with UnifiedMemory to maintain project knowledge:
|
|
10
|
+
|
|
11
|
+
## 1. Search Before Responding
|
|
12
|
+
|
|
13
|
+
**When**: User submits any query or request
|
|
14
|
+
**Action**: Use the \`unifiedmemory\` MCP tool to search for relevant context
|
|
15
|
+
**Usage**:
|
|
16
|
+
- Send the user's query to the search tool
|
|
17
|
+
- Review the search results for relevant prior work, decisions, or context
|
|
18
|
+
- Incorporate findings into your response
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
\`\`\`
|
|
22
|
+
User: "Add dark mode to the dashboard"
|
|
23
|
+
→ Search: unifiedmemory search --query "dark mode dashboard styling"
|
|
24
|
+
→ Review results for existing patterns or decisions
|
|
25
|
+
→ Respond with context-aware answer
|
|
26
|
+
\`\`\`
|
|
27
|
+
|
|
28
|
+
## 2. Record Plans
|
|
29
|
+
|
|
30
|
+
**When**: Creating an implementation plan or design approach
|
|
31
|
+
**Action**: Record the plan as a note using the \`unifiedmemory\` tool
|
|
32
|
+
**Format**:
|
|
33
|
+
- Include: objective, approach, key steps, important decisions
|
|
34
|
+
- Keep concise but informative
|
|
35
|
+
- Limit to under 1000 characters for optimal indexing
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
\`\`\`
|
|
39
|
+
Plan created for dark mode feature
|
|
40
|
+
→ unifiedmemory create_note --content "Plan: Dark mode implementation. Approach: CSS variables + localStorage persistence. Steps: 1) Define color tokens, 2) Add toggle component, 3) Persist preference. Decision: Use system preference as default."
|
|
41
|
+
\`\`\`
|
|
42
|
+
|
|
43
|
+
## 3. Record Task Completions
|
|
44
|
+
|
|
45
|
+
**When**: After completing any task, feature, or significant milestone
|
|
46
|
+
**Action**: Create a summary note using the \`unifiedmemory\` tool
|
|
47
|
+
**Format**:
|
|
48
|
+
- Summarize what was accomplished and why
|
|
49
|
+
- Include key technical decisions or gotchas
|
|
50
|
+
- **IMPORTANT**: Limit to under 1000 characters
|
|
51
|
+
- Focus on outcomes, not detailed code
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
\`\`\`
|
|
55
|
+
Completed dark mode feature
|
|
56
|
+
→ unifiedmemory create_note --content "Completed: Dark mode toggle in dashboard. Implemented CSS custom properties with theme switcher component. User preference stored in localStorage. Respects system prefers-color-scheme by default. All components updated to use theme tokens."
|
|
57
|
+
\`\`\`
|
|
58
|
+
|
|
59
|
+
## Summary Guidelines
|
|
60
|
+
|
|
61
|
+
- ✅ **DO**: Search context before responding
|
|
62
|
+
- ✅ **DO**: Record plans and completions automatically
|
|
63
|
+
- ✅ **DO**: Keep notes under 1000 characters
|
|
64
|
+
- ✅ **DO**: Focus on decisions, outcomes, and context
|
|
65
|
+
- ❌ **DON'T**: Record trivial tasks (typo fixes, minor edits)
|
|
66
|
+
- ❌ **DON'T**: Include full code in notes
|
|
67
|
+
- ❌ **DON'T**: Duplicate information already in git commits
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
*Auto-generated by UnifiedMemory CLI v1.0*
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Organization Selection UI
|
|
3
|
+
*
|
|
4
|
+
* Shared UI for prompting users to select an organization context.
|
|
5
|
+
* Extracted from duplicate implementations in login.js and org.js.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import inquirer from 'inquirer';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { formatOrganization } from './clerk-api.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Prompt user to select an organization context
|
|
14
|
+
* @param {Array} memberships - Array of organization memberships from Clerk
|
|
15
|
+
* @param {Object|null} currentOrg - Currently selected organization (optional)
|
|
16
|
+
* @param {Object} options - Display options
|
|
17
|
+
* @param {boolean} options.allowPersonal - Whether to show "Personal Account" option (default: true)
|
|
18
|
+
* @param {string} options.message - Custom prompt message (default: "Select account context:")
|
|
19
|
+
* @returns {Promise<Object|null>} Selected organization data or null for personal context
|
|
20
|
+
*/
|
|
21
|
+
export async function promptOrganizationSelection(memberships, currentOrg = null, options = {}) {
|
|
22
|
+
const {
|
|
23
|
+
allowPersonal = true,
|
|
24
|
+
message = 'Select account context (use arrow keys):',
|
|
25
|
+
} = options;
|
|
26
|
+
|
|
27
|
+
if (memberships.length === 0) {
|
|
28
|
+
if (!allowPersonal) {
|
|
29
|
+
throw new Error('No organizations available');
|
|
30
|
+
}
|
|
31
|
+
console.log(chalk.gray('No organizations found. Using personal account context.'));
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log(chalk.green(`\nFound ${memberships.length} organization(s)!`));
|
|
36
|
+
console.log(chalk.cyan('💡 Use ↑/↓ arrow keys to navigate, Enter to select'));
|
|
37
|
+
|
|
38
|
+
// Format organizations for display
|
|
39
|
+
const formattedOrgs = memberships.map(formatOrganization);
|
|
40
|
+
|
|
41
|
+
const currentOrgId = currentOrg?.id;
|
|
42
|
+
|
|
43
|
+
// Build choices for inquirer
|
|
44
|
+
const choices = [];
|
|
45
|
+
|
|
46
|
+
if (allowPersonal) {
|
|
47
|
+
choices.push({
|
|
48
|
+
name: chalk.cyan('Personal Account') +
|
|
49
|
+
chalk.gray(' (no organization)') +
|
|
50
|
+
(currentOrgId ? '' : chalk.green(' ← current')),
|
|
51
|
+
value: null,
|
|
52
|
+
short: 'Personal Account',
|
|
53
|
+
});
|
|
54
|
+
choices.push(new inquirer.Separator(chalk.gray('--- Organizations ---')));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
choices.push(...formattedOrgs.map(org => ({
|
|
58
|
+
name: `${chalk.green(org.name)} ${chalk.gray(`(${org.slug})`)} ${chalk.yellow(`[${org.role}]`)}${org.id === currentOrgId ? chalk.green(' ← current') : ''}`,
|
|
59
|
+
value: org,
|
|
60
|
+
short: org.name,
|
|
61
|
+
})));
|
|
62
|
+
|
|
63
|
+
// Calculate default selection
|
|
64
|
+
let defaultIndex = 0;
|
|
65
|
+
if (currentOrgId && allowPersonal) {
|
|
66
|
+
const orgIndex = formattedOrgs.findIndex(org => org.id === currentOrgId);
|
|
67
|
+
if (orgIndex >= 0) {
|
|
68
|
+
defaultIndex = orgIndex + 2; // +2 for personal + separator
|
|
69
|
+
}
|
|
70
|
+
} else if (currentOrgId && !allowPersonal) {
|
|
71
|
+
const orgIndex = formattedOrgs.findIndex(org => org.id === currentOrgId);
|
|
72
|
+
if (orgIndex >= 0) {
|
|
73
|
+
defaultIndex = orgIndex;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Prompt user to select
|
|
78
|
+
const answer = await inquirer.prompt([
|
|
79
|
+
{
|
|
80
|
+
type: 'select',
|
|
81
|
+
name: 'organization',
|
|
82
|
+
message: message,
|
|
83
|
+
choices: choices,
|
|
84
|
+
pageSize: 15,
|
|
85
|
+
default: defaultIndex,
|
|
86
|
+
},
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
return answer.organization;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Display organization selection result
|
|
94
|
+
* @param {Object|null} selectedOrg - Selected organization or null for personal account
|
|
95
|
+
*/
|
|
96
|
+
export function displayOrganizationSelection(selectedOrg) {
|
|
97
|
+
if (selectedOrg) {
|
|
98
|
+
console.log(chalk.green(`\n✅ Selected organization: ${chalk.bold(selectedOrg.name)}`));
|
|
99
|
+
console.log(chalk.gray(` Organization ID: ${selectedOrg.id}`));
|
|
100
|
+
console.log(chalk.gray(` Your role: ${selectedOrg.role}`));
|
|
101
|
+
} else {
|
|
102
|
+
console.log(chalk.green('\n✅ Using personal account context'));
|
|
103
|
+
}
|
|
104
|
+
}
|