@yubild/cli 0.1.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.
Files changed (88) hide show
  1. package/dist/commands/deploy.d.ts +6 -0
  2. package/dist/commands/deploy.d.ts.map +1 -0
  3. package/dist/commands/deploy.js +73 -0
  4. package/dist/commands/deploy.js.map +1 -0
  5. package/dist/commands/diff.d.ts +6 -0
  6. package/dist/commands/diff.d.ts.map +1 -0
  7. package/dist/commands/diff.js +97 -0
  8. package/dist/commands/diff.js.map +1 -0
  9. package/dist/commands/export.d.ts +9 -0
  10. package/dist/commands/export.d.ts.map +1 -0
  11. package/dist/commands/export.js +93 -0
  12. package/dist/commands/export.js.map +1 -0
  13. package/dist/commands/init.d.ts +8 -0
  14. package/dist/commands/init.d.ts.map +1 -0
  15. package/dist/commands/init.js +95 -0
  16. package/dist/commands/init.js.map +1 -0
  17. package/dist/commands/login.d.ts +9 -0
  18. package/dist/commands/login.d.ts.map +1 -0
  19. package/dist/commands/login.js +81 -0
  20. package/dist/commands/login.js.map +1 -0
  21. package/dist/commands/logout.d.ts +6 -0
  22. package/dist/commands/logout.d.ts.map +1 -0
  23. package/dist/commands/logout.js +17 -0
  24. package/dist/commands/logout.js.map +1 -0
  25. package/dist/commands/projects.d.ts +6 -0
  26. package/dist/commands/projects.d.ts.map +1 -0
  27. package/dist/commands/projects.js +76 -0
  28. package/dist/commands/projects.js.map +1 -0
  29. package/dist/commands/publish.d.ts +8 -0
  30. package/dist/commands/publish.d.ts.map +1 -0
  31. package/dist/commands/publish.js +81 -0
  32. package/dist/commands/publish.js.map +1 -0
  33. package/dist/commands/pull.d.ts +8 -0
  34. package/dist/commands/pull.d.ts.map +1 -0
  35. package/dist/commands/pull.js +86 -0
  36. package/dist/commands/pull.js.map +1 -0
  37. package/dist/commands/push.d.ts +6 -0
  38. package/dist/commands/push.d.ts.map +1 -0
  39. package/dist/commands/push.js +73 -0
  40. package/dist/commands/push.js.map +1 -0
  41. package/dist/commands/sync.d.ts +8 -0
  42. package/dist/commands/sync.d.ts.map +1 -0
  43. package/dist/commands/sync.js +111 -0
  44. package/dist/commands/sync.js.map +1 -0
  45. package/dist/commands/watch.d.ts +8 -0
  46. package/dist/commands/watch.d.ts.map +1 -0
  47. package/dist/commands/watch.js +91 -0
  48. package/dist/commands/watch.js.map +1 -0
  49. package/dist/commands/whoami.d.ts +6 -0
  50. package/dist/commands/whoami.d.ts.map +1 -0
  51. package/dist/commands/whoami.js +32 -0
  52. package/dist/commands/whoami.js.map +1 -0
  53. package/dist/index.d.ts +7 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +89 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/lib/api-client.d.ts +141 -0
  58. package/dist/lib/api-client.d.ts.map +1 -0
  59. package/dist/lib/api-client.js +154 -0
  60. package/dist/lib/api-client.js.map +1 -0
  61. package/dist/lib/auth.d.ts +36 -0
  62. package/dist/lib/auth.d.ts.map +1 -0
  63. package/dist/lib/auth.js +108 -0
  64. package/dist/lib/auth.js.map +1 -0
  65. package/dist/lib/config.d.ts +28 -0
  66. package/dist/lib/config.d.ts.map +1 -0
  67. package/dist/lib/config.js +100 -0
  68. package/dist/lib/config.js.map +1 -0
  69. package/package.json +30 -0
  70. package/src/commands/deploy.ts +42 -0
  71. package/src/commands/diff.ts +81 -0
  72. package/src/commands/export.ts +71 -0
  73. package/src/commands/init.ts +69 -0
  74. package/src/commands/login.ts +52 -0
  75. package/src/commands/logout.ts +16 -0
  76. package/src/commands/projects.ts +53 -0
  77. package/src/commands/publish.ts +51 -0
  78. package/src/commands/pull.ts +59 -0
  79. package/src/commands/push.ts +42 -0
  80. package/src/commands/sync.ts +86 -0
  81. package/src/commands/watch.ts +71 -0
  82. package/src/commands/whoami.ts +31 -0
  83. package/src/index.ts +104 -0
  84. package/src/lib/api-client.ts +231 -0
  85. package/src/lib/auth.ts +86 -0
  86. package/src/lib/config.ts +77 -0
  87. package/src/types/esm-modules.d.ts +41 -0
  88. package/tsconfig.json +19 -0
