teamix-evo 0.1.0 → 0.2.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.
@@ -0,0 +1,1166 @@
1
+ // src/core/registry-client.ts
2
+ import * as path from "path";
3
+ import * as fs from "fs/promises";
4
+ import { createRequire } from "module";
5
+ import { loadVariantManifest } from "@teamix-evo/registry";
6
+
7
+ // src/utils/logger.ts
8
+ import { red, yellow, cyan, green, gray } from "kolorist";
9
+ var isDebug = process.env.TEAMIX_DEBUG === "1";
10
+ var logger = {
11
+ info(msg) {
12
+ console.log(cyan("\u2139"), msg);
13
+ },
14
+ warn(msg) {
15
+ console.warn(yellow("\u26A0"), msg);
16
+ },
17
+ error(msg) {
18
+ console.error(red("\u2716"), msg);
19
+ },
20
+ success(msg) {
21
+ console.log(green("\u2714"), msg);
22
+ },
23
+ debug(msg) {
24
+ if (isDebug) {
25
+ console.log(gray("\u22A1"), gray(msg));
26
+ }
27
+ }
28
+ };
29
+
30
+ // src/core/registry-client.ts
31
+ var require2 = createRequire(import.meta.url);
32
+ function resolvePackageRoot(packageName) {
33
+ const pkgJsonPath = require2.resolve(`${packageName}/package.json`);
34
+ return path.dirname(pkgJsonPath);
35
+ }
36
+ async function loadVariantData(packageName, variant) {
37
+ const packageRoot = resolvePackageRoot(packageName);
38
+ const variantDir = path.join(packageRoot, "library", variant);
39
+ logger.debug(`Resolved variant dir: ${variantDir}`);
40
+ logger.debug(`Package root: ${packageRoot}`);
41
+ const manifest = await loadVariantManifest(variantDir);
42
+ let data = {};
43
+ const dataPath = path.join(variantDir, "_data.json");
44
+ try {
45
+ const raw = await fs.readFile(dataPath, "utf-8");
46
+ data = JSON.parse(raw);
47
+ } catch (err) {
48
+ if (err.code !== "ENOENT") {
49
+ throw err;
50
+ }
51
+ logger.debug(`No _data.json found at ${dataPath}, using empty data`);
52
+ }
53
+ return { manifest, data, variantDir, packageRoot };
54
+ }
55
+
56
+ // src/core/installer.ts
57
+ import * as path4 from "path";
58
+ import * as fs5 from "fs/promises";
59
+
60
+ // src/utils/fs.ts
61
+ import * as fs2 from "fs/promises";
62
+ import * as path2 from "path";
63
+ async function ensureDir(dir) {
64
+ await fs2.mkdir(dir, { recursive: true });
65
+ }
66
+ async function writeFileSafe(filePath, content) {
67
+ const dir = path2.dirname(filePath);
68
+ await ensureDir(dir);
69
+ const tmp = filePath + ".tmp";
70
+ await fs2.writeFile(tmp, content, "utf-8");
71
+ await fs2.rename(tmp, filePath);
72
+ }
73
+ async function readFileOrNull(filePath) {
74
+ try {
75
+ return await fs2.readFile(filePath, "utf-8");
76
+ } catch (err) {
77
+ if (err.code === "ENOENT") {
78
+ return null;
79
+ }
80
+ throw err;
81
+ }
82
+ }
83
+ async function backupFile(filePath, projectRoot) {
84
+ const content = await readFileOrNull(filePath);
85
+ if (content === null) {
86
+ logger.debug(`Skip backup: ${filePath} does not exist`);
87
+ return;
88
+ }
89
+ const rel2 = path2.relative(projectRoot, filePath);
90
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
91
+ const backupPath = path2.join(
92
+ projectRoot,
93
+ ".teamix-evo",
94
+ ".backups",
95
+ `${rel2}.${timestamp}.bak`
96
+ );
97
+ await ensureDir(path2.dirname(backupPath));
98
+ await fs2.writeFile(backupPath, content, "utf-8");
99
+ logger.debug(`Backed up ${rel2} \u2192 ${path2.relative(projectRoot, backupPath)}`);
100
+ }
101
+ async function fileExists(filePath) {
102
+ try {
103
+ await fs2.access(filePath);
104
+ return true;
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+
110
+ // src/utils/hash.ts
111
+ import { createHash } from "crypto";
112
+ function computeHash(content) {
113
+ const hash = createHash("sha256").update(content, "utf-8").digest("hex");
114
+ return `sha256:${hash}`;
115
+ }
116
+
117
+ // src/utils/template.ts
118
+ import Handlebars from "handlebars";
119
+ import * as fs3 from "fs/promises";
120
+ Handlebars.registerHelper("lowercase", (str) => {
121
+ return typeof str === "string" ? str.toLowerCase() : String(str ?? "").toLowerCase();
122
+ });
123
+ var compiledCache = /* @__PURE__ */ new Map();
124
+ var MAX_CACHE_SIZE = 64;
125
+ function getCompiledTemplate(templateContent) {
126
+ let compiled = compiledCache.get(templateContent);
127
+ if (!compiled) {
128
+ if (compiledCache.size >= MAX_CACHE_SIZE) {
129
+ const firstKey = compiledCache.keys().next().value;
130
+ compiledCache.delete(firstKey);
131
+ }
132
+ compiled = Handlebars.compile(templateContent, { noEscape: true });
133
+ compiledCache.set(templateContent, compiled);
134
+ }
135
+ return compiled;
136
+ }
137
+ function renderTemplate(templateContent, data) {
138
+ const compiled = getCompiledTemplate(templateContent);
139
+ return compiled(data);
140
+ }
141
+ async function loadTemplateFile(filePath) {
142
+ return fs3.readFile(filePath, "utf-8");
143
+ }
144
+
145
+ // src/utils/path.ts
146
+ import * as path3 from "path";
147
+ import * as fs4 from "fs/promises";
148
+ function resolveSourcePath(source, variantDir, packageRoot) {
149
+ if (source.startsWith("_template/")) {
150
+ return path3.join(packageRoot, source);
151
+ }
152
+ return path3.join(variantDir, source);
153
+ }
154
+ async function walkDir(dir) {
155
+ const files = [];
156
+ const entries = await fs4.readdir(dir, { withFileTypes: true });
157
+ for (const entry of entries) {
158
+ const fullPath = path3.join(dir, entry.name);
159
+ if (entry.isDirectory()) {
160
+ files.push(...await walkDir(fullPath));
161
+ } else if (entry.isFile()) {
162
+ files.push(fullPath);
163
+ }
164
+ }
165
+ return files;
166
+ }
167
+
168
+ // src/core/installer.ts
169
+ async function installResources(options) {
170
+ const { projectRoot, manifest, data, variantDir, packageRoot } = options;
171
+ const installedResources = [];
172
+ for (const resource of manifest.resources) {
173
+ logger.debug(`Installing resource: ${resource.id} \u2192 ${resource.target}`);
174
+ if (resource.recursive) {
175
+ const results = await installRecursiveResource(
176
+ resource,
177
+ projectRoot,
178
+ data,
179
+ variantDir,
180
+ packageRoot
181
+ );
182
+ installedResources.push(...results);
183
+ } else {
184
+ const result = await installSingleResource(
185
+ resource,
186
+ projectRoot,
187
+ data,
188
+ variantDir,
189
+ packageRoot
190
+ );
191
+ installedResources.push(result);
192
+ }
193
+ }
194
+ return {
195
+ resources: installedResources,
196
+ count: installedResources.length
197
+ };
198
+ }
199
+ async function installSingleResource(resource, projectRoot, data, variantDir, packageRoot) {
200
+ const sourcePath = resolveSourcePath(
201
+ resource.source,
202
+ variantDir,
203
+ packageRoot
204
+ );
205
+ const targetPath = path4.join(projectRoot, resource.target);
206
+ let content;
207
+ if (resource.template) {
208
+ const templateContent = await loadTemplateFile(sourcePath);
209
+ content = renderTemplate(templateContent, data);
210
+ } else {
211
+ content = await fs5.readFile(sourcePath, "utf-8");
212
+ }
213
+ await writeFileSafe(targetPath, content);
214
+ const hash = computeHash(content);
215
+ logger.debug(` Written: ${resource.target} (${resource.updateStrategy})`);
216
+ return {
217
+ id: resource.id,
218
+ target: resource.target,
219
+ hash,
220
+ strategy: resource.updateStrategy
221
+ };
222
+ }
223
+ async function installRecursiveResource(resource, projectRoot, data, variantDir, packageRoot) {
224
+ const sourcePath = resolveSourcePath(
225
+ resource.source,
226
+ variantDir,
227
+ packageRoot
228
+ );
229
+ const targetDir = path4.join(projectRoot, resource.target);
230
+ const results = [];
231
+ await ensureDir(targetDir);
232
+ const entries = await walkDir(sourcePath);
233
+ for (const entry of entries) {
234
+ const relPath = path4.relative(sourcePath, entry);
235
+ let targetFile = path4.join(targetDir, relPath);
236
+ if (resource.template && targetFile.endsWith(".hbs")) {
237
+ targetFile = targetFile.slice(0, -4);
238
+ }
239
+ let content;
240
+ if (resource.template && entry.endsWith(".hbs")) {
241
+ const templateContent = await loadTemplateFile(entry);
242
+ content = renderTemplate(templateContent, data);
243
+ } else {
244
+ content = await fs5.readFile(entry, "utf-8");
245
+ }
246
+ await writeFileSafe(targetFile, content);
247
+ const hash = computeHash(content);
248
+ const targetRel = path4.relative(projectRoot, targetFile);
249
+ results.push({
250
+ id: `${resource.id}:${relPath}`,
251
+ target: targetRel,
252
+ hash,
253
+ strategy: resource.updateStrategy
254
+ });
255
+ logger.debug(` Written: ${targetRel}`);
256
+ }
257
+ return results;
258
+ }
259
+
260
+ // src/core/state.ts
261
+ import * as path5 from "path";
262
+ import { validateConfig, validateInstalled } from "@teamix-evo/registry";
263
+ var TEAMIX_DIR = ".teamix-evo";
264
+ var CONFIG_FILE = "config.json";
265
+ var MANIFEST_FILE = "manifest.json";
266
+ function getTeamixDir(projectRoot) {
267
+ return path5.join(projectRoot, TEAMIX_DIR);
268
+ }
269
+ async function ensureTeamixDir(projectRoot) {
270
+ const dir = getTeamixDir(projectRoot);
271
+ await ensureDir(dir);
272
+ return dir;
273
+ }
274
+ async function readProjectConfig(projectRoot) {
275
+ const configPath = path5.join(projectRoot, TEAMIX_DIR, CONFIG_FILE);
276
+ const raw = await readFileOrNull(configPath);
277
+ if (raw === null) return null;
278
+ try {
279
+ const data = JSON.parse(raw);
280
+ const result = validateConfig(data);
281
+ if (!result.success) {
282
+ logger.warn(`Invalid config.json: ${result.error}`);
283
+ return null;
284
+ }
285
+ return result.data;
286
+ } catch (err) {
287
+ logger.warn(`Failed to parse config.json: ${err.message}`);
288
+ return null;
289
+ }
290
+ }
291
+ async function writeProjectConfig(projectRoot, config) {
292
+ const configPath = path5.join(projectRoot, TEAMIX_DIR, CONFIG_FILE);
293
+ await writeFileSafe(configPath, JSON.stringify(config, null, 2) + "\n");
294
+ logger.debug(`Wrote config \u2192 ${configPath}`);
295
+ }
296
+ async function readInstalledManifest(projectRoot) {
297
+ const manifestPath = path5.join(projectRoot, TEAMIX_DIR, MANIFEST_FILE);
298
+ const raw = await readFileOrNull(manifestPath);
299
+ if (raw === null) return null;
300
+ try {
301
+ const data = JSON.parse(raw);
302
+ const result = validateInstalled(data);
303
+ if (!result.success) {
304
+ logger.warn(`Invalid manifest.json: ${result.error}`);
305
+ return null;
306
+ }
307
+ return result.data;
308
+ } catch (err) {
309
+ logger.warn(`Failed to parse manifest.json: ${err.message}`);
310
+ return null;
311
+ }
312
+ }
313
+ async function writeInstalledManifest(projectRoot, manifest) {
314
+ const manifestPath = path5.join(projectRoot, TEAMIX_DIR, MANIFEST_FILE);
315
+ await writeFileSafe(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
316
+ logger.debug(`Wrote manifest \u2192 ${manifestPath}`);
317
+ }
318
+
319
+ // src/core/design-init.ts
320
+ var DEFAULT_DESIGN_PACKAGE = "@teamix-evo/design";
321
+ async function runDesignInit(options) {
322
+ const { projectRoot, variant, tailwind, ide } = options;
323
+ const packageName = options.packageName ?? DEFAULT_DESIGN_PACKAGE;
324
+ await ensureTeamixDir(projectRoot);
325
+ const existingConfig = await readProjectConfig(projectRoot);
326
+ if (existingConfig?.packages?.design) {
327
+ return {
328
+ status: "already-initialized",
329
+ existingVariant: existingConfig.packages.design.variant
330
+ };
331
+ }
332
+ const { manifest, data, variantDir, packageRoot } = await loadVariantData(
333
+ packageName,
334
+ variant
335
+ );
336
+ const result = await installResources({
337
+ projectRoot,
338
+ manifest,
339
+ data,
340
+ variantDir,
341
+ packageRoot
342
+ });
343
+ const config = {
344
+ $schema: "https://teamix-evo.dev/schema/config/v1.json",
345
+ schemaVersion: 1,
346
+ ide,
347
+ packages: {
348
+ design: {
349
+ variant,
350
+ version: manifest.version,
351
+ tailwind
352
+ }
353
+ }
354
+ };
355
+ await writeProjectConfig(projectRoot, config);
356
+ const installedManifest = {
357
+ schemaVersion: 1,
358
+ installed: [
359
+ {
360
+ package: packageName,
361
+ variant,
362
+ version: manifest.version,
363
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
364
+ resources: result.resources
365
+ }
366
+ ]
367
+ };
368
+ await writeInstalledManifest(projectRoot, installedManifest);
369
+ return {
370
+ status: "installed",
371
+ packageName,
372
+ variant,
373
+ version: manifest.version,
374
+ tailwind,
375
+ count: result.count,
376
+ resources: result.resources
377
+ };
378
+ }
379
+
380
+ // src/core/skills-client.ts
381
+ import * as path6 from "path";
382
+ import * as fs6 from "fs/promises";
383
+ import { createRequire as createRequire2 } from "module";
384
+ import { loadSkillsPackageManifest } from "@teamix-evo/registry";
385
+ var require3 = createRequire2(import.meta.url);
386
+ function resolvePackageRoot2(packageName) {
387
+ const pkgJsonPath = require3.resolve(`${packageName}/package.json`);
388
+ return path6.dirname(pkgJsonPath);
389
+ }
390
+ async function loadSkillsData(packageName) {
391
+ const packageRoot = resolvePackageRoot2(packageName);
392
+ logger.debug(`Resolved skills package root: ${packageRoot}`);
393
+ const manifest = await loadSkillsPackageManifest(packageRoot);
394
+ let data = {};
395
+ const dataPath = path6.join(packageRoot, "_data.json");
396
+ try {
397
+ const raw = await fs6.readFile(dataPath, "utf-8");
398
+ data = JSON.parse(raw);
399
+ } catch (err) {
400
+ if (err.code !== "ENOENT") {
401
+ throw err;
402
+ }
403
+ logger.debug(`No _data.json found at ${dataPath}, using empty data`);
404
+ }
405
+ return { manifest, data, packageRoot };
406
+ }
407
+
408
+ // src/core/skills-installer.ts
409
+ import * as path9 from "path";
410
+ import * as fs7 from "fs/promises";
411
+ import { replaceManagedRegion } from "@teamix-evo/registry";
412
+
413
+ // src/ide/QoderAdapter.ts
414
+ import * as os from "os";
415
+ import * as path7 from "path";
416
+ var QoderAdapter = class {
417
+ kind = "qoder";
418
+ name = "qoder";
419
+ getProjectRoot() {
420
+ return process.cwd();
421
+ }
422
+ detectIde() {
423
+ return true;
424
+ }
425
+ getSkillTargetDir(skillName, scope, projectRoot) {
426
+ const base = scope === "global" ? path7.join(os.homedir(), ".qoder") : path7.join(projectRoot ?? this.getProjectRoot(), ".qoder");
427
+ return path7.join(base, "skills", skillName);
428
+ }
429
+ };
430
+
431
+ // src/ide/ClaudeAdapter.ts
432
+ import * as os2 from "os";
433
+ import * as path8 from "path";
434
+ var ClaudeAdapter = class {
435
+ kind = "claude";
436
+ name = "claude";
437
+ getProjectRoot() {
438
+ return process.cwd();
439
+ }
440
+ detectIde() {
441
+ return Boolean(process.env.CLAUDECODE);
442
+ }
443
+ getSkillTargetDir(skillName, scope, projectRoot) {
444
+ const base = scope === "global" ? path8.join(os2.homedir(), ".claude") : path8.join(projectRoot ?? this.getProjectRoot(), ".claude");
445
+ return path8.join(base, "skills", skillName);
446
+ }
447
+ };
448
+
449
+ // src/ide/index.ts
450
+ function getAdapter(kind) {
451
+ switch (kind) {
452
+ case "qoder":
453
+ return new QoderAdapter();
454
+ case "claude":
455
+ return new ClaudeAdapter();
456
+ default: {
457
+ const _exhaustive = kind;
458
+ throw new Error(`Unsupported IDE kind: ${_exhaustive}`);
459
+ }
460
+ }
461
+ }
462
+
463
+ // src/core/skills-installer.ts
464
+ async function installSkills(options) {
465
+ const { manifest, ides, scope, onlyIds } = options;
466
+ const installed = [];
467
+ const targets = manifest.skills.filter(
468
+ (s) => !onlyIds || onlyIds.includes(s.id)
469
+ );
470
+ for (const skill of targets) {
471
+ const skillIdes = skill.ides.filter((i) => ides.includes(i));
472
+ if (skillIdes.length === 0) {
473
+ logger.warn(
474
+ `Skill "${skill.name}" supports [${skill.ides.join(
475
+ ","
476
+ )}], no overlap with [${ides.join(",")}]; skipped.`
477
+ );
478
+ continue;
479
+ }
480
+ for (const ide of skillIdes) {
481
+ const result = await installSkillForIde(skill, ide, scope, options);
482
+ installed.push(...result);
483
+ }
484
+ }
485
+ return { resources: installed, count: installed.length };
486
+ }
487
+ async function installSkillForIde(skill, ide, scope, options) {
488
+ const { data, packageRoot, projectRoot } = options;
489
+ const adapter = getAdapter(ide);
490
+ const targetDir = adapter.getSkillTargetDir(skill.name, scope, projectRoot);
491
+ const sourceAbs = path9.resolve(packageRoot, skill.source);
492
+ const stat2 = await fs7.stat(sourceAbs);
493
+ const results = [];
494
+ if (stat2.isFile()) {
495
+ const targetFile = path9.join(targetDir, "SKILL.md");
496
+ const content = await renderSkillContent(sourceAbs, skill, data);
497
+ await writeFileSafe(targetFile, content);
498
+ results.push(makeInstalledRecord(skill, targetFile, content, ide, scope));
499
+ logger.debug(` Wrote ${ide}:${scope}: ${targetFile}`);
500
+ return results;
501
+ }
502
+ await ensureDir(targetDir);
503
+ const entries = await walkDir(sourceAbs);
504
+ for (const entry of entries) {
505
+ const rel2 = path9.relative(sourceAbs, entry);
506
+ let targetFile = path9.join(targetDir, rel2);
507
+ if (skill.template && targetFile.endsWith(".hbs")) {
508
+ targetFile = targetFile.slice(0, -4);
509
+ }
510
+ let content;
511
+ if (skill.template && entry.endsWith(".hbs")) {
512
+ const tpl = await loadTemplateFile(entry);
513
+ content = renderTemplate(tpl, { ...data, skill });
514
+ } else {
515
+ content = await fs7.readFile(entry, "utf-8");
516
+ }
517
+ await writeFileSafe(targetFile, content);
518
+ results.push(
519
+ makeInstalledRecord(skill, targetFile, content, ide, scope, rel2)
520
+ );
521
+ logger.debug(` Wrote ${ide}:${scope}: ${targetFile}`);
522
+ }
523
+ return results;
524
+ }
525
+ async function renderSkillContent(sourceAbs, skill, data) {
526
+ if (skill.template ?? sourceAbs.endsWith(".hbs")) {
527
+ const tpl = await loadTemplateFile(sourceAbs);
528
+ return renderTemplate(tpl, { ...data, skill });
529
+ }
530
+ return fs7.readFile(sourceAbs, "utf-8");
531
+ }
532
+ function makeInstalledRecord(skill, targetAbs, content, ide, scope, rel2) {
533
+ const id = rel2 ? `${skill.id}:${rel2}` : skill.id;
534
+ return {
535
+ id,
536
+ target: targetAbs,
537
+ hash: computeHash(content),
538
+ strategy: skill.updateStrategy,
539
+ ide,
540
+ scope
541
+ };
542
+ }
543
+ async function updateSkills(options) {
544
+ const { manifest, ides, scope, installed, projectRoot, data, packageRoot } = options;
545
+ const summary = { overwritten: 0, managed: 0, skipped: 0, created: 0 };
546
+ const updated = [];
547
+ const installedMap = /* @__PURE__ */ new Map();
548
+ for (const r of installed) {
549
+ installedMap.set(installedKey(r), r);
550
+ }
551
+ for (const skill of manifest.skills) {
552
+ const skillIdes = skill.ides.filter((i) => ides.includes(i));
553
+ for (const ide of skillIdes) {
554
+ const records = await updateSkillForIde(
555
+ skill,
556
+ ide,
557
+ scope,
558
+ data,
559
+ packageRoot,
560
+ projectRoot,
561
+ installedMap,
562
+ summary
563
+ );
564
+ updated.push(...records);
565
+ }
566
+ }
567
+ return { resources: updated, summary };
568
+ }
569
+ async function updateSkillForIde(skill, ide, scope, data, packageRoot, projectRoot, installedMap, summary) {
570
+ const adapter = getAdapter(ide);
571
+ const targetDir = adapter.getSkillTargetDir(skill.name, scope, projectRoot);
572
+ const sourceAbs = path9.resolve(packageRoot, skill.source);
573
+ const stat2 = await fs7.stat(sourceAbs);
574
+ const records = [];
575
+ if (!stat2.isFile()) {
576
+ const entries = await walkDir(sourceAbs);
577
+ await ensureDir(targetDir);
578
+ for (const entry of entries) {
579
+ const rel2 = path9.relative(sourceAbs, entry);
580
+ let targetFile2 = path9.join(targetDir, rel2);
581
+ if (skill.template && targetFile2.endsWith(".hbs")) {
582
+ targetFile2 = targetFile2.slice(0, -4);
583
+ }
584
+ let content;
585
+ if (skill.template && entry.endsWith(".hbs")) {
586
+ content = renderTemplate(await loadTemplateFile(entry), {
587
+ ...data,
588
+ skill
589
+ });
590
+ } else {
591
+ content = await fs7.readFile(entry, "utf-8");
592
+ }
593
+ const exists2 = await fileExists(targetFile2);
594
+ if (exists2) {
595
+ await backupFile(targetFile2, projectRoot);
596
+ summary.overwritten++;
597
+ } else {
598
+ summary.created++;
599
+ }
600
+ await writeFileSafe(targetFile2, content);
601
+ records.push(
602
+ makeInstalledRecord(skill, targetFile2, content, ide, scope, rel2)
603
+ );
604
+ }
605
+ return records;
606
+ }
607
+ const targetFile = path9.join(targetDir, "SKILL.md");
608
+ const newContent = await renderSkillContent(sourceAbs, skill, data);
609
+ const exists = await fileExists(targetFile);
610
+ const installedKeyStr = `${skill.id}|${ide}|${scope}`;
611
+ const prior = installedMap.get(installedKeyStr);
612
+ if (skill.updateStrategy === "frozen") {
613
+ if (exists) {
614
+ summary.skipped++;
615
+ return [
616
+ prior ?? makeInstalledRecord(skill, targetFile, newContent, ide, scope)
617
+ ];
618
+ }
619
+ await writeFileSafe(targetFile, newContent);
620
+ summary.created++;
621
+ records.push(
622
+ makeInstalledRecord(skill, targetFile, newContent, ide, scope)
623
+ );
624
+ return records;
625
+ }
626
+ if (skill.updateStrategy === "regenerable" || !exists) {
627
+ if (exists) {
628
+ await backupFile(targetFile, projectRoot);
629
+ summary.overwritten++;
630
+ } else {
631
+ summary.created++;
632
+ }
633
+ await writeFileSafe(targetFile, newContent);
634
+ records.push(
635
+ makeInstalledRecord(skill, targetFile, newContent, ide, scope)
636
+ );
637
+ return records;
638
+ }
639
+ const current = await readFileOrNull(targetFile);
640
+ let updated = current ?? newContent;
641
+ const regionIds = skill.managedRegions ?? [];
642
+ for (const regionId of regionIds) {
643
+ const re = new RegExp(
644
+ `<!-- teamix-evo:managed:start id="${escapeRegExp(
645
+ regionId
646
+ )}" -->([\\s\\S]*?)<!-- teamix-evo:managed:end id="${escapeRegExp(
647
+ regionId
648
+ )}" -->`
649
+ );
650
+ const match = newContent.match(re);
651
+ if (match) {
652
+ const region = match[1].replace(/^\n/, "").replace(/\n$/, "");
653
+ try {
654
+ updated = replaceManagedRegion(updated, regionId, region);
655
+ } catch {
656
+ logger.warn(
657
+ `Managed region "${regionId}" not found in ${targetFile}. Skipped.`
658
+ );
659
+ }
660
+ }
661
+ }
662
+ await backupFile(targetFile, projectRoot);
663
+ await writeFileSafe(targetFile, updated);
664
+ summary.managed++;
665
+ records.push(makeInstalledRecord(skill, targetFile, updated, ide, scope));
666
+ return records;
667
+ }
668
+ function installedKey(r) {
669
+ return `${r.id}|${r.ide ?? ""}|${r.scope ?? ""}`;
670
+ }
671
+ function escapeRegExp(str) {
672
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
673
+ }
674
+ async function removeSkillFiles(records) {
675
+ const removed = [];
676
+ for (const r of records) {
677
+ try {
678
+ await fs7.unlink(r.target);
679
+ removed.push(r.target);
680
+ } catch (err) {
681
+ if (err.code !== "ENOENT") {
682
+ logger.warn(`Failed to remove ${r.target}: ${err.message}`);
683
+ }
684
+ }
685
+ }
686
+ const parents = new Set(records.map((r) => path9.dirname(r.target)));
687
+ for (const dir of parents) {
688
+ try {
689
+ const entries = await fs7.readdir(dir);
690
+ if (entries.length === 0) await fs7.rmdir(dir);
691
+ } catch {
692
+ }
693
+ }
694
+ return removed;
695
+ }
696
+
697
+ // src/core/skills-add.ts
698
+ var DEFAULT_SKILLS_PACKAGE = "@teamix-evo/skills";
699
+ var FLAT_VARIANT = "_flat";
700
+ async function runSkillsAdd(options) {
701
+ const { projectRoot, names: requestedNames } = options;
702
+ const packageName = options.packageName ?? DEFAULT_SKILLS_PACKAGE;
703
+ const ideIdent = options.ide ?? "qoder";
704
+ const isIncremental = !!requestedNames && requestedNames.length > 0;
705
+ await ensureTeamixDir(projectRoot);
706
+ const existingConfig = await readProjectConfig(projectRoot);
707
+ const existingSkillsCfg = existingConfig?.packages?.skills;
708
+ if (!isIncremental && existingSkillsCfg) {
709
+ return { status: "already-added" };
710
+ }
711
+ const ides = options.ides && options.ides.length > 0 ? [...options.ides] : existingSkillsCfg?.ides ? [...existingSkillsCfg.ides] : [];
712
+ const scope = options.scope ?? existingSkillsCfg?.scope;
713
+ if (ides.length === 0) {
714
+ throw new Error("At least one IDE must be selected.");
715
+ }
716
+ if (!scope) {
717
+ throw new Error("Scope must be specified (project | global).");
718
+ }
719
+ const { manifest, data, packageRoot } = await loadSkillsData(packageName);
720
+ if (isIncremental) {
721
+ const known = new Set(manifest.skills.map((s) => s.id));
722
+ const unknown = requestedNames.filter((n) => !known.has(n));
723
+ if (unknown.length > 0) {
724
+ const available = [...known].join(", ");
725
+ throw new Error(
726
+ `Unknown skill id(s): ${unknown.join(", ")}. Available: ${available || "(none)"}.`
727
+ );
728
+ }
729
+ }
730
+ const existingInstalled = await readInstalledManifest(projectRoot);
731
+ const existingPkg = existingInstalled?.installed.find(
732
+ (p) => p.package === packageName
733
+ );
734
+ const existingSkillIds = new Set(
735
+ (existingPkg?.resources ?? []).map((r) => r.id.split(":")[0])
736
+ );
737
+ let onlyIds;
738
+ let skippedSkillIds;
739
+ if (isIncremental) {
740
+ skippedSkillIds = requestedNames.filter((n) => existingSkillIds.has(n));
741
+ onlyIds = requestedNames.filter((n) => !existingSkillIds.has(n));
742
+ } else {
743
+ skippedSkillIds = [];
744
+ onlyIds = manifest.skills.map((s) => s.id);
745
+ }
746
+ if (isIncremental && onlyIds.length === 0) {
747
+ return {
748
+ status: "installed",
749
+ packageName,
750
+ version: existingSkillsCfg?.version ?? manifest.version,
751
+ ides,
752
+ scope,
753
+ skillCount: 0,
754
+ fileCount: 0,
755
+ resources: [],
756
+ addedSkillIds: [],
757
+ skippedSkillIds
758
+ };
759
+ }
760
+ const result = await installSkills({
761
+ projectRoot,
762
+ manifest,
763
+ data,
764
+ packageRoot,
765
+ ides,
766
+ scope,
767
+ onlyIds
768
+ });
769
+ const config = existingConfig ?? {
770
+ $schema: "https://teamix-evo.dev/schema/config/v1.json",
771
+ schemaVersion: 1,
772
+ ide: ideIdent,
773
+ packages: {}
774
+ };
775
+ config.packages.skills = {
776
+ variant: FLAT_VARIANT,
777
+ version: manifest.version,
778
+ ides,
779
+ scope
780
+ };
781
+ await writeProjectConfig(projectRoot, config);
782
+ const installedManifest = existingInstalled ?? {
783
+ schemaVersion: 1,
784
+ installed: []
785
+ };
786
+ const idx = installedManifest.installed.findIndex(
787
+ (p) => p.package === packageName
788
+ );
789
+ const mergedResources = mergeInstalledResources(
790
+ existingPkg?.resources ?? [],
791
+ result.resources
792
+ );
793
+ const entry = {
794
+ package: packageName,
795
+ variant: FLAT_VARIANT,
796
+ version: manifest.version,
797
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
798
+ resources: mergedResources
799
+ };
800
+ if (idx >= 0) installedManifest.installed[idx] = entry;
801
+ else installedManifest.installed.push(entry);
802
+ await writeInstalledManifest(projectRoot, installedManifest);
803
+ return {
804
+ status: "installed",
805
+ packageName,
806
+ version: manifest.version,
807
+ ides,
808
+ scope,
809
+ skillCount: onlyIds.length,
810
+ fileCount: result.count,
811
+ resources: result.resources,
812
+ addedSkillIds: onlyIds,
813
+ skippedSkillIds
814
+ };
815
+ }
816
+ function mergeInstalledResources(existing, next) {
817
+ const map = /* @__PURE__ */ new Map();
818
+ const key = (r) => `${r.id}|${r.ide ?? ""}|${r.scope ?? ""}`;
819
+ for (const r of existing) map.set(key(r), r);
820
+ for (const r of next) map.set(key(r), r);
821
+ return [...map.values()];
822
+ }
823
+
824
+ // src/core/ui-init.ts
825
+ var DEFAULT_UI_ALIASES = {
826
+ components: "src/components/ui",
827
+ hooks: "src/hooks",
828
+ utils: "src/lib/utils",
829
+ lib: "src/lib"
830
+ };
831
+ var DEFAULT_UI_ICON_LIBRARY = "lucide";
832
+ async function runUiInit(options) {
833
+ const { projectRoot } = options;
834
+ const ideIdent = options.ide ?? "qoder";
835
+ await ensureTeamixDir(projectRoot);
836
+ const existingConfig = await readProjectConfig(projectRoot);
837
+ if (existingConfig?.packages?.ui) {
838
+ return { status: "already-initialized" };
839
+ }
840
+ const aliases = {
841
+ components: options.aliases?.components ?? DEFAULT_UI_ALIASES.components,
842
+ hooks: options.aliases?.hooks ?? DEFAULT_UI_ALIASES.hooks,
843
+ utils: options.aliases?.utils ?? DEFAULT_UI_ALIASES.utils,
844
+ lib: options.aliases?.lib ?? DEFAULT_UI_ALIASES.lib
845
+ };
846
+ const iconLibrary = options.iconLibrary ?? DEFAULT_UI_ICON_LIBRARY;
847
+ const tsx = options.tsx ?? true;
848
+ const rsc = options.rsc ?? false;
849
+ const config = existingConfig ?? {
850
+ $schema: "https://teamix-evo.dev/schema/config/v1.json",
851
+ schemaVersion: 1,
852
+ ide: ideIdent,
853
+ packages: {}
854
+ };
855
+ config.packages.ui = {
856
+ variant: "_flat",
857
+ version: "0.0.0",
858
+ aliases,
859
+ iconLibrary,
860
+ tsx,
861
+ rsc
862
+ };
863
+ await writeProjectConfig(projectRoot, config);
864
+ return {
865
+ status: "installed",
866
+ aliases,
867
+ iconLibrary,
868
+ tsx,
869
+ rsc
870
+ };
871
+ }
872
+
873
+ // src/core/ui-client.ts
874
+ import * as path10 from "path";
875
+ import * as fs8 from "fs/promises";
876
+ import { createRequire as createRequire3 } from "module";
877
+ import { loadUiPackageManifest } from "@teamix-evo/registry";
878
+ var require4 = createRequire3(import.meta.url);
879
+ function resolvePackageRoot3(packageName) {
880
+ const pkgJsonPath = require4.resolve(`${packageName}/package.json`);
881
+ return path10.dirname(pkgJsonPath);
882
+ }
883
+ async function loadUiData(packageName) {
884
+ const packageRoot = resolvePackageRoot3(packageName);
885
+ logger.debug(`Resolved ui package root: ${packageRoot}`);
886
+ const manifest = await loadUiPackageManifest(packageRoot);
887
+ let data = {};
888
+ const dataPath = path10.join(packageRoot, "_data.json");
889
+ try {
890
+ const raw = await fs8.readFile(dataPath, "utf-8");
891
+ data = JSON.parse(raw);
892
+ } catch (err) {
893
+ if (err.code !== "ENOENT") {
894
+ throw err;
895
+ }
896
+ logger.debug(`No _data.json found at ${dataPath}, using empty data`);
897
+ }
898
+ return { manifest, data, packageRoot };
899
+ }
900
+
901
+ // src/core/ui-installer.ts
902
+ import * as path11 from "path";
903
+ import * as fs9 from "fs/promises";
904
+ import { resolveUiEntryOrder } from "@teamix-evo/registry";
905
+
906
+ // src/utils/transform-imports.ts
907
+ var SOURCE_ROOT_TO_ALIAS_KEY = {
908
+ components: "components",
909
+ hooks: "hooks",
910
+ utils: "utils",
911
+ lib: "lib"
912
+ };
913
+ function rewriteImports(source, aliases) {
914
+ return source.replace(
915
+ /(['"])@\/([a-z][a-z0-9-]*)(\/[^'"]*)?\1/g,
916
+ (full, quote, root, rest) => {
917
+ const aliasKey = SOURCE_ROOT_TO_ALIAS_KEY[root];
918
+ if (!aliasKey) return full;
919
+ const alias = aliases[aliasKey];
920
+ const normalized = aliasToImportPath(alias);
921
+ return `${quote}${normalized}${rest ?? ""}${quote}`;
922
+ }
923
+ );
924
+ }
925
+ function aliasToImportPath(alias) {
926
+ const trimmed = alias.replace(/^\.\//, "").replace(/\/$/, "");
927
+ if (trimmed.startsWith("src/")) {
928
+ return `@/${trimmed.slice("src/".length)}`;
929
+ }
930
+ return `@/${trimmed}`;
931
+ }
932
+
933
+ // src/core/ui-installer.ts
934
+ var DESIGN_COMPONENTS_DIR = ".teamix-evo/design/components";
935
+ async function installUiEntries(options) {
936
+ const {
937
+ projectRoot,
938
+ manifest,
939
+ packageRoot,
940
+ aliases,
941
+ requested,
942
+ skipExisting = true
943
+ } = options;
944
+ const orderedIds = resolveUiEntryOrder(manifest.entries, requested);
945
+ const idToEntry = new Map(manifest.entries.map((e) => [e.id, e]));
946
+ const resources = [];
947
+ const npmDeps = {};
948
+ const metaFiles = [];
949
+ let written = 0;
950
+ let skipped = 0;
951
+ for (const id of orderedIds) {
952
+ const entry = idToEntry.get(id);
953
+ if (!entry) continue;
954
+ if (entry.dependencies) {
955
+ for (const [name, range] of Object.entries(entry.dependencies)) {
956
+ npmDeps[name] = range;
957
+ }
958
+ }
959
+ for (const file of entry.files) {
960
+ const targetAbs = resolveTargetPath(projectRoot, aliases, entry, file);
961
+ const exists = await fileExists(targetAbs);
962
+ if (exists && skipExisting && (entry.updateStrategy ?? "frozen") === "frozen") {
963
+ logger.info(` skip (frozen, exists): ${rel(projectRoot, targetAbs)}`);
964
+ skipped++;
965
+ continue;
966
+ }
967
+ const sourceAbs = path11.resolve(packageRoot, file.source);
968
+ const raw = await fs9.readFile(sourceAbs, "utf-8");
969
+ const transformed = rewriteImports(raw, aliases);
970
+ await writeFileSafe(targetAbs, transformed);
971
+ written++;
972
+ logger.info(` write: ${rel(projectRoot, targetAbs)}`);
973
+ resources.push({
974
+ id: `${entry.id}:${file.targetName}`,
975
+ target: targetAbs,
976
+ hash: computeHash(transformed),
977
+ strategy: entry.updateStrategy ?? "frozen"
978
+ });
979
+ }
980
+ if (entry.meta) {
981
+ const metaSourceAbs = path11.resolve(packageRoot, entry.meta);
982
+ const metaContent = await fs9.readFile(metaSourceAbs, "utf-8");
983
+ const metaTargetAbs = path11.join(
984
+ projectRoot,
985
+ DESIGN_COMPONENTS_DIR,
986
+ `${entry.id}.meta.md`
987
+ );
988
+ await writeFileSafe(metaTargetAbs, metaContent);
989
+ metaFiles.push(metaTargetAbs);
990
+ resources.push({
991
+ id: `${entry.id}:meta`,
992
+ target: metaTargetAbs,
993
+ hash: computeHash(metaContent),
994
+ strategy: "regenerable"
995
+ });
996
+ logger.info(` meta: ${rel(projectRoot, metaTargetAbs)}`);
997
+ }
998
+ }
999
+ return {
1000
+ orderedIds,
1001
+ resources,
1002
+ npmDependencies: npmDeps,
1003
+ written,
1004
+ skipped,
1005
+ metaFiles
1006
+ };
1007
+ }
1008
+ function resolveTargetPath(projectRoot, aliases, entry, file) {
1009
+ const aliasDir = aliases[file.targetAlias];
1010
+ if (!aliasDir) {
1011
+ throw new Error(
1012
+ `Entry "${entry.id}" requires alias "${file.targetAlias}" but it is not configured.`
1013
+ );
1014
+ }
1015
+ return path11.join(projectRoot, aliasDir, file.targetName);
1016
+ }
1017
+ function rel(projectRoot, abs) {
1018
+ return path11.relative(projectRoot, abs);
1019
+ }
1020
+ async function removeUiFiles(records) {
1021
+ const removed = [];
1022
+ for (const r of records) {
1023
+ try {
1024
+ await fs9.unlink(r.target);
1025
+ removed.push(r.target);
1026
+ } catch (err) {
1027
+ if (err.code !== "ENOENT") {
1028
+ logger.warn(`Failed to remove ${r.target}: ${err.message}`);
1029
+ }
1030
+ }
1031
+ }
1032
+ const parents = new Set(records.map((r) => path11.dirname(r.target)));
1033
+ for (const dir of parents) {
1034
+ try {
1035
+ const entries = await fs9.readdir(dir);
1036
+ if (entries.length === 0) await fs9.rmdir(dir);
1037
+ } catch {
1038
+ }
1039
+ }
1040
+ return removed;
1041
+ }
1042
+
1043
+ // src/core/ui-add.ts
1044
+ var DEFAULT_UI_PACKAGE = "@teamix-evo/ui";
1045
+ async function runUiAdd(options) {
1046
+ const { projectRoot, ids, overwrite } = options;
1047
+ const packageName = options.packageName ?? DEFAULT_UI_PACKAGE;
1048
+ if (ids.length === 0) {
1049
+ throw new Error("At least one entry id must be provided.");
1050
+ }
1051
+ const config = await readProjectConfig(projectRoot);
1052
+ const uiCfg = config?.packages?.ui;
1053
+ if (!config || !uiCfg?.aliases) {
1054
+ throw new Error(
1055
+ "UI not initialized. Run `runUiInit` (or `teamix-evo ui init`) first."
1056
+ );
1057
+ }
1058
+ const { manifest, packageRoot } = await loadUiData(packageName);
1059
+ const knownIds = new Set(manifest.entries.map((e) => e.id));
1060
+ const unknown = ids.filter((id) => !knownIds.has(id));
1061
+ if (unknown.length > 0) {
1062
+ throw new Error(
1063
+ `Unknown entry id(s): ${unknown.map((s) => `"${s}"`).join(", ")}. Run \`teamix-evo ui list\` to see options.`
1064
+ );
1065
+ }
1066
+ const result = await installUiEntries({
1067
+ projectRoot,
1068
+ manifest,
1069
+ packageRoot,
1070
+ aliases: uiCfg.aliases,
1071
+ requested: ids,
1072
+ skipExisting: !overwrite
1073
+ });
1074
+ const installed = await readInstalledManifest(
1075
+ projectRoot
1076
+ ) ?? { schemaVersion: 1, installed: [] };
1077
+ const idx = installed.installed.findIndex((p) => p.package === packageName);
1078
+ const prior = idx >= 0 ? installed.installed[idx] : null;
1079
+ const mergedResources = mergeResources(
1080
+ prior?.resources ?? [],
1081
+ result.resources
1082
+ );
1083
+ const entry = {
1084
+ package: packageName,
1085
+ variant: "_flat",
1086
+ version: manifest.version,
1087
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1088
+ resources: mergedResources
1089
+ };
1090
+ if (idx >= 0) installed.installed[idx] = entry;
1091
+ else installed.installed.push(entry);
1092
+ await writeInstalledManifest(projectRoot, installed);
1093
+ if (uiCfg.version !== manifest.version) {
1094
+ uiCfg.version = manifest.version;
1095
+ await writeProjectConfig(projectRoot, config);
1096
+ }
1097
+ return {
1098
+ packageName,
1099
+ orderedIds: result.orderedIds,
1100
+ written: result.written,
1101
+ skipped: result.skipped,
1102
+ metaFiles: result.metaFiles,
1103
+ npmDependencies: result.npmDependencies,
1104
+ resources: result.resources
1105
+ };
1106
+ }
1107
+ function mergeResources(prior, next) {
1108
+ const merged = /* @__PURE__ */ new Map();
1109
+ for (const r of prior) merged.set(r.id, r);
1110
+ for (const r of next) merged.set(r.id, r);
1111
+ return Array.from(merged.values());
1112
+ }
1113
+
1114
+ // src/core/ui-list.ts
1115
+ var DEFAULT_UI_PACKAGE2 = "@teamix-evo/ui";
1116
+ async function runUiList(options) {
1117
+ const { projectRoot, installedOnly } = options;
1118
+ const packageName = options.packageName ?? DEFAULT_UI_PACKAGE2;
1119
+ const { manifest } = await loadUiData(packageName);
1120
+ const installedManifest = await readInstalledManifest(projectRoot);
1121
+ const installedIds = /* @__PURE__ */ new Set();
1122
+ const uiPkg = installedManifest?.installed.find(
1123
+ (p) => p.package === packageName
1124
+ );
1125
+ for (const r of uiPkg?.resources ?? []) {
1126
+ const colon = r.id.indexOf(":");
1127
+ installedIds.add(colon >= 0 ? r.id.slice(0, colon) : r.id);
1128
+ }
1129
+ const entries = manifest.entries.filter((e) => !installedOnly || installedIds.has(e.id)).map((e) => ({
1130
+ id: e.id,
1131
+ type: e.type,
1132
+ description: e.description,
1133
+ installed: installedIds.has(e.id)
1134
+ }));
1135
+ return {
1136
+ packageName,
1137
+ total: manifest.entries.length,
1138
+ installedCount: installedIds.size,
1139
+ entries
1140
+ };
1141
+ }
1142
+ export {
1143
+ DEFAULT_UI_ALIASES,
1144
+ DEFAULT_UI_ICON_LIBRARY,
1145
+ ensureTeamixDir,
1146
+ getTeamixDir,
1147
+ installResources,
1148
+ installSkills,
1149
+ installUiEntries,
1150
+ loadSkillsData,
1151
+ loadUiData,
1152
+ loadVariantData,
1153
+ readInstalledManifest,
1154
+ readProjectConfig,
1155
+ removeSkillFiles,
1156
+ removeUiFiles,
1157
+ runDesignInit,
1158
+ runSkillsAdd,
1159
+ runUiAdd,
1160
+ runUiInit,
1161
+ runUiList,
1162
+ updateSkills,
1163
+ writeInstalledManifest,
1164
+ writeProjectConfig
1165
+ };
1166
+ //# sourceMappingURL=index.js.map