apero-kit-cli 2.3.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -823,6 +823,167 @@ async function copyDiscordBaseFiles(destDir, mergeMode = false) {
823
823
  }
824
824
  return copied;
825
825
  }
826
+ function extractKeywords(content, commandName) {
827
+ const keywords = /* @__PURE__ */ new Set();
828
+ keywords.add(commandName);
829
+ keywords.add(commandName.replace(/-/g, " "));
830
+ const keywordPatterns = [
831
+ /keywords?:\s*([^\n]+)/gi,
832
+ /when.*(?:says?|mentions?|asks?).*["']([^"']+)["']/gi,
833
+ /trigger.*["']([^"']+)["']/gi
834
+ ];
835
+ for (const pattern of keywordPatterns) {
836
+ const matches = content.matchAll(pattern);
837
+ for (const match of matches) {
838
+ match[1].split(/[,;]/).forEach((k) => keywords.add(k.trim().toLowerCase()));
839
+ }
840
+ }
841
+ const intentMap = {
842
+ "plan": ["plan", "design", "architect", "implement", "create plan"],
843
+ "brainstorm": ["brainstorm", "ideas", "options", "alternatives", "think"],
844
+ "fix": ["fix", "debug", "error", "broken", "issue", "bug"],
845
+ "code": ["code", "implement", "build", "develop", "write code"],
846
+ "review": ["review", "check", "audit", "look at"],
847
+ "test": ["test", "testing", "spec", "unit test"],
848
+ "cook": ["cook", "implement", "build feature", "develop"],
849
+ "scout": ["scout", "search", "find", "explore codebase"],
850
+ "debug": ["debug", "trace", "diagnose", "investigate"]
851
+ };
852
+ const baseName = commandName.split("/")[0].split(":")[0];
853
+ if (intentMap[baseName]) {
854
+ intentMap[baseName].forEach((k) => keywords.add(k));
855
+ }
856
+ return Array.from(keywords).slice(0, 10);
857
+ }
858
+ function convertCommandToSkill(mdContent, commandName) {
859
+ const { description, argumentHint, body } = parseFrontmatter(mdContent);
860
+ const prompt = body.replace(/\$ARGUMENTS/g, "{{args}}");
861
+ const keywords = extractKeywords(body, commandName);
862
+ const skillContent = `---
863
+ name: ${commandName.replace(/\//g, "-")}
864
+ description: ${description || `Execute ${commandName} task`}
865
+ user-invocable: true
866
+ disable-model-invocation: false
867
+ metadata: {"openclaw": {"always": true}}
868
+ ---
869
+
870
+ # ${commandName.charAt(0).toUpperCase() + commandName.slice(1).replace(/-/g, " ")}
871
+
872
+ ## Trigger Conditions
873
+
874
+ Activate when user mentions:
875
+ ${keywords.map((k) => `- "${k}"`).join("\n")}
876
+
877
+ ## Input
878
+ ${argumentHint ? `Expected input: ${argumentHint.replace("$ARGUMENTS", "{{args}}")}` : "User provides task description in natural language."}
879
+
880
+ ## Workflow
881
+
882
+ ${prompt}
883
+
884
+ ## Output Format
885
+
886
+ Provide clear, actionable response based on the workflow above.
887
+ `;
888
+ return skillContent;
889
+ }
890
+ async function convertCommandsToSkills(items, sourceDir, destDir, mergeMode = false) {
891
+ const typeDir = join2(sourceDir, "commands");
892
+ const destTypeDir = join2(destDir, "skills");
893
+ if (!fs.existsSync(typeDir)) {
894
+ return { copied: [], skipped: [], errors: [] };
895
+ }
896
+ await fs.ensureDir(destTypeDir);
897
+ const copied = [];
898
+ const skipped = [];
899
+ const errors = [];
900
+ let itemList;
901
+ if (items === "all") {
902
+ const entries = fs.readdirSync(typeDir);
903
+ itemList = entries.filter((e) => e.endsWith(".md") && e !== "README.md").map((e) => e.replace(/\.md$/, ""));
904
+ const dirs = entries.filter((e) => {
905
+ const fullPath = join2(typeDir, e);
906
+ return fs.statSync(fullPath).isDirectory();
907
+ });
908
+ itemList = [.../* @__PURE__ */ new Set([...itemList, ...dirs])];
909
+ } else {
910
+ itemList = items;
911
+ }
912
+ for (const item of itemList) {
913
+ try {
914
+ const srcPathMd = join2(typeDir, item + ".md");
915
+ const srcPathDir = join2(typeDir, item);
916
+ if (fs.existsSync(srcPathMd) && fs.statSync(srcPathMd).isFile()) {
917
+ const skillDir = join2(destTypeDir, item.replace(/\//g, "-"));
918
+ const skillPath = join2(skillDir, "SKILL.md");
919
+ if (mergeMode && fs.existsSync(skillPath)) {
920
+ skipped.push(item);
921
+ continue;
922
+ }
923
+ await fs.ensureDir(skillDir);
924
+ const mdContent = fs.readFileSync(srcPathMd, "utf-8");
925
+ const skillContent = convertCommandToSkill(mdContent, item);
926
+ await fs.writeFile(skillPath, skillContent, "utf-8");
927
+ copied.push(item);
928
+ }
929
+ if (fs.existsSync(srcPathDir) && fs.statSync(srcPathDir).isDirectory()) {
930
+ await convertNestedCommandsToSkills(srcPathDir, destTypeDir, item, mergeMode);
931
+ copied.push(item + "/*");
932
+ }
933
+ } catch (err) {
934
+ errors.push({ item, error: err.message });
935
+ }
936
+ }
937
+ return { copied, skipped, errors };
938
+ }
939
+ async function convertNestedCommandsToSkills(srcDir, destDir, parentName, mergeMode) {
940
+ const entries = fs.readdirSync(srcDir);
941
+ for (const entry of entries) {
942
+ const srcPath = join2(srcDir, entry);
943
+ const stat = fs.statSync(srcPath);
944
+ if (stat.isDirectory()) {
945
+ await convertNestedCommandsToSkills(
946
+ srcPath,
947
+ destDir,
948
+ `${parentName}-${entry}`,
949
+ mergeMode
950
+ );
951
+ } else if (entry.endsWith(".md") && entry !== "README.md") {
952
+ const skillName = `${parentName}-${entry.replace(/\.md$/, "")}`;
953
+ const skillDir = join2(destDir, skillName);
954
+ const skillPath = join2(skillDir, "SKILL.md");
955
+ if (mergeMode && fs.existsSync(skillPath)) {
956
+ continue;
957
+ }
958
+ await fs.ensureDir(skillDir);
959
+ const mdContent = fs.readFileSync(srcPath, "utf-8");
960
+ const skillContent = convertCommandToSkill(mdContent, skillName);
961
+ await fs.writeFile(skillPath, skillContent, "utf-8");
962
+ }
963
+ }
964
+ }
965
+ async function copyBundledSkillsForDiscord(destDir, mergeMode = false) {
966
+ const bundledSkillsDir = join2(CLI_ROOT, "templates", "discord", "skills");
967
+ const destSkillsDir = join2(destDir, "skills");
968
+ const copied = [];
969
+ if (!fs.existsSync(bundledSkillsDir)) {
970
+ return copied;
971
+ }
972
+ await fs.ensureDir(destSkillsDir);
973
+ const skills = fs.readdirSync(bundledSkillsDir);
974
+ for (const skill of skills) {
975
+ const srcPath = join2(bundledSkillsDir, skill);
976
+ const destPath = join2(destSkillsDir, skill);
977
+ if (fs.statSync(srcPath).isDirectory()) {
978
+ if (mergeMode && fs.existsSync(destPath)) {
979
+ continue;
980
+ }
981
+ await fs.copy(srcPath, destPath, { overwrite: !mergeMode });
982
+ copied.push(skill);
983
+ }
984
+ }
985
+ return copied;
986
+ }
826
987
  var init_copy = __esm({
827
988
  "src/utils/copy.ts"() {
828
989
  "use strict";
@@ -1325,6 +1486,10 @@ async function initCommand(projectName, options) {
1325
1486
  await copyCommandsForGemini(toInstall.commands, source.claudeDir, targetDir, mergeMode);
1326
1487
  } else if (target === "discord") {
1327
1488
  await copyCommandsForDiscord(toInstall.commands, source.claudeDir, targetDir, mergeMode);
1489
+ spinner.text = mergeMode ? `Converting commands to skills (${targetLabel})...` : `Converting commands to skills (${targetLabel})...`;
1490
+ await convertCommandsToSkills(toInstall.commands, source.claudeDir, targetDir, mergeMode);
1491
+ spinner.text = `Copying bundled skills (${targetLabel})...`;
1492
+ await copyBundledSkillsForDiscord(targetDir, mergeMode);
1328
1493
  } else {
1329
1494
  if (toInstall.commands === "all") {
1330
1495
  await copyAllOfType("commands", source.claudeDir, targetDir, mergeMode);