@unifiedmemory/cli 1.3.9 → 1.3.11
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 +49 -8
- package/commands/login.js +3 -1
- package/index.js +1 -1
- package/lib/token-refresh.js +0 -4
- package/lib/token-validation.js +85 -1
- package/package.json +1 -1
package/commands/init.js
CHANGED
|
@@ -69,17 +69,58 @@ export async function init(options = {}) {
|
|
|
69
69
|
console.log(chalk.green(`✓ Organization: ${authData.org_id}`));
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
// Step
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
// Step 1.5: Check for existing config
|
|
73
|
+
const umDir = path.join(process.cwd(), '.um');
|
|
74
|
+
const configPath = path.join(umDir, 'config.json');
|
|
75
|
+
let projectData = null;
|
|
76
|
+
let skipProjectSetup = false;
|
|
77
|
+
|
|
78
|
+
if (fs.existsSync(configPath)) {
|
|
79
|
+
try {
|
|
80
|
+
const existingConfig = fs.readJSONSync(configPath);
|
|
81
|
+
|
|
82
|
+
console.log(chalk.cyan('\n📋 Existing configuration found:\n'));
|
|
83
|
+
console.log(chalk.gray(` Project: ${existingConfig.project_name}`));
|
|
84
|
+
console.log(chalk.gray(` Project ID: ${existingConfig.project_id}`));
|
|
85
|
+
console.log(chalk.gray(` Organization: ${existingConfig.org_id}`));
|
|
86
|
+
console.log('');
|
|
87
|
+
|
|
88
|
+
const { override } = await inquirer.prompt([{
|
|
89
|
+
type: 'confirm',
|
|
90
|
+
name: 'override',
|
|
91
|
+
message: 'Override existing configuration?',
|
|
92
|
+
default: false,
|
|
93
|
+
}]);
|
|
94
|
+
|
|
95
|
+
if (!override) {
|
|
96
|
+
// Use existing config - skip project selection
|
|
97
|
+
projectData = {
|
|
98
|
+
project_id: existingConfig.project_id,
|
|
99
|
+
project_name: existingConfig.project_name,
|
|
100
|
+
project_slug: existingConfig.project_slug,
|
|
101
|
+
};
|
|
102
|
+
skipProjectSetup = true;
|
|
103
|
+
console.log(chalk.green(`\n✓ Using existing project: ${projectData.project_name}`));
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.log(chalk.yellow(`\n⚠ Could not read existing config: ${error.message}`));
|
|
107
|
+
console.log(chalk.gray('Continuing with new project setup...\n'));
|
|
108
|
+
}
|
|
77
109
|
}
|
|
78
110
|
|
|
79
|
-
|
|
111
|
+
// Step 2: Select/create project (skip if using existing config)
|
|
112
|
+
if (!skipProjectSetup) {
|
|
113
|
+
projectData = await selectOrCreateProject(authData, options);
|
|
114
|
+
if (!projectData) {
|
|
115
|
+
console.error(chalk.red('❌ Project setup failed'));
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
console.log(chalk.green(`✓ Project: ${projectData.project_name} (${projectData.project_id})`));
|
|
80
120
|
|
|
81
|
-
|
|
82
|
-
|
|
121
|
+
// Step 3: Save project config (only if we selected a new project)
|
|
122
|
+
await saveProjectConfig(authData, projectData);
|
|
123
|
+
}
|
|
83
124
|
|
|
84
125
|
// Step 3.5: Fetch available MCP tools for permissions
|
|
85
126
|
let mcpToolPermissions = null;
|
package/commands/login.js
CHANGED
|
@@ -222,6 +222,7 @@ export async function login() {
|
|
|
222
222
|
);
|
|
223
223
|
|
|
224
224
|
// Update saved token with org-scoped version
|
|
225
|
+
// Preserve originalSid for recovery purposes (org-scoped token won't have sid)
|
|
225
226
|
saveToken({
|
|
226
227
|
accessToken: tokenData.access_token,
|
|
227
228
|
idToken: orgToken.jwt,
|
|
@@ -230,7 +231,8 @@ export async function login() {
|
|
|
230
231
|
expiresIn: tokenData.expires_in,
|
|
231
232
|
receivedAt: Date.now(),
|
|
232
233
|
decoded: parseJWT(orgToken.jwt),
|
|
233
|
-
selectedOrg: selectedOrg
|
|
234
|
+
selectedOrg: selectedOrg,
|
|
235
|
+
originalSid: decoded.sid // Preserve session ID from original OAuth token
|
|
234
236
|
});
|
|
235
237
|
|
|
236
238
|
console.log(chalk.green(`\n✅ Using organization context: ${chalk.bold(selectedOrg.name)}`));
|
package/index.js
CHANGED
package/lib/token-refresh.js
CHANGED
|
@@ -77,10 +77,6 @@ export async function refreshAccessToken(tokenData) {
|
|
|
77
77
|
let finalIdToken = newTokenData.id_token;
|
|
78
78
|
let decoded = parseJWT(refreshedToken);
|
|
79
79
|
|
|
80
|
-
// Debug: log what we got from refresh
|
|
81
|
-
console.error(` Refreshed token has sid: ${decoded?.sid ? 'yes' : 'no'}`);
|
|
82
|
-
console.error(` selectedOrg: ${tokenData.selectedOrg?.id || 'none'}`);
|
|
83
|
-
|
|
84
80
|
// If we have org context, get org-scoped token to ensure JWT has org claims
|
|
85
81
|
if (tokenData.selectedOrg?.id && decoded?.sid) {
|
|
86
82
|
try {
|
package/lib/token-validation.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { getToken } from './token-storage.js';
|
|
1
|
+
import { getToken, saveToken } from './token-storage.js';
|
|
2
2
|
import { isTokenExpired, refreshAccessToken } from './token-refresh.js';
|
|
3
|
+
import { getOrgScopedToken } from './clerk-api.js';
|
|
4
|
+
import { parseJWT } from './jwt-utils.js';
|
|
5
|
+
import { config } from './config.js';
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Load token and refresh if expired
|
|
@@ -43,5 +46,86 @@ export async function loadAndRefreshToken(requireAuth = true) {
|
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
// Check if token has org context but lacks org claims in JWT
|
|
50
|
+
// This can happen if getOrgScopedToken() failed during login
|
|
51
|
+
if (tokenData.selectedOrg?.id && !tokenData.decoded?.o) {
|
|
52
|
+
console.error('⚠️ Token missing org claims, attempting to fix...');
|
|
53
|
+
|
|
54
|
+
// Check for sid in decoded token or preserved originalSid from login
|
|
55
|
+
let sessionId = tokenData.decoded?.sid || tokenData.originalSid;
|
|
56
|
+
let currentToken = tokenData.idToken || tokenData.accessToken;
|
|
57
|
+
|
|
58
|
+
// If no sid, try to refresh first to get a new base token with sid
|
|
59
|
+
if (!sessionId && tokenData.refresh_token) {
|
|
60
|
+
console.error(' Refreshing to obtain session ID...');
|
|
61
|
+
try {
|
|
62
|
+
const tokenParams = {
|
|
63
|
+
client_id: config.clerkClientId,
|
|
64
|
+
refresh_token: tokenData.refresh_token,
|
|
65
|
+
grant_type: 'refresh_token',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
if (config.clerkClientSecret) {
|
|
69
|
+
tokenParams.client_secret = config.clerkClientSecret;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const response = await fetch(`https://${config.clerkDomain}/oauth/token`, {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
75
|
+
body: new URLSearchParams(tokenParams),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (response.ok) {
|
|
79
|
+
const newTokenData = await response.json();
|
|
80
|
+
const refreshedToken = newTokenData.id_token || newTokenData.access_token;
|
|
81
|
+
const refreshedDecoded = parseJWT(refreshedToken);
|
|
82
|
+
|
|
83
|
+
sessionId = refreshedDecoded?.sid;
|
|
84
|
+
currentToken = refreshedToken;
|
|
85
|
+
|
|
86
|
+
// Update tokenData with refreshed values
|
|
87
|
+
tokenData.accessToken = newTokenData.access_token;
|
|
88
|
+
tokenData.idToken = newTokenData.id_token;
|
|
89
|
+
tokenData.refresh_token = newTokenData.refresh_token || tokenData.refresh_token;
|
|
90
|
+
tokenData.decoded = refreshedDecoded;
|
|
91
|
+
|
|
92
|
+
if (sessionId) {
|
|
93
|
+
console.error(' ✓ Obtained session ID');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error(` Could not refresh token: ${error.message}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Now try to get org-scoped token if we have sessionId
|
|
102
|
+
if (sessionId) {
|
|
103
|
+
try {
|
|
104
|
+
const orgToken = await getOrgScopedToken(
|
|
105
|
+
sessionId,
|
|
106
|
+
tokenData.selectedOrg.id,
|
|
107
|
+
currentToken
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const decoded = parseJWT(orgToken.jwt);
|
|
111
|
+
const updatedToken = {
|
|
112
|
+
...tokenData,
|
|
113
|
+
idToken: orgToken.jwt,
|
|
114
|
+
decoded: decoded,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
saveToken(updatedToken);
|
|
118
|
+
console.error('✓ Token updated with org claims');
|
|
119
|
+
return updatedToken;
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error(`⚠️ Could not get org-scoped token: ${error.message}`);
|
|
122
|
+
// Continue with existing token - API calls may fail
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
console.error('⚠️ Could not obtain session ID for org-scoped token');
|
|
126
|
+
console.error(' Please run: um login');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
46
130
|
return tokenData;
|
|
47
131
|
}
|