@atomixstudio/mcp 0.1.1 → 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.
- package/README.md +72 -288
- package/dist/index.d.ts +1 -201
- package/dist/index.js +1288 -2740
- package/dist/index.js.map +1 -1
- package/package.json +31 -32
- package/data/component-tokens-snapshot.json +0 -659
- package/data/tenants/default.json +0 -73
- package/scripts/sync-component-tokens.cjs +0 -974
- package/scripts/sync-component-tokens.js +0 -678
- package/src/ai-rules-generator.ts +0 -1144
- package/src/component-tokens.ts +0 -702
- package/src/index.ts +0 -1155
- package/src/tenant-store.ts +0 -436
- package/src/tokens.ts +0 -208
- package/src/user-tokens.ts +0 -268
- package/src/utils.ts +0 -465
- package/tests/stress-test.cjs +0 -907
- package/tsconfig.json +0 -21
- package/tsup.config.ts +0 -16
package/src/tenant-store.ts
DELETED
|
@@ -1,436 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tenant Store
|
|
3
|
-
*
|
|
4
|
-
* Manages per-tenant token configurations with support for:
|
|
5
|
-
* - Local file storage (default)
|
|
6
|
-
* - Cloud API storage (future)
|
|
7
|
-
* - In-memory caching
|
|
8
|
-
*
|
|
9
|
-
* Each tenant gets isolated storage for:
|
|
10
|
-
* - Component defaults
|
|
11
|
-
* - Token overrides
|
|
12
|
-
* - Locked token configurations
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import fs from "fs";
|
|
16
|
-
import path from "path";
|
|
17
|
-
import { fileURLToPath } from "url";
|
|
18
|
-
|
|
19
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
-
const __dirname = path.dirname(__filename);
|
|
21
|
-
|
|
22
|
-
// ============================================
|
|
23
|
-
// TYPES
|
|
24
|
-
// ============================================
|
|
25
|
-
|
|
26
|
-
export interface TenantConfig {
|
|
27
|
-
tenantId: string;
|
|
28
|
-
createdAt: string;
|
|
29
|
-
updatedAt: string;
|
|
30
|
-
componentDefaults: Record<string, unknown>;
|
|
31
|
-
lockedTokens: string[];
|
|
32
|
-
tokenOverrides: Record<string, string>;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface TenantStoreOptions {
|
|
36
|
-
/** Base directory for tenant data (default: ./data/tenants) */
|
|
37
|
-
dataDir?: string;
|
|
38
|
-
/** Cloud API URL for remote storage (optional) */
|
|
39
|
-
cloudApiUrl?: string;
|
|
40
|
-
/** Enable in-memory caching */
|
|
41
|
-
enableCache?: boolean;
|
|
42
|
-
/** Cache TTL in milliseconds */
|
|
43
|
-
cacheTtlMs?: number;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface CacheEntry {
|
|
47
|
-
data: TenantConfig;
|
|
48
|
-
timestamp: number;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ============================================
|
|
52
|
-
// TENANT STORE
|
|
53
|
-
// ============================================
|
|
54
|
-
|
|
55
|
-
export class TenantStore {
|
|
56
|
-
private dataDir: string;
|
|
57
|
-
private cloudApiUrl: string | null;
|
|
58
|
-
private cache: Map<string, CacheEntry>;
|
|
59
|
-
private enableCache: boolean;
|
|
60
|
-
private cacheTtlMs: number;
|
|
61
|
-
|
|
62
|
-
constructor(options: TenantStoreOptions = {}) {
|
|
63
|
-
this.dataDir = options.dataDir || path.join(__dirname, "../data/tenants");
|
|
64
|
-
this.cloudApiUrl = options.cloudApiUrl || null;
|
|
65
|
-
this.cache = new Map();
|
|
66
|
-
this.enableCache = options.enableCache ?? true;
|
|
67
|
-
this.cacheTtlMs = options.cacheTtlMs ?? 60000; // 1 minute default
|
|
68
|
-
|
|
69
|
-
// Ensure data directory exists
|
|
70
|
-
this.ensureDataDir();
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
private ensureDataDir(): void {
|
|
74
|
-
if (!fs.existsSync(this.dataDir)) {
|
|
75
|
-
fs.mkdirSync(this.dataDir, { recursive: true });
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
private getTenantFilePath(tenantId: string): string {
|
|
80
|
-
// Sanitize tenant ID for filesystem safety
|
|
81
|
-
const safeId = tenantId.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
82
|
-
return path.join(this.dataDir, `${safeId}.json`);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
private isCacheValid(entry: CacheEntry): boolean {
|
|
86
|
-
return Date.now() - entry.timestamp < this.cacheTtlMs;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// ============================================
|
|
90
|
-
// PUBLIC API
|
|
91
|
-
// ============================================
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Get tenant configuration
|
|
95
|
-
*/
|
|
96
|
-
async getTenant(tenantId: string): Promise<TenantConfig | null> {
|
|
97
|
-
// Check cache first
|
|
98
|
-
if (this.enableCache) {
|
|
99
|
-
const cached = this.cache.get(tenantId);
|
|
100
|
-
if (cached && this.isCacheValid(cached)) {
|
|
101
|
-
return cached.data;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Try cloud API first if configured
|
|
106
|
-
if (this.cloudApiUrl) {
|
|
107
|
-
try {
|
|
108
|
-
const cloudData = await this.fetchFromCloud(tenantId);
|
|
109
|
-
if (cloudData) {
|
|
110
|
-
this.updateCache(tenantId, cloudData);
|
|
111
|
-
return cloudData;
|
|
112
|
-
}
|
|
113
|
-
} catch (error) {
|
|
114
|
-
console.warn(`Cloud fetch failed for tenant ${tenantId}, falling back to local`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Fall back to local storage
|
|
119
|
-
const localData = this.loadFromFile(tenantId);
|
|
120
|
-
if (localData) {
|
|
121
|
-
this.updateCache(tenantId, localData);
|
|
122
|
-
}
|
|
123
|
-
return localData;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Save tenant configuration
|
|
128
|
-
*/
|
|
129
|
-
async saveTenant(tenantId: string, config: Partial<TenantConfig>): Promise<TenantConfig> {
|
|
130
|
-
const existing = await this.getTenant(tenantId);
|
|
131
|
-
const now = new Date().toISOString();
|
|
132
|
-
|
|
133
|
-
const updated: TenantConfig = {
|
|
134
|
-
tenantId,
|
|
135
|
-
createdAt: existing?.createdAt || now,
|
|
136
|
-
updatedAt: now,
|
|
137
|
-
componentDefaults: config.componentDefaults || existing?.componentDefaults || {},
|
|
138
|
-
lockedTokens: config.lockedTokens || existing?.lockedTokens || [],
|
|
139
|
-
tokenOverrides: config.tokenOverrides || existing?.tokenOverrides || {},
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
// Save to cloud if configured
|
|
143
|
-
if (this.cloudApiUrl) {
|
|
144
|
-
try {
|
|
145
|
-
await this.saveToCloud(tenantId, updated);
|
|
146
|
-
} catch (error) {
|
|
147
|
-
console.warn(`Cloud save failed for tenant ${tenantId}, saving locally`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Always save locally as backup
|
|
152
|
-
this.saveToFile(tenantId, updated);
|
|
153
|
-
this.updateCache(tenantId, updated);
|
|
154
|
-
|
|
155
|
-
return updated;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Update component defaults for a tenant
|
|
160
|
-
*/
|
|
161
|
-
async updateComponentDefaults(
|
|
162
|
-
tenantId: string,
|
|
163
|
-
componentKey: string,
|
|
164
|
-
defaults: unknown
|
|
165
|
-
): Promise<TenantConfig> {
|
|
166
|
-
const existing = await this.getTenant(tenantId);
|
|
167
|
-
const componentDefaults = existing?.componentDefaults || {};
|
|
168
|
-
|
|
169
|
-
return this.saveTenant(tenantId, {
|
|
170
|
-
...existing,
|
|
171
|
-
componentDefaults: {
|
|
172
|
-
...componentDefaults,
|
|
173
|
-
[componentKey]: defaults,
|
|
174
|
-
},
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Check if a token is locked for a tenant
|
|
180
|
-
*/
|
|
181
|
-
async isTokenLocked(tenantId: string, tokenPath: string): Promise<boolean> {
|
|
182
|
-
const tenant = await this.getTenant(tenantId);
|
|
183
|
-
if (!tenant) return false;
|
|
184
|
-
|
|
185
|
-
return tenant.lockedTokens.some(locked => {
|
|
186
|
-
// Support wildcards: "button.*.bgColor" matches "button.primary.bgColor"
|
|
187
|
-
if (locked.includes("*")) {
|
|
188
|
-
const regex = new RegExp("^" + locked.replace(/\*/g, "[^.]+") + "$");
|
|
189
|
-
return regex.test(tokenPath);
|
|
190
|
-
}
|
|
191
|
-
return locked === tokenPath;
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Lock tokens for a tenant
|
|
197
|
-
*/
|
|
198
|
-
async lockTokens(tenantId: string, tokenPaths: string[]): Promise<TenantConfig> {
|
|
199
|
-
const existing = await this.getTenant(tenantId);
|
|
200
|
-
const currentLocked = existing?.lockedTokens || [];
|
|
201
|
-
const newLocked = [...new Set([...currentLocked, ...tokenPaths])];
|
|
202
|
-
|
|
203
|
-
return this.saveTenant(tenantId, {
|
|
204
|
-
...existing,
|
|
205
|
-
lockedTokens: newLocked,
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Unlock tokens for a tenant
|
|
211
|
-
*/
|
|
212
|
-
async unlockTokens(tenantId: string, tokenPaths: string[]): Promise<TenantConfig> {
|
|
213
|
-
const existing = await this.getTenant(tenantId);
|
|
214
|
-
const currentLocked = existing?.lockedTokens || [];
|
|
215
|
-
const newLocked = currentLocked.filter(t => !tokenPaths.includes(t));
|
|
216
|
-
|
|
217
|
-
return this.saveTenant(tenantId, {
|
|
218
|
-
...existing,
|
|
219
|
-
lockedTokens: newLocked,
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* List all tenants
|
|
225
|
-
*/
|
|
226
|
-
listTenants(): string[] {
|
|
227
|
-
if (!fs.existsSync(this.dataDir)) {
|
|
228
|
-
return [];
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return fs.readdirSync(this.dataDir)
|
|
232
|
-
.filter(f => f.endsWith(".json"))
|
|
233
|
-
.map(f => f.replace(".json", ""));
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Delete a tenant
|
|
238
|
-
*/
|
|
239
|
-
async deleteTenant(tenantId: string): Promise<boolean> {
|
|
240
|
-
const filePath = this.getTenantFilePath(tenantId);
|
|
241
|
-
|
|
242
|
-
// Remove from cache
|
|
243
|
-
this.cache.delete(tenantId);
|
|
244
|
-
|
|
245
|
-
// Delete from cloud if configured
|
|
246
|
-
if (this.cloudApiUrl) {
|
|
247
|
-
try {
|
|
248
|
-
await this.deleteFromCloud(tenantId);
|
|
249
|
-
} catch (error) {
|
|
250
|
-
console.warn(`Cloud delete failed for tenant ${tenantId}`);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Delete local file
|
|
255
|
-
if (fs.existsSync(filePath)) {
|
|
256
|
-
fs.unlinkSync(filePath);
|
|
257
|
-
return true;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return false;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Clear cache
|
|
265
|
-
*/
|
|
266
|
-
clearCache(): void {
|
|
267
|
-
this.cache.clear();
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// ============================================
|
|
271
|
-
// PRIVATE: File Storage
|
|
272
|
-
// ============================================
|
|
273
|
-
|
|
274
|
-
private loadFromFile(tenantId: string): TenantConfig | null {
|
|
275
|
-
const filePath = this.getTenantFilePath(tenantId);
|
|
276
|
-
|
|
277
|
-
if (!fs.existsSync(filePath)) {
|
|
278
|
-
return null;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
try {
|
|
282
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
283
|
-
return JSON.parse(content) as TenantConfig;
|
|
284
|
-
} catch (error) {
|
|
285
|
-
console.error(`Failed to load tenant ${tenantId}:`, error);
|
|
286
|
-
return null;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
private saveToFile(tenantId: string, config: TenantConfig): void {
|
|
291
|
-
const filePath = this.getTenantFilePath(tenantId);
|
|
292
|
-
|
|
293
|
-
try {
|
|
294
|
-
fs.writeFileSync(filePath, JSON.stringify(config, null, 2), "utf-8");
|
|
295
|
-
} catch (error) {
|
|
296
|
-
console.error(`Failed to save tenant ${tenantId}:`, error);
|
|
297
|
-
throw error;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// ============================================
|
|
302
|
-
// PRIVATE: Cloud Storage (Future)
|
|
303
|
-
// ============================================
|
|
304
|
-
|
|
305
|
-
private async fetchFromCloud(tenantId: string): Promise<TenantConfig | null> {
|
|
306
|
-
if (!this.cloudApiUrl) return null;
|
|
307
|
-
|
|
308
|
-
const response = await fetch(`${this.cloudApiUrl}/tenants/${tenantId}`);
|
|
309
|
-
|
|
310
|
-
if (!response.ok) {
|
|
311
|
-
if (response.status === 404) return null;
|
|
312
|
-
throw new Error(`Cloud API error: ${response.status}`);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return response.json() as Promise<TenantConfig>;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
private async saveToCloud(tenantId: string, config: TenantConfig): Promise<void> {
|
|
319
|
-
if (!this.cloudApiUrl) return;
|
|
320
|
-
|
|
321
|
-
const response = await fetch(`${this.cloudApiUrl}/tenants/${tenantId}`, {
|
|
322
|
-
method: "PUT",
|
|
323
|
-
headers: { "Content-Type": "application/json" },
|
|
324
|
-
body: JSON.stringify(config),
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
if (!response.ok) {
|
|
328
|
-
throw new Error(`Cloud API error: ${response.status}`);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
private async deleteFromCloud(tenantId: string): Promise<void> {
|
|
333
|
-
if (!this.cloudApiUrl) return;
|
|
334
|
-
|
|
335
|
-
const response = await fetch(`${this.cloudApiUrl}/tenants/${tenantId}`, {
|
|
336
|
-
method: "DELETE",
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
if (!response.ok && response.status !== 404) {
|
|
340
|
-
throw new Error(`Cloud API error: ${response.status}`);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// ============================================
|
|
345
|
-
// PRIVATE: Cache
|
|
346
|
-
// ============================================
|
|
347
|
-
|
|
348
|
-
private updateCache(tenantId: string, data: TenantConfig): void {
|
|
349
|
-
if (this.enableCache) {
|
|
350
|
-
this.cache.set(tenantId, {
|
|
351
|
-
data,
|
|
352
|
-
timestamp: Date.now(),
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// ============================================
|
|
359
|
-
// SINGLETON INSTANCE
|
|
360
|
-
// ============================================
|
|
361
|
-
|
|
362
|
-
let defaultStore: TenantStore | null = null;
|
|
363
|
-
|
|
364
|
-
export function getTenantStore(options?: TenantStoreOptions): TenantStore {
|
|
365
|
-
if (!defaultStore) {
|
|
366
|
-
defaultStore = new TenantStore({
|
|
367
|
-
cloudApiUrl: process.env.ATOMIX_CLOUD_API || undefined,
|
|
368
|
-
...options,
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
return defaultStore;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// ============================================
|
|
375
|
-
// VALIDATION HELPERS
|
|
376
|
-
// ============================================
|
|
377
|
-
|
|
378
|
-
export interface TokenChangeValidation {
|
|
379
|
-
valid: boolean;
|
|
380
|
-
blockedChanges: Array<{
|
|
381
|
-
path: string;
|
|
382
|
-
reason: string;
|
|
383
|
-
}>;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Validate token changes against locked tokens
|
|
388
|
-
*/
|
|
389
|
-
export async function validateTokenChanges(
|
|
390
|
-
tenantId: string,
|
|
391
|
-
changes: Record<string, unknown>,
|
|
392
|
-
store?: TenantStore
|
|
393
|
-
): Promise<TokenChangeValidation> {
|
|
394
|
-
const tenantStore = store || getTenantStore();
|
|
395
|
-
const blockedChanges: TokenChangeValidation["blockedChanges"] = [];
|
|
396
|
-
|
|
397
|
-
// Flatten changes to paths
|
|
398
|
-
const changePaths = flattenToPaths(changes);
|
|
399
|
-
|
|
400
|
-
for (const changePath of changePaths) {
|
|
401
|
-
const isLocked = await tenantStore.isTokenLocked(tenantId, changePath);
|
|
402
|
-
if (isLocked) {
|
|
403
|
-
blockedChanges.push({
|
|
404
|
-
path: changePath,
|
|
405
|
-
reason: `Token "${changePath}" is locked for tenant "${tenantId}"`,
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
return {
|
|
411
|
-
valid: blockedChanges.length === 0,
|
|
412
|
-
blockedChanges,
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Flatten an object to dot-notation paths
|
|
418
|
-
*/
|
|
419
|
-
function flattenToPaths(obj: unknown, prefix = ""): string[] {
|
|
420
|
-
const paths: string[] = [];
|
|
421
|
-
|
|
422
|
-
if (obj && typeof obj === "object" && !Array.isArray(obj)) {
|
|
423
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
424
|
-
const newPrefix = prefix ? `${prefix}.${key}` : key;
|
|
425
|
-
|
|
426
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
427
|
-
paths.push(...flattenToPaths(value, newPrefix));
|
|
428
|
-
} else {
|
|
429
|
-
paths.push(newPrefix);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
return paths;
|
|
435
|
-
}
|
|
436
|
-
|
package/src/tokens.ts
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Token data for MCP server
|
|
3
|
-
*
|
|
4
|
-
* Loads primitives dynamically from the local @atomix/tokens package.
|
|
5
|
-
* This module provides the single source of truth for all design tokens.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { createRequire } from "node:module";
|
|
9
|
-
import { fileURLToPath } from "node:url";
|
|
10
|
-
import { dirname, join } from "node:path";
|
|
11
|
-
|
|
12
|
-
// Get the directory of this file
|
|
13
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
-
const __dirname = dirname(__filename);
|
|
15
|
-
|
|
16
|
-
// Load primitives from the sibling atomix package
|
|
17
|
-
// Path: packages/atomix-mcp/dist -> packages/atomix/dist
|
|
18
|
-
const atomixPath = join(__dirname, "../../atomix/dist/index.mjs");
|
|
19
|
-
|
|
20
|
-
// Dynamic import with fallback to inline primitives
|
|
21
|
-
let loadedPrimitives: Record<string, unknown> | null = null;
|
|
22
|
-
|
|
23
|
-
async function loadPrimitives(): Promise<Record<string, unknown>> {
|
|
24
|
-
if (loadedPrimitives) return loadedPrimitives;
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
const module = await import(atomixPath);
|
|
28
|
-
loadedPrimitives = module.primitives as Record<string, unknown>;
|
|
29
|
-
return loadedPrimitives;
|
|
30
|
-
} catch (error) {
|
|
31
|
-
console.error(`Failed to load @atomix/tokens from ${atomixPath}:`, error);
|
|
32
|
-
console.error("Falling back to inline primitives...");
|
|
33
|
-
loadedPrimitives = FALLBACK_PRIMITIVES;
|
|
34
|
-
return FALLBACK_PRIMITIVES;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Export a synchronous reference (populated on first use)
|
|
39
|
-
// For the MCP server, we'll use loadPrimitives() to ensure it's loaded
|
|
40
|
-
export { loadPrimitives };
|
|
41
|
-
|
|
42
|
-
// Synchronous export (may be empty until loadPrimitives is called)
|
|
43
|
-
export let primitives: Record<string, unknown> = {};
|
|
44
|
-
|
|
45
|
-
// Initialize primitives immediately
|
|
46
|
-
loadPrimitives().then((p) => {
|
|
47
|
-
primitives = p;
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
export const TOKEN_CATEGORIES = [
|
|
51
|
-
"colors",
|
|
52
|
-
"typography",
|
|
53
|
-
"spacing",
|
|
54
|
-
"sizing",
|
|
55
|
-
"shadows",
|
|
56
|
-
"radius",
|
|
57
|
-
"motion",
|
|
58
|
-
"zIndex",
|
|
59
|
-
"borders",
|
|
60
|
-
] as const;
|
|
61
|
-
|
|
62
|
-
export type TokenCategory = typeof TOKEN_CATEGORIES[number];
|
|
63
|
-
|
|
64
|
-
// Fallback primitives (minimal set for testing when @atomix/tokens isn't available)
|
|
65
|
-
const FALLBACK_PRIMITIVES: Record<string, unknown> = {
|
|
66
|
-
colors: {
|
|
67
|
-
static: {
|
|
68
|
-
brand: {
|
|
69
|
-
primary: "#007061",
|
|
70
|
-
primaryLight: "#00A389",
|
|
71
|
-
primaryDark: "#005A4D",
|
|
72
|
-
primaryForeground: "#FFFFFF",
|
|
73
|
-
},
|
|
74
|
-
white: "#FFFFFF",
|
|
75
|
-
black: "#000000",
|
|
76
|
-
},
|
|
77
|
-
modes: {
|
|
78
|
-
light: {
|
|
79
|
-
bgPage: "#FFFFFF",
|
|
80
|
-
bgSurface: "#FFFFFF",
|
|
81
|
-
bgMuted: "#F5F5F5",
|
|
82
|
-
textPrimary: "#171717",
|
|
83
|
-
textSecondary: "#525252",
|
|
84
|
-
textMuted: "#A3A3A3",
|
|
85
|
-
// Icon colors (derived from brand/text)
|
|
86
|
-
iconBrand: "#007061", // = brand.primary
|
|
87
|
-
iconStrong: "#171717", // = textPrimary
|
|
88
|
-
iconSubtle: "#525252", // = textSecondary
|
|
89
|
-
iconDisabled: "#A3A3A3", // = textMuted
|
|
90
|
-
borderPrimary: "#E5E5E5",
|
|
91
|
-
},
|
|
92
|
-
dark: {
|
|
93
|
-
bgPage: "#0A0A0A",
|
|
94
|
-
bgSurface: "#1A1A1A",
|
|
95
|
-
bgMuted: "#262626",
|
|
96
|
-
textPrimary: "#FAFAFA",
|
|
97
|
-
textSecondary: "#A3A3A3",
|
|
98
|
-
textMuted: "#737373",
|
|
99
|
-
// Icon colors (derived from brand/text)
|
|
100
|
-
iconBrand: "#007061", // = brand.primary
|
|
101
|
-
iconStrong: "#FAFAFA", // = textPrimary
|
|
102
|
-
iconSubtle: "#A3A3A3", // = textSecondary
|
|
103
|
-
iconDisabled: "#737373", // = textMuted
|
|
104
|
-
borderPrimary: "#404040",
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
scales: {
|
|
108
|
-
green: {
|
|
109
|
-
50: "#E6F5F2",
|
|
110
|
-
500: "#007061",
|
|
111
|
-
900: "#002E28",
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
typography: {
|
|
116
|
-
fontFamily: {
|
|
117
|
-
sans: "Inter, system-ui, sans-serif",
|
|
118
|
-
mono: "JetBrains Mono, monospace",
|
|
119
|
-
},
|
|
120
|
-
fontSize: {
|
|
121
|
-
xs: "0.75rem",
|
|
122
|
-
sm: "0.875rem",
|
|
123
|
-
md: "1rem",
|
|
124
|
-
lg: "1.125rem",
|
|
125
|
-
xl: "1.25rem",
|
|
126
|
-
},
|
|
127
|
-
fontWeight: {
|
|
128
|
-
regular: 400,
|
|
129
|
-
medium: 500,
|
|
130
|
-
semibold: 600,
|
|
131
|
-
bold: 700,
|
|
132
|
-
},
|
|
133
|
-
lineHeight: {
|
|
134
|
-
tight: 1.25,
|
|
135
|
-
normal: 1.5,
|
|
136
|
-
relaxed: 1.625,
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
spacing: {
|
|
140
|
-
scale: {
|
|
141
|
-
xs: "0.25rem",
|
|
142
|
-
sm: "0.5rem",
|
|
143
|
-
md: "1rem",
|
|
144
|
-
lg: "1.5rem",
|
|
145
|
-
xl: "2rem",
|
|
146
|
-
},
|
|
147
|
-
inset: {
|
|
148
|
-
xs: "0.25rem",
|
|
149
|
-
sm: "0.5rem",
|
|
150
|
-
md: "1rem",
|
|
151
|
-
lg: "1.5rem",
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
sizing: {
|
|
155
|
-
button: {
|
|
156
|
-
sm: { height: "32px" },
|
|
157
|
-
md: { height: "40px" },
|
|
158
|
-
lg: { height: "48px" },
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
shadows: {
|
|
162
|
-
elevation: {
|
|
163
|
-
none: "none",
|
|
164
|
-
sm: "0 1px 2px rgba(0, 0, 0, 0.05)",
|
|
165
|
-
md: "0 4px 6px rgba(0, 0, 0, 0.1)",
|
|
166
|
-
lg: "0 10px 15px rgba(0, 0, 0, 0.1)",
|
|
167
|
-
},
|
|
168
|
-
focus: {
|
|
169
|
-
ring: "0 0 0 2px var(--atomix-brand)",
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
radius: {
|
|
173
|
-
scale: {
|
|
174
|
-
none: "0",
|
|
175
|
-
sm: "0.25rem",
|
|
176
|
-
md: "0.5rem",
|
|
177
|
-
lg: "0.75rem",
|
|
178
|
-
xl: "1rem",
|
|
179
|
-
full: "9999px",
|
|
180
|
-
},
|
|
181
|
-
},
|
|
182
|
-
motion: {
|
|
183
|
-
duration: {
|
|
184
|
-
instant: "0ms",
|
|
185
|
-
fast: "150ms",
|
|
186
|
-
normal: "200ms",
|
|
187
|
-
slow: "300ms",
|
|
188
|
-
},
|
|
189
|
-
easing: {
|
|
190
|
-
ease: "cubic-bezier(0.4, 0, 0.2, 1)",
|
|
191
|
-
easeIn: "cubic-bezier(0.4, 0, 1, 1)",
|
|
192
|
-
easeOut: "cubic-bezier(0, 0, 0.2, 1)",
|
|
193
|
-
},
|
|
194
|
-
},
|
|
195
|
-
zIndex: {
|
|
196
|
-
dropdown: 1000,
|
|
197
|
-
modal: 1100,
|
|
198
|
-
tooltip: 1200,
|
|
199
|
-
},
|
|
200
|
-
borders: {
|
|
201
|
-
width: {
|
|
202
|
-
none: "0",
|
|
203
|
-
thin: "1px",
|
|
204
|
-
medium: "2px",
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
};
|
|
208
|
-
|