cyber-agentkit 0.7.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/commands.js CHANGED
@@ -1,7 +1,7 @@
1
- import { C as userReferencesDir, E as require_dist, M as pluginManifestName, S as repositoryRoot, _ as parseSections, a as interfaceLookup, b as projectReferencesDir, c as readExtends, d as parseReferenceName, f as fitsName, g as parseDocument, h as isMergeOperation, i as checkResolved, m as hasBody, n as referenceImplementations, o as listReferences, p as frontmatterPattern, r as checkReferences, s as searchReferences, t as whereReference, u as resolveReference, v as canonical, w as skillName, x as referenceLayers } from "./reference-where-BhymfNgA.js";
2
- import { dirname, join, resolve, sep } from "node:path";
1
+ import { A as skillName, C as repositoryRoot, M as require_dist, R as pluginManifestName, S as referenceLayers, _ as parseDocument, a as interfaceLookup, c as readExtends, d as parseReferenceName, f as fitsName, g as isMergeOperation, h as hasBody, i as checkResolved, m as frontmatterPattern, n as referenceImplementations, o as listReferences, p as dataExtensions, r as checkReferences, s as searchReferences, t as whereReference, u as resolveReference, v as parseSections, w as userReferencesDir, x as projectReferencesDir, y as canonical } from "./reference-where-C84J6MsR.js";
3
2
  import { homedir } from "node:os";
4
- import { existsSync, mkdirSync, readFileSync, rmSync, rmdirSync, writeFileSync } from "node:fs";
3
+ import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, rmdirSync, writeFileSync } from "node:fs";
4
+ import { basename, dirname, extname, join, resolve, sep } from "node:path";
5
5
  import { command, exitCodes, t } from "clibuilder";
6
6
  import { CliError, document, encode, isCliError, printAnswer, result } from "clibuilder/axi";
7
7
  //#region src/command-output/collapse-home.ts
@@ -286,12 +286,20 @@ function selectContribution(layers, args, options = {}) {
286
286
  after: resolveReference(name, layers, options)
287
287
  };
288
288
  }