@@ -0,0 +1,86 @@
1
+ /**
2
+ * yubild sync
3
+ * Sync design tokens between local project and Figma
4
+ */
5
+
6
+ import { loadConfig } from '../lib/config';
7
+ import { isAuthenticated } from '../lib/auth';
8
+ import { ApiClient } from '../lib/api-client';
9
+
10
+ export async function syncCommand(options: { autoMerge?: boolean }): Promise<void> {
11
+ if (!isAuthenticated()) {
12
+ console.error('Error: Not authenticated. Run `yubild login` first.');
13
+ process.exit(1);
14
+ }
15
+
16
+ const config = loadConfig();
17
+ if (!config) {
18
+ console.error('Error: No .yubildrc found. Run `yubild init` first.');
19
+ process.exit(1);
20
+ }
21
+
22
+ const { default: chalk } = await import('chalk');
23
+ const { default: ora } = await import('ora');
24
+
25
+ const spinner = ora('Syncing with Figma...').start();
26
+
27
+ try {
28
+ const client = new ApiClient();
29
+ const result = await client.syncTokens(config.projectId, options.autoMerge ?? false);
30
+
31
+ if (result.synced) {
32
+ spinner.succeed('Sync complete!');
33
+ const summary = result.summary;
34
+ if (summary) {
35
+ console.log('');
36
+ console.log(` ${chalk.green(`+ ${summary.added} added`)}`);
37
+ console.log(` ${chalk.yellow(`~ ${summary.modified} modified`)}`);
38
+ console.log(` ${chalk.red(`- ${summary.removed} removed`)}`);
39
+ console.log(` ${chalk.gray(` ${summary.unchanged} unchanged`)}`);
40
+ }
41
+ if (result.warnings && result.warnings.length > 0) {
42
+ console.log('');
43
+ for (const warning of result.warnings) {
44
+ console.log(` ${chalk.yellow('Warning:')} ${warning}`);
45
+ }
46
+ }
47
+ } else {
48
+ spinner.warn('Sync requires conflict resolution');
49
+ if (result.diff) {
50
+ console.log('');
51
+ console.log('Diff:');
52
+ console.log('');
53
+ const padKey = Math.max(...result.diff.entries.map((e) => e.tokenKey.length), 10);
54
+ const padStatus = 10;
55
+ console.log(
56
+ ` ${'Token'.padEnd(padKey)} ${'Status'.padEnd(padStatus)} ${'Local'.padEnd(20)} Figma`
57
+ );
58
+ console.log(` ${''.padEnd(padKey, '-')} ${''.padEnd(padStatus, '-')} ${''.padEnd(20, '-')} ${''.padEnd(20, '-')}`);
59
+ for (const entry of result.diff.entries) {
60
+ const statusColor =
61
+ entry.status === 'added'
62
+ ? chalk.green
63
+ : entry.status === 'modified'
64
+ ? chalk.yellow
65
+ : entry.status === 'removed'
66
+ ? chalk.red
67
+ : chalk.gray;
68
+ console.log(
69
+ ` ${entry.tokenKey.padEnd(padKey)} ${statusColor(entry.status.padEnd(padStatus))} ${(entry.localValue ?? '-').padEnd(20)} ${entry.figmaValue ?? '-'}`
70
+ );
71
+ }
72
+ const s = result.diff.summary;
73
+ console.log('');
74
+ console.log(
75
+ ` Summary: ${chalk.green(`+${s.added}`)} ${chalk.yellow(`~${s.modified}`)} ${chalk.red(`-${s.removed}`)} ${chalk.gray(`${s.unchanged} unchanged`)}`
76
+ );
77
+ }
78
+ console.log('');
79
+ console.log(` Use ${chalk.cyan('yubild sync --auto-merge')} to auto-merge, or resolve conflicts in the Yubild UI.`);
80
+ }
81
+ } catch (error) {
82
+ spinner.fail('Sync failed');
83
+ console.error(`\nFailed to sync tokens: ${(error as Error).message}`);
84
+ process.exit(1);
85
+ }
86
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * yubild watch
3
+ * Watch for token changes and auto-sync
4
+ */
5
+
6
+ import { loadConfig } from '../lib/config';
7
+ import { isAuthenticated } from '../lib/auth';
8
+ import { ApiClient } from '../lib/api-client';
9
+
10
+ export async function watchCommand(options: {
11
+ interval?: string;
12
+ }): Promise<void> {
13
+ if (!isAuthenticated()) {
14
+ console.error('Error: Not authenticated. Run `yubild login` first.');
15
+ process.exit(1);
16
+ }
17
+
18
+ const config = loadConfig();
19
+ if (!config) {
20
+ console.error('Error: No .yubildrc found. Run `yubild init` first.');
21
+ process.exit(1);
22
+ }
23
+
24
+ const { default: chalk } = await import('chalk');
25
+
26
+ const intervalSeconds = parseInt(options.interval ?? '30', 10);
27
+ if (isNaN(intervalSeconds) || intervalSeconds < 5) {
28
+ console.error('Error: Interval must be a number >= 5 seconds.');
29
+ process.exit(1);
30
+ }
31
+
32
+ const client = new ApiClient();
33
+
34
+ console.log(chalk.cyan(`Watching for changes every ${intervalSeconds}s... (Ctrl+C to stop)`));
35
+ console.log('');
36
+
37
+ const check = async () => {
38
+ try {
39
+ const diff = await client.diffTokens(config.projectId);
40
+
41
+ const totalChanges = diff.summary.added + diff.summary.modified + diff.summary.removed;
42
+
43
+ if (totalChanges === 0) {
44
+ console.log(chalk.gray(`[${new Date().toLocaleTimeString()}] No changes detected.`));
45
+ return;
46
+ }
47
+
48
+ console.log(
49
+ chalk.yellow(`[${new Date().toLocaleTimeString()}] Changes detected: `) +
50
+ `${chalk.green(`+${diff.summary.added}`)} ${chalk.yellow(`~${diff.summary.modified}`)} ${chalk.red(`-${diff.summary.removed}`)}`
51
+ );
52
+
53
+ console.log(chalk.cyan(' Auto-syncing...'));
54
+ const result = await client.syncTokens(config.projectId, true);
55
+
56
+ if (result.synced) {
57
+ console.log(chalk.green(' Sync complete!'));
58
+ } else {
59
+ console.log(chalk.yellow(' Sync requires manual resolution. Run `yubild sync` to resolve.'));
60
+ }
61
+ } catch (error) {
62
+ console.error(chalk.red(` Error: ${(error as Error).message}`));
63
+ }
64
+ };
65
+
66
+ // Run immediately on start
67
+ await check();
68
+
69
+ // Then run on interval
70
+ setInterval(check, intervalSeconds * 1000);
71
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * yubild whoami
3
+ * Show the currently authenticated user
4
+ */
5
+
6
+ import { loadCredentials, isAuthenticated } from '../lib/auth';
7
+ import { ApiClient } from '../lib/api-client';
8
+
9
+ export async function whoamiCommand(): Promise<void> {
10
+ if (!isAuthenticated()) {
11
+ console.log('Not authenticated. Run `yubild login` to get started.');
12
+ process.exit(0);
13
+ }
14
+
15
+ const creds = loadCredentials();
16
+ if (!creds) {
17
+ console.log('Not authenticated. Run `yubild login` to get started.');
18
+ process.exit(0);
19
+ }
20
+
21
+ try {
22
+ const client = new ApiClient();
23
+ const { user } = await client.authenticate();
24
+ console.log(`Authenticated as: ${user.email}`);
25
+ console.log(`API URL: ${creds.apiUrl}`);
26
+ } catch {
27
+ console.log(`Stored email: ${creds.email || 'unknown'}`);
28
+ console.log(`API URL: ${creds.apiUrl}`);
29
+ console.log('(Could not verify - API may be unreachable)');
30
+ }
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Yubild CLI
5
+ * Pull design tokens from Yubild into your project
6
+ */
7
+
8
+ import { Command, Option } from 'commander';
9
+ import { loginCommand } from './commands/login';
10
+ import { initCommand } from './commands/init';
11
+ import { pullCommand } from './commands/pull';
12
+ import { whoamiCommand } from './commands/whoami';
13
+ import { logoutCommand } from './commands/logout';
14
+ import { syncCommand } from './commands/sync';
15
+ import { diffCommand } from './commands/diff';
16
+ import { pushCommand } from './commands/push';
17
+ import { exportCommand } from './commands/export';
18
+ import { deployCommand } from './commands/deploy';
19
+ import { publishCommand } from './commands/publish';
20
+ import { projectsCommand } from './commands/projects';
21
+ import { watchCommand } from './commands/watch';
22
+
23
+ const program = new Command();
24
+
25
+ program
26
+ .name('yubild')
27
+ .description('Yubild CLI - Design system token management')
28
+ .version('0.1.0');
29
+
30
+ program
31
+ .command('login')
32
+ .description('Authenticate with your Yubild account')
33
+ .option('--api-key <key>', 'API key (starts with nxui_sk_)')
34
+ .option('--api-url <url>', 'Yubild API URL', 'https://yubild.com')
35
+ .action(loginCommand);
36
+
37
+ program
38
+ .command('init')
39
+ .description('Initialize a project and create .yubildrc configuration')
40
+ .option('--project-id <id>', 'Yubild project ID')
41
+ .action(initCommand);
42
+
43
+ program
44
+ .command('pull')
45
+ .description('Pull latest design tokens into your project')
46
+ .option('-f, --format <format>', 'Specific format to pull (css, tailwind, json)')
47
+ .action(pullCommand);
48
+
49
+ program
50
+ .command('whoami')
51
+ .description('Show the currently authenticated user')
52
+ .action(whoamiCommand);
53
+
54
+ program
55
+ .command('logout')
56
+ .description('Clear stored authentication credentials')
57
+ .action(logoutCommand);
58
+
59
+ program
60
+ .command('sync')
61
+ .description('Sync design tokens between local project and Figma')
62
+ .option('--auto-merge', 'Automatically merge changes without prompting', false)
63
+ .action(syncCommand);
64
+
65
+ program
66
+ .command('diff')
67
+ .description('Show differences between local tokens and Figma tokens')
68
+ .action(diffCommand);
69
+
70
+ program
71
+ .command('push')
72
+ .description('Push design tokens to Figma')
73
+ .action(pushCommand);
74
+
75
+ program
76
+ .command('export')
77
+ .description('Export design tokens in a specific format')
78
+ .addOption(new Option('-f, --format <format>', 'Output format').choices(['css', 'tailwind', 'json', 'scss', 'react', 'vue', 'angular']).makeOptionMandatory())
79
+ .option('-o, --output <dir>', 'Output directory')
80
+ .action(exportCommand);
81
+
82
+ program
83
+ .command('deploy')
84
+ .description('Deploy design tokens to CDN')
85
+ .action(deployCommand);
86
+
87
+ program
88
+ .command('publish')
89
+ .description('Publish design tokens to Git via Pull Request')
90
+ .option('--repo <repositoryId>', 'Target repository ID')
91
+ .action(publishCommand);
92
+
93
+ program
94
+ .command('projects')
95
+ .description('List all projects')
96
+ .action(projectsCommand);
97
+
98
+ program
99
+ .command('watch')
100
+ .description('Watch for token changes and auto-sync')
101
+ .option('--interval <seconds>', 'Polling interval in seconds', '30')
102
+ .action(watchCommand);
103
+
104
+ program.parse();
@@ -0,0 +1,231 @@
1
+ /**
2
+ * HTTP client for Yubild API
3
+ */
4
+
5
+ import { getApiKey, getApiUrl } from './auth';
6
+
7
+ export class ApiClient {
8
+ private baseUrl: string;
9
+ private apiKey: string;
10
+
11
+ constructor(apiUrl?: string, apiKey?: string) {
12
+ this.baseUrl = apiUrl || getApiUrl();
13
+ this.apiKey = apiKey || getApiKey() || '';
14
+ }
15
+
16
+ private async request<T>(
17
+ endpoint: string,
18
+ options: RequestInit = {}
19
+ ): Promise<T> {
20
+ const url = `${this.baseUrl}/api${endpoint}`;
21
+
22
+ const response = await fetch(url, {
23
+ ...options,
24
+ headers: {
25
+ Authorization: `Bearer ${this.apiKey}`,
26
+ 'Content-Type': 'application/json',
27
+ 'User-Agent': 'yubild-cli',
28
+ ...options.headers,
29
+ },
30
+ });
31
+
32
+ if (response.status === 401) {
33
+ throw new Error('Authentication failed. Run `yubild login` to authenticate.');
34
+ }
35
+
36
+ if (!response.ok) {
37
+ let errorMessage = `API error: ${response.status}`;
38
+ try {
39
+ const body = (await response.json()) as Record<string, string>;
40
+ errorMessage = body.error || body.message || errorMessage;
41
+ } catch {
42
+ // Use default
43
+ }
44
+ throw new Error(errorMessage);
45
+ }
46
+
47
+ return response.json() as Promise<T>;
48
+ }
49
+
50
+ private async rawRequest(
51
+ endpoint: string,
52
+ options: RequestInit = {}
53
+ ): Promise<Response> {
54
+ const url = `${this.baseUrl}/api${endpoint}`;
55
+
56
+ const response = await fetch(url, {
57
+ ...options,
58
+ headers: {
59
+ Authorization: `Bearer ${this.apiKey}`,
60
+ 'Content-Type': 'application/json',
61
+ 'User-Agent': 'yubild-cli',
62
+ ...options.headers,
63
+ },
64
+ });
65
+
66
+ if (response.status === 401) {
67
+ throw new Error('Authentication failed. Run `yubild login` to authenticate.');
68
+ }
69
+
70
+ if (!response.ok) {
71
+ let errorMessage = `API error: ${response.status}`;
72
+ try {
73
+ const body = (await response.json()) as Record<string, string>;
74
+ errorMessage = body.error || body.message || errorMessage;
75
+ } catch {
76
+ // Use default
77
+ }
78
+ throw new Error(errorMessage);
79
+ }
80
+
81
+ return response;
82
+ }
83
+
84
+ /**
85
+ * Authenticate with API key
86
+ */
87
+ async authenticate(): Promise<{ user: { email: string; name: string | null } }> {
88
+ return this.request('/cli/auth', {
89
+ method: 'POST',
90
+ });
91
+ }
92
+
93
+ /**
94
+ * Get project status
95
+ */
96
+ async getProjectStatus(projectId: string): Promise<{
97
+ project: { name: string; version: string; framework: string };
98
+ lastBuild: { version: string; createdAt: string } | null;
99
+ }> {
100
+ return this.request(`/cli/status?projectId=${projectId}`);
101
+ }
102
+
103
+ /**
104
+ * Pull tokens for a project
105
+ */
106
+ async pullTokens(
107
+ projectId: string,
108
+ formats: string[]
109
+ ): Promise<{
110
+ files: Array<{ path: string; content: string }>;
111
+ version: string;
112
+ }> {
113
+ return this.request('/cli/pull', {
114
+ method: 'POST',
115
+ body: JSON.stringify({ projectId, formats }),
116
+ });
117
+ }
118
+
119
+ /**
120
+ * Sync tokens between local and Figma
121
+ */
122
+ async syncTokens(
123
+ projectId: string,
124
+ autoMerge: boolean
125
+ ): Promise<{
126
+ synced: boolean;
127
+ diff?: {
128
+ entries: Array<{ tokenKey: string; status: string; localValue?: string; figmaValue?: string }>;
129
+ summary: { added: number; modified: number; removed: number; unchanged: number };
130
+ };
131
+ summary?: { added: number; modified: number; removed: number; unchanged: number };
132
+ warnings?: string[];
133
+ }> {
134
+ return this.request('/cli/sync', {
135
+ method: 'POST',
136
+ body: JSON.stringify({ projectId, autoMerge }),
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Diff tokens between local and Figma
142
+ */
143
+ async diffTokens(projectId: string): Promise<{
144
+ entries: Array<{ tokenKey: string; status: string; localValue?: string; figmaValue?: string }>;
145
+ summary: { added: number; modified: number; removed: number; unchanged: number };
146
+ hasConflicts: boolean;
147
+ warnings?: string[];
148
+ }> {
149
+ return this.request('/cli/diff', {
150
+ method: 'POST',
151
+ body: JSON.stringify({ projectId }),
152
+ });
153
+ }
154
+
155
+ /**
156
+ * Push tokens to Figma
157
+ */
158
+ async pushToFigma(projectId: string): Promise<{
159
+ data: { variables: unknown[]; projectName: string; exportedAt: string };
160
+ }> {
161
+ return this.request('/cli/push', {
162
+ method: 'POST',
163
+ body: JSON.stringify({ projectId }),
164
+ });
165
+ }
166
+
167
+ /**
168
+ * Export tokens in a specific format
169
+ */
170
+ async exportTokens(
171
+ projectId: string,
172
+ format: string
173
+ ): Promise<{
174
+ files: Array<{ path: string; content: string }>;
175
+ format: string;
176
+ version: string;
177
+ }> {
178
+ return this.request('/cli/export', {
179
+ method: 'POST',
180
+ body: JSON.stringify({ projectId, format }),
181
+ });
182
+ }
183
+
184
+ /**
185
+ * Deploy tokens to CDN
186
+ */
187
+ async deployToCdn(projectId: string): Promise<{
188
+ deployed: boolean;
189
+ version: string;
190
+ buildId: string;
191
+ cdnUrl: string;
192
+ }> {
193
+ return this.request('/cli/deploy', {
194
+ method: 'POST',
195
+ body: JSON.stringify({ projectId }),
196
+ });
197
+ }
198
+
199
+ /**
200
+ * Publish tokens to Git via Pull Request
201
+ */
202
+ async publishToGit(
203
+ projectId: string,
204
+ repositoryId?: string
205
+ ): Promise<{
206
+ published: boolean;
207
+ prUrl?: string;
208
+ prNumber?: number;
209
+ }> {
210
+ return this.request('/cli/publish', {
211
+ method: 'POST',
212
+ body: JSON.stringify({ projectId, repositoryId }),
213
+ });
214
+ }
215
+
216
+ /**
217
+ * List all projects
218
+ */
219
+ async listProjects(): Promise<{
220
+ projects: Array<{
221
+ id: string;
222
+ name: string;
223
+ slug: string;
224
+ version: string;
225
+ framework: string;
226
+ updatedAt: string;
227
+ }>;
228
+ }> {
229
+ return this.request('/cli/projects');
230
+ }
231
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Authentication management for CLI
3
+ * Stores credentials in user's home directory
4
+ */
5
+
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ import * as os from 'os';
9
+
10
+ interface AuthCredentials {
11
+ apiKey: string;
12
+ apiUrl: string;
13
+ email?: string;
14
+ expiresAt?: string;
15
+ }
16
+
17
+ const AUTH_DIR = path.join(os.homedir(), '.yubild');
18
+ const AUTH_FILE = path.join(AUTH_DIR, 'credentials.json');
19
+
20
+ /**
21
+ * Store authentication credentials
22
+ */
23
+ export function saveCredentials(credentials: AuthCredentials): void {
24
+ if (!fs.existsSync(AUTH_DIR)) {
25
+ fs.mkdirSync(AUTH_DIR, { recursive: true, mode: 0o700 });
26
+ }
27
+
28
+ fs.writeFileSync(
29
+ AUTH_FILE,
30
+ JSON.stringify(credentials, null, 2),
31
+ { encoding: 'utf-8', mode: 0o600 }
32
+ );
33
+ }
34
+
35
+ /**
36
+ * Load stored credentials
37
+ */
38
+ export function loadCredentials(): AuthCredentials | null {
39
+ if (!fs.existsSync(AUTH_FILE)) return null;
40
+
41
+ try {
42
+ const content = fs.readFileSync(AUTH_FILE, 'utf-8');
43
+ return JSON.parse(content) as AuthCredentials;
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Clear stored credentials
51
+ */
52
+ export function clearCredentials(): void {
53
+ if (fs.existsSync(AUTH_FILE)) {
54
+ fs.unlinkSync(AUTH_FILE);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Check if authenticated
60
+ */
61
+ export function isAuthenticated(): boolean {
62
+ const creds = loadCredentials();
63
+ if (!creds) return false;
64
+
65
+ if (creds.expiresAt && new Date(creds.expiresAt) < new Date()) {
66
+ return false;
67
+ }
68
+
69
+ return !!creds.apiKey;
70
+ }
71
+
72
+ /**
73
+ * Get the API key for making requests
74
+ */
75
+ export function getApiKey(): string | null {
76
+ const creds = loadCredentials();
77
+ return creds?.apiKey || null;
78
+ }
79
+
80
+ /**
81
+ * Get the API URL
82
+ */
83
+ export function getApiUrl(): string {
84
+ const creds = loadCredentials();
85
+ return creds?.apiUrl || 'https://yubild.com';
86
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Configuration file management for .yubildrc
3
+ */
4
+
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+
8
+ export interface YubildConfig {
9
+ projectId: string;
10
+ apiUrl: string;
11
+ outputDir: string;
12
+ formats: string[];
13
+ framework?: string;
14
+ watchInterval?: number;
15
+ }
16
+
17
+ const CONFIG_FILENAME = '.yubildrc';
18
+ const DEFAULT_CONFIG: Partial<YubildConfig> = {
19
+ apiUrl: 'https://yubild.com',
20
+ outputDir: './src/tokens',
21
+ formats: ['css', 'tailwind', 'json'],
22
+ };
23
+
24
+ /**
25
+ * Find .yubildrc by walking up from cwd
26
+ */
27
+ export function findConfigFile(): string | null {
28
+ let dir = process.cwd();
29
+
30
+ while (true) {
31
+ const configPath = path.join(dir, CONFIG_FILENAME);
32
+ if (fs.existsSync(configPath)) {
33
+ return configPath;
34
+ }
35
+
36
+ const parentDir = path.dirname(dir);
37
+ if (parentDir === dir) break;
38
+ dir = parentDir;
39
+ }
40
+
41
+ return null;
42
+ }
43
+
44
+ /**
45
+ * Load configuration from .yubildrc
46
+ */
47
+ export function loadConfig(): YubildConfig | null {
48
+ const configPath = findConfigFile();
49
+ if (!configPath) return null;
50
+
51
+ try {
52
+ const content = fs.readFileSync(configPath, 'utf-8');
53
+ const config = JSON.parse(content) as YubildConfig;
54
+
55
+ return {
56
+ ...DEFAULT_CONFIG,
57
+ ...config,
58
+ } as YubildConfig;
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Save configuration to .yubildrc in cwd
66
+ */
67
+ export function saveConfig(config: YubildConfig): void {
68
+ const configPath = path.join(process.cwd(), CONFIG_FILENAME);
69
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
70
+ }
71
+
72
+ /**
73
+ * Get config path in cwd
74
+ */
75
+ export function getConfigPath(): string {
76
+ return path.join(process.cwd(), CONFIG_FILENAME);
77
+ }