@kitnai/cli 0.1.7 → 0.1.9
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/dist/index.js +949 -140
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -215,7 +215,7 @@ async function resolveDependencies(names, fetchItem) {
|
|
|
215
215
|
const visited = /* @__PURE__ */ new Set();
|
|
216
216
|
const items = /* @__PURE__ */ new Map();
|
|
217
217
|
const edges = [];
|
|
218
|
-
async function
|
|
218
|
+
async function resolve3(name) {
|
|
219
219
|
if (visited.has(name)) return;
|
|
220
220
|
visited.add(name);
|
|
221
221
|
const item = await fetchItem(name);
|
|
@@ -223,11 +223,11 @@ async function resolveDependencies(names, fetchItem) {
|
|
|
223
223
|
const deps = item.registryDependencies ?? [];
|
|
224
224
|
for (const dep of deps) {
|
|
225
225
|
edges.push([dep, name]);
|
|
226
|
-
await
|
|
226
|
+
await resolve3(dep);
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
for (const name of names) {
|
|
230
|
-
await
|
|
230
|
+
await resolve3(name);
|
|
231
231
|
}
|
|
232
232
|
return topologicalSort(items, edges);
|
|
233
233
|
}
|
|
@@ -326,25 +326,119 @@ var init_dep_installer = __esm({
|
|
|
326
326
|
}
|
|
327
327
|
});
|
|
328
328
|
|
|
329
|
-
// src/installers/env-
|
|
329
|
+
// src/installers/env-writer.ts
|
|
330
|
+
import * as p2 from "@clack/prompts";
|
|
330
331
|
import pc3 from "picocolors";
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
332
|
+
import { readFile as readFile4, writeFile as writeFile4, access as access3 } from "fs/promises";
|
|
333
|
+
import { join as join4 } from "path";
|
|
334
|
+
function parseEnvKeys(content) {
|
|
335
|
+
const keys = /* @__PURE__ */ new Set();
|
|
336
|
+
for (const line of content.split("\n")) {
|
|
337
|
+
const trimmed = line.trim();
|
|
338
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
339
|
+
const eqIndex = trimmed.indexOf("=");
|
|
340
|
+
if (eqIndex > 0) {
|
|
341
|
+
keys.add(trimmed.slice(0, eqIndex).trim());
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return keys;
|
|
345
|
+
}
|
|
346
|
+
async function readEnvFile(path) {
|
|
347
|
+
try {
|
|
348
|
+
return await readFile4(path, "utf-8");
|
|
349
|
+
} catch {
|
|
350
|
+
return "";
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function collectEnvVars(items) {
|
|
354
|
+
const merged = {};
|
|
355
|
+
for (const item of items) {
|
|
356
|
+
if (item.envVars) {
|
|
357
|
+
Object.assign(merged, item.envVars);
|
|
336
358
|
}
|
|
337
359
|
}
|
|
338
|
-
return
|
|
360
|
+
return merged;
|
|
339
361
|
}
|
|
340
|
-
|
|
341
|
-
|
|
362
|
+
async function handleEnvVars(cwd, envVars) {
|
|
363
|
+
const keys = Object.keys(envVars);
|
|
364
|
+
if (keys.length === 0) return;
|
|
365
|
+
const envPath = join4(cwd, ".env");
|
|
366
|
+
const examplePath = join4(cwd, ".env.example");
|
|
367
|
+
const envContent = await readEnvFile(envPath);
|
|
368
|
+
const exampleContent = await readEnvFile(examplePath);
|
|
369
|
+
const envKeys = parseEnvKeys(envContent);
|
|
370
|
+
const exampleKeys = parseEnvKeys(exampleContent);
|
|
371
|
+
const missingFromExample = keys.filter((k) => !exampleKeys.has(k));
|
|
372
|
+
const missingFromEnv = keys.filter((k) => !envKeys.has(k) && !process.env[k]);
|
|
373
|
+
if (missingFromExample.length > 0) {
|
|
374
|
+
const lines = [];
|
|
375
|
+
if (exampleContent && !exampleContent.endsWith("\n")) lines.push("");
|
|
376
|
+
for (const key of missingFromExample) {
|
|
377
|
+
const config = envVars[key];
|
|
378
|
+
lines.push(`# ${config.description}${config.url ? ` (${config.url})` : ""}`);
|
|
379
|
+
lines.push(`${key}=`);
|
|
380
|
+
}
|
|
381
|
+
await writeFile4(examplePath, exampleContent + lines.join("\n") + "\n");
|
|
382
|
+
p2.log.info(`Updated ${pc3.cyan(".env.example")} with ${missingFromExample.length} variable(s)`);
|
|
383
|
+
}
|
|
384
|
+
if (missingFromEnv.length === 0) return;
|
|
385
|
+
p2.log.message("");
|
|
386
|
+
p2.log.warn(
|
|
387
|
+
`${missingFromEnv.length} environment variable(s) needed:`
|
|
388
|
+
);
|
|
389
|
+
for (const key of missingFromEnv) {
|
|
390
|
+
const config = envVars[key];
|
|
391
|
+
const req = config.required !== false ? pc3.red("*") : "";
|
|
392
|
+
p2.log.message(` ${pc3.yellow(key)}${req}: ${config.description}${config.url ? pc3.dim(` -> ${config.url}`) : ""}`);
|
|
393
|
+
}
|
|
394
|
+
const shouldPrompt = await p2.confirm({
|
|
395
|
+
message: "Would you like to enter values now?",
|
|
396
|
+
initialValue: true
|
|
397
|
+
});
|
|
398
|
+
if (p2.isCancel(shouldPrompt) || !shouldPrompt) {
|
|
399
|
+
p2.log.info(`Add them to ${pc3.cyan(".env")} when ready.`);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
const newEntries = [];
|
|
403
|
+
for (const key of missingFromEnv) {
|
|
404
|
+
const config = envVars[key];
|
|
405
|
+
const isSecret = config.secret !== false;
|
|
406
|
+
let value;
|
|
407
|
+
if (isSecret) {
|
|
408
|
+
value = await p2.password({
|
|
409
|
+
message: `${key}:`
|
|
410
|
+
});
|
|
411
|
+
} else {
|
|
412
|
+
value = await p2.text({
|
|
413
|
+
message: `${key}:`,
|
|
414
|
+
placeholder: config.description
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
if (p2.isCancel(value)) {
|
|
418
|
+
p2.log.info(`Skipped remaining variables. Add them to ${pc3.cyan(".env")} when ready.`);
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
if (value) {
|
|
422
|
+
newEntries.push(`${key}=${value}`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (newEntries.length > 0) {
|
|
426
|
+
const existingEnv = await readEnvFile(envPath);
|
|
427
|
+
const lines = [];
|
|
428
|
+
if (existingEnv && !existingEnv.endsWith("\n")) lines.push("");
|
|
429
|
+
lines.push(...newEntries);
|
|
430
|
+
await writeFile4(envPath, existingEnv + lines.join("\n") + "\n");
|
|
431
|
+
p2.log.success(`Wrote ${newEntries.length} variable(s) to ${pc3.cyan(".env")}`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
var init_env_writer = __esm({
|
|
435
|
+
"src/installers/env-writer.ts"() {
|
|
342
436
|
"use strict";
|
|
343
437
|
}
|
|
344
438
|
});
|
|
345
439
|
|
|
346
440
|
// src/installers/import-rewriter.ts
|
|
347
|
-
import { relative, join as
|
|
441
|
+
import { relative, join as join5 } from "path";
|
|
348
442
|
function rewriteKitnImports(content, fileType, fileName, aliases) {
|
|
349
443
|
const sourceAliasKey = TYPE_TO_ALIAS_KEY[fileType];
|
|
350
444
|
if (!sourceAliasKey) return content;
|
|
@@ -356,7 +450,7 @@ function rewriteKitnImports(content, fileType, fileName, aliases) {
|
|
|
356
450
|
return `${prefix}@kitn/${type}/${targetPath}${quote}`;
|
|
357
451
|
}
|
|
358
452
|
const targetDir = aliases[type];
|
|
359
|
-
const targetFile =
|
|
453
|
+
const targetFile = join5(targetDir, targetPath);
|
|
360
454
|
let rel = relative(sourceDir, targetFile);
|
|
361
455
|
rel = rel.split("\\").join("/");
|
|
362
456
|
if (!rel.startsWith(".")) {
|
|
@@ -381,10 +475,10 @@ var init_import_rewriter = __esm({
|
|
|
381
475
|
});
|
|
382
476
|
|
|
383
477
|
// src/installers/tsconfig-patcher.ts
|
|
384
|
-
import { readFile as
|
|
385
|
-
import { join as
|
|
386
|
-
function stripJsonc(
|
|
387
|
-
return
|
|
478
|
+
import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
479
|
+
import { join as join6 } from "path";
|
|
480
|
+
function stripJsonc(text3) {
|
|
481
|
+
return text3.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/,\s*([}\]])/g, "$1");
|
|
388
482
|
}
|
|
389
483
|
function patchTsconfig(tsconfigContent, paths) {
|
|
390
484
|
const config = JSON.parse(stripJsonc(tsconfigContent));
|
|
@@ -400,15 +494,15 @@ function patchTsconfig(tsconfigContent, paths) {
|
|
|
400
494
|
return JSON.stringify(config, null, 2) + "\n";
|
|
401
495
|
}
|
|
402
496
|
async function patchProjectTsconfig(projectDir, paths) {
|
|
403
|
-
const tsconfigPath =
|
|
497
|
+
const tsconfigPath = join6(projectDir, "tsconfig.json");
|
|
404
498
|
let content;
|
|
405
499
|
try {
|
|
406
|
-
content = await
|
|
500
|
+
content = await readFile5(tsconfigPath, "utf-8");
|
|
407
501
|
} catch {
|
|
408
502
|
content = "{}";
|
|
409
503
|
}
|
|
410
504
|
const patched = patchTsconfig(content, paths);
|
|
411
|
-
await
|
|
505
|
+
await writeFile5(tsconfigPath, patched);
|
|
412
506
|
}
|
|
413
507
|
var init_tsconfig_patcher = __esm({
|
|
414
508
|
"src/installers/tsconfig-patcher.ts"() {
|
|
@@ -416,6 +510,40 @@ var init_tsconfig_patcher = __esm({
|
|
|
416
510
|
}
|
|
417
511
|
});
|
|
418
512
|
|
|
513
|
+
// src/installers/barrel-manager.ts
|
|
514
|
+
function createBarrelFile() {
|
|
515
|
+
return `${BARREL_COMMENT}
|
|
516
|
+
${EXPORT_LINE}
|
|
517
|
+
`;
|
|
518
|
+
}
|
|
519
|
+
function addImportToBarrel(content, importPath) {
|
|
520
|
+
const importLine = `import "${importPath}";`;
|
|
521
|
+
if (content.includes(importLine)) return content;
|
|
522
|
+
const exportIndex = content.indexOf(EXPORT_LINE);
|
|
523
|
+
if (exportIndex === -1) {
|
|
524
|
+
return `${content.trimEnd()}
|
|
525
|
+
${importLine}
|
|
526
|
+
${EXPORT_LINE}
|
|
527
|
+
`;
|
|
528
|
+
}
|
|
529
|
+
const before = content.slice(0, exportIndex);
|
|
530
|
+
const after = content.slice(exportIndex);
|
|
531
|
+
return `${before}${importLine}
|
|
532
|
+
${after}`;
|
|
533
|
+
}
|
|
534
|
+
function removeImportFromBarrel(content, importPath) {
|
|
535
|
+
const importLine = `import "${importPath}";`;
|
|
536
|
+
return content.split("\n").filter((line) => line.trim() !== importLine).join("\n");
|
|
537
|
+
}
|
|
538
|
+
var EXPORT_LINE, BARREL_COMMENT;
|
|
539
|
+
var init_barrel_manager = __esm({
|
|
540
|
+
"src/installers/barrel-manager.ts"() {
|
|
541
|
+
"use strict";
|
|
542
|
+
EXPORT_LINE = 'export { registerWithPlugin } from "@kitnai/core";';
|
|
543
|
+
BARREL_COMMENT = "// Managed by kitn CLI \u2014 components auto-imported below";
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
|
|
419
547
|
// src/utils/hash.ts
|
|
420
548
|
import { createHash } from "crypto";
|
|
421
549
|
function contentHash(content) {
|
|
@@ -457,7 +585,7 @@ var init_parse_ref = __esm({
|
|
|
457
585
|
|
|
458
586
|
// src/registry/schema.ts
|
|
459
587
|
import { z as z2 } from "zod";
|
|
460
|
-
var componentType2, registryFileSchema, changelogEntrySchema, registryItemSchema, registryIndexItemSchema, registryIndexSchema, typeToDir;
|
|
588
|
+
var componentType2, registryFileSchema, changelogEntrySchema, envVarConfigSchema, componentConfigSchema, registryItemSchema, registryIndexItemSchema, registryIndexSchema, typeToDir;
|
|
461
589
|
var init_schema = __esm({
|
|
462
590
|
"src/registry/schema.ts"() {
|
|
463
591
|
"use strict";
|
|
@@ -473,6 +601,31 @@ var init_schema = __esm({
|
|
|
473
601
|
type: z2.enum(["feature", "fix", "breaking", "initial"]),
|
|
474
602
|
note: z2.string()
|
|
475
603
|
});
|
|
604
|
+
envVarConfigSchema = z2.object({
|
|
605
|
+
description: z2.string(),
|
|
606
|
+
required: z2.boolean().optional(),
|
|
607
|
+
secret: z2.boolean().optional(),
|
|
608
|
+
url: z2.string().optional()
|
|
609
|
+
});
|
|
610
|
+
componentConfigSchema = z2.object({
|
|
611
|
+
$schema: z2.string().optional(),
|
|
612
|
+
type: componentType2,
|
|
613
|
+
name: z2.string().optional(),
|
|
614
|
+
version: z2.string().optional(),
|
|
615
|
+
description: z2.string().optional(),
|
|
616
|
+
dependencies: z2.array(z2.string()).optional(),
|
|
617
|
+
devDependencies: z2.array(z2.string()).optional(),
|
|
618
|
+
registryDependencies: z2.array(z2.string()).optional(),
|
|
619
|
+
files: z2.array(z2.string()).optional(),
|
|
620
|
+
sourceDir: z2.string().optional(),
|
|
621
|
+
exclude: z2.array(z2.string()).optional(),
|
|
622
|
+
installDir: z2.string().optional(),
|
|
623
|
+
tsconfig: z2.record(z2.string(), z2.array(z2.string())).optional(),
|
|
624
|
+
envVars: z2.record(z2.string(), envVarConfigSchema).optional(),
|
|
625
|
+
categories: z2.array(z2.string()).optional(),
|
|
626
|
+
docs: z2.string().optional(),
|
|
627
|
+
changelog: z2.array(changelogEntrySchema).optional()
|
|
628
|
+
});
|
|
476
629
|
registryItemSchema = z2.object({
|
|
477
630
|
$schema: z2.string().optional(),
|
|
478
631
|
name: z2.string(),
|
|
@@ -481,7 +634,7 @@ var init_schema = __esm({
|
|
|
481
634
|
dependencies: z2.array(z2.string()).optional(),
|
|
482
635
|
devDependencies: z2.array(z2.string()).optional(),
|
|
483
636
|
registryDependencies: z2.array(z2.string()).optional(),
|
|
484
|
-
envVars: z2.record(z2.string(),
|
|
637
|
+
envVars: z2.record(z2.string(), envVarConfigSchema).optional(),
|
|
485
638
|
files: z2.array(registryFileSchema),
|
|
486
639
|
installDir: z2.string().optional(),
|
|
487
640
|
tsconfig: z2.record(z2.string(), z2.array(z2.string())).optional(),
|
|
@@ -521,19 +674,22 @@ var add_exports = {};
|
|
|
521
674
|
__export(add_exports, {
|
|
522
675
|
addCommand: () => addCommand
|
|
523
676
|
});
|
|
524
|
-
import * as
|
|
677
|
+
import * as p3 from "@clack/prompts";
|
|
525
678
|
import pc4 from "picocolors";
|
|
526
|
-
import { join as
|
|
679
|
+
import { join as join7 } from "path";
|
|
680
|
+
import { existsSync } from "fs";
|
|
681
|
+
import { readFile as readFile6, writeFile as writeFile6, mkdir as mkdir3 } from "fs/promises";
|
|
682
|
+
import { relative as relative2 } from "path";
|
|
527
683
|
async function addCommand(components, opts) {
|
|
528
|
-
|
|
684
|
+
p3.intro(pc4.bgCyan(pc4.black(" kitn add ")));
|
|
529
685
|
const cwd = process.cwd();
|
|
530
686
|
const config = await readConfig(cwd);
|
|
531
687
|
if (!config) {
|
|
532
|
-
|
|
688
|
+
p3.log.error("No kitn.json found. Run `kitn init` first.");
|
|
533
689
|
process.exit(1);
|
|
534
690
|
}
|
|
535
691
|
if (components.length === 0) {
|
|
536
|
-
|
|
692
|
+
p3.log.error("Please specify at least one component to add.");
|
|
537
693
|
process.exit(1);
|
|
538
694
|
}
|
|
539
695
|
const resolvedComponents = components.map((c) => {
|
|
@@ -545,7 +701,7 @@ async function addCommand(components, opts) {
|
|
|
545
701
|
});
|
|
546
702
|
const refs = resolvedComponents.map(parseComponentRef);
|
|
547
703
|
const fetcher = new RegistryFetcher(config.registries);
|
|
548
|
-
const s =
|
|
704
|
+
const s = p3.spinner();
|
|
549
705
|
s.start("Resolving dependencies...");
|
|
550
706
|
let resolved;
|
|
551
707
|
try {
|
|
@@ -559,31 +715,27 @@ async function addCommand(components, opts) {
|
|
|
559
715
|
});
|
|
560
716
|
} catch (err) {
|
|
561
717
|
s.stop(pc4.red("Failed to resolve dependencies"));
|
|
562
|
-
|
|
718
|
+
p3.log.error(err.message);
|
|
563
719
|
process.exit(1);
|
|
564
720
|
}
|
|
565
721
|
s.stop(`Resolved ${resolved.length} component(s)`);
|
|
566
|
-
|
|
722
|
+
p3.log.info("Components to install:");
|
|
567
723
|
for (const item of resolved) {
|
|
568
724
|
const isExplicit = resolvedComponents.includes(item.name) || components.includes(item.name);
|
|
569
725
|
const label = isExplicit ? item.name : `${item.name} ${pc4.dim("(dependency)")}`;
|
|
570
|
-
|
|
726
|
+
p3.log.message(` ${pc4.cyan(label)}`);
|
|
571
727
|
}
|
|
572
728
|
const created = [];
|
|
573
729
|
const updated = [];
|
|
574
730
|
const skipped = [];
|
|
575
731
|
const allDeps = [];
|
|
576
|
-
const allEnvWarnings = [];
|
|
577
732
|
for (const item of resolved) {
|
|
578
733
|
if (item.dependencies) allDeps.push(...item.dependencies);
|
|
579
|
-
if (item.envVars) {
|
|
580
|
-
allEnvWarnings.push(...checkEnvVars(item.envVars));
|
|
581
|
-
}
|
|
582
734
|
if (item.type === "kitn:package") {
|
|
583
|
-
const
|
|
735
|
+
const baseDir2 = config.aliases.base ?? "src/ai";
|
|
584
736
|
for (const file of item.files) {
|
|
585
|
-
const targetPath =
|
|
586
|
-
const relativePath =
|
|
737
|
+
const targetPath = join7(cwd, baseDir2, file.path);
|
|
738
|
+
const relativePath = join7(baseDir2, file.path);
|
|
587
739
|
const status = await checkFileStatus(targetPath, file.content);
|
|
588
740
|
switch (status) {
|
|
589
741
|
case "new" /* New */:
|
|
@@ -600,15 +752,15 @@ async function addCommand(components, opts) {
|
|
|
600
752
|
} else {
|
|
601
753
|
const existing = await readExistingFile(targetPath);
|
|
602
754
|
const diff = generateDiff(relativePath, existing ?? "", file.content);
|
|
603
|
-
|
|
604
|
-
const action = await
|
|
755
|
+
p3.log.message(pc4.dim(diff));
|
|
756
|
+
const action = await p3.select({
|
|
605
757
|
message: `${relativePath} already exists and differs. What to do?`,
|
|
606
758
|
options: [
|
|
607
759
|
{ value: "skip", label: "Keep local version" },
|
|
608
760
|
{ value: "overwrite", label: "Overwrite with registry version" }
|
|
609
761
|
]
|
|
610
762
|
});
|
|
611
|
-
if (!
|
|
763
|
+
if (!p3.isCancel(action) && action === "overwrite") {
|
|
612
764
|
await writeComponentFile(targetPath, file.content);
|
|
613
765
|
updated.push(relativePath);
|
|
614
766
|
} else {
|
|
@@ -622,10 +774,10 @@ async function addCommand(components, opts) {
|
|
|
622
774
|
const resolvedPaths = {};
|
|
623
775
|
const installDir = item.installDir ?? item.name;
|
|
624
776
|
for (const [key, values] of Object.entries(item.tsconfig)) {
|
|
625
|
-
resolvedPaths[key] = values.map((v) => `./${
|
|
777
|
+
resolvedPaths[key] = values.map((v) => `./${join7(baseDir2, installDir, v)}`);
|
|
626
778
|
}
|
|
627
779
|
await patchProjectTsconfig(cwd, resolvedPaths);
|
|
628
|
-
|
|
780
|
+
p3.log.info(`Patched tsconfig.json with paths: ${Object.keys(resolvedPaths).join(", ")}`);
|
|
629
781
|
}
|
|
630
782
|
const installed = config.installed ?? {};
|
|
631
783
|
const allContent = item.files.map((f) => f.content).join("\n");
|
|
@@ -635,7 +787,7 @@ async function addCommand(components, opts) {
|
|
|
635
787
|
registry: ref.namespace,
|
|
636
788
|
version: item.version ?? "1.0.0",
|
|
637
789
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
638
|
-
files: item.files.map((f) =>
|
|
790
|
+
files: item.files.map((f) => join7(baseDir2, f.path)),
|
|
639
791
|
hash: contentHash(allContent)
|
|
640
792
|
};
|
|
641
793
|
config.installed = installed;
|
|
@@ -654,8 +806,8 @@ async function addCommand(components, opts) {
|
|
|
654
806
|
}
|
|
655
807
|
})();
|
|
656
808
|
const fileName = file.path.split("/").pop();
|
|
657
|
-
const targetPath =
|
|
658
|
-
const relativePath =
|
|
809
|
+
const targetPath = join7(cwd, config.aliases[aliasKey], fileName);
|
|
810
|
+
const relativePath = join7(config.aliases[aliasKey], fileName);
|
|
659
811
|
const content = rewriteKitnImports(file.content, item.type, fileName, config.aliases);
|
|
660
812
|
const status = await checkFileStatus(targetPath, content);
|
|
661
813
|
switch (status) {
|
|
@@ -673,15 +825,15 @@ async function addCommand(components, opts) {
|
|
|
673
825
|
} else {
|
|
674
826
|
const existing = await readExistingFile(targetPath);
|
|
675
827
|
const diff = generateDiff(relativePath, existing ?? "", content);
|
|
676
|
-
|
|
677
|
-
const action = await
|
|
828
|
+
p3.log.message(pc4.dim(diff));
|
|
829
|
+
const action = await p3.select({
|
|
678
830
|
message: `${relativePath} already exists and differs. What to do?`,
|
|
679
831
|
options: [
|
|
680
832
|
{ value: "skip", label: "Keep local version" },
|
|
681
833
|
{ value: "overwrite", label: "Overwrite with registry version" }
|
|
682
834
|
]
|
|
683
835
|
});
|
|
684
|
-
if (!
|
|
836
|
+
if (!p3.isCancel(action) && action === "overwrite") {
|
|
685
837
|
await writeComponentFile(targetPath, content);
|
|
686
838
|
updated.push(relativePath);
|
|
687
839
|
} else {
|
|
@@ -716,13 +868,68 @@ async function addCommand(components, opts) {
|
|
|
716
868
|
}
|
|
717
869
|
})();
|
|
718
870
|
const fileName = f.path.split("/").pop();
|
|
719
|
-
return
|
|
871
|
+
return join7(config.aliases[aliasKey], fileName);
|
|
720
872
|
}),
|
|
721
873
|
hash: contentHash(allContent)
|
|
722
874
|
};
|
|
723
875
|
config.installed = installed;
|
|
724
876
|
}
|
|
725
877
|
}
|
|
878
|
+
const BARREL_ELIGIBLE = /* @__PURE__ */ new Set(["kitn:agent", "kitn:tool", "kitn:skill"]);
|
|
879
|
+
const baseDir = config.aliases.base ?? "src/ai";
|
|
880
|
+
const barrelPath = join7(cwd, baseDir, "index.ts");
|
|
881
|
+
const barrelDir = join7(cwd, baseDir);
|
|
882
|
+
const barrelImports = [];
|
|
883
|
+
for (const item of resolved) {
|
|
884
|
+
if (!BARREL_ELIGIBLE.has(item.type)) continue;
|
|
885
|
+
for (const file of item.files) {
|
|
886
|
+
const aliasKey = (() => {
|
|
887
|
+
switch (item.type) {
|
|
888
|
+
case "kitn:agent":
|
|
889
|
+
return "agents";
|
|
890
|
+
case "kitn:tool":
|
|
891
|
+
return "tools";
|
|
892
|
+
case "kitn:skill":
|
|
893
|
+
return "skills";
|
|
894
|
+
}
|
|
895
|
+
})();
|
|
896
|
+
const fileName = file.path.split("/").pop();
|
|
897
|
+
const filePath = join7(cwd, config.aliases[aliasKey], fileName);
|
|
898
|
+
const importPath = "./" + relative2(barrelDir, filePath).replace(/\\/g, "/");
|
|
899
|
+
barrelImports.push(importPath);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
if (barrelImports.length > 0) {
|
|
903
|
+
const barrelExisted = existsSync(barrelPath);
|
|
904
|
+
let barrelContent;
|
|
905
|
+
if (barrelExisted) {
|
|
906
|
+
barrelContent = await readFile6(barrelPath, "utf-8");
|
|
907
|
+
} else {
|
|
908
|
+
await mkdir3(barrelDir, { recursive: true });
|
|
909
|
+
barrelContent = createBarrelFile();
|
|
910
|
+
}
|
|
911
|
+
for (const importPath of barrelImports) {
|
|
912
|
+
barrelContent = addImportToBarrel(barrelContent, importPath);
|
|
913
|
+
}
|
|
914
|
+
await writeFile6(barrelPath, barrelContent);
|
|
915
|
+
p3.log.info(`Updated barrel file: ${join7(baseDir, "index.ts")}`);
|
|
916
|
+
if (!barrelExisted) {
|
|
917
|
+
p3.note(
|
|
918
|
+
[
|
|
919
|
+
`import { createAIPlugin } from "@kitnai/hono";`,
|
|
920
|
+
`import { registerWithPlugin } from "./ai";`,
|
|
921
|
+
``,
|
|
922
|
+
`const plugin = createAIPlugin({`,
|
|
923
|
+
` model: (model) => yourProvider(model ?? "default-model"),`,
|
|
924
|
+
`});`,
|
|
925
|
+
``,
|
|
926
|
+
`registerWithPlugin(plugin);`,
|
|
927
|
+
`app.route("/api", plugin.app);`
|
|
928
|
+
].join("\n"),
|
|
929
|
+
"Add this to your app setup"
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
726
933
|
await writeConfig(cwd, config);
|
|
727
934
|
const uniqueDeps = [...new Set(allDeps)];
|
|
728
935
|
if (uniqueDeps.length > 0) {
|
|
@@ -738,24 +945,22 @@ async function addCommand(components, opts) {
|
|
|
738
945
|
}
|
|
739
946
|
}
|
|
740
947
|
if (created.length > 0) {
|
|
741
|
-
|
|
742
|
-
for (const f of created)
|
|
948
|
+
p3.log.success(`Created ${created.length} file(s):`);
|
|
949
|
+
for (const f of created) p3.log.message(` ${pc4.green("+")} ${f}`);
|
|
743
950
|
}
|
|
744
951
|
if (updated.length > 0) {
|
|
745
|
-
|
|
746
|
-
for (const f of updated)
|
|
952
|
+
p3.log.success(`Updated ${updated.length} file(s):`);
|
|
953
|
+
for (const f of updated) p3.log.message(` ${pc4.yellow("~")} ${f}`);
|
|
747
954
|
}
|
|
748
955
|
if (skipped.length > 0) {
|
|
749
|
-
|
|
750
|
-
for (const f of skipped)
|
|
751
|
-
}
|
|
752
|
-
if (allEnvWarnings.length > 0) {
|
|
753
|
-
p2.log.warn("Missing environment variables:");
|
|
754
|
-
for (const w of allEnvWarnings) p2.log.message(w);
|
|
956
|
+
p3.log.info(`Skipped ${skipped.length} file(s):`);
|
|
957
|
+
for (const f of skipped) p3.log.message(` ${pc4.dim("-")} ${f}`);
|
|
755
958
|
}
|
|
959
|
+
const allEnvVars = collectEnvVars(resolved);
|
|
960
|
+
await handleEnvVars(cwd, allEnvVars);
|
|
756
961
|
for (const item of resolved) {
|
|
757
962
|
if (item.docs) {
|
|
758
|
-
|
|
963
|
+
p3.log.info(`${pc4.bold(item.name)}: ${item.docs}`);
|
|
759
964
|
}
|
|
760
965
|
}
|
|
761
966
|
const installedNames = new Set(resolved.map((r) => r.name));
|
|
@@ -773,7 +978,7 @@ async function addCommand(components, opts) {
|
|
|
773
978
|
hints.push(pc4.dim(` import { yourProvider } from "your-ai-provider";`));
|
|
774
979
|
hints.push(pc4.dim(``));
|
|
775
980
|
hints.push(pc4.dim(` const plugin = createAIPlugin({`));
|
|
776
|
-
hints.push(pc4.dim(`
|
|
981
|
+
hints.push(pc4.dim(` model: (model) => yourProvider(model ?? "default-model"),`));
|
|
777
982
|
hints.push(pc4.dim(` });`));
|
|
778
983
|
hints.push(pc4.dim(``));
|
|
779
984
|
hints.push(pc4.dim(` const app = new Hono();`));
|
|
@@ -783,10 +988,10 @@ async function addCommand(components, opts) {
|
|
|
783
988
|
}
|
|
784
989
|
}
|
|
785
990
|
if (hints.length > 0) {
|
|
786
|
-
|
|
787
|
-
for (const hint of hints)
|
|
991
|
+
p3.log.message(pc4.bold("\nNext steps:"));
|
|
992
|
+
for (const hint of hints) p3.log.message(hint);
|
|
788
993
|
}
|
|
789
|
-
|
|
994
|
+
p3.outro(pc4.green("Done!"));
|
|
790
995
|
}
|
|
791
996
|
var init_add = __esm({
|
|
792
997
|
"src/commands/add.ts"() {
|
|
@@ -797,9 +1002,10 @@ var init_add = __esm({
|
|
|
797
1002
|
init_resolver();
|
|
798
1003
|
init_file_writer();
|
|
799
1004
|
init_dep_installer();
|
|
800
|
-
|
|
1005
|
+
init_env_writer();
|
|
801
1006
|
init_import_rewriter();
|
|
802
1007
|
init_tsconfig_patcher();
|
|
1008
|
+
init_barrel_manager();
|
|
803
1009
|
init_hash();
|
|
804
1010
|
init_parse_ref();
|
|
805
1011
|
init_schema();
|
|
@@ -811,22 +1017,22 @@ var list_exports = {};
|
|
|
811
1017
|
__export(list_exports, {
|
|
812
1018
|
listCommand: () => listCommand
|
|
813
1019
|
});
|
|
814
|
-
import * as
|
|
1020
|
+
import * as p4 from "@clack/prompts";
|
|
815
1021
|
import pc5 from "picocolors";
|
|
816
1022
|
async function listCommand(opts) {
|
|
817
1023
|
const cwd = process.cwd();
|
|
818
1024
|
const config = await readConfig(cwd);
|
|
819
1025
|
if (!config) {
|
|
820
|
-
|
|
1026
|
+
p4.log.error("No kitn.json found. Run `kitn init` first.");
|
|
821
1027
|
process.exit(1);
|
|
822
1028
|
}
|
|
823
1029
|
const fetcher = new RegistryFetcher(config.registries);
|
|
824
1030
|
const namespacesToFetch = opts.registry ? [opts.registry] : Object.keys(config.registries);
|
|
825
1031
|
if (opts.registry && !config.registries[opts.registry]) {
|
|
826
|
-
|
|
1032
|
+
p4.log.error(`Registry ${pc5.bold(opts.registry)} is not configured. Run ${pc5.bold("kitn registry list")} to see configured registries.`);
|
|
827
1033
|
process.exit(1);
|
|
828
1034
|
}
|
|
829
|
-
const s =
|
|
1035
|
+
const s = p4.spinner();
|
|
830
1036
|
s.start("Fetching registry index...");
|
|
831
1037
|
const allItems = [];
|
|
832
1038
|
const errors = [];
|
|
@@ -842,12 +1048,12 @@ async function listCommand(opts) {
|
|
|
842
1048
|
}
|
|
843
1049
|
if (allItems.length === 0 && errors.length > 0) {
|
|
844
1050
|
s.stop(pc5.red("Failed to fetch registries"));
|
|
845
|
-
for (const e of errors)
|
|
1051
|
+
for (const e of errors) p4.log.error(e);
|
|
846
1052
|
process.exit(1);
|
|
847
1053
|
}
|
|
848
1054
|
s.stop(`Found ${allItems.length} components across ${namespacesToFetch.length - errors.length} ${namespacesToFetch.length - errors.length === 1 ? "registry" : "registries"}`);
|
|
849
1055
|
for (const e of errors) {
|
|
850
|
-
|
|
1056
|
+
p4.log.warn(`${pc5.yellow("\u26A0")} Failed to fetch ${e}`);
|
|
851
1057
|
}
|
|
852
1058
|
const installed = config.installed ?? {};
|
|
853
1059
|
const typeGroups = /* @__PURE__ */ new Map();
|
|
@@ -860,7 +1066,7 @@ async function listCommand(opts) {
|
|
|
860
1066
|
let installedCount = 0;
|
|
861
1067
|
let updateCount = 0;
|
|
862
1068
|
for (const [group, items] of typeGroups) {
|
|
863
|
-
|
|
1069
|
+
p4.log.message(pc5.bold(`
|
|
864
1070
|
${group.charAt(0).toUpperCase() + group.slice(1)}s:`));
|
|
865
1071
|
for (const item of items) {
|
|
866
1072
|
const displayName = item.namespace === "@kitn" ? item.name : `${item.namespace}/${item.name}`;
|
|
@@ -873,18 +1079,18 @@ ${group.charAt(0).toUpperCase() + group.slice(1)}s:`));
|
|
|
873
1079
|
const hasUpdate = item.version && inst.version !== item.version;
|
|
874
1080
|
const updateTag = hasUpdate ? pc5.yellow(` \u2B06 v${item.version} available`) : "";
|
|
875
1081
|
if (hasUpdate) updateCount++;
|
|
876
|
-
|
|
1082
|
+
p4.log.message(` ${status} ${displayName.padEnd(20)} ${version} ${pc5.dim(item.description)}${updateTag}`);
|
|
877
1083
|
} else {
|
|
878
1084
|
const status = pc5.dim("\u25CB");
|
|
879
|
-
|
|
1085
|
+
p4.log.message(` ${status} ${displayName.padEnd(20)} ${version} ${pc5.dim(item.description)}`);
|
|
880
1086
|
}
|
|
881
1087
|
}
|
|
882
1088
|
}
|
|
883
1089
|
const availableCount = allItems.length - installedCount;
|
|
884
1090
|
const parts = [`${installedCount} installed`, `${availableCount} available`];
|
|
885
1091
|
if (updateCount > 0) parts.push(`${updateCount} update${updateCount === 1 ? "" : "s"} available`);
|
|
886
|
-
|
|
887
|
-
|
|
1092
|
+
p4.log.message("");
|
|
1093
|
+
p4.log.message(pc5.dim(` ${parts.join(", ")}`));
|
|
888
1094
|
}
|
|
889
1095
|
var init_list = __esm({
|
|
890
1096
|
"src/commands/list.ts"() {
|
|
@@ -899,13 +1105,13 @@ var diff_exports = {};
|
|
|
899
1105
|
__export(diff_exports, {
|
|
900
1106
|
diffCommand: () => diffCommand
|
|
901
1107
|
});
|
|
902
|
-
import * as
|
|
903
|
-
import { join as
|
|
1108
|
+
import * as p5 from "@clack/prompts";
|
|
1109
|
+
import { join as join8 } from "path";
|
|
904
1110
|
async function diffCommand(componentName) {
|
|
905
1111
|
const cwd = process.cwd();
|
|
906
1112
|
const config = await readConfig(cwd);
|
|
907
1113
|
if (!config) {
|
|
908
|
-
|
|
1114
|
+
p5.log.error("No kitn.json found. Run `kitn init` first.");
|
|
909
1115
|
process.exit(1);
|
|
910
1116
|
}
|
|
911
1117
|
const input = componentName === "routes" ? config.framework ?? "hono" : componentName;
|
|
@@ -913,7 +1119,7 @@ async function diffCommand(componentName) {
|
|
|
913
1119
|
const installedKey = ref.namespace === "@kitn" ? ref.name : `${ref.namespace}/${ref.name}`;
|
|
914
1120
|
const installed = config.installed?.[installedKey];
|
|
915
1121
|
if (!installed) {
|
|
916
|
-
|
|
1122
|
+
p5.log.error(`Component '${ref.name}' is not installed.`);
|
|
917
1123
|
process.exit(1);
|
|
918
1124
|
}
|
|
919
1125
|
const namespace = installed.registry ?? ref.namespace;
|
|
@@ -921,7 +1127,7 @@ async function diffCommand(componentName) {
|
|
|
921
1127
|
const index = await fetcher.fetchIndex(namespace);
|
|
922
1128
|
const indexItem = index.items.find((i) => i.name === ref.name);
|
|
923
1129
|
if (!indexItem) {
|
|
924
|
-
|
|
1130
|
+
p5.log.error(`Component '${ref.name}' not found in ${namespace} registry.`);
|
|
925
1131
|
process.exit(1);
|
|
926
1132
|
}
|
|
927
1133
|
const dir = typeToDir[indexItem.type];
|
|
@@ -930,11 +1136,11 @@ async function diffCommand(componentName) {
|
|
|
930
1136
|
for (const file of registryItem.files) {
|
|
931
1137
|
if (indexItem.type === "kitn:package") {
|
|
932
1138
|
const baseDir = config.aliases.base ?? "src/ai";
|
|
933
|
-
const localPath =
|
|
934
|
-
const relativePath =
|
|
1139
|
+
const localPath = join8(cwd, baseDir, file.path);
|
|
1140
|
+
const relativePath = join8(baseDir, file.path);
|
|
935
1141
|
const localContent = await readExistingFile(localPath);
|
|
936
1142
|
if (localContent === null) {
|
|
937
|
-
|
|
1143
|
+
p5.log.warn(`${relativePath}: file missing locally`);
|
|
938
1144
|
hasDiff = true;
|
|
939
1145
|
} else if (localContent !== file.content) {
|
|
940
1146
|
const diff = generateDiff(relativePath, localContent, file.content);
|
|
@@ -955,10 +1161,10 @@ async function diffCommand(componentName) {
|
|
|
955
1161
|
return "storage";
|
|
956
1162
|
}
|
|
957
1163
|
})();
|
|
958
|
-
const localPath =
|
|
1164
|
+
const localPath = join8(cwd, config.aliases[aliasKey], fileName);
|
|
959
1165
|
const localContent = await readExistingFile(localPath);
|
|
960
1166
|
if (localContent === null) {
|
|
961
|
-
|
|
1167
|
+
p5.log.warn(`${fileName}: file missing locally`);
|
|
962
1168
|
hasDiff = true;
|
|
963
1169
|
} else if (localContent !== file.content) {
|
|
964
1170
|
const diff = generateDiff(fileName, localContent, file.content);
|
|
@@ -968,7 +1174,7 @@ async function diffCommand(componentName) {
|
|
|
968
1174
|
}
|
|
969
1175
|
}
|
|
970
1176
|
if (!hasDiff) {
|
|
971
|
-
|
|
1177
|
+
p5.log.success(`${ref.name}: up to date, no differences.`);
|
|
972
1178
|
}
|
|
973
1179
|
}
|
|
974
1180
|
var init_diff = __esm({
|
|
@@ -987,15 +1193,16 @@ var remove_exports = {};
|
|
|
987
1193
|
__export(remove_exports, {
|
|
988
1194
|
removeCommand: () => removeCommand
|
|
989
1195
|
});
|
|
990
|
-
import * as
|
|
1196
|
+
import * as p6 from "@clack/prompts";
|
|
991
1197
|
import pc6 from "picocolors";
|
|
992
|
-
import { join as
|
|
993
|
-
import { unlink } from "fs/promises";
|
|
1198
|
+
import { join as join9, relative as relative3, dirname as dirname3 } from "path";
|
|
1199
|
+
import { unlink, readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
|
|
1200
|
+
import { existsSync as existsSync2 } from "fs";
|
|
994
1201
|
async function removeCommand(componentName) {
|
|
995
1202
|
const cwd = process.cwd();
|
|
996
1203
|
const config = await readConfig(cwd);
|
|
997
1204
|
if (!config) {
|
|
998
|
-
|
|
1205
|
+
p6.log.error("No kitn.json found. Run `kitn init` first.");
|
|
999
1206
|
process.exit(1);
|
|
1000
1207
|
}
|
|
1001
1208
|
const input = componentName === "routes" ? config.framework ?? "hono" : componentName;
|
|
@@ -1003,24 +1210,50 @@ async function removeCommand(componentName) {
|
|
|
1003
1210
|
const installedKey = ref.namespace === "@kitn" ? ref.name : `${ref.namespace}/${ref.name}`;
|
|
1004
1211
|
const installed = config.installed?.[installedKey];
|
|
1005
1212
|
if (!installed) {
|
|
1006
|
-
|
|
1213
|
+
p6.log.error(`Component '${ref.name}' is not installed.`);
|
|
1007
1214
|
process.exit(1);
|
|
1008
1215
|
}
|
|
1009
|
-
const shouldRemove = await
|
|
1216
|
+
const shouldRemove = await p6.confirm({
|
|
1010
1217
|
message: `Remove ${ref.name}? This will delete ${installed.files.length} file(s).`,
|
|
1011
1218
|
initialValue: false
|
|
1012
1219
|
});
|
|
1013
|
-
if (
|
|
1014
|
-
|
|
1220
|
+
if (p6.isCancel(shouldRemove) || !shouldRemove) {
|
|
1221
|
+
p6.cancel("Remove cancelled.");
|
|
1015
1222
|
process.exit(0);
|
|
1016
1223
|
}
|
|
1017
1224
|
const deleted = [];
|
|
1018
1225
|
for (const filePath of installed.files) {
|
|
1019
1226
|
try {
|
|
1020
|
-
await unlink(
|
|
1227
|
+
await unlink(join9(cwd, filePath));
|
|
1021
1228
|
deleted.push(filePath);
|
|
1022
1229
|
} catch {
|
|
1023
|
-
|
|
1230
|
+
p6.log.warn(`Could not delete ${filePath} (may have been moved or renamed)`);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
const baseDir = config.aliases.base ?? "src/ai";
|
|
1234
|
+
const barrelPath = join9(cwd, baseDir, "index.ts");
|
|
1235
|
+
const barrelDir = join9(cwd, baseDir);
|
|
1236
|
+
const barrelEligibleDirs = /* @__PURE__ */ new Set([
|
|
1237
|
+
config.aliases.agents,
|
|
1238
|
+
config.aliases.tools,
|
|
1239
|
+
config.aliases.skills
|
|
1240
|
+
]);
|
|
1241
|
+
if (existsSync2(barrelPath) && deleted.length > 0) {
|
|
1242
|
+
let barrelContent = await readFile7(barrelPath, "utf-8");
|
|
1243
|
+
let barrelChanged = false;
|
|
1244
|
+
for (const filePath of deleted) {
|
|
1245
|
+
const fileDir = dirname3(filePath);
|
|
1246
|
+
if (!barrelEligibleDirs.has(fileDir)) continue;
|
|
1247
|
+
const importPath = "./" + relative3(barrelDir, join9(cwd, filePath)).replace(/\\/g, "/");
|
|
1248
|
+
const updated = removeImportFromBarrel(barrelContent, importPath);
|
|
1249
|
+
if (updated !== barrelContent) {
|
|
1250
|
+
barrelContent = updated;
|
|
1251
|
+
barrelChanged = true;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
if (barrelChanged) {
|
|
1255
|
+
await writeFile7(barrelPath, barrelContent);
|
|
1256
|
+
p6.log.info(`Updated barrel file: ${join9(baseDir, "index.ts")}`);
|
|
1024
1257
|
}
|
|
1025
1258
|
}
|
|
1026
1259
|
delete config.installed[installedKey];
|
|
@@ -1029,8 +1262,8 @@ async function removeCommand(componentName) {
|
|
|
1029
1262
|
}
|
|
1030
1263
|
await writeConfig(cwd, config);
|
|
1031
1264
|
if (deleted.length > 0) {
|
|
1032
|
-
|
|
1033
|
-
for (const f of deleted)
|
|
1265
|
+
p6.log.success(`Removed ${ref.name}:`);
|
|
1266
|
+
for (const f of deleted) p6.log.message(` ${pc6.red("-")} ${f}`);
|
|
1034
1267
|
}
|
|
1035
1268
|
}
|
|
1036
1269
|
var init_remove = __esm({
|
|
@@ -1038,6 +1271,7 @@ var init_remove = __esm({
|
|
|
1038
1271
|
"use strict";
|
|
1039
1272
|
init_config();
|
|
1040
1273
|
init_parse_ref();
|
|
1274
|
+
init_barrel_manager();
|
|
1041
1275
|
}
|
|
1042
1276
|
});
|
|
1043
1277
|
|
|
@@ -1046,18 +1280,18 @@ var update_exports = {};
|
|
|
1046
1280
|
__export(update_exports, {
|
|
1047
1281
|
updateCommand: () => updateCommand
|
|
1048
1282
|
});
|
|
1049
|
-
import * as
|
|
1283
|
+
import * as p7 from "@clack/prompts";
|
|
1050
1284
|
async function updateCommand(components) {
|
|
1051
1285
|
if (components.length === 0) {
|
|
1052
1286
|
const cwd = process.cwd();
|
|
1053
1287
|
const config = await readConfig(cwd);
|
|
1054
1288
|
if (!config) {
|
|
1055
|
-
|
|
1289
|
+
p7.log.error("No kitn.json found. Run `kitn init` first.");
|
|
1056
1290
|
process.exit(1);
|
|
1057
1291
|
}
|
|
1058
1292
|
const installed = config.installed;
|
|
1059
1293
|
if (!installed || Object.keys(installed).length === 0) {
|
|
1060
|
-
|
|
1294
|
+
p7.log.info("No installed components to update.");
|
|
1061
1295
|
return;
|
|
1062
1296
|
}
|
|
1063
1297
|
components = Object.keys(installed);
|
|
@@ -1072,36 +1306,603 @@ var init_update = __esm({
|
|
|
1072
1306
|
}
|
|
1073
1307
|
});
|
|
1074
1308
|
|
|
1309
|
+
// src/registry/build-output.ts
|
|
1310
|
+
import { readdir, writeFile as writeFile8, mkdir as mkdir4, access as access4 } from "fs/promises";
|
|
1311
|
+
import { join as join10, resolve } from "path";
|
|
1312
|
+
async function fileExists(path) {
|
|
1313
|
+
try {
|
|
1314
|
+
await access4(path);
|
|
1315
|
+
return true;
|
|
1316
|
+
} catch {
|
|
1317
|
+
return false;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
async function walkForRegistryJson(dir) {
|
|
1321
|
+
const results = [];
|
|
1322
|
+
let entries;
|
|
1323
|
+
try {
|
|
1324
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
1325
|
+
} catch {
|
|
1326
|
+
return results;
|
|
1327
|
+
}
|
|
1328
|
+
if (await fileExists(join10(dir, "registry.json"))) {
|
|
1329
|
+
results.push(dir);
|
|
1330
|
+
return results;
|
|
1331
|
+
}
|
|
1332
|
+
for (const entry of entries) {
|
|
1333
|
+
if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {
|
|
1334
|
+
const subResults = await walkForRegistryJson(join10(dir, entry.name));
|
|
1335
|
+
results.push(...subResults);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
return results;
|
|
1339
|
+
}
|
|
1340
|
+
async function scanForComponents(cwd, paths) {
|
|
1341
|
+
const resolvedCwd = resolve(cwd);
|
|
1342
|
+
if (paths && paths.length > 0) {
|
|
1343
|
+
const results = [];
|
|
1344
|
+
for (const p12 of paths) {
|
|
1345
|
+
const absPath = resolve(resolvedCwd, p12);
|
|
1346
|
+
if (await fileExists(join10(absPath, "registry.json"))) {
|
|
1347
|
+
results.push(absPath);
|
|
1348
|
+
continue;
|
|
1349
|
+
}
|
|
1350
|
+
let entries;
|
|
1351
|
+
try {
|
|
1352
|
+
entries = await readdir(absPath, { withFileTypes: true });
|
|
1353
|
+
} catch {
|
|
1354
|
+
continue;
|
|
1355
|
+
}
|
|
1356
|
+
for (const entry of entries) {
|
|
1357
|
+
if (entry.isDirectory()) {
|
|
1358
|
+
const subDir = join10(absPath, entry.name);
|
|
1359
|
+
if (await fileExists(join10(subDir, "registry.json"))) {
|
|
1360
|
+
results.push(subDir);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
return results;
|
|
1366
|
+
}
|
|
1367
|
+
return walkForRegistryJson(resolvedCwd);
|
|
1368
|
+
}
|
|
1369
|
+
function parseVersionFromFilename(name, componentName) {
|
|
1370
|
+
const prefix = `${componentName}@`;
|
|
1371
|
+
const suffix = ".json";
|
|
1372
|
+
if (name.startsWith(prefix) && name.endsWith(suffix)) {
|
|
1373
|
+
return name.slice(prefix.length, -suffix.length);
|
|
1374
|
+
}
|
|
1375
|
+
return null;
|
|
1376
|
+
}
|
|
1377
|
+
async function writeRegistryOutput(outputDir, items) {
|
|
1378
|
+
const written = [];
|
|
1379
|
+
const skipped = [];
|
|
1380
|
+
const resolvedOutput = resolve(outputDir);
|
|
1381
|
+
const indexItems = [];
|
|
1382
|
+
for (const item of items) {
|
|
1383
|
+
const dir = typeToDir[item.type];
|
|
1384
|
+
const typeDir = join10(resolvedOutput, dir);
|
|
1385
|
+
await mkdir4(typeDir, { recursive: true });
|
|
1386
|
+
const itemJson = JSON.stringify(item, null, 2);
|
|
1387
|
+
const latestPath = join10(typeDir, `${item.name}.json`);
|
|
1388
|
+
const latestRelative = `${dir}/${item.name}.json`;
|
|
1389
|
+
await writeFile8(latestPath, itemJson, "utf-8");
|
|
1390
|
+
written.push(latestRelative);
|
|
1391
|
+
if (item.version) {
|
|
1392
|
+
const versionedFilename = `${item.name}@${item.version}.json`;
|
|
1393
|
+
const versionedPath = join10(typeDir, versionedFilename);
|
|
1394
|
+
const versionedRelative = `${dir}/${versionedFilename}`;
|
|
1395
|
+
if (await fileExists(versionedPath)) {
|
|
1396
|
+
skipped.push(versionedRelative);
|
|
1397
|
+
} else {
|
|
1398
|
+
await writeFile8(versionedPath, itemJson, "utf-8");
|
|
1399
|
+
written.push(versionedRelative);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
const versions = [];
|
|
1403
|
+
let entries;
|
|
1404
|
+
try {
|
|
1405
|
+
entries = await readdir(typeDir);
|
|
1406
|
+
} catch {
|
|
1407
|
+
entries = [];
|
|
1408
|
+
}
|
|
1409
|
+
for (const filename of entries) {
|
|
1410
|
+
const ver = parseVersionFromFilename(filename, item.name);
|
|
1411
|
+
if (ver) {
|
|
1412
|
+
versions.push(ver);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
versions.sort();
|
|
1416
|
+
indexItems.push({
|
|
1417
|
+
name: item.name,
|
|
1418
|
+
type: item.type,
|
|
1419
|
+
description: item.description,
|
|
1420
|
+
...item.registryDependencies && item.registryDependencies.length > 0 && {
|
|
1421
|
+
registryDependencies: item.registryDependencies
|
|
1422
|
+
},
|
|
1423
|
+
...item.categories && item.categories.length > 0 && { categories: item.categories },
|
|
1424
|
+
...item.version && { version: item.version },
|
|
1425
|
+
...versions.length > 0 && { versions },
|
|
1426
|
+
...item.updatedAt && { updatedAt: item.updatedAt }
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
const index = {
|
|
1430
|
+
version: "1",
|
|
1431
|
+
items: indexItems
|
|
1432
|
+
};
|
|
1433
|
+
const indexPath = join10(resolvedOutput, "registry.json");
|
|
1434
|
+
await writeFile8(indexPath, JSON.stringify(index, null, 2), "utf-8");
|
|
1435
|
+
written.push("registry.json");
|
|
1436
|
+
return { written, skipped };
|
|
1437
|
+
}
|
|
1438
|
+
var SKIP_DIRS;
|
|
1439
|
+
var init_build_output = __esm({
|
|
1440
|
+
"src/registry/build-output.ts"() {
|
|
1441
|
+
"use strict";
|
|
1442
|
+
init_schema();
|
|
1443
|
+
SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
1444
|
+
"node_modules",
|
|
1445
|
+
"dist",
|
|
1446
|
+
".git",
|
|
1447
|
+
"r",
|
|
1448
|
+
"test",
|
|
1449
|
+
"tests",
|
|
1450
|
+
".claude"
|
|
1451
|
+
]);
|
|
1452
|
+
}
|
|
1453
|
+
});
|
|
1454
|
+
|
|
1455
|
+
// src/registry/builder.ts
|
|
1456
|
+
import { readFile as readFile9, readdir as readdir2 } from "fs/promises";
|
|
1457
|
+
import { join as join11, relative as relative5 } from "path";
|
|
1458
|
+
function isExcludedDevDep(name) {
|
|
1459
|
+
return EXCLUDED_DEV_DEPS.has(name) || name.startsWith("@types/");
|
|
1460
|
+
}
|
|
1461
|
+
function stripScope(name) {
|
|
1462
|
+
const match = name.match(/^@[^/]+\/(.+)$/);
|
|
1463
|
+
return match ? match[1] : name;
|
|
1464
|
+
}
|
|
1465
|
+
async function readTsFiles(dir, baseDir, exclude) {
|
|
1466
|
+
const results = [];
|
|
1467
|
+
const entries = await readdir2(dir, { withFileTypes: true });
|
|
1468
|
+
for (const entry of entries) {
|
|
1469
|
+
const fullPath = join11(dir, entry.name);
|
|
1470
|
+
const relPath = relative5(baseDir, fullPath);
|
|
1471
|
+
if (entry.isDirectory()) {
|
|
1472
|
+
const nested = await readTsFiles(fullPath, baseDir, exclude);
|
|
1473
|
+
results.push(...nested);
|
|
1474
|
+
} else if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
1475
|
+
if (exclude.includes(relPath)) {
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1478
|
+
const content = await readFile9(fullPath, "utf-8");
|
|
1479
|
+
results.push({ relativePath: relPath, content });
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
return results;
|
|
1483
|
+
}
|
|
1484
|
+
async function buildComponent(componentDir) {
|
|
1485
|
+
let rawConfig;
|
|
1486
|
+
try {
|
|
1487
|
+
rawConfig = await readFile9(join11(componentDir, "registry.json"), "utf-8");
|
|
1488
|
+
} catch {
|
|
1489
|
+
throw new Error(
|
|
1490
|
+
`No registry.json found in ${componentDir}. Every component must have a registry.json file.`
|
|
1491
|
+
);
|
|
1492
|
+
}
|
|
1493
|
+
let config;
|
|
1494
|
+
try {
|
|
1495
|
+
config = componentConfigSchema.parse(JSON.parse(rawConfig));
|
|
1496
|
+
} catch (err) {
|
|
1497
|
+
throw new Error(
|
|
1498
|
+
`Invalid registry.json in ${componentDir}: ${err instanceof Error ? err.message : String(err)}`
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1501
|
+
let pkg = null;
|
|
1502
|
+
try {
|
|
1503
|
+
const rawPkg = await readFile9(join11(componentDir, "package.json"), "utf-8");
|
|
1504
|
+
pkg = JSON.parse(rawPkg);
|
|
1505
|
+
} catch {
|
|
1506
|
+
}
|
|
1507
|
+
const name = config.name ?? (pkg?.name ? stripScope(pkg.name) : void 0);
|
|
1508
|
+
const version = config.version ?? pkg?.version;
|
|
1509
|
+
const description = config.description ?? pkg?.description;
|
|
1510
|
+
if (!name) {
|
|
1511
|
+
throw new Error(
|
|
1512
|
+
`Component in ${componentDir} is missing a name. Provide "name" in registry.json or have a package.json with a "name" field.`
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
if (!description) {
|
|
1516
|
+
throw new Error(
|
|
1517
|
+
`Component in ${componentDir} is missing a description. Provide "description" in registry.json or have a package.json with a "description" field.`
|
|
1518
|
+
);
|
|
1519
|
+
}
|
|
1520
|
+
let dependencies = config.dependencies;
|
|
1521
|
+
let devDependencies = config.devDependencies;
|
|
1522
|
+
if (pkg && !config.dependencies) {
|
|
1523
|
+
const deps = [];
|
|
1524
|
+
if (pkg.dependencies) {
|
|
1525
|
+
for (const [depName, depVersion] of Object.entries(pkg.dependencies)) {
|
|
1526
|
+
if (depVersion !== "workspace:*") {
|
|
1527
|
+
deps.push(depName);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
if (pkg.peerDependencies) {
|
|
1532
|
+
for (const [depName, depVersion] of Object.entries(pkg.peerDependencies)) {
|
|
1533
|
+
if (depVersion !== "workspace:*") {
|
|
1534
|
+
deps.push(depName);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
if (deps.length > 0) {
|
|
1539
|
+
dependencies = deps;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
if (pkg && !config.devDependencies) {
|
|
1543
|
+
const devDeps = [];
|
|
1544
|
+
if (pkg.devDependencies) {
|
|
1545
|
+
for (const depName of Object.keys(pkg.devDependencies)) {
|
|
1546
|
+
if (!isExcludedDevDep(depName)) {
|
|
1547
|
+
devDeps.push(depName);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
if (devDeps.length > 0) {
|
|
1552
|
+
devDependencies = devDeps;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
const isPackage = config.type === "kitn:package";
|
|
1556
|
+
const dirPrefix = config.installDir ?? typeToDir[config.type];
|
|
1557
|
+
let files;
|
|
1558
|
+
if (isPackage) {
|
|
1559
|
+
const sourceDir = config.sourceDir ?? "src";
|
|
1560
|
+
const sourcePath = join11(componentDir, sourceDir);
|
|
1561
|
+
const exclude = config.exclude ?? [];
|
|
1562
|
+
let tsFiles;
|
|
1563
|
+
try {
|
|
1564
|
+
tsFiles = await readTsFiles(sourcePath, sourcePath, exclude);
|
|
1565
|
+
} catch {
|
|
1566
|
+
throw new Error(
|
|
1567
|
+
`Cannot read source directory "${sourceDir}" in ${componentDir}. Make sure it exists.`
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
files = tsFiles.map((f) => ({
|
|
1571
|
+
path: `${dirPrefix}/${f.relativePath}`,
|
|
1572
|
+
content: f.content,
|
|
1573
|
+
type: config.type
|
|
1574
|
+
}));
|
|
1575
|
+
} else {
|
|
1576
|
+
if (!config.files || config.files.length === 0) {
|
|
1577
|
+
throw new Error(
|
|
1578
|
+
`Component "${name}" (type: ${config.type}) has no "files" array in registry.json. Standalone components must list their source files.`
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1581
|
+
files = await Promise.all(
|
|
1582
|
+
config.files.map(async (filePath) => {
|
|
1583
|
+
const fullPath = join11(componentDir, filePath);
|
|
1584
|
+
let content;
|
|
1585
|
+
try {
|
|
1586
|
+
content = await readFile9(fullPath, "utf-8");
|
|
1587
|
+
} catch {
|
|
1588
|
+
throw new Error(
|
|
1589
|
+
`Cannot read file "${filePath}" referenced in registry.json for component "${name}". Make sure the file exists at ${fullPath}.`
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
return {
|
|
1593
|
+
path: `${dirPrefix}/${filePath}`,
|
|
1594
|
+
content,
|
|
1595
|
+
type: config.type
|
|
1596
|
+
};
|
|
1597
|
+
})
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
const item = {
|
|
1601
|
+
name,
|
|
1602
|
+
type: config.type,
|
|
1603
|
+
description,
|
|
1604
|
+
files
|
|
1605
|
+
};
|
|
1606
|
+
if (version) item.version = version;
|
|
1607
|
+
if (dependencies && dependencies.length > 0) item.dependencies = dependencies;
|
|
1608
|
+
if (devDependencies && devDependencies.length > 0) item.devDependencies = devDependencies;
|
|
1609
|
+
if (config.registryDependencies && config.registryDependencies.length > 0) {
|
|
1610
|
+
item.registryDependencies = config.registryDependencies;
|
|
1611
|
+
}
|
|
1612
|
+
if (config.envVars) item.envVars = config.envVars;
|
|
1613
|
+
if (config.tsconfig) item.tsconfig = config.tsconfig;
|
|
1614
|
+
if (config.docs) item.docs = config.docs;
|
|
1615
|
+
if (config.categories && config.categories.length > 0) item.categories = config.categories;
|
|
1616
|
+
if (config.changelog && config.changelog.length > 0) item.changelog = config.changelog;
|
|
1617
|
+
if (isPackage && config.installDir) item.installDir = config.installDir;
|
|
1618
|
+
try {
|
|
1619
|
+
return registryItemSchema.parse(item);
|
|
1620
|
+
} catch (err) {
|
|
1621
|
+
throw new Error(
|
|
1622
|
+
`Built component "${name}" failed validation: ${err instanceof Error ? err.message : String(err)}`
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
var EXCLUDED_DEV_DEPS;
|
|
1627
|
+
var init_builder = __esm({
|
|
1628
|
+
"src/registry/builder.ts"() {
|
|
1629
|
+
"use strict";
|
|
1630
|
+
init_schema();
|
|
1631
|
+
EXCLUDED_DEV_DEPS = /* @__PURE__ */ new Set([
|
|
1632
|
+
"typescript",
|
|
1633
|
+
"@types/bun",
|
|
1634
|
+
"@types/node",
|
|
1635
|
+
"tsup",
|
|
1636
|
+
"vitest",
|
|
1637
|
+
"jest",
|
|
1638
|
+
"@types/jest"
|
|
1639
|
+
]);
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
|
|
1643
|
+
// src/commands/build.ts
|
|
1644
|
+
var build_exports = {};
|
|
1645
|
+
__export(build_exports, {
|
|
1646
|
+
buildCommand: () => buildCommand
|
|
1647
|
+
});
|
|
1648
|
+
import * as p8 from "@clack/prompts";
|
|
1649
|
+
import pc7 from "picocolors";
|
|
1650
|
+
import { resolve as resolve2, relative as relative6 } from "path";
|
|
1651
|
+
async function buildCommand(paths, opts) {
|
|
1652
|
+
p8.intro(pc7.bgCyan(pc7.black(" kitn build ")));
|
|
1653
|
+
const cwd = process.cwd();
|
|
1654
|
+
const outputDir = resolve2(cwd, opts.output ?? "dist/r");
|
|
1655
|
+
const s = p8.spinner();
|
|
1656
|
+
s.start("Scanning for components...");
|
|
1657
|
+
const componentDirs = await scanForComponents(cwd, paths.length > 0 ? paths : void 0);
|
|
1658
|
+
if (componentDirs.length === 0) {
|
|
1659
|
+
s.stop("No components found");
|
|
1660
|
+
p8.log.info(
|
|
1661
|
+
`No directories with ${pc7.bold("registry.json")} found. Run ${pc7.bold("kitn create")} to scaffold a component.`
|
|
1662
|
+
);
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
s.stop(`Found ${componentDirs.length} component(s)`);
|
|
1666
|
+
for (const dir of componentDirs) {
|
|
1667
|
+
p8.log.message(` ${pc7.dim(relative6(cwd, dir))}`);
|
|
1668
|
+
}
|
|
1669
|
+
s.start("Building components...");
|
|
1670
|
+
const items = [];
|
|
1671
|
+
const errors = [];
|
|
1672
|
+
for (const dir of componentDirs) {
|
|
1673
|
+
try {
|
|
1674
|
+
const item = await buildComponent(dir);
|
|
1675
|
+
items.push(item);
|
|
1676
|
+
} catch (err) {
|
|
1677
|
+
errors.push({ dir: relative6(cwd, dir), error: err.message });
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
if (errors.length > 0) {
|
|
1681
|
+
s.stop(pc7.red(`Build failed with ${errors.length} error(s)`));
|
|
1682
|
+
for (const { dir, error } of errors) {
|
|
1683
|
+
p8.log.error(`${pc7.bold(dir)}: ${error}`);
|
|
1684
|
+
}
|
|
1685
|
+
process.exit(1);
|
|
1686
|
+
}
|
|
1687
|
+
const { written, skipped } = await writeRegistryOutput(outputDir, items);
|
|
1688
|
+
s.stop(pc7.green(`Built ${items.length} component(s)`));
|
|
1689
|
+
if (written.length > 0) {
|
|
1690
|
+
p8.log.success(`Wrote ${written.length} file(s):`);
|
|
1691
|
+
for (const f of written) {
|
|
1692
|
+
p8.log.message(` ${pc7.green("+")} ${f}`);
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
if (skipped.length > 0) {
|
|
1696
|
+
p8.log.info(`Skipped ${skipped.length} file(s) (already exist):`);
|
|
1697
|
+
for (const f of skipped) {
|
|
1698
|
+
p8.log.message(` ${pc7.dim("-")} ${f}`);
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
p8.outro(`Output: ${pc7.cyan(relative6(cwd, outputDir) || ".")}`);
|
|
1702
|
+
}
|
|
1703
|
+
var init_build = __esm({
|
|
1704
|
+
"src/commands/build.ts"() {
|
|
1705
|
+
"use strict";
|
|
1706
|
+
init_build_output();
|
|
1707
|
+
init_builder();
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
|
|
1711
|
+
// src/commands/create.ts
|
|
1712
|
+
var create_exports = {};
|
|
1713
|
+
__export(create_exports, {
|
|
1714
|
+
createCommand: () => createCommand,
|
|
1715
|
+
createComponent: () => createComponent
|
|
1716
|
+
});
|
|
1717
|
+
import * as p9 from "@clack/prompts";
|
|
1718
|
+
import pc8 from "picocolors";
|
|
1719
|
+
import { join as join12 } from "path";
|
|
1720
|
+
import { mkdir as mkdir5, writeFile as writeFile9 } from "fs/promises";
|
|
1721
|
+
function toCamelCase(str) {
|
|
1722
|
+
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
1723
|
+
}
|
|
1724
|
+
function toTitleCase(str) {
|
|
1725
|
+
return str.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
1726
|
+
}
|
|
1727
|
+
function generateRegistryJson(type, name, sourceFile) {
|
|
1728
|
+
const base = {
|
|
1729
|
+
$schema: "https://kitn.dev/schema/registry.json",
|
|
1730
|
+
name,
|
|
1731
|
+
type: `kitn:${type}`,
|
|
1732
|
+
version: "0.1.0",
|
|
1733
|
+
description: "",
|
|
1734
|
+
files: [sourceFile],
|
|
1735
|
+
categories: []
|
|
1736
|
+
};
|
|
1737
|
+
if (type === "tool") {
|
|
1738
|
+
base.dependencies = ["ai", "zod"];
|
|
1739
|
+
} else if (type === "agent" || type === "storage") {
|
|
1740
|
+
base.dependencies = [];
|
|
1741
|
+
}
|
|
1742
|
+
return base;
|
|
1743
|
+
}
|
|
1744
|
+
function generateAgentSource(name) {
|
|
1745
|
+
const camel = toCamelCase(name);
|
|
1746
|
+
return `import { registerAgent } from "@kitnai/core";
|
|
1747
|
+
|
|
1748
|
+
const SYSTEM_PROMPT = "You are a helpful assistant.";
|
|
1749
|
+
|
|
1750
|
+
registerAgent({
|
|
1751
|
+
name: "${name}",
|
|
1752
|
+
description: "",
|
|
1753
|
+
system: SYSTEM_PROMPT,
|
|
1754
|
+
tools: {},
|
|
1755
|
+
});
|
|
1756
|
+
`;
|
|
1757
|
+
}
|
|
1758
|
+
function generateToolSource(name) {
|
|
1759
|
+
const camel = toCamelCase(name);
|
|
1760
|
+
return `import { registerTool } from "@kitnai/core";
|
|
1761
|
+
import { tool } from "ai";
|
|
1762
|
+
import { z } from "zod";
|
|
1763
|
+
|
|
1764
|
+
export const ${camel} = tool({
|
|
1765
|
+
description: "",
|
|
1766
|
+
parameters: z.object({
|
|
1767
|
+
input: z.string().describe("Input parameter"),
|
|
1768
|
+
}),
|
|
1769
|
+
execute: async ({ input }) => {
|
|
1770
|
+
// TODO: implement
|
|
1771
|
+
return { result: input };
|
|
1772
|
+
},
|
|
1773
|
+
});
|
|
1774
|
+
|
|
1775
|
+
registerTool({
|
|
1776
|
+
name: "${name}",
|
|
1777
|
+
description: "",
|
|
1778
|
+
inputSchema: z.object({ input: z.string() }),
|
|
1779
|
+
tool: ${camel},
|
|
1780
|
+
});
|
|
1781
|
+
`;
|
|
1782
|
+
}
|
|
1783
|
+
function generateSkillSource(name) {
|
|
1784
|
+
const title = toTitleCase(name);
|
|
1785
|
+
return `---
|
|
1786
|
+
name: ${name}
|
|
1787
|
+
description: ""
|
|
1788
|
+
---
|
|
1789
|
+
|
|
1790
|
+
# ${title}
|
|
1791
|
+
|
|
1792
|
+
Describe what this skill does and how to use it.
|
|
1793
|
+
`;
|
|
1794
|
+
}
|
|
1795
|
+
function generateStorageSource(name) {
|
|
1796
|
+
const camel = toCamelCase("create-" + name);
|
|
1797
|
+
return `import type { StorageProvider } from "@kitnai/core";
|
|
1798
|
+
|
|
1799
|
+
export function ${camel}(config?: Record<string, unknown>): StorageProvider {
|
|
1800
|
+
// TODO: implement storage provider
|
|
1801
|
+
throw new Error("Not implemented");
|
|
1802
|
+
}
|
|
1803
|
+
`;
|
|
1804
|
+
}
|
|
1805
|
+
async function dirExists(path) {
|
|
1806
|
+
try {
|
|
1807
|
+
const { stat: stat2 } = await import("fs/promises");
|
|
1808
|
+
const s = await stat2(path);
|
|
1809
|
+
return s.isDirectory();
|
|
1810
|
+
} catch {
|
|
1811
|
+
return false;
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
async function createComponent(type, name, opts) {
|
|
1815
|
+
if (!VALID_TYPES.includes(type)) {
|
|
1816
|
+
throw new Error(
|
|
1817
|
+
`Invalid component type: "${type}". Valid types: ${VALID_TYPES.join(", ")}`
|
|
1818
|
+
);
|
|
1819
|
+
}
|
|
1820
|
+
const cwd = opts?.cwd ?? process.cwd();
|
|
1821
|
+
const dir = join12(cwd, name);
|
|
1822
|
+
if (await dirExists(dir)) {
|
|
1823
|
+
throw new Error(`Directory "${name}" already exists`);
|
|
1824
|
+
}
|
|
1825
|
+
await mkdir5(dir, { recursive: true });
|
|
1826
|
+
const validType = type;
|
|
1827
|
+
const sourceFile = validType === "skill" ? "README.md" : `${name}.ts`;
|
|
1828
|
+
const registryJson = generateRegistryJson(validType, name, sourceFile);
|
|
1829
|
+
await writeFile9(
|
|
1830
|
+
join12(dir, "registry.json"),
|
|
1831
|
+
JSON.stringify(registryJson, null, 2) + "\n"
|
|
1832
|
+
);
|
|
1833
|
+
let source;
|
|
1834
|
+
switch (validType) {
|
|
1835
|
+
case "agent":
|
|
1836
|
+
source = generateAgentSource(name);
|
|
1837
|
+
break;
|
|
1838
|
+
case "tool":
|
|
1839
|
+
source = generateToolSource(name);
|
|
1840
|
+
break;
|
|
1841
|
+
case "skill":
|
|
1842
|
+
source = generateSkillSource(name);
|
|
1843
|
+
break;
|
|
1844
|
+
case "storage":
|
|
1845
|
+
source = generateStorageSource(name);
|
|
1846
|
+
break;
|
|
1847
|
+
}
|
|
1848
|
+
await writeFile9(join12(dir, sourceFile), source);
|
|
1849
|
+
return { dir, files: ["registry.json", sourceFile] };
|
|
1850
|
+
}
|
|
1851
|
+
async function createCommand(type, name) {
|
|
1852
|
+
p9.intro(pc8.bgCyan(pc8.black(" kitn create ")));
|
|
1853
|
+
try {
|
|
1854
|
+
const { dir, files } = await createComponent(type, name);
|
|
1855
|
+
p9.log.success(`Created ${pc8.bold(type)} component ${pc8.cyan(name)}`);
|
|
1856
|
+
for (const file of files) {
|
|
1857
|
+
p9.log.message(` ${pc8.green("+")} ${file}`);
|
|
1858
|
+
}
|
|
1859
|
+
const editFile = files.find((f) => f !== "registry.json") ?? files[0];
|
|
1860
|
+
p9.outro(
|
|
1861
|
+
`Edit ${pc8.cyan(`${name}/${editFile}`)}, then run ${pc8.bold("kitn build")}`
|
|
1862
|
+
);
|
|
1863
|
+
} catch (err) {
|
|
1864
|
+
p9.log.error(err.message);
|
|
1865
|
+
process.exit(1);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
var VALID_TYPES;
|
|
1869
|
+
var init_create = __esm({
|
|
1870
|
+
"src/commands/create.ts"() {
|
|
1871
|
+
"use strict";
|
|
1872
|
+
VALID_TYPES = ["agent", "tool", "skill", "storage"];
|
|
1873
|
+
}
|
|
1874
|
+
});
|
|
1875
|
+
|
|
1075
1876
|
// src/commands/info.ts
|
|
1076
1877
|
var info_exports = {};
|
|
1077
1878
|
__export(info_exports, {
|
|
1078
1879
|
infoCommand: () => infoCommand
|
|
1079
1880
|
});
|
|
1080
|
-
import * as
|
|
1081
|
-
import
|
|
1881
|
+
import * as p10 from "@clack/prompts";
|
|
1882
|
+
import pc9 from "picocolors";
|
|
1082
1883
|
async function infoCommand(component) {
|
|
1083
1884
|
const cwd = process.cwd();
|
|
1084
1885
|
const config = await readConfig(cwd);
|
|
1085
1886
|
if (!config) {
|
|
1086
|
-
|
|
1887
|
+
p10.log.error("No kitn.json found. Run `kitn init` first.");
|
|
1087
1888
|
process.exit(1);
|
|
1088
1889
|
}
|
|
1089
1890
|
const ref = parseComponentRef(component);
|
|
1090
1891
|
const fetcher = new RegistryFetcher(config.registries);
|
|
1091
|
-
const s =
|
|
1892
|
+
const s = p10.spinner();
|
|
1092
1893
|
s.start("Fetching component info...");
|
|
1093
1894
|
let index;
|
|
1094
1895
|
try {
|
|
1095
1896
|
index = await fetcher.fetchIndex(ref.namespace);
|
|
1096
1897
|
} catch (err) {
|
|
1097
|
-
s.stop(
|
|
1098
|
-
|
|
1898
|
+
s.stop(pc9.red("Failed to fetch registry"));
|
|
1899
|
+
p10.log.error(err.message);
|
|
1099
1900
|
process.exit(1);
|
|
1100
1901
|
}
|
|
1101
1902
|
const indexItem = index.items.find((i) => i.name === ref.name);
|
|
1102
1903
|
if (!indexItem) {
|
|
1103
|
-
s.stop(
|
|
1104
|
-
|
|
1904
|
+
s.stop(pc9.red("Component not found"));
|
|
1905
|
+
p10.log.error(`Component '${ref.name}' not found in registry.`);
|
|
1105
1906
|
process.exit(1);
|
|
1106
1907
|
}
|
|
1107
1908
|
const dir = typeToDir[indexItem.type];
|
|
@@ -1109,8 +1910,8 @@ async function infoCommand(component) {
|
|
|
1109
1910
|
try {
|
|
1110
1911
|
item = await fetcher.fetchItem(ref.name, dir, ref.namespace, ref.version);
|
|
1111
1912
|
} catch (err) {
|
|
1112
|
-
s.stop(
|
|
1113
|
-
|
|
1913
|
+
s.stop(pc9.red("Failed to fetch component"));
|
|
1914
|
+
p10.log.error(err.message);
|
|
1114
1915
|
process.exit(1);
|
|
1115
1916
|
}
|
|
1116
1917
|
s.stop("Component found");
|
|
@@ -1118,62 +1919,62 @@ async function infoCommand(component) {
|
|
|
1118
1919
|
const typeName = indexItem.type.replace("kitn:", "");
|
|
1119
1920
|
console.log();
|
|
1120
1921
|
console.log(
|
|
1121
|
-
` ${
|
|
1922
|
+
` ${pc9.bold(item.name)} ${pc9.cyan(`v${version}`)}${" ".repeat(Math.max(1, 40 - item.name.length - version.length - 2))}${pc9.dim(ref.namespace)}`
|
|
1122
1923
|
);
|
|
1123
|
-
console.log(` ${
|
|
1924
|
+
console.log(` ${pc9.dim(item.description)}`);
|
|
1124
1925
|
console.log();
|
|
1125
|
-
console.log(` ${
|
|
1926
|
+
console.log(` ${pc9.dim("Type:")} ${typeName}`);
|
|
1126
1927
|
if (item.dependencies?.length) {
|
|
1127
1928
|
console.log(
|
|
1128
|
-
` ${
|
|
1929
|
+
` ${pc9.dim("Dependencies:")} ${item.dependencies.join(", ")}`
|
|
1129
1930
|
);
|
|
1130
1931
|
}
|
|
1131
1932
|
if (item.registryDependencies?.length) {
|
|
1132
1933
|
console.log(
|
|
1133
|
-
` ${
|
|
1934
|
+
` ${pc9.dim("Registry deps:")} ${item.registryDependencies.join(", ")}`
|
|
1134
1935
|
);
|
|
1135
1936
|
}
|
|
1136
1937
|
if (item.categories?.length) {
|
|
1137
1938
|
console.log(
|
|
1138
|
-
` ${
|
|
1939
|
+
` ${pc9.dim("Categories:")} ${item.categories.join(", ")}`
|
|
1139
1940
|
);
|
|
1140
1941
|
}
|
|
1141
1942
|
if (item.updatedAt) {
|
|
1142
|
-
console.log(` ${
|
|
1943
|
+
console.log(` ${pc9.dim("Updated:")} ${item.updatedAt}`);
|
|
1143
1944
|
}
|
|
1144
1945
|
const versions = indexItem.versions;
|
|
1145
1946
|
if (versions?.length) {
|
|
1146
|
-
console.log(` ${
|
|
1947
|
+
console.log(` ${pc9.dim("Versions:")} ${versions.join(", ")}`);
|
|
1147
1948
|
}
|
|
1148
1949
|
if (item.changelog?.length) {
|
|
1149
1950
|
console.log();
|
|
1150
|
-
console.log(` ${
|
|
1951
|
+
console.log(` ${pc9.bold("Changelog:")}`);
|
|
1151
1952
|
for (const entry of item.changelog) {
|
|
1152
|
-
const tag = entry.type === "feature" ?
|
|
1953
|
+
const tag = entry.type === "feature" ? pc9.green(entry.type) : entry.type === "fix" ? pc9.yellow(entry.type) : entry.type === "breaking" ? pc9.red(entry.type) : pc9.dim(entry.type);
|
|
1153
1954
|
console.log(
|
|
1154
|
-
` ${
|
|
1955
|
+
` ${pc9.cyan(entry.version)} ${pc9.dim(entry.date)} ${tag} ${entry.note}`
|
|
1155
1956
|
);
|
|
1156
1957
|
}
|
|
1157
1958
|
}
|
|
1158
1959
|
console.log();
|
|
1159
1960
|
const fileCount = item.files.length;
|
|
1160
|
-
console.log(` ${
|
|
1961
|
+
console.log(` ${pc9.bold(`Files:`)} ${pc9.dim(`(${fileCount})`)}`);
|
|
1161
1962
|
const maxShown = 10;
|
|
1162
1963
|
for (const file of item.files.slice(0, maxShown)) {
|
|
1163
|
-
console.log(` ${
|
|
1964
|
+
console.log(` ${pc9.dim(file.path)}`);
|
|
1164
1965
|
}
|
|
1165
1966
|
if (fileCount > maxShown) {
|
|
1166
|
-
console.log(` ${
|
|
1967
|
+
console.log(` ${pc9.dim(`... and ${fileCount - maxShown} more`)}`);
|
|
1167
1968
|
}
|
|
1168
1969
|
const installed = config.installed?.[item.name];
|
|
1169
1970
|
if (installed) {
|
|
1170
1971
|
console.log();
|
|
1171
1972
|
console.log(
|
|
1172
|
-
` ${
|
|
1973
|
+
` ${pc9.green("Installed")} ${pc9.dim(`v${installed.version}`)}`
|
|
1173
1974
|
);
|
|
1174
1975
|
if (version !== installed.version) {
|
|
1175
1976
|
console.log(
|
|
1176
|
-
` ${
|
|
1977
|
+
` ${pc9.yellow("Update available:")} ${pc9.dim(`v${installed.version}`)} \u2192 ${pc9.cyan(`v${version}`)}`
|
|
1177
1978
|
);
|
|
1178
1979
|
}
|
|
1179
1980
|
}
|
|
@@ -1196,8 +1997,8 @@ __export(registry_exports, {
|
|
|
1196
1997
|
registryListCommand: () => registryListCommand,
|
|
1197
1998
|
registryRemoveCommand: () => registryRemoveCommand
|
|
1198
1999
|
});
|
|
1199
|
-
import * as
|
|
1200
|
-
import
|
|
2000
|
+
import * as p11 from "@clack/prompts";
|
|
2001
|
+
import pc10 from "picocolors";
|
|
1201
2002
|
async function registryAddCommand(namespace, url, opts = {}) {
|
|
1202
2003
|
const cwd = opts.cwd ?? process.cwd();
|
|
1203
2004
|
const config = await readConfig(cwd);
|
|
@@ -1216,8 +2017,8 @@ async function registryAddCommand(namespace, url, opts = {}) {
|
|
|
1216
2017
|
}
|
|
1217
2018
|
config.registries[namespace] = url;
|
|
1218
2019
|
await writeConfig(cwd, config);
|
|
1219
|
-
|
|
1220
|
-
|
|
2020
|
+
p11.log.success(`Added registry ${pc10.bold(namespace)}`);
|
|
2021
|
+
p11.log.message(pc10.dim(` ${url}`));
|
|
1221
2022
|
}
|
|
1222
2023
|
async function registryRemoveCommand(namespace, opts = {}) {
|
|
1223
2024
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -1239,11 +2040,11 @@ async function registryRemoveCommand(namespace, opts = {}) {
|
|
|
1239
2040
|
}
|
|
1240
2041
|
delete config.registries[namespace];
|
|
1241
2042
|
await writeConfig(cwd, config);
|
|
1242
|
-
|
|
2043
|
+
p11.log.success(`Removed registry ${pc10.bold(namespace)}`);
|
|
1243
2044
|
if (affectedComponents.length > 0) {
|
|
1244
|
-
|
|
2045
|
+
p11.log.warn(`${affectedComponents.length} installed component(s) referenced this registry:`);
|
|
1245
2046
|
for (const name of affectedComponents) {
|
|
1246
|
-
|
|
2047
|
+
p11.log.message(` ${pc10.yellow("!")} ${name}`);
|
|
1247
2048
|
}
|
|
1248
2049
|
}
|
|
1249
2050
|
return { affectedComponents };
|
|
@@ -1254,10 +2055,10 @@ async function registryListCommand(opts = {}) {
|
|
|
1254
2055
|
if (!config) throw new Error("No kitn.json found. Run `kitn init` first.");
|
|
1255
2056
|
const entries = Object.entries(config.registries).map(([namespace, url]) => ({ namespace, url }));
|
|
1256
2057
|
if (entries.length === 0) {
|
|
1257
|
-
|
|
2058
|
+
p11.log.message(pc10.dim(" No registries configured."));
|
|
1258
2059
|
} else {
|
|
1259
2060
|
for (const { namespace, url } of entries) {
|
|
1260
|
-
|
|
2061
|
+
p11.log.message(` ${pc10.bold(namespace.padEnd(16))} ${pc10.dim(url)}`);
|
|
1261
2062
|
}
|
|
1262
2063
|
}
|
|
1263
2064
|
return entries;
|
|
@@ -1347,7 +2148,7 @@ function startUpdateCheck(currentVersion) {
|
|
|
1347
2148
|
}
|
|
1348
2149
|
|
|
1349
2150
|
// src/index.ts
|
|
1350
|
-
var VERSION = true ? "0.1.
|
|
2151
|
+
var VERSION = true ? "0.1.9" : "0.0.0-dev";
|
|
1351
2152
|
var printUpdateNotice = startUpdateCheck(VERSION);
|
|
1352
2153
|
var program = new Command().name("kitn").description("Install AI agent components from the kitn registry").version(VERSION);
|
|
1353
2154
|
program.command("init").description("Initialize kitn in your project").action(async () => {
|
|
@@ -1374,6 +2175,14 @@ program.command("update").description("Update installed components to latest reg
|
|
|
1374
2175
|
const { updateCommand: updateCommand2 } = await Promise.resolve().then(() => (init_update(), update_exports));
|
|
1375
2176
|
await updateCommand2(components);
|
|
1376
2177
|
});
|
|
2178
|
+
program.command("build").description("Build registry JSON from components with registry.json files").argument("[paths...]", "directories to build (default: scan from cwd)").option("-o, --output <dir>", "output directory", "dist/r").action(async (paths, opts) => {
|
|
2179
|
+
const { buildCommand: buildCommand2 } = await Promise.resolve().then(() => (init_build(), build_exports));
|
|
2180
|
+
await buildCommand2(paths, opts);
|
|
2181
|
+
});
|
|
2182
|
+
program.command("create").description("Scaffold a new kitn component").argument("<type>", "component type (agent, tool, skill, storage)").argument("<name>", "component name").action(async (type, name) => {
|
|
2183
|
+
const { createCommand: createCommand2 } = await Promise.resolve().then(() => (init_create(), create_exports));
|
|
2184
|
+
await createCommand2(type, name);
|
|
2185
|
+
});
|
|
1377
2186
|
program.command("info").description("Show details about a component").argument("<component>", "component name (e.g. weather-agent, @acme/tool@1.0.0)").action(async (component) => {
|
|
1378
2187
|
const { infoCommand: infoCommand2 } = await Promise.resolve().then(() => (init_info(), info_exports));
|
|
1379
2188
|
await infoCommand2(component);
|