289
+ /** The data files that can answer a name in a folder. */
290
+ function dataFiles(dir, name) {
291
+ return dataExtensions.map(({ extension }) => join(dir, `${name}${extension}`));
292
+ }
289
293
  function deleteContribution(layers, args, options = {}) {
290
294
  const name = bareName(args.name);
291
295
  const target = writeTarget(name, args);
292
296
  const shown = options.display ?? ((path) => path);
293
297
  const before = resolveReference(name, layers, options);
294
- const path = (target.scope === "project" || target.scope === "user" ? [target.path, join(target.dir, name.name, "README.md")] : [target.path]).find((candidate) => candidate !== options.without && existsSync(candidate));
298
+ const path = (target.scope === "project" || target.scope === "user" ? [
299
+ target.path,
300
+ join(target.dir, name.name, "README.md"),
301
+ ...dataFiles(target.dir, name.name)
302
+ ] : [target.path]).find((candidate) => candidate !== options.without && existsSync(candidate));
295
303
  if (!path) throw new ReferenceWriteError("no-file", `${shown(target.path)} does not exist, so there is no ${target.scope} contribution of "${name.name}" to delete. ${describeAnswer(before, options.display)} An installed plugin's, a managed, or an installed skill's contribution is never deleted.`);
296
304
  const steps = readExtends(parseDocument(tryRead(path) ?? "", () => {}).metadata["extends"], () => {});
297
305
  const dryRun = Boolean(args.dryRun);
@@ -316,7 +324,7 @@ function deleteContribution(layers, args, options = {}) {
316
324
  }
317
325
  //#endregion
318
326
  //#region src/references/reference.command.ts
319
- const rootOption = {
327
+ const rootOption$1 = {
320
328
  description: "Directory the project tier is read from, and each folder above it up to the repository root; a project write goes to its `.agents/references/`. Defaults to the current directory.",
321
329
  type: t.string().optional()
322
330
  };
@@ -328,14 +336,15 @@ const defaultOption = {
328
336
  description: "The calling skill's folder: its `references/<name>.default.md` joins as the `default` tier.",
329
337
  type: t.string().optional()
330
338
  };
331
- function layerSource(plugin) {
339
+ function layerSource(plugin, folder = "references") {
332
340
  return (args, home) => referenceLayers({
333
341
  root: args.root ?? process.cwd(),
334
342
  home,
335
343
  platform: process.platform,
336
344
  env: process.env,
337
345
  plugin,
338
- defaultSkill: args.default
346
+ defaultSkill: args.default,
347
+ folder
339
348
  });
340
349
  }
341
350
  /** A field with nothing to say is left out: `undefined`, an empty string, or an empty list. */
@@ -343,7 +352,7 @@ function compact(value) {
343
352
  return Object.fromEntries(Object.entries(value).filter(([, field]) => field !== void 0 && field !== "" && !(Array.isArray(field) && !field.length)));
344
353
  }
345
354
  /** stderr, not stdout: an agent parses stdout. Rethrows what the cli already reports. */
346
- function fail(error, fallback) {
355
+ function fail$1(error, fallback) {
347
356
  if (isCliError(error)) throw error;
348
357
  if (error instanceof ReferenceWriteError) {
349
358
  process.stderr.write(`error: ${error.code}: ${error.message}\n`);
@@ -352,7 +361,7 @@ function fail(error, fallback) {
352
361
  process.stderr.write(`error: ${error instanceof Error ? error.message : fallback}\n`);
353
362
  throw CliError.reported(exitCodes.error);
354
363
  }
355
- function usage(message) {
364
+ function usage$1(message) {
356
365
  process.stderr.write(`error: usage: ${message}\n`);
357
366
  throw CliError.reported(exitCodes.usage);
358
367
  }
@@ -408,11 +417,12 @@ function errorText(entry) {
408
417
  return `${entry.name}: ${entry.message}${options}`;
409
418
  }
410
419
  function showEntry(resolved, home, { metadata, trace }) {
411
- if (resolved.status === "found" && !resolved.warnings.length && !metadata && !trace) return resolved.content;
420
+ if (resolved.status === "found" && !resolved.warnings.length && !metadata && !trace && resolved.format === "markdown") return resolved.content;
412
421
  return compact({
413
422
  name: resolved.name,
414
423
  status: resolved.status,
415
424
  content: resolved.content,
425
+ format: resolved.format === "markdown" ? void 0 : resolved.format,
416
426
  code: resolved.code,
417
427
  options: resolved.options,
418
428
  metadata: metadata ? resolved.metadata : void 0,
@@ -433,9 +443,9 @@ function showText(resolved) {
433
443
  const blocks = resolved.map((entry) => entry.content === void 0 ? `<reference name="${entry.name}" status="${entry.status}" />` : `<reference name="${entry.name}">\n${entry.content}</reference>`);
434
444
  return document(blocks.join("\n\n"));
435
445
  }
436
- function showCommand(layersFor) {
446
+ function showCommand$1(layersFor, name = "show") {
437
447
  return command({
438
- name: "show",
448
+ name,
439
449
  description: "Print one or more references, each composed from its contributions as their extends and merge declare.",
440
450
  arguments: [{
441
451
  name: "names",
@@ -443,7 +453,7 @@ function showCommand(layersFor) {
443
453
  type: t.array(t.string())
444
454
  }],
445
455
  options: {
446
- root: rootOption,
456
+ root: rootOption$1,
447
457
  default: defaultOption,
448
458
  metadata: {
449
459
  description: "Make every entry a record with its status and composed frontmatter.",
@@ -453,7 +463,7 @@ function showCommand(layersFor) {
453
463
  },
454
464
  async run(args) {
455
465
  const format = this.format;
456
- if (!args.names.length) usage("name at least one reference.");
466
+ if (!args.names.length) usage$1("name at least one reference.");
457
467
  try {
458
468
  const home = homedir();
459
469
  const layers = await layersFor(args, home);
@@ -488,14 +498,14 @@ function showCommand(layersFor) {
488
498
  const answer = result(resolved.map((entry) => showEntry(entry, home, flags)), { render: bareArray });
489
499
  return ok ? answer : answerThenFail(answer, format);
490
500
  } catch (error) {
491
- return fail(error, "Reference lookup failed.");
501
+ return fail$1(error, "Reference lookup failed.");
492
502
  }
493
503
  }
494
504
  });
495
505
  }
496
- function listCommand(layersFor) {
506
+ function listCommand$1(layersFor, name = "list") {
497
507
  return command({
498
- name: "list",
508
+ name,
499
509
  description: "List every reference name, one row each, with its description, and its status when not found. A pattern keeps only the names it matches exactly.",
500
510
  arguments: [{
501
511
  name: "pattern",
@@ -503,7 +513,7 @@ function listCommand(layersFor) {
503
513
  type: t.string().optional()
504
514
  }],
505
515
  options: {
506
- root: rootOption,
516
+ root: rootOption$1,
507
517
  trace: {
508
518
  ...traceOption,
509
519
  description: "Add one row per name per layer holding it, with its outcome."
@@ -521,6 +531,7 @@ function listCommand(layersFor) {
521
531
  references: listing.references.length ? listing.references.map((row) => compact({
522
532
  name: row.name,
523
533
  description: row.description,
534
+ format: row.format,
524
535
  status: row.status === "found" ? void 0 : row.status,
525
536
  options: row.options
526
537
  })) : args.pattern ? `0 references match "${args.pattern}"` : "0 references — no layer holds one",
@@ -533,7 +544,7 @@ function listCommand(layersFor) {
533
544
  if (listing.warnings.length) report.warnings = listing.warnings;
534
545
  return result(report);
535
546
  } catch (error) {
536
- return fail(error, "Reference listing failed.");
547
+ return fail$1(error, "Reference listing failed.");
537
548
  }
538
549
  }
539
550
  });
@@ -548,7 +559,7 @@ function searchCommand(layersFor) {
548
559
  type: t.string()
549
560
  }],
550
561
  options: {
551
- root: rootOption,
562
+ root: rootOption$1,
552
563
  trace: {
553
564
  ...traceOption,
554
565
  description: "Add the tier and plugin or skill of each match."
@@ -556,7 +567,7 @@ function searchCommand(layersFor) {
556
567
  },
557
568
  async run(args) {
558
569
  const query = args.query.trim();
559
- if (!query) usage("search needs a query.");
570
+ if (!query) usage$1("search needs a query.");
560
571
  try {
561
572
  const home = homedir();
562
573
  const { matches, defaults } = searchReferences(query, await layersFor(args, home), { display: (path) => collapseHome(home, path) });
@@ -579,14 +590,14 @@ function searchCommand(layersFor) {
579
590
  });
580
591
  return result(report);
581
592
  } catch (error) {
582
- return fail(error, "Reference search failed.");
593
+ return fail$1(error, "Reference search failed.");
583
594
  }
584
595
  }
585
596
  });
586
597
  }
587
- function whereCommand(layersFor) {
598
+ function whereCommand(layersFor, assets = false, name = "where") {
588
599
  return command({
589
- name: "where",
600
+ name,
590
601
  description: "Give the project and user files a customization or a selection of a reference is written to, who each reaches, and the heading paths of the document below each.",
591
602
  arguments: [{
592
603
  name: "name",
@@ -594,7 +605,7 @@ function whereCommand(layersFor) {
594
605
  type: t.string()
595
606
  }],
596
607
  options: {
597
- root: rootOption,
608
+ root: rootOption$1,
598
609
  default: defaultOption,
599
610
  trace: {
600
611
  ...traceOption,
@@ -608,7 +619,8 @@ function whereCommand(layersFor) {
608
619
  const display = (path) => collapseHome(home, path);
609
620
  const report = whereReference(args.name, await layersFor(args, home), {
610
621
  root: resolve(args.root ?? process.cwd()),
611
- display
622
+ display,
623
+ assets
612
624
  });
613
625
  const output = compact({
614
626
  name: report.name,
@@ -627,7 +639,7 @@ function whereCommand(layersFor) {
627
639
  process.stderr.write(`error: reference-invalid: ${parseReferenceName(args.name).invalid}\n`);
628
640
  return answerThenFail(answer, format);
629
641
  } catch (error) {
630
- return fail(error, "Reference placement lookup failed.");
642
+ return fail$1(error, "Reference placement lookup failed.");
631
643
  }
632
644
  }
633
645
  });
@@ -635,7 +647,7 @@ function whereCommand(layersFor) {
635
647
  function parseAccept(value) {
636
648
  if (value === void 0) return void 0;
637
649
  const versions = value.split(",").map((part) => part.trim());
638
- if (!versions.length || versions.some((part) => !/^[1-9]\d*$/.test(part))) usage(`--accept is a comma-separated list of versions, such as 1,2; got "${value}".`);
650
+ if (!versions.length || versions.some((part) => !/^[1-9]\d*$/.test(part))) usage$1(`--accept is a comma-separated list of versions, such as 1,2; got "${value}".`);
639
651
  return versions.map(Number);
640
652
  }
641
653
  function implementationsCommand(layersFor) {
@@ -648,7 +660,7 @@ function implementationsCommand(layersFor) {
648
660
  type: t.string()
649
661
  }],
650
662
  options: {
651
- root: rootOption,
663
+ root: rootOption$1,
652
664
  accept: {
653
665
  description: "Versions the consumer knows, comma-separated, such as 1,2. Defaults to the interface's `versions`.",
654
666
  type: t.string().optional()
@@ -688,7 +700,7 @@ function implementationsCommand(layersFor) {
688
700
  }));
689
701
  return result(report);
690
702
  } catch (error) {
691
- return fail(error, "Implementation lookup failed.");
703
+ return fail$1(error, "Implementation lookup failed.");
692
704
  }
693
705
  }
694
706
  });
@@ -703,7 +715,7 @@ function checkCommand(layersFor) {
703
715
  type: t.array(t.string()).optional()
704
716
  }],
705
717
  options: {
706
- root: rootOption,
718
+ root: rootOption$1,
707
719
  trace: {
708
720
  ...traceOption,
709
721
  description: "Add every entry checked, with its kind and unsupported sections."
@@ -743,7 +755,7 @@ function checkCommand(layersFor) {
743
755
  if (format === "text") process.stderr.write(`error: interface-check: ${failing.length} of ${entries.length} references fail their check.\n`);
744
756
  return answerThenFail(answer, format);
745
757
  } catch (error) {
746
- return fail(error, "Reference check failed.");
758
+ return fail$1(error, "Reference check failed.");
747
759
  }
748
760
  }
749
761
  });
@@ -756,7 +768,7 @@ const pluginOption = {
756
768
  description: "A plugin's source folder in this repository: acts on its `references/<name>.md`.",
757
769
  type: t.string().optional()
758
770
  };
759
- const skillOption = {
771
+ const skillOption$1 = {
760
772
  description: "A skill's source folder in this repository: acts on its `references/<name>.default.md`.",
761
773
  type: t.string().optional()
762
774
  };
@@ -770,10 +782,10 @@ function createCommand(layersFor) {
770
782
  type: t.string()
771
783
  }],
772
784
  options: {
773
- root: rootOption,
785
+ root: rootOption$1,
774
786
  scope: scopeOption,
775
787
  plugin: pluginOption,
776
- default: skillOption,
788
+ default: skillOption$1,
777
789
  merge: {
778
790
  description: "How it applies over what is below: replace, patch (default), combine, or remove. remove writes no body.",
779
791
  type: t.string().optional()
@@ -828,12 +840,12 @@ function createCommand(layersFor) {
828
840
  if (args.trace && created.after) process.stderr.write(traceText(created.after, home));
829
841
  return document(created.dryRun ? `${report.path}\n\n${created.content}` : `${report.path}\n\n${report.hint}\n`);
830
842
  } catch (error) {
831
- return fail(error, "Reference creation failed.");
843
+ return fail$1(error, "Reference creation failed.");
832
844
  }
833
845
  }
834
846
  });
835
847
  }
836
- function selectCommand(layersFor) {
848
+ function selectCommand$1(layersFor) {
837
849
  return command({
838
850
  name: "select",
839
851
  description: "Choose what a name builds on: write an `extends` listing the options, bottom first, in the project or user file. Its body and other keys are kept.",
@@ -847,7 +859,7 @@ function selectCommand(layersFor) {
847
859
  type: t.array(t.string()).optional()
848
860
  }],
849
861
  options: {
850
- root: rootOption,
862
+ root: rootOption$1,
851
863
  scope: scopeOption,
852
864
  clear: {
853
865
  description: "Remove the selection; a file left with nothing in it is deleted.",
@@ -881,7 +893,7 @@ function selectCommand(layersFor) {
881
893
  const what = args.clear ? selected.deleted ? "selection cleared; the file was left empty and deleted" : "selection cleared" : `extends: [${selected.extends.join(", ")}]`;
882
894
  return document(`${report.path}\n\n${what}. ${report.answer}\n`);
883
895
  } catch (error) {
884
- return fail(error, "Reference selection failed.");
896
+ return fail$1(error, "Reference selection failed.");
885
897
  }
886
898
  }
887
899
  });
@@ -898,7 +910,7 @@ function deleteReport(deleted, home) {
898
910
  options: deleted.after.options
899
911
  });
900
912
  }
901
- function deleteCommand(layersFor) {
913
+ function deleteCommand$1(layersFor) {
902
914
  return command({
903
915
  name: "delete",
904
916
  description: "Delete a project or user contribution, or one in a plugin's or a skill's source folder, and report what answers the name afterwards. Never deletes an installed or managed one.",
@@ -908,13 +920,13 @@ function deleteCommand(layersFor) {
908
920
  type: t.string()
909
921
  }],
910
922
  options: {
911
- root: rootOption,
923
+ root: rootOption$1,
912
924
  scope: {
913
925
  ...scopeOption,
914
926
  description: "Tier to delete from: project (default) or user."
915
927
  },
916
928
  plugin: pluginOption,
917
- default: skillOption,
929
+ default: skillOption$1,
918
930
  "dry-run": {
919
931
  description: "Print the file that would be deleted and what would answer the name then, and delete nothing.",
920
932
  type: t.boolean().optional()
@@ -942,7 +954,7 @@ function deleteCommand(layersFor) {
942
954
  const selection = report.selection ? ` Also, ${report.selection}.` : "";
943
955
  return document(`${report.path}\n\n${deleted.dryRun ? "would delete" : "deleted"}. ${report.answer}${selection}\n`);
944
956
  } catch (error) {
945
- return fail(error, "Reference deletion failed.");
957
+ return fail$1(error, "Reference deletion failed.");
946
958
  }
947
959
  }
948
960
  });
@@ -951,13 +963,13 @@ function deleteCommand(layersFor) {
951
963
  function createReferenceCommands({ plugin } = {}) {
952
964
  const layersFor = layerSource(plugin);
953
965
  return {
954
- show: showCommand(layersFor),
955
- list: listCommand(layersFor),
966
+ show: showCommand$1(layersFor),
967
+ list: listCommand$1(layersFor),
956
968
  search: searchCommand(layersFor),
957
969
  where: whereCommand(layersFor),
958
970
  create: createCommand(layersFor),
959
- select: selectCommand(layersFor),
960
- delete: deleteCommand(layersFor),
971
+ select: selectCommand$1(layersFor),
972
+ delete: deleteCommand$1(layersFor),
961
973
  check: checkCommand(layersFor),
962
974
  implementations: implementationsCommand(layersFor)
963
975
  };
@@ -980,5 +992,414 @@ function createReferenceCommand(options = {}) {
980
992
  ]
981
993
  });
982
994
  }
995
+ /**
996
+ * The tiered, by-name commands over `assets/` folders: the reference engine reading `assets/` where it
997
+ * reads `references/`. `load` is `show`, `names` is `list`. `asset`'s own commands act on a path.
998
+ */
999
+ function createAssetResolveCommands({ plugin } = {}) {
1000
+ const layersFor = layerSource(plugin, "assets");
1001
+ return {
1002
+ load: showCommand$1(layersFor, "load"),
1003
+ names: listCommand$1(layersFor, "names"),
1004
+ where: whereCommand(layersFor, true)
1005
+ };
1006
+ }
1007
+ //#endregion
1008
+ //#region src/assets/skill-assets.ts
1009
+ /** A refusal. `usage` marks a wrong invocation (exit 2) rather than a refusal (exit 1). */
1010
+ var AssetError = class extends Error {
1011
+ code;
1012
+ usage;
1013
+ constructor(code, message, usage = false) {
1014
+ super(message);
1015
+ this.code = code;
1016
+ this.usage = usage;
1017
+ }
1018
+ };
1019
+ /** The skill's `assets/` folder, for a skill folder that holds a `SKILL.md`. */
1020
+ function assetsDir(skill, root, write) {
1021
+ const folder = resolve(skill);
1022
+ if (skillName(folder) === void 0) throw new AssetError("folder-invalid", `${folder} holds no SKILL.md, so it is not a skill folder.`);
1023
+ if (write && !insideRepository(folder, root)) throw new AssetError("folder-invalid", `${folder} is outside the repository, such as an installed copy; write to the skill's source instead.`);
1024
+ return join(folder, "assets");
1025
+ }
1026
+ /** An asset path: relative, `/`-separated, with no `..` or empty part. */
1027
+ function assetFile(dir, path) {
1028
+ const parts = path.split("/");
1029
+ if (!path || path.includes("\\") || path.startsWith("/") || parts.some((part) => part === "" || part === "." || part === "..")) throw new AssetError("path-invalid", `"${path}" is not an asset path. Use a relative path inside assets/, with "/" separators and no "..".`, true);
1030
+ return join(dir, ...parts);
1031
+ }
1032
+ /** True when the real location of an existing file or its nearest existing parent stays inside the folder. */
1033
+ function contained(dir, file) {
1034
+ if (!existsSync(dir)) return true;
1035
+ const base = realpathSync(dir);
1036
+ let probe = file;
1037
+ while (!existsSync(probe)) probe = dirname(probe);
1038
+ const real = realpathSync(probe);
1039
+ return real === base || real.startsWith(base + sep);
1040
+ }
1041
+ function guard(dir, file, path) {
1042
+ if (!contained(dir, file)) throw new AssetError("path-invalid", `"${path}" leaves the assets/ folder through a link.`, true);
1043
+ }
1044
+ function listAssets(skill, root) {
1045
+ const dir = assetsDir(skill, root, false);
1046
+ const entries = [];
1047
+ const walk = (folder, prefix) => {
1048
+ let items;
1049
+ try {
1050
+ items = readdirSync(folder, { withFileTypes: true });
1051
+ } catch {
1052
+ return;
1053
+ }
1054
+ for (const item of items) {
1055
+ const full = join(folder, item.name);
1056
+ const stat = lstatSync(full);
1057
+ if (stat.isSymbolicLink()) continue;
1058
+ if (stat.isDirectory()) walk(full, `${prefix}${item.name}/`);
1059
+ else if (stat.isFile()) entries.push({
1060
+ path: `${prefix}${item.name}`,
1061
+ bytes: stat.size
1062
+ });
1063
+ }
1064
+ };
1065
+ walk(dir, "");
1066
+ return entries.sort((a, b) => a.path.localeCompare(b.path));
1067
+ }
1068
+ function showAsset(skill, root, path) {
1069
+ const dir = assetsDir(skill, root, false);
1070
+ const file = assetFile(dir, path);
1071
+ guard(dir, file, path);
1072
+ if (!existsSync(file) || !lstatSync(file).isFile()) throw new AssetError("no-file", `${path} does not exist in assets/.`);
1073
+ const bytes = readFileSync(file);
1074
+ if (bytes.includes(0)) throw new AssetError("binary", `${path} is binary (${bytes.length} bytes); point to it by path instead of showing it.`);
1075
+ return bytes.toString("utf8");
1076
+ }
1077
+ function writeAsset(mode, skill, root, path, content) {
1078
+ const dir = assetsDir(skill, root, true);
1079
+ const file = assetFile(dir, path);
1080
+ guard(dir, file, path);
1081
+ const exists = existsSync(file);
1082
+ if (mode === "create" && exists) throw new AssetError("exists", `${path} already exists in assets/; use update to replace it.`);
1083
+ if (mode === "update" && !exists) throw new AssetError("no-file", `${path} does not exist in assets/; use create to add it.`);
1084
+ mkdirSync(dirname(file), { recursive: true });
1085
+ writeFileSync(file, content);
1086
+ return file;
1087
+ }
1088
+ function deleteAsset(skill, root, path) {
1089
+ const dir = assetsDir(skill, root, true);
1090
+ const file = assetFile(dir, path);
1091
+ guard(dir, file, path);
1092
+ if (!existsSync(file) || !lstatSync(file).isFile()) throw new AssetError("no-file", `${path} does not exist in assets/.`);
1093
+ rmSync(file);
1094
+ for (let folder = dirname(file); folder !== dir && folder.startsWith(dir + sep); folder = dirname(folder)) try {
1095
+ rmdirSync(folder);
1096
+ } catch {
1097
+ break;
1098
+ }
1099
+ return file;
1100
+ }
1101
+ //#endregion
1102
+ //#region src/assets/copy-asset.ts
1103
+ /**
1104
+ * Creates `<name>.<ext>` in the project or user `assets/` folder as a copy of the file another tier or
1105
+ * plugin holds, so it can be edited as an override. The managed tier is never a source: it is policy an
1106
+ * administrator keeps out of repositories and home folders, and a copy would show stale text as if it
1107
+ * were in effect (the managed file would still win).
1108
+ */
1109
+ function copyAsset(layers, args) {
1110
+ const name = parseReferenceName(args.name);
1111
+ if (name.invalid || name.plugin !== void 0) throw new AssetError("name-invalid", name.invalid ?? "create takes a bare name.", true);
1112
+ if (args.scope !== "project" && args.scope !== "user") throw new AssetError("options-invalid", "--scope is project or user.", true);
1113
+ if (args.copy === "managed") throw new AssetError("managed-copy-refused", "the managed tier is policy and is never copied into a project or user folder. Ask the administrator, or read it with \"asset load\".");
1114
+ const found = resolveReference(name.name, layers).trace.layers.filter(({ file }) => file);
1115
+ const source = found.find((layer) => args.copy === "default" || args.copy === "project" || args.copy === "user" ? layer.tier === args.copy : layer.tier === "plugin" && layer.plugin === args.copy);
1116
+ if (!source) {
1117
+ const held = found.map((layer) => layer.tier === "plugin" ? layer.plugin : layer.tier);
1118
+ throw new AssetError("no-source", `"${args.copy}" holds no asset "${name.name}". Held by: ${held.filter((tier) => tier !== "managed").join(", ") || "nothing"}.`);
1119
+ }
1120
+ const extension = extname(source.path);
1121
+ if (source.file && ![...dataExtensions.map((item) => item.extension), ".md"].includes(extension)) throw new AssetError("no-source", `${basename(source.path)} is not a file an asset name resolves to.`);
1122
+ const base = args.scope === "user" ? userReferencesDir(args.home) : projectReferencesDir(resolve(args.root));
1123
+ const dir = join(dirname(base), "assets");
1124
+ const taken = [".md", ...dataExtensions.map((item) => item.extension)].map((item) => join(dir, `${name.name}${item}`)).find((path) => existsSync(path));
1125
+ if (taken) throw new AssetError("exists", `${taken} already exists; edit it, or delete it first.`);
1126
+ const path = join(dir, `${name.name}${extension}`);
1127
+ mkdirSync(dir, { recursive: true });
1128
+ copyFileSync(source.path, path);
1129
+ return {
1130
+ path,
1131
+ source: source.path,
1132
+ from: args.copy
1133
+ };
1134
+ }
1135
+ //#endregion
1136
+ //#region src/assets/select-asset.ts
1137
+ /** The options a name can be selected between: each plugin holding it, and `default` when the skill's file exists. */
1138
+ function options(layers, name) {
1139
+ const found = resolveReference(name, layers).trace.layers;
1140
+ return [...found.filter(({ tier, file }) => tier === "plugin" && file).map(({ plugin }) => plugin), ...found.some(({ tier, file }) => tier === "default" && file) ? ["default"] : []];
1141
+ }
1142
+ /**
1143
+ * Writes `<name>.select` in the project or user `assets/` folder: one line, the plugin or `default` that
1144
+ * answers the name. Nothing else is in the file, and nothing extends or merges.
1145
+ */
1146
+ function selectAsset(layers, args) {
1147
+ const name = parseReferenceName(args.name);
1148
+ if (name.invalid || name.plugin !== void 0) throw new AssetError("name-invalid", name.invalid ?? "select takes a bare name.", true);
1149
+ if (args.scope !== void 0 && args.scope !== "project" && args.scope !== "user") throw new AssetError("options-invalid", "--scope is project or user.", true);
1150
+ const base = args.scope === "user" ? userReferencesDir(args.home) : projectReferencesDir(resolve(args.root));
1151
+ const path = join(dirname(base), "assets", `${name.name}.select`);
1152
+ if (args.clear) {
1153
+ if (!existsSync(path)) throw new AssetError("no-file", `${path} does not exist, so there is no selection to clear.`);
1154
+ rmSync(path);
1155
+ } else {
1156
+ const held = options(layers, name.name);
1157
+ if (!args.option || !held.includes(args.option)) throw new AssetError("option-invalid", `"${args.option ?? ""}" cannot be selected for "${name.name}". Options: ${held.join(", ") || "none, no plugin or default holds it"}.`, !args.option);
1158
+ mkdirSync(dirname(path), { recursive: true });
1159
+ writeFileSync(path, `${args.option}\n`);
1160
+ }
1161
+ const result = {
1162
+ path,
1163
+ cleared: Boolean(args.clear),
1164
+ after: resolveReference(name.name, layers, { without: args.clear ? path : void 0 })
1165
+ };
1166
+ return args.clear ? result : {
1167
+ ...result,
1168
+ selected: args.option
1169
+ };
1170
+ }
1171
+ //#endregion
1172
+ //#region src/assets/asset.command.ts
1173
+ const skillOption = {
1174
+ description: "A skill's source folder in this repository: its `assets/` folder is acted on.",
1175
+ type: t.string()
1176
+ };
1177
+ const rootOption = {
1178
+ description: "The directory the repository is found from. Defaults to the current directory.",
1179
+ type: t.string().optional()
1180
+ };
1181
+ function fail(error, fallback) {
1182
+ if (isCliError(error)) throw error;
1183
+ if (error instanceof AssetError) {
1184
+ process.stderr.write(`error: ${error.code}: ${error.message}\n`);
1185
+ throw CliError.reported(error.usage ? exitCodes.usage : exitCodes.error);
1186
+ }
1187
+ process.stderr.write(`error: ${error instanceof Error ? error.message : fallback}\n`);
1188
+ throw CliError.reported(exitCodes.error);
1189
+ }
1190
+ function usage(message) {
1191
+ process.stderr.write(`error: usage: ${message}\n`);
1192
+ throw CliError.reported(exitCodes.usage);
1193
+ }
1194
+ const pathDescription = "Asset path inside `assets/`, such as `data/rules.json`.";
1195
+ function rootOf(args) {
1196
+ return resolve(args.root ?? process.cwd());
1197
+ }
1198
+ function listCommand() {
1199
+ return command({
1200
+ name: "list",
1201
+ description: "List every file in a skill's assets/ folder with its size.",
1202
+ options: {
1203
+ skill: skillOption,
1204
+ root: rootOption
1205
+ },
1206
+ async run(args) {
1207
+ try {
1208
+ const entries = listAssets(args.skill, rootOf(args));
1209
+ return result({ assets: entries.length ? entries : "0 assets — the skill holds none" });
1210
+ } catch (error) {
1211
+ return fail(error, "Asset listing failed.");
1212
+ }
1213
+ }
1214
+ });
1215
+ }
1216
+ function showCommand() {
1217
+ return command({
1218
+ name: "show",
1219
+ description: "Print a text asset as written.",
1220
+ arguments: [{
1221
+ name: "path",
1222
+ description: pathDescription,
1223
+ type: t.string()
1224
+ }],
1225
+ options: {
1226
+ skill: skillOption,
1227
+ root: rootOption
1228
+ },
1229
+ async run(args) {
1230
+ try {
1231
+ return document(showAsset(args.skill, rootOf(args), args.path));
1232
+ } catch (error) {
1233
+ return fail(error, "Asset read failed.");
1234
+ }
1235
+ }
1236
+ });
1237
+ }
1238
+ function writeCommand(mode, host) {
1239
+ const create = mode === "create";
1240
+ return command({
1241
+ name: mode,
1242
+ description: create ? "Add a new asset: by path in a skill from --content or --from, or, with --copy, by name in the project or user assets folder as a copy of what a plugin, the default, or another tier holds (never the managed tier). Never overwrites a file." : "Replace an existing asset from --content or --from.",
1243
+ arguments: [{
1244
+ name: "path",
1245
+ description: `${pathDescription} With --copy, the asset name.`,
1246
+ type: t.string()
1247
+ }],
1248
+ options: {
1249
+ skill: {
1250
+ ...skillOption,
1251
+ type: t.string().optional()
1252
+ },
1253
+ root: rootOption,
1254
+ content: {
1255
+ description: "The text to write.",
1256
+ type: t.string().optional()
1257
+ },
1258
+ from: {
1259
+ description: "A local file whose bytes to write.",
1260
+ type: t.string().optional()
1261
+ },
1262
+ ...create ? {
1263
+ default: {
1264
+ description: "With --copy, the calling skill's folder: its `assets/<name>.<ext>` is the `default` source.",
1265
+ type: t.string().optional()
1266
+ },
1267
+ copy: {
1268
+ description: "Copy the asset of this source: a plugin's name, `default` (the skill's own file), `project`, or `user`. `managed` is refused.",
1269
+ type: t.string().optional()
1270
+ },
1271
+ scope: {
1272
+ description: "With --copy, where the copy goes: project (default), `.agents/assets/`, or user, `~/.agents/assets/`.",
1273
+ type: t.string().optional()
1274
+ }
1275
+ } : {}
1276
+ },
1277
+ async run(args) {
1278
+ try {
1279
+ if (args.copy !== void 0) {
1280
+ if (args.content !== void 0 || args.from !== void 0) usage("--copy takes neither --content nor --from.");
1281
+ const home = homedir();
1282
+ const root = rootOf(args);
1283
+ const done = copyAsset(await referenceLayers({
1284
+ root,
1285
+ home,
1286
+ platform: process.platform,
1287
+ env: process.env,
1288
+ plugin: host.plugin,
1289
+ defaultSkill: args.default ?? args.skill,
1290
+ folder: "assets"
1291
+ }), {
1292
+ root,
1293
+ home,
1294
+ name: args.path,
1295
+ scope: args.scope ?? "project",
1296
+ copy: args.copy
1297
+ });
1298
+ return document(`${done.path}\n\ncopied from ${done.from}. Edit it; it now answers over the plugin tier.\n`);
1299
+ }
1300
+ if (!args.skill) usage("pass --skill, or --copy to create an override by name.");
1301
+ if (args.content === void 0 === (args.from === void 0)) usage("pass exactly one of --content and --from.");
1302
+ const content = args.content ?? readFileSync(resolve(args.from), "utf8");
1303
+ const file = writeAsset(mode, args.skill, rootOf(args), args.path, content);
1304
+ return document(`${file}\n\n${create ? "created" : "updated"}.\n`);
1305
+ } catch (error) {
1306
+ return fail(error, `Asset ${mode} failed.`);
1307
+ }
1308
+ }
1309
+ });
1310
+ }
1311
+ function deleteCommand() {
1312
+ return command({
1313
+ name: "delete",
1314
+ description: "Delete an asset from a skill's source folder, and any folder it leaves empty.",
1315
+ arguments: [{
1316
+ name: "path",
1317
+ description: pathDescription,
1318
+ type: t.string()
1319
+ }],
1320
+ options: {
1321
+ skill: skillOption,
1322
+ root: rootOption
1323
+ },
1324
+ async run(args) {
1325
+ try {
1326
+ return document(`${deleteAsset(args.skill, rootOf(args), args.path)}\n\ndeleted.\n`);
1327
+ } catch (error) {
1328
+ return fail(error, "Asset deletion failed.");
1329
+ }
1330
+ }
1331
+ });
1332
+ }
1333
+ function selectCommand(options) {
1334
+ return command({
1335
+ name: "select",
1336
+ description: "Choose which plugin, or the skill itself, answers an asset name that several hold, by writing `<name>.select` in the project or user assets folder.",
1337
+ arguments: [{
1338
+ name: "name",
1339
+ description: "Asset name, without its extension.",
1340
+ type: t.string()
1341
+ }, {
1342
+ name: "option",
1343
+ description: "A plugin name, or `default` for the skill's own file. Not used with --clear.",
1344
+ type: t.string().optional()
1345
+ }],
1346
+ options: {
1347
+ root: rootOption,
1348
+ default: {
1349
+ description: "The calling skill's folder: its `assets/<name>.<ext>` is the `default` option.",
1350
+ type: t.string().optional()
1351
+ },
1352
+ scope: {
1353
+ description: "Where to write: project (default), `.agents/assets/`, or user, `~/.agents/assets/`.",
1354
+ type: t.string().optional()
1355
+ },
1356
+ clear: {
1357
+ description: "Remove the selection.",
1358
+ type: t.boolean().optional()
1359
+ }
1360
+ },
1361
+ async run(args) {
1362
+ try {
1363
+ const home = homedir();
1364
+ const root = resolve(args.root ?? process.cwd());
1365
+ const done = selectAsset(await referenceLayers({
1366
+ root,
1367
+ home,
1368
+ platform: process.platform,
1369
+ env: process.env,
1370
+ plugin: options.plugin,
1371
+ defaultSkill: args.default,
1372
+ folder: "assets"
1373
+ }), {
1374
+ ...args,
1375
+ root,
1376
+ home
1377
+ });
1378
+ const answer = done.after.status === "found" ? `${args.name} now answers from ${done.after.trace.stack.join(", ") || "a tier"}.` : `${args.name} is ${done.after.status}: ${done.after.message ?? ""}`;
1379
+ return document(`${done.path}\n\n${done.cleared ? "selection cleared" : `selected ${done.selected}`}. ${answer}\n`);
1380
+ } catch (error) {
1381
+ return fail(error, "Asset selection failed.");
1382
+ }
1383
+ }
1384
+ });
1385
+ }
1386
+ function createAssetCommand(options = {}) {
1387
+ const { load, names, where } = createAssetResolveCommands(options);
1388
+ return command({
1389
+ name: "asset",
1390
+ description: "Manage a skill's assets/ files by path (list, show, create, update, delete), and load one by name through the tiers (load, names, where, select).",
1391
+ commands: [
1392
+ listCommand(),
1393
+ showCommand(),
1394
+ writeCommand("create", options),
1395
+ writeCommand("update", options),
1396
+ deleteCommand(),
1397
+ load,
1398
+ names,
1399
+ where,
1400
+ selectCommand(options)
1401
+ ]
1402
+ });
1403
+ }
983
1404
  //#endregion
984
- export { createReferenceCommand, createReferenceCommands };
1405
+ export { createAssetCommand, createReferenceCommand, createReferenceCommands };