figmanage 1.2.2 → 1.2.3

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.
@@ -459,14 +459,15 @@ export async function quarterlyDesignOpsReport(config, params) {
459
459
  params: { plan_parent_id: orgId, plan_type: 'organization' },
460
460
  }),
461
461
  ]);
462
- // Paginate all members (cursor-based, max 500)
462
+ // Paginate all members (cursor-based)
463
463
  const errors = [];
464
464
  const allMembers = [];
465
465
  let cursor;
466
- const maxPages = 20;
466
+ let membersComplete = true;
467
+ const maxPages = 200; // safety cap: 200 * 50 = 10,000 members
467
468
  for (let page = 0; page < maxPages; page++) {
468
469
  try {
469
- const params = { page_size: 25 };
470
+ const params = { page_size: 50 };
470
471
  if (cursor)
471
472
  params.cursor = cursor;
472
473
  const res = await api.get(`/api/v2/orgs/${orgId}/org_users`, { params });
@@ -476,14 +477,19 @@ export async function quarterlyDesignOpsReport(config, params) {
476
477
  break;
477
478
  allMembers.push(...batch);
478
479
  cursor = Array.isArray(meta.cursor) ? meta.cursor[0] : meta.cursor;
479
- if (!cursor || batch.length < 25)
480
+ if (!cursor || batch.length < 50)
480
481
  break;
481
482
  }
482
483
  catch (e) {
483
- errors.push(`members: pagination stopped at page ${page} (${e.response?.status || e.message})`);
484
+ membersComplete = false;
485
+ errors.push(`members: pagination stopped at page ${page + 1} of ${maxPages}, fetched ${allMembers.length} members (${e.response?.status || e.message})`);
484
486
  break;
485
487
  }
486
488
  }
489
+ if (cursor && allMembers.length >= maxPages * 50) {
490
+ membersComplete = false;
491
+ errors.push(`members: hit ${maxPages}-page safety cap at ${allMembers.length} members, org may have more`);
492
+ }
487
493
  // Process teams
488
494
  const teamsRaw = teamsResult.status === 'fulfilled'
489
495
  ? (teamsResult.value.data?.meta || teamsResult.value.data || [])
@@ -612,6 +618,7 @@ export async function quarterlyDesignOpsReport(config, params) {
612
618
  total_teams: teams.length,
613
619
  total_members: allMembers.length,
614
620
  total_paid_seats: totalPaid,
621
+ members_complete: membersComplete,
615
622
  },
616
623
  seat_utilization: {
617
624
  active_paid: activePaid,
@@ -47,17 +47,7 @@ export declare function seatOptimization(config: AuthConfig, params: {
47
47
  org_id?: string;
48
48
  days_inactive: number;
49
49
  include_cost: boolean;
50
- }): Promise<{
51
- summary: {
52
- total_paid: number;
53
- inactive_paid: number;
54
- monthly_waste_cents: any;
55
- annual_savings_cents: number;
56
- };
57
- seat_breakdown: any;
58
- inactive_users: any[];
59
- recommendations: string[];
60
- }>;
50
+ }): Promise<Record<string, any>>;
61
51
  export declare function permissionAudit(config: AuthConfig, params: {
62
52
  scope_type: 'project' | 'team';
63
53
  scope_id: string;
@@ -242,23 +242,36 @@ export async function seatOptimization(config, params) {
242
242
  const api = internalClient(config);
243
243
  const cutoff = Date.now() - days_inactive * 86400000;
244
244
  const paidKeys = new Set(['expert', 'developer', 'collaborator']);
245
- // Paginate org members (cursor-based, max 500)
245
+ // Paginate org members (cursor-based)
246
246
  const allMembers = [];
247
- const MAX_PAGES = 20;
247
+ const warnings = [];
248
+ let membersComplete = true;
249
+ const MAX_PAGES = 200; // safety cap: 200 * 50 = 10,000 members
248
250
  let cursor;
249
251
  for (let page = 0; page < MAX_PAGES; page++) {
250
- const params = { page_size: 25 };
251
- if (cursor)
252
- params.cursor = cursor;
253
- const res = await api.get(`/api/v2/orgs/${orgId}/org_users`, { params });
254
- const meta = res.data?.meta || {};
255
- const members = meta.users || [];
256
- if (!Array.isArray(members) || members.length === 0)
257
- break;
258
- allMembers.push(...members);
259
- cursor = Array.isArray(meta.cursor) ? meta.cursor[0] : meta.cursor;
260
- if (!cursor || members.length < 25)
252
+ try {
253
+ const params = { page_size: 50 };
254
+ if (cursor)
255
+ params.cursor = cursor;
256
+ const res = await api.get(`/api/v2/orgs/${orgId}/org_users`, { params });
257
+ const meta = res.data?.meta || {};
258
+ const members = meta.users || [];
259
+ if (!Array.isArray(members) || members.length === 0)
260
+ break;
261
+ allMembers.push(...members);
262
+ cursor = Array.isArray(meta.cursor) ? meta.cursor[0] : meta.cursor;
263
+ if (!cursor || members.length < 50)
264
+ break;
265
+ }
266
+ catch (e) {
267
+ membersComplete = false;
268
+ warnings.push(`members: pagination stopped at page ${page + 1}, fetched ${allMembers.length} members (${e.response?.status || e.message})`);
261
269
  break;
270
+ }
271
+ }
272
+ if (cursor && allMembers.length >= MAX_PAGES * 50) {
273
+ membersComplete = false;
274
+ warnings.push(`members: hit ${MAX_PAGES}-page safety cap at ${allMembers.length} members, org may have more`);
262
275
  }
263
276
  // Fetch seat breakdown and optionally contract rates in parallel
264
277
  const parallelCalls = [
@@ -317,17 +330,21 @@ export async function seatOptimization(config, params) {
317
330
  if (monthlyWasteCents > 0) {
318
331
  recommendations.push(`Potential monthly savings: $${(monthlyWasteCents / 100).toFixed(2)} ($${((monthlyWasteCents * 12) / 100).toFixed(2)}/yr).`);
319
332
  }
320
- return {
333
+ const result = {
321
334
  summary: {
322
335
  total_paid: totalPaid,
323
336
  inactive_paid: inactiveUsers.length,
324
337
  monthly_waste_cents: monthlyWasteCents,
325
338
  annual_savings_cents: monthlyWasteCents * 12,
339
+ members_complete: membersComplete,
326
340
  },
327
341
  seat_breakdown: seats,
328
342
  inactive_users: inactiveUsers,
329
343
  recommendations,
330
344
  };
345
+ if (warnings.length > 0)
346
+ result.warnings = warnings;
347
+ return result;
331
348
  }
332
349
  // -- permission_audit --
333
350
  export async function permissionAudit(config, params) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "figmanage",
3
3
  "mcpName": "io.github.dannykeane/figmanage",
4
- "version": "1.2.2",
4
+ "version": "1.2.3",
5
5
  "description": "MCP server for managing your Figma workspace from the terminal.",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",