@girardmedia/bootspring 2.0.24 → 2.0.26
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/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +1539 -1697
- package/dist/cli/index.js.map +1 -1
- package/dist/core/index.js +3 -0
- package/dist/core/index.js.map +1 -1
- package/dist/{index-DlXygBAE.d.ts → index-DJD8HAyK.d.ts} +1 -1
- package/dist/index.d.ts +199 -2
- package/dist/index.js +1542 -1697
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/index.js.map +1 -1
- package/intelligence/learning/insights.json +1 -1
- package/mcp/contracts/mcp-contract.v1.json +1 -1
- package/package.json +7 -4
- package/src/cli/org.ts +82 -50
- package/src/core/organizations.ts +42 -69
- package/src/core/policy-matrix.ts +25 -34
- package/src/types/index.ts +3 -0
- package/src/types/policy.ts +216 -0
- package/src/version.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@girardmedia/bootspring",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.26",
|
|
4
4
|
"description": "Development scaffolding with intelligence - AI-powered context, agents, and workflows for any MCP-compatible assistant",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -68,9 +68,11 @@
|
|
|
68
68
|
"start": "node bin/bootspring.js",
|
|
69
69
|
"dashboard": "node bin/bootspring.js dashboard",
|
|
70
70
|
"mcp": "node mcp/server.js",
|
|
71
|
-
"test": "
|
|
72
|
-
"test:watch": "
|
|
73
|
-
"test:coverage": "
|
|
71
|
+
"test": "vitest run",
|
|
72
|
+
"test:watch": "vitest",
|
|
73
|
+
"test:coverage": "vitest run --coverage",
|
|
74
|
+
"test:jest": "jest",
|
|
75
|
+
"test:jest:watch": "jest --watch",
|
|
74
76
|
"lint": "eslint .",
|
|
75
77
|
"lint:fix": "eslint . --fix",
|
|
76
78
|
"typecheck": "tsc --noEmit",
|
|
@@ -90,6 +92,7 @@
|
|
|
90
92
|
"@swc/core": "^1.15.13",
|
|
91
93
|
"@swc/jest": "^0.2.39",
|
|
92
94
|
"@types/node": "^25.3.1",
|
|
95
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
93
96
|
"eslint": "^9.39.2",
|
|
94
97
|
"globals": "^17.3.0",
|
|
95
98
|
"jest": "^29.7.0",
|
package/src/cli/org.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
/**
|
|
3
2
|
* Bootspring Organization Command
|
|
4
3
|
* Manage organization policies and members
|
|
@@ -16,17 +15,44 @@
|
|
|
16
15
|
* @command org
|
|
17
16
|
*/
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
import type {
|
|
19
|
+
Organization,
|
|
20
|
+
OrgMember,
|
|
21
|
+
Tier,
|
|
22
|
+
PolicyProfile,
|
|
23
|
+
MemberRole,
|
|
24
|
+
PolicyScopes,
|
|
25
|
+
} from '../types/policy';
|
|
26
|
+
|
|
27
|
+
import * as utils from '../core/utils';
|
|
28
|
+
import * as auth from '../core/auth';
|
|
29
|
+
import * as organizations from '../core/organizations';
|
|
30
|
+
|
|
31
|
+
// Type for API client methods
|
|
32
|
+
interface ApiClient {
|
|
33
|
+
listOrganizations: () => Promise<Organization[]>;
|
|
34
|
+
getOrganization: (orgId: string) => Promise<Organization>;
|
|
35
|
+
updateOrgPolicy: (orgId: string, data: { policyProfile: string }) => Promise<void>;
|
|
36
|
+
listOrgMembers: (orgId: string) => Promise<OrgMember[]>;
|
|
37
|
+
getMemberPolicy: (orgId: string, userId: string) => Promise<{ role: MemberRole; overrides?: Record<string, unknown> }>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Type for policies module
|
|
41
|
+
interface PoliciesModule {
|
|
42
|
+
resolvePolicyProfile: () => PolicyProfile;
|
|
43
|
+
getPolicyProfile: (profile: PolicyProfile) => { allowExternalSkills: boolean; blockedWorkflows: string[] };
|
|
44
|
+
getPolicyScopes: () => PolicyScopes;
|
|
45
|
+
listPolicyProfiles: () => Array<{ id: PolicyProfile; tier: string; description: string }>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Dynamic imports for modules that may not have proper types yet
|
|
49
|
+
const apiClient = require('../core/api-client') as ApiClient;
|
|
50
|
+
const policies = require('../core/policies') as PoliciesModule;
|
|
25
51
|
|
|
26
52
|
/**
|
|
27
53
|
* Run the org command
|
|
28
54
|
*/
|
|
29
|
-
async function run(args = []) {
|
|
55
|
+
export async function run(args: string[] = []): Promise<number> {
|
|
30
56
|
const subcommand = args[0] || 'list';
|
|
31
57
|
|
|
32
58
|
switch (subcommand) {
|
|
@@ -43,7 +69,8 @@ async function run(args = []) {
|
|
|
43
69
|
case 'help':
|
|
44
70
|
case '--help':
|
|
45
71
|
case '-h':
|
|
46
|
-
|
|
72
|
+
showHelp();
|
|
73
|
+
return 0;
|
|
47
74
|
default:
|
|
48
75
|
console.log(`${utils.COLORS.red}Unknown command: ${subcommand}${utils.COLORS.reset}`);
|
|
49
76
|
showHelp();
|
|
@@ -54,8 +81,8 @@ async function run(args = []) {
|
|
|
54
81
|
/**
|
|
55
82
|
* List user's organizations
|
|
56
83
|
*/
|
|
57
|
-
async function listOrganizations() {
|
|
58
|
-
if (!auth.isAuthenticated()) {
|
|
84
|
+
async function listOrganizations(): Promise<number> {
|
|
85
|
+
if (!(auth as { isAuthenticated?: () => boolean }).isAuthenticated?.()) {
|
|
59
86
|
console.log(`${utils.COLORS.yellow}Not authenticated. Run: bootspring auth login${utils.COLORS.reset}`);
|
|
60
87
|
return 1;
|
|
61
88
|
}
|
|
@@ -86,7 +113,8 @@ async function listOrganizations() {
|
|
|
86
113
|
|
|
87
114
|
return 0;
|
|
88
115
|
} catch (error) {
|
|
89
|
-
|
|
116
|
+
const err = error as Error;
|
|
117
|
+
spinner.fail(`Failed to fetch organizations: ${err.message}`);
|
|
90
118
|
return 1;
|
|
91
119
|
}
|
|
92
120
|
}
|
|
@@ -94,8 +122,9 @@ async function listOrganizations() {
|
|
|
94
122
|
/**
|
|
95
123
|
* Show organization info
|
|
96
124
|
*/
|
|
97
|
-
async function showOrgInfo(orgId) {
|
|
98
|
-
const
|
|
125
|
+
async function showOrgInfo(orgId?: string): Promise<number> {
|
|
126
|
+
const credentials = (auth as { getCredentials?: () => { orgId?: string } | null }).getCredentials?.();
|
|
127
|
+
const resolvedOrgId = orgId || process.env.BOOTSPRING_ORG_ID || credentials?.orgId;
|
|
99
128
|
|
|
100
129
|
if (!resolvedOrgId) {
|
|
101
130
|
console.log(`${utils.COLORS.yellow}No organization specified.${utils.COLORS.reset}`);
|
|
@@ -118,7 +147,7 @@ ${utils.COLORS.bold}Details:${utils.COLORS.reset}
|
|
|
118
147
|
ID: ${org.id}
|
|
119
148
|
Tier: ${getTierBadge(org.tier)}
|
|
120
149
|
Profile: ${getProfileBadge(org.policyProfile)}
|
|
121
|
-
Created: ${new Date(org.createdAt).toLocaleDateString()}
|
|
150
|
+
Created: ${org.createdAt ? new Date(org.createdAt).toLocaleDateString() : 'N/A'}
|
|
122
151
|
|
|
123
152
|
${utils.COLORS.bold}Policy:${utils.COLORS.reset}
|
|
124
153
|
External Skills: ${org.policy?.allowExternalSkills ? '✓ Allowed' : '✗ Blocked'}
|
|
@@ -140,7 +169,8 @@ ${utils.COLORS.bold}Members:${utils.COLORS.reset} ${org.memberCount || org.membe
|
|
|
140
169
|
|
|
141
170
|
return 0;
|
|
142
171
|
} catch (error) {
|
|
143
|
-
|
|
172
|
+
const err = error as Error;
|
|
173
|
+
spinner.fail(`Failed to fetch organization: ${err.message}`);
|
|
144
174
|
return 1;
|
|
145
175
|
}
|
|
146
176
|
}
|
|
@@ -148,7 +178,7 @@ ${utils.COLORS.bold}Members:${utils.COLORS.reset} ${org.memberCount || org.membe
|
|
|
148
178
|
/**
|
|
149
179
|
* Handle policy subcommands
|
|
150
180
|
*/
|
|
151
|
-
async function handlePolicyCommand(args) {
|
|
181
|
+
async function handlePolicyCommand(args: string[]): Promise<number> {
|
|
152
182
|
const action = args[0] || 'get';
|
|
153
183
|
|
|
154
184
|
switch (action) {
|
|
@@ -170,7 +200,7 @@ async function handlePolicyCommand(args) {
|
|
|
170
200
|
/**
|
|
171
201
|
* Show current policy
|
|
172
202
|
*/
|
|
173
|
-
async function showPolicy(orgId) {
|
|
203
|
+
async function showPolicy(orgId?: string): Promise<number> {
|
|
174
204
|
try {
|
|
175
205
|
const summary = await organizations.getOrgPolicySummary({ orgId });
|
|
176
206
|
|
|
@@ -197,26 +227,27 @@ ${utils.COLORS.cyan}${utils.COLORS.bold}Organization Policy${utils.COLORS.reset}
|
|
|
197
227
|
${utils.COLORS.dim}${'─'.repeat(50)}${utils.COLORS.reset}
|
|
198
228
|
|
|
199
229
|
Organization: ${summary.orgName || summary.orgId}
|
|
200
|
-
Tier: ${getTierBadge(summary.tier)}
|
|
201
|
-
Profile: ${getProfileBadge(summary.profile)}
|
|
202
|
-
Your Role: ${getRoleBadge(summary.role)}
|
|
230
|
+
Tier: ${getTierBadge(summary.tier!)}
|
|
231
|
+
Profile: ${getProfileBadge(summary.profile!)}
|
|
232
|
+
Your Role: ${getRoleBadge(summary.role!)}
|
|
203
233
|
|
|
204
234
|
${utils.COLORS.bold}Scope Summary:${utils.COLORS.reset}
|
|
205
235
|
Allowed Scopes: ${utils.COLORS.green}${summary.allowedScopes}${utils.COLORS.reset}
|
|
206
236
|
Blocked Scopes: ${utils.COLORS.red}${summary.blockedScopes}${utils.COLORS.reset}
|
|
207
237
|
|
|
208
238
|
${utils.COLORS.bold}Limits:${utils.COLORS.reset}
|
|
209
|
-
Skills/Day: ${formatLimit(summary.limits
|
|
210
|
-
Workflows/Day: ${formatLimit(summary.limits
|
|
211
|
-
Agent Calls/Day: ${formatLimit(summary.limits
|
|
212
|
-
Team Members: ${formatLimit(summary.limits
|
|
239
|
+
Skills/Day: ${formatLimit(summary.limits?.skillsPerDay)}
|
|
240
|
+
Workflows/Day: ${formatLimit(summary.limits?.workflowsPerDay)}
|
|
241
|
+
Agent Calls/Day: ${formatLimit(summary.limits?.agentInvocationsPerDay)}
|
|
242
|
+
Team Members: ${formatLimit(summary.limits?.teamMembers)}
|
|
213
243
|
|
|
214
|
-
${summary.overrides.length > 0 ? `${utils.COLORS.bold}Active Overrides:${utils.COLORS.reset} ${summary.overrides.join(', ')}` : ''}
|
|
244
|
+
${summary.overrides && summary.overrides.length > 0 ? `${utils.COLORS.bold}Active Overrides:${utils.COLORS.reset} ${summary.overrides.join(', ')}` : ''}
|
|
215
245
|
`);
|
|
216
246
|
|
|
217
247
|
return 0;
|
|
218
248
|
} catch (error) {
|
|
219
|
-
|
|
249
|
+
const err = error as Error;
|
|
250
|
+
console.log(`${utils.COLORS.red}Failed to get policy: ${err.message}${utils.COLORS.reset}`);
|
|
220
251
|
return 1;
|
|
221
252
|
}
|
|
222
253
|
}
|
|
@@ -224,7 +255,7 @@ ${summary.overrides.length > 0 ? `${utils.COLORS.bold}Active Overrides:${utils.C
|
|
|
224
255
|
/**
|
|
225
256
|
* Set policy profile
|
|
226
257
|
*/
|
|
227
|
-
async function setPolicy(profile, orgId) {
|
|
258
|
+
async function setPolicy(profile?: string, orgId?: string): Promise<number> {
|
|
228
259
|
if (!profile) {
|
|
229
260
|
console.log(`${utils.COLORS.yellow}Please specify a profile: startup, regulated, or enterprise${utils.COLORS.reset}`);
|
|
230
261
|
return 1;
|
|
@@ -261,7 +292,8 @@ To set org-level policy, specify an org ID:
|
|
|
261
292
|
spinner.succeed(`Policy updated to "${profile}"`);
|
|
262
293
|
return 0;
|
|
263
294
|
} catch (error) {
|
|
264
|
-
|
|
295
|
+
const err = error as Error;
|
|
296
|
+
spinner.fail(`Failed to update policy: ${err.message}`);
|
|
265
297
|
return 1;
|
|
266
298
|
}
|
|
267
299
|
}
|
|
@@ -269,7 +301,7 @@ To set org-level policy, specify an org ID:
|
|
|
269
301
|
/**
|
|
270
302
|
* Show available scopes
|
|
271
303
|
*/
|
|
272
|
-
function showScopes() {
|
|
304
|
+
function showScopes(): number {
|
|
273
305
|
const scopes = policies.getPolicyScopes();
|
|
274
306
|
|
|
275
307
|
console.log(`
|
|
@@ -279,7 +311,7 @@ ${utils.COLORS.dim}${'─'.repeat(50)}${utils.COLORS.reset}
|
|
|
279
311
|
|
|
280
312
|
for (const [category, categoryScopes] of Object.entries(scopes)) {
|
|
281
313
|
console.log(`${utils.COLORS.bold}${category}:${utils.COLORS.reset}`);
|
|
282
|
-
for (const [name, scope] of Object.entries(categoryScopes)) {
|
|
314
|
+
for (const [name, scope] of Object.entries(categoryScopes as Record<string, string>)) {
|
|
283
315
|
console.log(` ${utils.COLORS.cyan}${scope}${utils.COLORS.reset} ${utils.COLORS.dim}(${name})${utils.COLORS.reset}`);
|
|
284
316
|
}
|
|
285
317
|
console.log();
|
|
@@ -291,15 +323,15 @@ ${utils.COLORS.dim}${'─'.repeat(50)}${utils.COLORS.reset}
|
|
|
291
323
|
/**
|
|
292
324
|
* Show available profiles
|
|
293
325
|
*/
|
|
294
|
-
function showProfiles() {
|
|
295
|
-
const
|
|
326
|
+
function showProfiles(): number {
|
|
327
|
+
const profilesList = policies.listPolicyProfiles();
|
|
296
328
|
|
|
297
329
|
console.log(`
|
|
298
330
|
${utils.COLORS.cyan}${utils.COLORS.bold}Policy Profiles${utils.COLORS.reset}
|
|
299
331
|
${utils.COLORS.dim}${'─'.repeat(50)}${utils.COLORS.reset}
|
|
300
332
|
`);
|
|
301
333
|
|
|
302
|
-
for (const profile of
|
|
334
|
+
for (const profile of profilesList) {
|
|
303
335
|
console.log(` ${getProfileBadge(profile.id)} ${utils.COLORS.dim}(${profile.tier} tier+)${utils.COLORS.reset}`);
|
|
304
336
|
console.log(` ${utils.COLORS.dim}${profile.description}${utils.COLORS.reset}`);
|
|
305
337
|
console.log();
|
|
@@ -311,7 +343,7 @@ ${utils.COLORS.dim}${'─'.repeat(50)}${utils.COLORS.reset}
|
|
|
311
343
|
/**
|
|
312
344
|
* List organization members
|
|
313
345
|
*/
|
|
314
|
-
async function listMembers(orgId) {
|
|
346
|
+
async function listMembers(orgId?: string): Promise<number> {
|
|
315
347
|
const resolvedOrgId = orgId || process.env.BOOTSPRING_ORG_ID;
|
|
316
348
|
|
|
317
349
|
if (!resolvedOrgId) {
|
|
@@ -335,7 +367,8 @@ async function listMembers(orgId) {
|
|
|
335
367
|
|
|
336
368
|
return 0;
|
|
337
369
|
} catch (error) {
|
|
338
|
-
|
|
370
|
+
const err = error as Error;
|
|
371
|
+
spinner.fail(`Failed to fetch members: ${err.message}`);
|
|
339
372
|
return 1;
|
|
340
373
|
}
|
|
341
374
|
}
|
|
@@ -343,7 +376,7 @@ async function listMembers(orgId) {
|
|
|
343
376
|
/**
|
|
344
377
|
* Handle member subcommands
|
|
345
378
|
*/
|
|
346
|
-
async function handleMemberCommand(args) {
|
|
379
|
+
async function handleMemberCommand(args: string[]): Promise<number> {
|
|
347
380
|
const userId = args[0];
|
|
348
381
|
const action = args[1] || 'info';
|
|
349
382
|
|
|
@@ -365,7 +398,7 @@ async function handleMemberCommand(args) {
|
|
|
365
398
|
/**
|
|
366
399
|
* Show member policy
|
|
367
400
|
*/
|
|
368
|
-
async function showMemberPolicy(userId, orgId) {
|
|
401
|
+
async function showMemberPolicy(userId: string, orgId?: string): Promise<number> {
|
|
369
402
|
const resolvedOrgId = orgId || process.env.BOOTSPRING_ORG_ID;
|
|
370
403
|
|
|
371
404
|
if (!resolvedOrgId) {
|
|
@@ -394,15 +427,16 @@ ${utils.COLORS.bold}Policy Overrides:${utils.COLORS.reset}
|
|
|
394
427
|
|
|
395
428
|
return 0;
|
|
396
429
|
} catch (error) {
|
|
397
|
-
|
|
430
|
+
const err = error as Error;
|
|
431
|
+
spinner.fail(`Failed to fetch member policy: ${err.message}`);
|
|
398
432
|
return 1;
|
|
399
433
|
}
|
|
400
434
|
}
|
|
401
435
|
|
|
402
436
|
// Helper functions
|
|
403
437
|
|
|
404
|
-
function getTierBadge(tier) {
|
|
405
|
-
const badges = {
|
|
438
|
+
function getTierBadge(tier: Tier): string {
|
|
439
|
+
const badges: Record<Tier, string> = {
|
|
406
440
|
free: `${utils.COLORS.dim}[free]${utils.COLORS.reset}`,
|
|
407
441
|
pro: `${utils.COLORS.blue}[pro]${utils.COLORS.reset}`,
|
|
408
442
|
team: `${utils.COLORS.green}[team]${utils.COLORS.reset}`,
|
|
@@ -411,17 +445,17 @@ function getTierBadge(tier) {
|
|
|
411
445
|
return badges[tier] || `[${tier}]`;
|
|
412
446
|
}
|
|
413
447
|
|
|
414
|
-
function getProfileBadge(profile) {
|
|
415
|
-
const badges = {
|
|
448
|
+
function getProfileBadge(profile: PolicyProfile): string {
|
|
449
|
+
const badges: Record<PolicyProfile, string> = {
|
|
416
450
|
startup: `${utils.COLORS.cyan}startup${utils.COLORS.reset}`,
|
|
417
451
|
regulated: `${utils.COLORS.yellow}regulated${utils.COLORS.reset}`,
|
|
418
452
|
enterprise: `${utils.COLORS.magenta}enterprise${utils.COLORS.reset}`
|
|
419
453
|
};
|
|
420
|
-
return badges[profile] || profile;
|
|
454
|
+
return badges[profile] || String(profile);
|
|
421
455
|
}
|
|
422
456
|
|
|
423
|
-
function getRoleBadge(role) {
|
|
424
|
-
const badges = {
|
|
457
|
+
function getRoleBadge(role: MemberRole): string {
|
|
458
|
+
const badges: Record<MemberRole, string> = {
|
|
425
459
|
owner: `${utils.COLORS.red}(owner)${utils.COLORS.reset}`,
|
|
426
460
|
admin: `${utils.COLORS.yellow}(admin)${utils.COLORS.reset}`,
|
|
427
461
|
member: `${utils.COLORS.dim}(member)${utils.COLORS.reset}`,
|
|
@@ -430,13 +464,13 @@ function getRoleBadge(role) {
|
|
|
430
464
|
return badges[role] || `(${role})`;
|
|
431
465
|
}
|
|
432
466
|
|
|
433
|
-
function formatLimit(value) {
|
|
467
|
+
function formatLimit(value?: number): string {
|
|
434
468
|
if (value === -1) return `${utils.COLORS.green}Unlimited${utils.COLORS.reset}`;
|
|
435
469
|
if (value === undefined) return `${utils.COLORS.dim}N/A${utils.COLORS.reset}`;
|
|
436
470
|
return value.toLocaleString();
|
|
437
471
|
}
|
|
438
472
|
|
|
439
|
-
function showHelp() {
|
|
473
|
+
function showHelp(): void {
|
|
440
474
|
console.log(`
|
|
441
475
|
${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Organization${utils.COLORS.reset}
|
|
442
476
|
${utils.COLORS.dim}Manage organization policies and members${utils.COLORS.reset}
|
|
@@ -471,5 +505,3 @@ ${utils.COLORS.bold}Examples:${utils.COLORS.reset}
|
|
|
471
505
|
bootspring org members
|
|
472
506
|
`);
|
|
473
507
|
}
|
|
474
|
-
|
|
475
|
-
export { run };
|
|
@@ -1,45 +1,35 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
/**
|
|
3
2
|
* Organizations Module
|
|
4
3
|
* Org-level policy resolution and caching
|
|
5
4
|
* @package bootspring
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
import type {
|
|
8
|
+
Organization,
|
|
9
|
+
OrgMember,
|
|
10
|
+
OrgContext,
|
|
11
|
+
OrgPolicyAccessResult,
|
|
12
|
+
OrgPolicySummary,
|
|
13
|
+
OrgOptions,
|
|
14
|
+
CacheEntry,
|
|
15
|
+
MemberRole,
|
|
16
|
+
} from '../types/policy';
|
|
17
|
+
|
|
18
|
+
import * as apiClient from './api-client';
|
|
19
|
+
import * as policyMatrix from './policy-matrix';
|
|
20
|
+
import * as auth from './auth';
|
|
11
21
|
|
|
12
22
|
// Cache for org data (5 minute TTL)
|
|
13
23
|
const ORG_CACHE_TTL = 5 * 60 * 1000;
|
|
14
|
-
const orgCache = new Map();
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Organization structure
|
|
18
|
-
* @typedef {object} Organization
|
|
19
|
-
* @property {string} id - Organization ID
|
|
20
|
-
* @property {string} name - Organization name
|
|
21
|
-
* @property {string} tier - Subscription tier (team/enterprise)
|
|
22
|
-
* @property {string} policyProfile - Default policy profile
|
|
23
|
-
* @property {object} settings - Org settings
|
|
24
|
-
* @property {OrgMember[]} members - Organization members
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Organization member structure
|
|
29
|
-
* @typedef {object} OrgMember
|
|
30
|
-
* @property {string} userId - User ID
|
|
31
|
-
* @property {string} email - User email
|
|
32
|
-
* @property {string} role - Member role (owner/admin/member/viewer)
|
|
33
|
-
* @property {object} policyOverrides - Per-member policy overrides
|
|
34
|
-
*/
|
|
24
|
+
const orgCache = new Map<string, CacheEntry<Organization>>();
|
|
35
25
|
|
|
36
26
|
/**
|
|
37
27
|
* Get organization from cache or API
|
|
38
|
-
* @param {string} orgId - Organization ID
|
|
39
|
-
* @param {object} options - Options including apiKey
|
|
40
|
-
* @returns {Promise<Organization|null>}
|
|
41
28
|
*/
|
|
42
|
-
async function getOrganization(
|
|
29
|
+
export async function getOrganization(
|
|
30
|
+
orgId: string,
|
|
31
|
+
options: OrgOptions = {}
|
|
32
|
+
): Promise<Organization | null> {
|
|
43
33
|
if (!orgId) return null;
|
|
44
34
|
|
|
45
35
|
const cacheKey = `org:${orgId}`;
|
|
@@ -50,11 +40,11 @@ async function getOrganization(orgId, options = {}) {
|
|
|
50
40
|
}
|
|
51
41
|
|
|
52
42
|
try {
|
|
53
|
-
const org = await apiClient.getOrganization(orgId, options);
|
|
43
|
+
const org = await (apiClient as { getOrganization?: (id: string, opts?: OrgOptions) => Promise<Organization | null> }).getOrganization?.(orgId, options);
|
|
54
44
|
if (org) {
|
|
55
45
|
orgCache.set(cacheKey, { data: org, timestamp: Date.now() });
|
|
56
46
|
}
|
|
57
|
-
return org;
|
|
47
|
+
return org ?? null;
|
|
58
48
|
} catch {
|
|
59
49
|
// Return cached data if available, even if stale
|
|
60
50
|
return cached?.data || null;
|
|
@@ -63,12 +53,12 @@ async function getOrganization(orgId, options = {}) {
|
|
|
63
53
|
|
|
64
54
|
/**
|
|
65
55
|
* Get organization member
|
|
66
|
-
* @param {string} orgId - Organization ID
|
|
67
|
-
* @param {string} userId - User ID
|
|
68
|
-
* @param {object} options - Options
|
|
69
|
-
* @returns {Promise<OrgMember|null>}
|
|
70
56
|
*/
|
|
71
|
-
async function getOrgMember(
|
|
57
|
+
export async function getOrgMember(
|
|
58
|
+
orgId: string,
|
|
59
|
+
userId: string,
|
|
60
|
+
options: OrgOptions = {}
|
|
61
|
+
): Promise<OrgMember | null> {
|
|
72
62
|
const org = await getOrganization(orgId, options);
|
|
73
63
|
if (!org?.members) return null;
|
|
74
64
|
return org.members.find(m => m.userId === userId) || null;
|
|
@@ -76,14 +66,13 @@ async function getOrgMember(orgId, userId, options = {}) {
|
|
|
76
66
|
|
|
77
67
|
/**
|
|
78
68
|
* Resolve organization context from credentials/env
|
|
79
|
-
* @param {object} options - Options
|
|
80
|
-
* @returns {Promise<object>} Org context
|
|
81
69
|
*/
|
|
82
|
-
async function resolveOrgContext(options = {}) {
|
|
70
|
+
export async function resolveOrgContext(options: OrgOptions = {}): Promise<OrgContext> {
|
|
83
71
|
// Try to get org ID from various sources
|
|
72
|
+
const credentials = (auth as { getCredentials?: () => { orgId?: string; userId?: string } | null }).getCredentials?.();
|
|
84
73
|
const orgId = options.orgId
|
|
85
74
|
|| process.env.BOOTSPRING_ORG_ID
|
|
86
|
-
||
|
|
75
|
+
|| credentials?.orgId;
|
|
87
76
|
|
|
88
77
|
if (!orgId) {
|
|
89
78
|
return {
|
|
@@ -107,7 +96,7 @@ async function resolveOrgContext(options = {}) {
|
|
|
107
96
|
}
|
|
108
97
|
|
|
109
98
|
// Get current user's membership
|
|
110
|
-
const userId = options.userId ||
|
|
99
|
+
const userId = options.userId || credentials?.userId;
|
|
111
100
|
const member = userId ? await getOrgMember(orgId, userId, options) : null;
|
|
112
101
|
|
|
113
102
|
// Build effective policy
|
|
@@ -129,27 +118,24 @@ async function resolveOrgContext(options = {}) {
|
|
|
129
118
|
|
|
130
119
|
/**
|
|
131
120
|
* Check if user has permission in org
|
|
132
|
-
* @param {string} permission - Permission to check
|
|
133
|
-
* @param {object} orgContext - Organization context
|
|
134
|
-
* @returns {boolean}
|
|
135
121
|
*/
|
|
136
|
-
function hasOrgPermission(permission, orgContext) {
|
|
122
|
+
export function hasOrgPermission(permission: string, orgContext: OrgContext | null): boolean {
|
|
137
123
|
if (!orgContext?.hasOrg) return false;
|
|
138
124
|
|
|
139
|
-
const role = orgContext.role || 'viewer';
|
|
125
|
+
const role: MemberRole = orgContext.role || 'viewer';
|
|
140
126
|
const rolePerms = policyMatrix.ROLE_PERMISSIONS[role];
|
|
141
127
|
|
|
142
128
|
if (!rolePerms) return false;
|
|
143
|
-
return rolePerms[permission] === true;
|
|
129
|
+
return (rolePerms as Record<string, boolean>)[permission] === true;
|
|
144
130
|
}
|
|
145
131
|
|
|
146
132
|
/**
|
|
147
133
|
* Check policy access for a scope
|
|
148
|
-
* @param {string} scope - Scope to check (e.g., 'skills.external')
|
|
149
|
-
* @param {object} options - Options including orgId
|
|
150
|
-
* @returns {Promise<object>} Access result
|
|
151
134
|
*/
|
|
152
|
-
async function checkOrgPolicyAccess(
|
|
135
|
+
export async function checkOrgPolicyAccess(
|
|
136
|
+
scope: string,
|
|
137
|
+
options: OrgOptions = {}
|
|
138
|
+
): Promise<OrgPolicyAccessResult> {
|
|
153
139
|
const orgContext = await resolveOrgContext(options);
|
|
154
140
|
|
|
155
141
|
// If no org, fall back to user-level policy
|
|
@@ -165,7 +151,7 @@ async function checkOrgPolicyAccess(scope, options = {}) {
|
|
|
165
151
|
return {
|
|
166
152
|
...result,
|
|
167
153
|
hasOrgPolicy: true,
|
|
168
|
-
orgId: orgContext.orgId,
|
|
154
|
+
orgId: orgContext.orgId ?? undefined,
|
|
169
155
|
tier: orgContext.policy.tier,
|
|
170
156
|
profile: orgContext.policy.profile
|
|
171
157
|
};
|
|
@@ -173,10 +159,8 @@ async function checkOrgPolicyAccess(scope, options = {}) {
|
|
|
173
159
|
|
|
174
160
|
/**
|
|
175
161
|
* Get org policy summary for display
|
|
176
|
-
* @param {object} options - Options
|
|
177
|
-
* @returns {Promise<object>} Policy summary
|
|
178
162
|
*/
|
|
179
|
-
async function getOrgPolicySummary(options = {}) {
|
|
163
|
+
export async function getOrgPolicySummary(options: OrgOptions = {}): Promise<OrgPolicySummary> {
|
|
180
164
|
const orgContext = await resolveOrgContext(options);
|
|
181
165
|
|
|
182
166
|
if (!orgContext.hasOrg) {
|
|
@@ -186,10 +170,10 @@ async function getOrgPolicySummary(options = {}) {
|
|
|
186
170
|
};
|
|
187
171
|
}
|
|
188
172
|
|
|
189
|
-
const policy = orgContext.policy
|
|
173
|
+
const policy = orgContext.policy!;
|
|
190
174
|
return {
|
|
191
175
|
hasOrg: true,
|
|
192
|
-
orgId: orgContext.orgId,
|
|
176
|
+
orgId: orgContext.orgId ?? undefined,
|
|
193
177
|
orgName: orgContext.org?.name,
|
|
194
178
|
tier: policy.tier,
|
|
195
179
|
profile: policy.profile,
|
|
@@ -203,22 +187,11 @@ async function getOrgPolicySummary(options = {}) {
|
|
|
203
187
|
|
|
204
188
|
/**
|
|
205
189
|
* Clear organization cache
|
|
206
|
-
* @param {string} [orgId] - Specific org to clear, or all if not specified
|
|
207
190
|
*/
|
|
208
|
-
function clearOrgCache(orgId) {
|
|
191
|
+
export function clearOrgCache(orgId?: string): void {
|
|
209
192
|
if (orgId) {
|
|
210
193
|
orgCache.delete(`org:${orgId}`);
|
|
211
194
|
} else {
|
|
212
195
|
orgCache.clear();
|
|
213
196
|
}
|
|
214
197
|
}
|
|
215
|
-
|
|
216
|
-
module.exports = {
|
|
217
|
-
getOrganization,
|
|
218
|
-
getOrgMember,
|
|
219
|
-
resolveOrgContext,
|
|
220
|
-
hasOrgPermission,
|
|
221
|
-
checkOrgPolicyAccess,
|
|
222
|
-
getOrgPolicySummary,
|
|
223
|
-
clearOrgCache
|
|
224
|
-
};
|