figmanage 0.3.0 → 1.0.1

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.
@@ -0,0 +1,13 @@
1
+ /** Check if stdout is a TTY (interactive terminal) */
2
+ export declare function isTTY(): boolean;
3
+ /** Format output: JSON if piped or --json flag, human-readable if TTY */
4
+ export declare function formatOutput(data: unknown, options: {
5
+ json?: boolean;
6
+ }): string;
7
+ /** Print formatted output to stdout */
8
+ export declare function output(data: unknown, options?: {
9
+ json?: boolean;
10
+ }): void;
11
+ /** Print error message to stderr */
12
+ export declare function error(message: string): void;
13
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1,21 @@
1
+ /** Check if stdout is a TTY (interactive terminal) */
2
+ export function isTTY() {
3
+ return process.stdout.isTTY === true;
4
+ }
5
+ /** Format output: JSON if piped or --json flag, human-readable if TTY */
6
+ export function formatOutput(data, options) {
7
+ if (options.json || !isTTY()) {
8
+ return JSON.stringify(data, null, 2);
9
+ }
10
+ // Table formatting will be added later; fall back to JSON for now
11
+ return JSON.stringify(data, null, 2);
12
+ }
13
+ /** Print formatted output to stdout */
14
+ export function output(data, options = {}) {
15
+ console.log(formatOutput(data, options));
16
+ }
17
+ /** Print error message to stderr */
18
+ export function error(message) {
19
+ console.error(`error: ${message}`);
20
+ }
21
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1,3 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerCliCommands(program: Command): void;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,115 @@
1
+ export function registerCliCommands(program) {
2
+ // Auth commands -- handlers live in cli/login.ts (built by another agent)
3
+ program
4
+ .command('login')
5
+ .description('Authenticate with Figma')
6
+ .option('--refresh', 'Refresh cookie only')
7
+ .option('--pat-only', 'PAT authentication only')
8
+ .action(async (options) => {
9
+ const { handleLogin } = await import('./login.js');
10
+ await handleLogin(options);
11
+ });
12
+ program
13
+ .command('whoami')
14
+ .description('Show current authentication status')
15
+ .action(async () => {
16
+ const { handleWhoami } = await import('./whoami.js');
17
+ await handleWhoami();
18
+ });
19
+ program
20
+ .command('logout')
21
+ .description('Clear stored credentials')
22
+ .action(async () => {
23
+ const { handleLogout } = await import('./login.js');
24
+ await handleLogout();
25
+ });
26
+ // Workspace management commands
27
+ program
28
+ .command('seat-optimization')
29
+ .description('Identify inactive paid seats and calculate savings')
30
+ .option('--days-inactive <days>', 'Days threshold (default: 90)', '90')
31
+ .option('--no-cost', 'Skip cost analysis')
32
+ .option('--json', 'Force JSON output')
33
+ .action(async (options) => {
34
+ const { runSeatOptimization } = await import('./commands.js');
35
+ await runSeatOptimization(options);
36
+ });
37
+ program
38
+ .command('offboard <user>')
39
+ .description('Audit or execute user offboarding')
40
+ .option('--execute', 'Execute the offboarding (default: audit only)')
41
+ .option('--transfer-to <user>', 'Transfer file ownership to this user')
42
+ .option('--json', 'Force JSON output')
43
+ .action(async (user, options) => {
44
+ const { runOffboard } = await import('./commands.js');
45
+ await runOffboard(user, options);
46
+ });
47
+ program
48
+ .command('onboard <email>')
49
+ .description('Invite user to teams and set up access')
50
+ .option('--teams <ids>', 'Comma-separated team IDs', (v) => v.split(','))
51
+ .option('--role <role>', 'Role: editor or viewer (default: editor)', 'editor')
52
+ .option('--share-files <keys>', 'Comma-separated file keys to share (viewer access)', (v) => v.split(','))
53
+ .option('--seat <type>', 'Seat type: full, dev, collab, view')
54
+ .option('--confirm', 'Confirm seat change')
55
+ .option('--json', 'Force JSON output')
56
+ .action(async (email, options) => {
57
+ const { runOnboard } = await import('./commands.js');
58
+ await runOnboard(email, options);
59
+ });
60
+ program
61
+ .command('quarterly-report')
62
+ .description('Org-wide design ops snapshot')
63
+ .option('--days <days>', 'Lookback period (default: 90)', '90')
64
+ .option('--json', 'Force JSON output')
65
+ .action(async (options) => {
66
+ const { runQuarterlyReport } = await import('./commands.js');
67
+ await runQuarterlyReport(options);
68
+ });
69
+ program
70
+ .command('members')
71
+ .description('List org members')
72
+ .option('--search <query>', 'Filter by name or email')
73
+ .option('--json', 'Force JSON output')
74
+ .action(async (options) => {
75
+ const { runMembers } = await import('./commands.js');
76
+ await runMembers(options);
77
+ });
78
+ program
79
+ .command('teams')
80
+ .description('List org teams')
81
+ .option('--json', 'Force JSON output')
82
+ .action(async (options) => {
83
+ const { runTeams } = await import('./commands.js');
84
+ await runTeams(options);
85
+ });
86
+ program
87
+ .command('permissions <type> <id>')
88
+ .description('Show who has access to a file, project, or team')
89
+ .option('--json', 'Force JSON output')
90
+ .action(async (type, id, options) => {
91
+ const { runPermissions } = await import('./commands.js');
92
+ await runPermissions(type, id, options);
93
+ });
94
+ program
95
+ .command('permission-audit')
96
+ .description('Audit permissions across a team or project')
97
+ .option('--scope <type>', 'Scope: team or project', 'team')
98
+ .option('--id <id>', 'Team or project ID')
99
+ .option('--json', 'Force JSON output')
100
+ .action(async (options) => {
101
+ const { runPermissionAudit } = await import('./commands.js');
102
+ await runPermissionAudit(options);
103
+ });
104
+ program
105
+ .command('branch-cleanup <project-id>')
106
+ .description('Find and optionally archive stale branches')
107
+ .option('--days-stale <days>', 'Days threshold (default: 60)', '60')
108
+ .option('--execute', 'Archive stale branches (default: dry run)')
109
+ .option('--json', 'Force JSON output')
110
+ .action(async (projectId, options) => {
111
+ const { runBranchCleanup } = await import('./commands.js');
112
+ await runBranchCleanup(projectId, options);
113
+ });
114
+ }
115
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ export interface LoginOptions {
2
+ refresh?: boolean;
3
+ patOnly?: boolean;
4
+ }
5
+ export declare function handleLogin(options?: LoginOptions): Promise<void>;
6
+ export declare function handleLogout(): Promise<void>;
7
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1,135 @@
1
+ import { createInterface } from 'node:readline';
2
+ import { platform } from 'node:os';
3
+ import { setActiveWorkspace, deleteConfig, getConfigPath } from '../config.js';
4
+ import { extractCookies, validateSession, validatePat } from '../auth/cookie.js';
5
+ async function prompt(question) {
6
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
7
+ const answer = await new Promise(resolve => rl.question(question, resolve));
8
+ rl.close();
9
+ return answer.trim();
10
+ }
11
+ export async function handleLogin(options = {}) {
12
+ const workspace = {};
13
+ const os = platform();
14
+ // Cookie extraction (unless --pat-only)
15
+ if (!options.patOnly) {
16
+ if (os !== 'darwin' && os !== 'linux' && os !== 'win32') {
17
+ console.log(`Cookie extraction not supported on ${os}. Use --pat-only.`);
18
+ }
19
+ else {
20
+ const promptLabel = os === 'darwin' ? ' (Keychain prompt may appear)' : '';
21
+ console.log(`Reading Chrome cookies${promptLabel}...`);
22
+ try {
23
+ const accounts = extractCookies();
24
+ if (accounts.length === 0) {
25
+ if (os === 'win32') {
26
+ console.log(' No Figma cookies extracted. Windows extraction is best-effort.');
27
+ }
28
+ else {
29
+ console.log(' No Figma cookies found. Log into figma.com in Chrome.');
30
+ }
31
+ }
32
+ else {
33
+ // Pick account if multiple
34
+ let selected = accounts[0];
35
+ if (accounts.length > 1) {
36
+ console.log(`\n Found ${accounts.length} Figma accounts:\n`);
37
+ for (let i = 0; i < accounts.length; i++) {
38
+ console.log(` [${i + 1}] User ${accounts[i].userId} (${accounts[i].profile})`);
39
+ }
40
+ const answer = await prompt(`\n Select account [1-${accounts.length}]: `);
41
+ const idx = parseInt(answer, 10) - 1;
42
+ if (idx < 0 || idx >= accounts.length) {
43
+ console.error(' Invalid selection.');
44
+ process.exit(1);
45
+ }
46
+ selected = accounts[idx];
47
+ }
48
+ console.log(` Cookie found for user ${selected.userId}`);
49
+ // Validate session and detect org
50
+ console.log('Validating session...');
51
+ try {
52
+ const session = await validateSession(selected.cookieValue, selected.userId);
53
+ console.log(` Session valid (user ${selected.userId})`);
54
+ if (session.teams.length > 0) {
55
+ console.log(` Teams: ${session.teams.map(t => t.name).join(', ')}`);
56
+ }
57
+ workspace.cookie = selected.cookieValue;
58
+ workspace.user_id = selected.userId;
59
+ workspace.cookie_extracted_at = new Date().toISOString();
60
+ // Org selection
61
+ let orgId = session.orgId;
62
+ if (session.orgs.length > 1) {
63
+ console.log(`\n Found ${session.orgs.length} workspaces:\n`);
64
+ for (let i = 0; i < session.orgs.length; i++) {
65
+ const o = session.orgs[i];
66
+ const marker = o.id === orgId ? ' (current)' : '';
67
+ console.log(` [${i + 1}] ${o.name} (${o.id})${marker}`);
68
+ }
69
+ const answer = await prompt(`\n Default workspace [1-${session.orgs.length}] (Enter for 1): `);
70
+ if (answer) {
71
+ const idx = parseInt(answer, 10) - 1;
72
+ if (idx >= 0 && idx < session.orgs.length) {
73
+ orgId = session.orgs[idx].id;
74
+ }
75
+ }
76
+ }
77
+ if (orgId) {
78
+ workspace.org_id = orgId;
79
+ const orgName = session.orgs.find(o => o.id === orgId)?.name;
80
+ console.log(` Workspace: ${orgName ? `${orgName} (${orgId})` : orgId}`);
81
+ }
82
+ }
83
+ catch (e) {
84
+ const status = e.response?.status;
85
+ if (status === 401 || status === 403) {
86
+ console.error(' Cookie expired. Log into figma.com in Chrome and try again.');
87
+ }
88
+ else {
89
+ console.error(` Session validation failed: ${e.message}`);
90
+ }
91
+ // Continue to PAT prompt -- cookie failed but PAT might work
92
+ }
93
+ }
94
+ }
95
+ catch (e) {
96
+ if (os === 'win32') {
97
+ console.log(` Cookie extraction failed: ${e.message}`);
98
+ }
99
+ else {
100
+ console.error(` Cookie extraction failed: ${e.message}`);
101
+ }
102
+ }
103
+ }
104
+ }
105
+ // PAT prompt
106
+ console.log('\nA Personal Access Token enables comments, export, and version history.');
107
+ console.log('Generate one at: https://www.figma.com/settings (Security > Personal access tokens)');
108
+ const patInput = await prompt('Paste your PAT (or press Enter to skip): ');
109
+ if (patInput) {
110
+ console.log('Validating PAT...');
111
+ try {
112
+ const patUser = await validatePat(patInput);
113
+ console.log(` PAT valid (${patUser})`);
114
+ workspace.pat = patInput;
115
+ }
116
+ catch {
117
+ console.log(' PAT invalid or expired -- skipping.');
118
+ }
119
+ }
120
+ // Must have at least one credential
121
+ if (!workspace.pat && !workspace.cookie) {
122
+ console.error('\nNo credentials configured. Need at least a PAT or browser cookie.');
123
+ process.exit(1);
124
+ }
125
+ // Derive a workspace name from the org or user
126
+ const workspaceName = workspace.org_id || workspace.user_id || 'default';
127
+ setActiveWorkspace(workspaceName, workspace);
128
+ console.log(`\nCredentials saved to ${getConfigPath()}`);
129
+ console.log('Done. figmanage will use these credentials automatically.');
130
+ }
131
+ export async function handleLogout() {
132
+ deleteConfig();
133
+ console.log('Logged out. Config file removed.');
134
+ }
135
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1,2 @@
1
+ export declare function handleWhoami(): Promise<void>;
2
+ //# sourceMappingURL=whoami.d.ts.map
@@ -0,0 +1,68 @@
1
+ import axios from 'axios';
2
+ import { loadAuthConfig, hasPat, hasCookie } from '../auth/client.js';
3
+ export async function handleWhoami() {
4
+ const config = loadAuthConfig();
5
+ if (!hasPat(config) && !hasCookie(config)) {
6
+ console.error('No auth configured. Run `figmanage login` or set environment variables.');
7
+ process.exit(1);
8
+ }
9
+ const authMethods = [];
10
+ // Cookie auth: call internal API /api/me
11
+ if (hasCookie(config)) {
12
+ authMethods.push('cookie');
13
+ try {
14
+ const res = await axios.get('https://www.figma.com/api/user/state', {
15
+ headers: {
16
+ 'Cookie': `__Host-figma.authn=${config.cookie}`,
17
+ 'X-CSRF-Bypass': 'yes',
18
+ 'X-Figma-User-Id': config.userId || '',
19
+ },
20
+ timeout: 15000,
21
+ });
22
+ const meta = res.data?.meta || res.data || {};
23
+ const user = meta.user || meta;
24
+ console.log(`User: ${user.handle || user.email || config.userId}`);
25
+ if (user.email)
26
+ console.log(`Email: ${user.email}`);
27
+ }
28
+ catch (e) {
29
+ const status = e.response?.status;
30
+ if (status === 401 || status === 403) {
31
+ console.log('Cookie: expired or invalid');
32
+ }
33
+ else {
34
+ console.log(`Cookie: request failed (${e.message})`);
35
+ }
36
+ }
37
+ }
38
+ // PAT auth: call public API /v1/me
39
+ if (hasPat(config)) {
40
+ authMethods.push('PAT');
41
+ try {
42
+ const res = await axios.get('https://api.figma.com/v1/me', {
43
+ headers: { 'X-Figma-Token': config.pat },
44
+ timeout: 15000,
45
+ });
46
+ const user = res.data;
47
+ // Only print user info if we didn't already from cookie
48
+ if (!hasCookie(config)) {
49
+ console.log(`User: ${user.handle || user.email}`);
50
+ if (user.email)
51
+ console.log(`Email: ${user.email}`);
52
+ }
53
+ }
54
+ catch (e) {
55
+ const status = e.response?.status;
56
+ if (status === 401 || status === 403) {
57
+ console.log('PAT: expired or invalid');
58
+ }
59
+ else {
60
+ console.log(`PAT: request failed (${e.message})`);
61
+ }
62
+ }
63
+ }
64
+ if (config.orgId)
65
+ console.log(`Org: ${config.orgId}`);
66
+ console.log(`Auth: ${authMethods.join(' + ')}`);
67
+ }
68
+ //# sourceMappingURL=whoami.js.map
@@ -0,0 +1,35 @@
1
+ export interface WorkspaceConfig {
2
+ cookie?: string;
3
+ user_id?: string;
4
+ org_id?: string;
5
+ pat?: string;
6
+ cookie_extracted_at?: string;
7
+ }
8
+ export interface FigmanageConfig {
9
+ workspaces: Record<string, WorkspaceConfig>;
10
+ active_workspace: string;
11
+ }
12
+ export declare function getConfigDir(): string;
13
+ export declare function getConfigPath(): string;
14
+ /**
15
+ * Read and parse the config file. Returns null if not found or malformed.
16
+ */
17
+ export declare function readConfig(): FigmanageConfig | null;
18
+ /**
19
+ * Write config to disk with restricted permissions (0o600).
20
+ * Creates the config directory if it doesn't exist.
21
+ */
22
+ export declare function writeConfig(config: FigmanageConfig): void;
23
+ /**
24
+ * Return the active workspace entry, or null if no config or workspace found.
25
+ */
26
+ export declare function getActiveWorkspace(): WorkspaceConfig | null;
27
+ /**
28
+ * Set or update a workspace entry and make it active. Merges with existing config.
29
+ */
30
+ export declare function setActiveWorkspace(name: string, workspace: WorkspaceConfig): void;
31
+ /**
32
+ * Delete the config file. No-op if it doesn't exist.
33
+ */
34
+ export declare function deleteConfig(): void;
35
+ //# sourceMappingURL=config.d.ts.map
package/dist/config.js ADDED
@@ -0,0 +1,83 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, chmodSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ export function getConfigDir() {
5
+ if (process.platform === 'win32' && process.env.APPDATA) {
6
+ return join(process.env.APPDATA, 'figmanage');
7
+ }
8
+ return join(homedir(), '.config', 'figmanage');
9
+ }
10
+ export function getConfigPath() {
11
+ return join(getConfigDir(), 'config.json');
12
+ }
13
+ /**
14
+ * Read and parse the config file. Returns null if not found or malformed.
15
+ */
16
+ export function readConfig() {
17
+ const configPath = getConfigPath();
18
+ if (!existsSync(configPath))
19
+ return null;
20
+ try {
21
+ const raw = readFileSync(configPath, 'utf-8');
22
+ const parsed = JSON.parse(raw);
23
+ // Basic shape validation
24
+ if (typeof parsed !== 'object' ||
25
+ parsed === null ||
26
+ typeof parsed.workspaces !== 'object' ||
27
+ parsed.workspaces === null ||
28
+ typeof parsed.active_workspace !== 'string') {
29
+ return null;
30
+ }
31
+ return parsed;
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ }
37
+ /**
38
+ * Write config to disk with restricted permissions (0o600).
39
+ * Creates the config directory if it doesn't exist.
40
+ */
41
+ export function writeConfig(config) {
42
+ const configDir = getConfigDir();
43
+ mkdirSync(configDir, { recursive: true, mode: 0o700 });
44
+ const configPath = getConfigPath();
45
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
46
+ // Ensure permissions even if file pre-existed with broader mode
47
+ if (process.platform !== 'win32') {
48
+ chmodSync(configPath, 0o600);
49
+ }
50
+ }
51
+ /**
52
+ * Return the active workspace entry, or null if no config or workspace found.
53
+ */
54
+ export function getActiveWorkspace() {
55
+ const config = readConfig();
56
+ if (!config)
57
+ return null;
58
+ const workspace = config.workspaces[config.active_workspace];
59
+ return workspace ?? null;
60
+ }
61
+ /**
62
+ * Set or update a workspace entry and make it active. Merges with existing config.
63
+ */
64
+ export function setActiveWorkspace(name, workspace) {
65
+ const existing = readConfig() ?? { workspaces: {}, active_workspace: name };
66
+ existing.workspaces[name] = workspace;
67
+ existing.active_workspace = name;
68
+ writeConfig(existing);
69
+ }
70
+ /**
71
+ * Delete the config file. No-op if it doesn't exist.
72
+ */
73
+ export function deleteConfig() {
74
+ const configPath = getConfigPath();
75
+ try {
76
+ unlinkSync(configPath);
77
+ }
78
+ catch (err) {
79
+ if (err.code !== 'ENOENT')
80
+ throw err;
81
+ }
82
+ }
83
+ //# sourceMappingURL=config.js.map
package/dist/index.d.ts CHANGED
@@ -1,20 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import './tools/navigate.js';
3
- import './tools/files.js';
4
- import './tools/projects.js';
5
- import './tools/permissions.js';
6
- import './tools/comments.js';
7
- import './tools/export.js';
8
- import './tools/versions.js';
9
- import './tools/branching.js';
10
- import './tools/components.js';
11
- import './tools/webhooks.js';
12
- import './tools/reading.js';
13
- import './tools/analytics.js';
14
- import './tools/variables.js';
15
- import './tools/org.js';
16
- import './tools/libraries.js';
17
- import './tools/teams.js';
18
- import './tools/compound.js';
19
- import './tools/compound-manager.js';
2
+ export {};
20
3
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,106 +1,34 @@
1
1
  #!/usr/bin/env node
2
- // Run setup if --setup flag is present
2
+ // --setup flag: run the interactive setup wizard and exit.
3
+ // Checked before commander parses to preserve existing behavior
4
+ // (setup uses its own interactive prompts).
3
5
  if (process.argv.includes('--setup')) {
4
6
  await import('./setup.js');
5
7
  process.exit(0);
6
8
  }
7
- import { createServer } from 'node:http';
8
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
- import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
11
- import { loadAuthConfig, hasPat, hasCookie } from './auth/client.js';
12
- import { registerTools } from './tools/register.js';
13
- // Import tool modules (side-effect: registers via defineTool)
14
- import './tools/navigate.js';
15
- import './tools/files.js';
16
- import './tools/projects.js';
17
- import './tools/permissions.js';
18
- import './tools/comments.js';
19
- import './tools/export.js';
20
- import './tools/versions.js';
21
- import './tools/branching.js';
22
- import './tools/components.js';
23
- import './tools/webhooks.js';
24
- import './tools/reading.js';
25
- import './tools/analytics.js';
26
- import './tools/variables.js';
27
- import './tools/org.js';
28
- import './tools/libraries.js';
29
- import './tools/teams.js';
30
- import './tools/compound.js';
31
- import './tools/compound-manager.js';
32
- const ALL_TOOLSETS = [
33
- 'navigate', 'files', 'projects', 'permissions', 'org',
34
- 'versions', 'branching', 'comments', 'export',
35
- 'analytics', 'reading', 'components', 'webhooks', 'variables',
36
- 'compound', 'teams', 'libraries',
37
- ];
38
- const TOOLSET_PRESETS = {
39
- starter: ['navigate', 'reading', 'comments', 'export'],
40
- admin: ['navigate', 'org', 'permissions', 'analytics', 'teams', 'libraries'],
41
- readonly: ['navigate', 'reading', 'comments', 'export', 'components', 'versions'],
42
- full: ALL_TOOLSETS,
43
- };
44
- function parseToolsets(env) {
45
- if (!env)
46
- return new Set(ALL_TOOLSETS);
47
- if (env in TOOLSET_PRESETS)
48
- return new Set(TOOLSET_PRESETS[env]);
49
- const requested = env.split(',').map(s => s.trim());
50
- const valid = requested.filter(t => ALL_TOOLSETS.includes(t));
51
- return new Set(valid.length > 0 ? valid : ALL_TOOLSETS);
9
+ // --mcp flag: start the MCP server (stdio or HTTP).
10
+ // This is the hot path for MCP clients -- tool modules are only
11
+ // loaded inside startMcpServer(), keeping CLI startup fast.
12
+ if (process.argv.includes('--mcp')) {
13
+ const { startMcpServer } = await import('./mcp.js');
14
+ await startMcpServer();
52
15
  }
53
- function parseHttpPort(argv) {
54
- const idx = argv.indexOf('--http');
55
- if (idx === -1)
56
- return undefined;
57
- const port = Number(argv[idx + 1]);
58
- if (!port || port < 1 || port > 65535) {
59
- console.error('--http requires a valid port number (1-65535)');
60
- process.exit(1);
16
+ else {
17
+ // CLI mode: parse commands with commander
18
+ const { Command } = await import('commander');
19
+ const { registerCliCommands } = await import('./cli/index.js');
20
+ const program = new Command();
21
+ program
22
+ .name('figmanage')
23
+ .description('Figma workspace management CLI')
24
+ .version(JSON.parse((await import('node:fs')).readFileSync(new URL('../package.json', import.meta.url), 'utf-8')).version);
25
+ registerCliCommands(program);
26
+ // No subcommand given -- show help
27
+ if (process.argv.length <= 2) {
28
+ program.outputHelp();
29
+ process.exit(0);
61
30
  }
62
- return port;
31
+ await program.parseAsync(process.argv);
63
32
  }
64
- async function main() {
65
- const config = loadAuthConfig();
66
- const readOnly = process.env.FIGMA_READ_ONLY === '1' || process.env.FIGMA_READ_ONLY === 'true';
67
- const enabledToolsets = parseToolsets(process.env.FIGMA_TOOLSETS);
68
- if (!hasPat(config) && !hasCookie(config)) {
69
- console.error('No auth configured. Set FIGMA_PAT for public API access, or ' +
70
- 'FIGMA_AUTH_COOKIE + FIGMA_USER_ID for internal API access.');
71
- process.exit(1);
72
- }
73
- const server = new McpServer({
74
- name: 'figmanage',
75
- version: '0.1.0',
76
- });
77
- registerTools(server, config, enabledToolsets, readOnly);
78
- const httpPort = parseHttpPort(process.argv);
79
- if (httpPort) {
80
- const transport = new StreamableHTTPServerTransport({
81
- sessionIdGenerator: undefined,
82
- });
83
- const httpServer = createServer(async (req, res) => {
84
- const url = new URL(req.url ?? '/', `http://localhost:${httpPort}`);
85
- if (url.pathname === '/mcp') {
86
- await transport.handleRequest(req, res);
87
- }
88
- else {
89
- res.writeHead(404).end('Not found');
90
- }
91
- });
92
- await server.connect(transport);
93
- httpServer.listen(httpPort, () => {
94
- console.error(`figmanage HTTP server listening on http://localhost:${httpPort}/mcp`);
95
- });
96
- }
97
- else {
98
- const transport = new StdioServerTransport();
99
- await server.connect(transport);
100
- }
101
- }
102
- main().catch((err) => {
103
- console.error('Fatal:', err);
104
- process.exit(1);
105
- });
33
+ export {};
106
34
  //# sourceMappingURL=index.js.map