@within-7/minto 0.3.9 → 0.3.10

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.
Files changed (141) hide show
  1. package/dist/commands/agents/AgentsCommand.js +459 -655
  2. package/dist/commands/agents/AgentsCommand.js.map +2 -2
  3. package/dist/commands/agents/types.js +1 -0
  4. package/dist/commands/agents/types.js.map +2 -2
  5. package/dist/commands/agents/utils/fileOperations.js +96 -36
  6. package/dist/commands/agents/utils/fileOperations.js.map +3 -3
  7. package/dist/commands/agents/utils/index.js +3 -1
  8. package/dist/commands/agents/utils/index.js.map +2 -2
  9. package/dist/commands/context.js +54 -23
  10. package/dist/commands/context.js.map +2 -2
  11. package/dist/commands/export.js +673 -93
  12. package/dist/commands/export.js.map +2 -2
  13. package/dist/commands/language.js +19 -46
  14. package/dist/commands/language.js.map +2 -2
  15. package/dist/commands/mcp-interactive.js +419 -217
  16. package/dist/commands/mcp-interactive.js.map +2 -2
  17. package/dist/commands/model.js +415 -66
  18. package/dist/commands/model.js.map +2 -2
  19. package/dist/commands/permissions.js +75 -49
  20. package/dist/commands/permissions.js.map +2 -2
  21. package/dist/commands/plugin.js +882 -185
  22. package/dist/commands/plugin.js.map +3 -3
  23. package/dist/commands/resume.js +1 -1
  24. package/dist/commands/resume.js.map +1 -1
  25. package/dist/commands/sandbox.js +168 -70
  26. package/dist/commands/sandbox.js.map +2 -2
  27. package/dist/commands/setup.js +593 -107
  28. package/dist/commands/setup.js.map +2 -2
  29. package/dist/commands/stats.js +188 -131
  30. package/dist/commands/stats.js.map +2 -2
  31. package/dist/commands/status.js +75 -13
  32. package/dist/commands/status.js.map +2 -2
  33. package/dist/commands/undo.js +138 -174
  34. package/dist/commands/undo.js.map +2 -2
  35. package/dist/commands.js.map +1 -1
  36. package/dist/components/Help.js +165 -32
  37. package/dist/components/Help.js.map +2 -2
  38. package/dist/components/InfoPanel/InfoPanel.js +123 -0
  39. package/dist/components/InfoPanel/InfoPanel.js.map +7 -0
  40. package/dist/components/InfoPanel/index.js +5 -0
  41. package/dist/components/InfoPanel/index.js.map +7 -0
  42. package/dist/components/InfoPanel/types.js +1 -0
  43. package/dist/components/InfoPanel/types.js.map +7 -0
  44. package/dist/components/ModelSelector/BrandTextInput.js +43 -0
  45. package/dist/components/ModelSelector/BrandTextInput.js.map +7 -0
  46. package/dist/components/ModelSelector/ModelSelector.js +419 -501
  47. package/dist/components/ModelSelector/ModelSelector.js.map +2 -2
  48. package/dist/components/ModelSelector/WizardContainer.js +45 -0
  49. package/dist/components/ModelSelector/WizardContainer.js.map +7 -0
  50. package/dist/components/ModelSelector/index.js +1 -3
  51. package/dist/components/ModelSelector/index.js.map +2 -2
  52. package/dist/components/PromptInput.js +5 -5
  53. package/dist/components/PromptInput.js.map +2 -2
  54. package/dist/components/SimpleSelector/SimpleSelector.js +154 -0
  55. package/dist/components/SimpleSelector/SimpleSelector.js.map +7 -0
  56. package/dist/components/SimpleSelector/index.js +5 -0
  57. package/dist/components/SimpleSelector/index.js.map +7 -0
  58. package/dist/components/SimpleSelector/types.js +1 -0
  59. package/dist/components/SimpleSelector/types.js.map +7 -0
  60. package/dist/components/StatusOverlayContent.js +21 -0
  61. package/dist/components/StatusOverlayContent.js.map +7 -0
  62. package/dist/components/TabbedListView/ScrollableList.js +31 -5
  63. package/dist/components/TabbedListView/ScrollableList.js.map +2 -2
  64. package/dist/components/TabbedListView/TabbedListView.js +122 -47
  65. package/dist/components/TabbedListView/TabbedListView.js.map +2 -2
  66. package/dist/core/backupHook.js +29 -0
  67. package/dist/core/backupHook.js.map +7 -0
  68. package/dist/core/config/defaults.js +8 -2
  69. package/dist/core/config/defaults.js.map +2 -2
  70. package/dist/core/config/schema.js +14 -2
  71. package/dist/core/config/schema.js.map +2 -2
  72. package/dist/core/costTracker.js +0 -16
  73. package/dist/core/costTracker.js.map +2 -2
  74. package/dist/cost-tracker.js +0 -16
  75. package/dist/cost-tracker.js.map +2 -2
  76. package/dist/entrypoints/bootstrap.js +3 -1
  77. package/dist/entrypoints/bootstrap.js.map +2 -2
  78. package/dist/entrypoints/cli.js +32 -0
  79. package/dist/entrypoints/cli.js.map +2 -2
  80. package/dist/i18n/locales/en.js +300 -1
  81. package/dist/i18n/locales/en.js.map +2 -2
  82. package/dist/i18n/locales/zh-CN.js +301 -2
  83. package/dist/i18n/locales/zh-CN.js.map +2 -2
  84. package/dist/i18n/types.js.map +1 -1
  85. package/dist/services/customCommands.js +30 -8
  86. package/dist/services/customCommands.js.map +2 -2
  87. package/dist/services/plugins/lspServers.js +1 -1
  88. package/dist/services/plugins/lspServers.js.map +2 -2
  89. package/dist/services/plugins/pluginRuntime.js +2 -1
  90. package/dist/services/plugins/pluginRuntime.js.map +2 -2
  91. package/dist/services/plugins/pluginValidation.js +10 -3
  92. package/dist/services/plugins/pluginValidation.js.map +2 -2
  93. package/dist/services/plugins/skillMarketplace.js +16 -8
  94. package/dist/services/plugins/skillMarketplace.js.map +2 -2
  95. package/dist/services/systemReminder.js +17 -6
  96. package/dist/services/systemReminder.js.map +2 -2
  97. package/dist/tools/FileEditTool/FileEditTool.js +7 -0
  98. package/dist/tools/FileEditTool/FileEditTool.js.map +2 -2
  99. package/dist/tools/FileWriteTool/FileWriteTool.js +7 -0
  100. package/dist/tools/FileWriteTool/FileWriteTool.js.map +2 -2
  101. package/dist/tools/MultiEditTool/MultiEditTool.js +7 -0
  102. package/dist/tools/MultiEditTool/MultiEditTool.js.map +2 -2
  103. package/dist/tools/NotebookEditTool/NotebookEditTool.js +2 -0
  104. package/dist/tools/NotebookEditTool/NotebookEditTool.js.map +2 -2
  105. package/dist/tools/TaskTool/TaskTool.js +9 -6
  106. package/dist/tools/TaskTool/TaskTool.js.map +2 -2
  107. package/dist/types/PermissionMode.js.map +1 -1
  108. package/dist/types/plugin.js +2 -4
  109. package/dist/types/plugin.js.map +2 -2
  110. package/dist/utils/agentHookExecutor.js +1 -4
  111. package/dist/utils/agentHookExecutor.js.map +2 -2
  112. package/dist/utils/agentLoader.js +67 -13
  113. package/dist/utils/agentLoader.js.map +2 -2
  114. package/dist/utils/agentMemory.js.map +2 -2
  115. package/dist/utils/claudeCodeSync.js +439 -0
  116. package/dist/utils/claudeCodeSync.js.map +7 -0
  117. package/dist/utils/config.js +1 -23
  118. package/dist/utils/config.js.map +2 -2
  119. package/dist/utils/execFileNoThrow.js +2 -1
  120. package/dist/utils/execFileNoThrow.js.map +2 -2
  121. package/dist/utils/marketplaceManager.js +80 -43
  122. package/dist/utils/marketplaceManager.js.map +2 -2
  123. package/dist/utils/messages.js +2 -2
  124. package/dist/utils/messages.js.map +2 -2
  125. package/dist/utils/pluginInstaller.js +34 -24
  126. package/dist/utils/pluginInstaller.js.map +2 -2
  127. package/dist/utils/pluginLoader.js +48 -25
  128. package/dist/utils/pluginLoader.js.map +2 -2
  129. package/dist/utils/repoFetcher.js +110 -0
  130. package/dist/utils/repoFetcher.js.map +7 -0
  131. package/dist/utils/skillLoader.js +18 -6
  132. package/dist/utils/skillLoader.js.map +2 -2
  133. package/dist/utils/stringSubstitution.js +4 -5
  134. package/dist/utils/stringSubstitution.js.map +2 -2
  135. package/dist/utils/teamConfig.js +153 -13
  136. package/dist/utils/teamConfig.js.map +2 -2
  137. package/dist/utils/terminal.js +1 -1
  138. package/dist/utils/terminal.js.map +2 -2
  139. package/dist/version.js +2 -2
  140. package/dist/version.js.map +1 -1
  141. package/package.json +6 -6
@@ -0,0 +1,110 @@
1
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
2
+ import { join, dirname, isAbsolute } from "path";
3
+ import { tmpdir } from "os";
4
+ import { execFileNoThrow } from "./execFileNoThrow.js";
5
+ async function fetchRepo(source, targetDir) {
6
+ if (source.type === "github") {
7
+ return fetchGitHubRepo(source.repo, source.ref, targetDir);
8
+ }
9
+ const ghMatch = source.url.match(/github\.com[/:]([^/]+\/[^/.]+?)(?:\.git)?$/);
10
+ if (ghMatch) {
11
+ return fetchGitHubRepo(ghMatch[1], source.ref, targetDir);
12
+ }
13
+ await gitCloneFallback(source.url, source.ref, targetDir);
14
+ }
15
+ async function fetchGitHubRepo(repo, ref, targetDir) {
16
+ try {
17
+ await fetchGitHubTarball(repo, ref, targetDir);
18
+ } catch {
19
+ const url = `https://github.com/${repo}.git`;
20
+ await gitCloneFallback(url, ref, targetDir);
21
+ }
22
+ }
23
+ async function fetchGitHubTarball(repo, ref, targetDir) {
24
+ const url = ref ? `https://api.github.com/repos/${repo}/tarball/${ref}` : `https://api.github.com/repos/${repo}/tarball`;
25
+ await downloadAndExtractTarball(url, targetDir);
26
+ }
27
+ async function downloadAndExtractTarball(url, targetDir) {
28
+ if (!existsSync(targetDir)) {
29
+ mkdirSync(targetDir, { recursive: true });
30
+ }
31
+ const tmpFile = join(
32
+ tmpdir(),
33
+ `minto-tarball-${Date.now()}-${Math.random().toString(36).slice(2)}.tar.gz`
34
+ );
35
+ try {
36
+ const response = await fetch(url, {
37
+ redirect: "follow",
38
+ signal: AbortSignal.timeout(6e4)
39
+ });
40
+ if (!response.ok) {
41
+ throw new Error(`HTTP ${response.status} ${response.statusText}`);
42
+ }
43
+ const buffer = Buffer.from(await response.arrayBuffer());
44
+ writeFileSync(tmpFile, buffer);
45
+ const result = await execFileNoThrow("tar", [
46
+ "xzf",
47
+ tmpFile,
48
+ "-C",
49
+ targetDir,
50
+ "--strip-components=1"
51
+ ]);
52
+ if (result.code !== 0) {
53
+ throw new Error(
54
+ `tar extraction failed: ${result.stderr || result.stdout}`
55
+ );
56
+ }
57
+ } finally {
58
+ try {
59
+ if (existsSync(tmpFile)) {
60
+ rmSync(tmpFile, { force: true });
61
+ }
62
+ } catch {
63
+ }
64
+ }
65
+ }
66
+ async function gitCloneFallback(url, ref, targetDir) {
67
+ await ensureGitEnv();
68
+ if (existsSync(targetDir)) {
69
+ rmSync(targetDir, { recursive: true, force: true });
70
+ }
71
+ const args = ["clone", "--depth", "1"];
72
+ if (ref) {
73
+ args.push("--branch", ref);
74
+ }
75
+ args.push(url, targetDir);
76
+ const result = await execFileNoThrow("git", args);
77
+ if (result.code !== 0) {
78
+ throw new Error(`Git clone failed: ${result.stderr || result.stdout}`);
79
+ }
80
+ }
81
+ let gitEnvFixed = false;
82
+ async function ensureGitEnv() {
83
+ if (gitEnvFixed) return;
84
+ gitEnvFixed = true;
85
+ if (process.env.GIT_EXEC_PATH) return;
86
+ try {
87
+ const result = await execFileNoThrow("git", ["--exec-path"]);
88
+ const execPath = result.stdout.trim();
89
+ if (execPath && (execPath.startsWith("//") || !isAbsolute(execPath))) {
90
+ const whichResult = await execFileNoThrow("which", ["git"]);
91
+ const gitBin = whichResult.stdout.trim();
92
+ if (gitBin) {
93
+ const prefix = dirname(dirname(gitBin));
94
+ const fixedExecPath = join(prefix, "libexec", "git-core");
95
+ if (existsSync(fixedExecPath)) {
96
+ process.env.GIT_EXEC_PATH = fixedExecPath;
97
+ const templateDir = join(prefix, "share", "git-core", "templates");
98
+ if (existsSync(templateDir) && !process.env.GIT_TEMPLATE_DIR) {
99
+ process.env.GIT_TEMPLATE_DIR = templateDir;
100
+ }
101
+ }
102
+ }
103
+ }
104
+ } catch {
105
+ }
106
+ }
107
+ export {
108
+ fetchRepo
109
+ };
110
+ //# sourceMappingURL=repoFetcher.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/utils/repoFetcher.ts"],
4
+ "sourcesContent": ["/**\n * Repo Fetcher\n *\n * Unified repository fetching utility that replaces git clone with HTTP tarball\n * downloads for GitHub sources (zero git dependency). Falls back to git clone\n * for non-GitHub URLs.\n *\n * GitHub tarball URL pattern: https://github.com/{owner}/{repo}/archive/{ref}.tar.gz\n * macOS/Linux systems have /usr/bin/tar built-in, so no extra dependencies needed.\n */\n\nimport { existsSync, mkdirSync, rmSync, writeFileSync } from 'fs'\nimport { join, dirname, isAbsolute } from 'path'\nimport { tmpdir } from 'os'\nimport { execFileNoThrow } from './execFileNoThrow'\n\n/**\n * Source descriptor for fetchRepo\n */\nexport type RepoSource =\n | { type: 'github'; repo: string; ref?: string }\n | { type: 'url'; url: string; ref?: string }\n\n/**\n * Fetch a repository into targetDir.\n *\n * - GitHub sources use HTTP tarball download (no git required)\n * - Non-GitHub URL sources fall back to git clone\n * - On GitHub tarball failure, falls back to git clone\n */\nexport async function fetchRepo(\n source: RepoSource,\n targetDir: string,\n): Promise<void> {\n if (source.type === 'github') {\n return fetchGitHubRepo(source.repo, source.ref, targetDir)\n }\n\n // URL source: check if it's a github.com URL\n const ghMatch = source.url.match(/github\\.com[/:]([^/]+\\/[^/.]+?)(?:\\.git)?$/)\n if (ghMatch) {\n return fetchGitHubRepo(ghMatch[1], source.ref, targetDir)\n }\n\n // Non-GitHub URL: git clone only\n await gitCloneFallback(source.url, source.ref, targetDir)\n}\n\n/**\n * Fetch a GitHub repo: try tarball first, fall back to git clone\n */\nasync function fetchGitHubRepo(\n repo: string,\n ref: string | undefined,\n targetDir: string,\n): Promise<void> {\n try {\n await fetchGitHubTarball(repo, ref, targetDir)\n } catch {\n // Tarball failed \u2014 fall back to git clone\n const url = `https://github.com/${repo}.git`\n await gitCloneFallback(url, ref, targetDir)\n }\n}\n\n/**\n * Download and extract a GitHub tarball into targetDir.\n *\n * GitHub tarballs contain a single top-level directory like `{repo}-{ref}/`.\n * We use `--strip-components=1` to remove that prefix.\n */\nasync function fetchGitHubTarball(\n repo: string,\n ref: string | undefined,\n targetDir: string,\n): Promise<void> {\n // Use GitHub API tarball endpoint \u2014 it auto-resolves the default branch\n // when no ref is given (unlike /archive/ which requires an explicit ref).\n const url = ref\n ? `https://api.github.com/repos/${repo}/tarball/${ref}`\n : `https://api.github.com/repos/${repo}/tarball`\n await downloadAndExtractTarball(url, targetDir)\n}\n\n/**\n * Download a .tar.gz URL to a temp file, then extract into targetDir\n * using the system `tar` command.\n */\nasync function downloadAndExtractTarball(\n url: string,\n targetDir: string,\n): Promise<void> {\n // Ensure target directory exists\n if (!existsSync(targetDir)) {\n mkdirSync(targetDir, { recursive: true })\n }\n\n const tmpFile = join(\n tmpdir(),\n `minto-tarball-${Date.now()}-${Math.random().toString(36).slice(2)}.tar.gz`,\n )\n\n try {\n // Download with fetch (built-in in Node 18+ / Bun)\n const response = await fetch(url, {\n redirect: 'follow',\n signal: AbortSignal.timeout(60_000),\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status} ${response.statusText}`)\n }\n\n const buffer = Buffer.from(await response.arrayBuffer())\n writeFileSync(tmpFile, buffer)\n\n // Extract with system tar\n const result = await execFileNoThrow('tar', [\n 'xzf',\n tmpFile,\n '-C',\n targetDir,\n '--strip-components=1',\n ])\n\n if (result.code !== 0) {\n throw new Error(\n `tar extraction failed: ${result.stderr || result.stdout}`,\n )\n }\n } finally {\n // Clean up temp file\n try {\n if (existsSync(tmpFile)) {\n rmSync(tmpFile, { force: true })\n }\n } catch {\n // Ignore cleanup errors\n }\n }\n}\n\n/**\n * Git clone fallback for non-GitHub URLs.\n * Includes ensureGitEnv() to fix broken bundled git installations.\n */\nasync function gitCloneFallback(\n url: string,\n ref: string | undefined,\n targetDir: string,\n): Promise<void> {\n await ensureGitEnv()\n\n // git clone requires the target to not exist or be empty.\n // Remove it first (may contain partial tarball extraction artifacts).\n if (existsSync(targetDir)) {\n rmSync(targetDir, { recursive: true, force: true })\n }\n\n const args = ['clone', '--depth', '1']\n if (ref) {\n args.push('--branch', ref)\n }\n args.push(url, targetDir)\n\n const result = await execFileNoThrow('git', args)\n\n if (result.code !== 0) {\n throw new Error(`Git clone failed: ${result.stderr || result.stdout}`)\n }\n}\n\n/**\n * Ensure GIT_EXEC_PATH is correctly set in the environment.\n *\n * Some bundled git binaries (e.g., dugite inside Electron apps) have a broken\n * compiled-in exec-path that resolves to `//libexec/git-core` instead of an\n * absolute path. We detect this and derive the correct path from the git\n * binary's location.\n */\nlet gitEnvFixed = false\nasync function ensureGitEnv(): Promise<void> {\n if (gitEnvFixed) return\n gitEnvFixed = true\n\n // Skip if user already set GIT_EXEC_PATH\n if (process.env.GIT_EXEC_PATH) return\n\n try {\n const result = await execFileNoThrow('git', ['--exec-path'])\n const execPath = result.stdout.trim()\n\n // Detect broken path: starts with // or is not absolute\n if (execPath && (execPath.startsWith('//') || !isAbsolute(execPath))) {\n const whichResult = await execFileNoThrow('which', ['git'])\n const gitBin = whichResult.stdout.trim()\n\n if (gitBin) {\n const prefix = dirname(dirname(gitBin))\n const fixedExecPath = join(prefix, 'libexec', 'git-core')\n\n if (existsSync(fixedExecPath)) {\n process.env.GIT_EXEC_PATH = fixedExecPath\n\n const templateDir = join(prefix, 'share', 'git-core', 'templates')\n if (existsSync(templateDir) && !process.env.GIT_TEMPLATE_DIR) {\n process.env.GIT_TEMPLATE_DIR = templateDir\n }\n }\n }\n }\n } catch {\n // Best-effort; if detection fails, proceed without fixing\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -73,14 +73,26 @@ function scanStandaloneSkillsDirectory(dirPath, source) {
73
73
  function loadStandaloneSkills() {
74
74
  const home = homedir();
75
75
  const cwd = getCwd();
76
- const userSkillsDir = join(home, ".minto", "skills");
77
- const projectSkillsDir = join(cwd, ".minto", "skills");
78
- const userSkills = scanStandaloneSkillsDirectory(userSkillsDir, "user");
79
- const projectSkills = scanStandaloneSkillsDirectory(
80
- projectSkillsDir,
76
+ const userClaudeDir = join(home, ".claude", "skills");
77
+ const userMintoDir = join(home, ".minto", "skills");
78
+ const projectClaudeDir = join(cwd, ".claude", "skills");
79
+ const projectMintoDir = join(cwd, ".minto", "skills");
80
+ const userClaudeSkills = scanStandaloneSkillsDirectory(userClaudeDir, "user");
81
+ const userMintoSkills = scanStandaloneSkillsDirectory(userMintoDir, "user");
82
+ const projectClaudeSkills = scanStandaloneSkillsDirectory(
83
+ projectClaudeDir,
81
84
  "project"
82
85
  );
83
- return [...userSkills, ...projectSkills];
86
+ const projectMintoSkills = scanStandaloneSkillsDirectory(
87
+ projectMintoDir,
88
+ "project"
89
+ );
90
+ return [
91
+ ...userClaudeSkills,
92
+ ...userMintoSkills,
93
+ ...projectClaudeSkills,
94
+ ...projectMintoSkills
95
+ ];
84
96
  }
85
97
  function loadAllSkills() {
86
98
  if (skillsCache !== null) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/skillLoader.ts"],
4
- "sourcesContent": ["/**\n * Skill Loader\n *\n * Progressive disclosure system for loading skills from plugins and standalone directories.\n * Implements Claude Code skill specification for plugin and standalone skills.\n *\n * Directory Priority (later overrides earlier):\n * 1. Plugin skills\n * 2. ~/.minto/skills/ (user directory)\n * 3. ./.minto/skills/ (project directory - highest priority)\n *\n * Supports two file patterns:\n * - skill-name/SKILL.md (subdirectory pattern)\n * - skill-name.md (flat file pattern)\n */\n\nimport { existsSync, readFileSync, readdirSync, statSync } from 'fs'\nimport { join, basename, dirname } from 'path'\nimport { homedir } from 'os'\nimport matter from 'gray-matter'\nimport { LoadedSkill, SkillConfig } from '../types/plugin'\nimport { loadAllPlugins } from './pluginLoader'\nimport { getCwd } from './state'\n\n/**\n * In-memory cache for loaded skills\n * Maps skill name to skill object\n */\nlet skillsCache: Map<string, LoadedSkill> | null = null\n\n/**\n * Cache for fully-loaded skill content\n * Maps skill name to full markdown content\n */\nconst contentCache = new Map<string, string>()\n\n/**\n * Parse a skill file and extract configuration\n * Supports Claude Code skill specification frontmatter fields\n */\nfunction parseSkillFile(\n filePath: string,\n source: 'user' | 'project',\n): LoadedSkill | null {\n try {\n if (!existsSync(filePath)) return null\n\n const fileContent = readFileSync(filePath, 'utf-8')\n const { data: frontmatter, content } = matter(fileContent)\n\n // Determine skill name from frontmatter or filename\n const fileName = basename(filePath, '.md')\n const name =\n frontmatter.name ||\n (fileName === 'SKILL' ? basename(dirname(filePath)) : fileName)\n\n // Parse Claude Code spec fields\n const config: SkillConfig = {\n name,\n description: frontmatter.description || '',\n argumentHint: frontmatter['argument-hint'] || frontmatter.argumentHint,\n disableModelInvocation:\n frontmatter['disable-model-invocation'] ??\n frontmatter.disableModelInvocation,\n userInvocable:\n frontmatter['user-invocable'] ?? frontmatter.userInvocable ?? true,\n allowedTools: parseStringOrArray(\n frontmatter['allowed-tools'] || frontmatter.allowedTools,\n ),\n model: frontmatter.model,\n context: frontmatter.context,\n agent: frontmatter.agent,\n content: content.trim(),\n }\n\n return {\n name,\n filePath,\n config,\n pluginName: 'standalone',\n source,\n }\n } catch (error) {\n console.warn(`Failed to parse skill file ${filePath}:`, error)\n return null\n }\n}\n\n/**\n * Parse a value that could be a string, array, or comma-separated string\n */\nfunction parseStringOrArray(value: unknown): string[] | undefined {\n if (!value) return undefined\n if (Array.isArray(value)) return value.map(String)\n if (typeof value === 'string') {\n return value.split(',').map(s => s.trim())\n }\n return undefined\n}\n\n/**\n * Scan a standalone skills directory\n * Supports both patterns:\n * - skill-name/SKILL.md (subdirectory pattern)\n * - skill-name.md (flat file pattern)\n */\nfunction scanStandaloneSkillsDirectory(\n dirPath: string,\n source: 'user' | 'project',\n): LoadedSkill[] {\n const skills: LoadedSkill[] = []\n\n if (!existsSync(dirPath)) return skills\n\n try {\n const entries = readdirSync(dirPath, { withFileTypes: true })\n\n for (const entry of entries) {\n const entryPath = join(dirPath, entry.name)\n\n if (entry.isDirectory()) {\n // Pattern 1: skill-name/SKILL.md\n const skillMdPath = join(entryPath, 'SKILL.md')\n if (existsSync(skillMdPath)) {\n const skill = parseSkillFile(skillMdPath, source)\n if (skill) skills.push(skill)\n }\n } else if (entry.name.endsWith('.md') && entry.name !== 'README.md') {\n // Pattern 2: skill-name.md\n const skill = parseSkillFile(entryPath, source)\n if (skill) skills.push(skill)\n }\n }\n } catch (error) {\n console.warn(`Failed to scan skills directory ${dirPath}:`, error)\n }\n\n return skills\n}\n\n/**\n * Load all standalone skills from user and project directories\n */\nfunction loadStandaloneSkills(): LoadedSkill[] {\n const home = homedir()\n const cwd = getCwd()\n\n const userSkillsDir = join(home, '.minto', 'skills')\n const projectSkillsDir = join(cwd, '.minto', 'skills')\n\n const userSkills = scanStandaloneSkillsDirectory(userSkillsDir, 'user')\n const projectSkills = scanStandaloneSkillsDirectory(\n projectSkillsDir,\n 'project',\n )\n\n return [...userSkills, ...projectSkills]\n}\n\n/**\n * Load all skills from plugins and standalone directories\n * Uses progressive disclosure - only loads metadata (name, description)\n * Content is lazy-loaded when needed\n *\n * Priority: plugin < user < project (later overrides earlier)\n */\nexport function loadAllSkills(): LoadedSkill[] {\n // Return cached skills if available\n if (skillsCache !== null) {\n return Array.from(skillsCache.values())\n }\n\n const pluginSkills: LoadedSkill[] = []\n const plugins = loadAllPlugins()\n\n // Collect all skills from all plugins\n for (const plugin of plugins) {\n if (!plugin.enabled) continue\n\n for (const skill of plugin.skills) {\n // Ensure plugin skills have source field\n pluginSkills.push({\n ...skill,\n source: 'plugin',\n })\n }\n }\n\n // Load standalone skills\n const standaloneSkills = loadStandaloneSkills()\n\n // Build cache map for fast lookups (later entries override earlier ones)\n skillsCache = new Map()\n\n // Add in priority order: plugin < standalone (user < project)\n for (const skill of pluginSkills) {\n skillsCache.set(skill.name, skill)\n }\n for (const skill of standaloneSkills) {\n skillsCache.set(skill.name, skill)\n }\n\n return Array.from(skillsCache.values())\n}\n\n/**\n * Get a specific skill by name\n * Returns undefined if skill not found\n */\nexport function getSkill(name: string): LoadedSkill | undefined {\n // Ensure cache is populated\n if (skillsCache === null) {\n loadAllSkills()\n }\n\n return skillsCache?.get(name)\n}\n\n/**\n * Load the full content of a skill\n * Uses caching to avoid repeated file reads\n */\nexport async function loadSkillContent(skill: LoadedSkill): Promise<string> {\n // Check content cache first\n if (contentCache.has(skill.name)) {\n return contentCache.get(skill.name)!\n }\n\n // If content was already loaded in the skill object, use it\n if (skill.config.content) {\n contentCache.set(skill.name, skill.config.content)\n return skill.config.content\n }\n\n // Otherwise, read from file\n try {\n const fileContent = readFileSync(skill.filePath, 'utf-8')\n const { content } = matter(fileContent)\n const trimmedContent = content.trim()\n\n // Cache the content\n contentCache.set(skill.name, trimmedContent)\n\n // Update the skill object\n skill.config.content = trimmedContent\n\n return trimmedContent\n } catch (error) {\n throw new Error(\n `Failed to load skill content for \"${skill.name}\": ${\n error instanceof Error ? error.message : String(error)\n }`,\n )\n }\n}\n\n/**\n * Search skills by name or description\n * Returns skills matching the query (case-insensitive)\n */\nexport function searchSkills(query: string): LoadedSkill[] {\n const allSkills = loadAllSkills()\n const lowerQuery = query.toLowerCase()\n\n return allSkills.filter(\n skill =>\n skill.name.toLowerCase().includes(lowerQuery) ||\n skill.config.description.toLowerCase().includes(lowerQuery) ||\n skill.pluginName.toLowerCase().includes(lowerQuery),\n )\n}\n\n/**\n * Get skills from a specific plugin\n */\nexport function getSkillsByPlugin(pluginName: string): LoadedSkill[] {\n const allSkills = loadAllSkills()\n return allSkills.filter(skill => skill.pluginName === pluginName)\n}\n\n/**\n * Clear the skills cache (useful for hot-reloading)\n */\nexport function clearSkillsCache(): void {\n skillsCache = null\n contentCache.clear()\n}\n\n/**\n * Get count of available skills\n */\nexport function getSkillCount(): number {\n const skills = loadAllSkills()\n return skills.length\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Skill Loader\n *\n * Progressive disclosure system for loading skills from plugins and standalone directories.\n * Implements Claude Code skill specification for plugin and standalone skills.\n *\n * Directory Priority (later overrides earlier):\n * 1. Plugin skills\n * 2. ~/.minto/skills/ (user directory)\n * 3. ./.minto/skills/ (project directory - highest priority)\n *\n * Supports two file patterns:\n * - skill-name/SKILL.md (subdirectory pattern)\n * - skill-name.md (flat file pattern)\n */\n\nimport { existsSync, readFileSync, readdirSync, statSync } from 'fs'\nimport { join, basename, dirname } from 'path'\nimport { homedir } from 'os'\nimport matter from 'gray-matter'\nimport { LoadedSkill, SkillConfig } from '../types/plugin'\nimport { loadAllPlugins } from './pluginLoader'\nimport { getCwd } from './state'\n\n/**\n * In-memory cache for loaded skills\n * Maps skill name to skill object\n */\nlet skillsCache: Map<string, LoadedSkill> | null = null\n\n/**\n * Cache for fully-loaded skill content\n * Maps skill name to full markdown content\n */\nconst contentCache = new Map<string, string>()\n\n/**\n * Parse a skill file and extract configuration\n * Supports Claude Code skill specification frontmatter fields\n */\nfunction parseSkillFile(\n filePath: string,\n source: 'user' | 'project',\n): LoadedSkill | null {\n try {\n if (!existsSync(filePath)) return null\n\n const fileContent = readFileSync(filePath, 'utf-8')\n const { data: frontmatter, content } = matter(fileContent)\n\n // Determine skill name from frontmatter or filename\n const fileName = basename(filePath, '.md')\n const name =\n frontmatter.name ||\n (fileName === 'SKILL' ? basename(dirname(filePath)) : fileName)\n\n // Parse Claude Code spec fields\n const config: SkillConfig = {\n name,\n description: frontmatter.description || '',\n argumentHint: frontmatter['argument-hint'] || frontmatter.argumentHint,\n disableModelInvocation:\n frontmatter['disable-model-invocation'] ??\n frontmatter.disableModelInvocation,\n userInvocable:\n frontmatter['user-invocable'] ?? frontmatter.userInvocable ?? true,\n allowedTools: parseStringOrArray(\n frontmatter['allowed-tools'] || frontmatter.allowedTools,\n ),\n model: frontmatter.model,\n context: frontmatter.context,\n agent: frontmatter.agent,\n content: content.trim(),\n }\n\n return {\n name,\n filePath,\n config,\n pluginName: 'standalone',\n source,\n }\n } catch (error) {\n console.warn(`Failed to parse skill file ${filePath}:`, error)\n return null\n }\n}\n\n/**\n * Parse a value that could be a string, array, or comma-separated string\n */\nfunction parseStringOrArray(value: unknown): string[] | undefined {\n if (!value) return undefined\n if (Array.isArray(value)) return value.map(String)\n if (typeof value === 'string') {\n return value.split(',').map(s => s.trim())\n }\n return undefined\n}\n\n/**\n * Scan a standalone skills directory\n * Supports both patterns:\n * - skill-name/SKILL.md (subdirectory pattern)\n * - skill-name.md (flat file pattern)\n */\nfunction scanStandaloneSkillsDirectory(\n dirPath: string,\n source: 'user' | 'project',\n): LoadedSkill[] {\n const skills: LoadedSkill[] = []\n\n if (!existsSync(dirPath)) return skills\n\n try {\n const entries = readdirSync(dirPath, { withFileTypes: true })\n\n for (const entry of entries) {\n const entryPath = join(dirPath, entry.name)\n\n if (entry.isDirectory()) {\n // Pattern 1: skill-name/SKILL.md\n const skillMdPath = join(entryPath, 'SKILL.md')\n if (existsSync(skillMdPath)) {\n const skill = parseSkillFile(skillMdPath, source)\n if (skill) skills.push(skill)\n }\n } else if (entry.name.endsWith('.md') && entry.name !== 'README.md') {\n // Pattern 2: skill-name.md\n const skill = parseSkillFile(entryPath, source)\n if (skill) skills.push(skill)\n }\n }\n } catch (error) {\n console.warn(`Failed to scan skills directory ${dirPath}:`, error)\n }\n\n return skills\n}\n\n/**\n * Load all standalone skills from user and project directories\n */\nfunction loadStandaloneSkills(): LoadedSkill[] {\n const home = homedir()\n const cwd = getCwd()\n\n // .claude/ dirs are legacy fallbacks, .minto/ dirs take precedence\n const userClaudeDir = join(home, '.claude', 'skills')\n const userMintoDir = join(home, '.minto', 'skills')\n const projectClaudeDir = join(cwd, '.claude', 'skills')\n const projectMintoDir = join(cwd, '.minto', 'skills')\n\n const userClaudeSkills = scanStandaloneSkillsDirectory(userClaudeDir, 'user')\n const userMintoSkills = scanStandaloneSkillsDirectory(userMintoDir, 'user')\n const projectClaudeSkills = scanStandaloneSkillsDirectory(\n projectClaudeDir,\n 'project',\n )\n const projectMintoSkills = scanStandaloneSkillsDirectory(\n projectMintoDir,\n 'project',\n )\n\n return [\n ...userClaudeSkills,\n ...userMintoSkills,\n ...projectClaudeSkills,\n ...projectMintoSkills,\n ]\n}\n\n/**\n * Load all skills from plugins and standalone directories\n * Uses progressive disclosure - only loads metadata (name, description)\n * Content is lazy-loaded when needed\n *\n * Priority: plugin < user < project (later overrides earlier)\n */\nexport function loadAllSkills(): LoadedSkill[] {\n // Return cached skills if available\n if (skillsCache !== null) {\n return Array.from(skillsCache.values())\n }\n\n const pluginSkills: LoadedSkill[] = []\n const plugins = loadAllPlugins()\n\n // Collect all skills from all plugins\n for (const plugin of plugins) {\n if (!plugin.enabled) continue\n\n for (const skill of plugin.skills) {\n // Ensure plugin skills have source field\n pluginSkills.push({\n ...skill,\n source: 'plugin',\n })\n }\n }\n\n // Load standalone skills\n const standaloneSkills = loadStandaloneSkills()\n\n // Build cache map for fast lookups (later entries override earlier ones)\n skillsCache = new Map()\n\n // Add in priority order: plugin < standalone (user < project)\n for (const skill of pluginSkills) {\n skillsCache.set(skill.name, skill)\n }\n for (const skill of standaloneSkills) {\n skillsCache.set(skill.name, skill)\n }\n\n return Array.from(skillsCache.values())\n}\n\n/**\n * Get a specific skill by name\n * Returns undefined if skill not found\n */\nexport function getSkill(name: string): LoadedSkill | undefined {\n // Ensure cache is populated\n if (skillsCache === null) {\n loadAllSkills()\n }\n\n return skillsCache?.get(name)\n}\n\n/**\n * Load the full content of a skill\n * Uses caching to avoid repeated file reads\n */\nexport async function loadSkillContent(skill: LoadedSkill): Promise<string> {\n // Check content cache first\n if (contentCache.has(skill.name)) {\n return contentCache.get(skill.name)!\n }\n\n // If content was already loaded in the skill object, use it\n if (skill.config.content) {\n contentCache.set(skill.name, skill.config.content)\n return skill.config.content\n }\n\n // Otherwise, read from file\n try {\n const fileContent = readFileSync(skill.filePath, 'utf-8')\n const { content } = matter(fileContent)\n const trimmedContent = content.trim()\n\n // Cache the content\n contentCache.set(skill.name, trimmedContent)\n\n // Update the skill object\n skill.config.content = trimmedContent\n\n return trimmedContent\n } catch (error) {\n throw new Error(\n `Failed to load skill content for \"${skill.name}\": ${\n error instanceof Error ? error.message : String(error)\n }`,\n )\n }\n}\n\n/**\n * Search skills by name or description\n * Returns skills matching the query (case-insensitive)\n */\nexport function searchSkills(query: string): LoadedSkill[] {\n const allSkills = loadAllSkills()\n const lowerQuery = query.toLowerCase()\n\n return allSkills.filter(\n skill =>\n skill.name.toLowerCase().includes(lowerQuery) ||\n skill.config.description.toLowerCase().includes(lowerQuery) ||\n skill.pluginName.toLowerCase().includes(lowerQuery),\n )\n}\n\n/**\n * Get skills from a specific plugin\n */\nexport function getSkillsByPlugin(pluginName: string): LoadedSkill[] {\n const allSkills = loadAllSkills()\n return allSkills.filter(skill => skill.pluginName === pluginName)\n}\n\n/**\n * Clear the skills cache (useful for hot-reloading)\n */\nexport function clearSkillsCache(): void {\n skillsCache = null\n contentCache.clear()\n}\n\n/**\n * Get count of available skills\n */\nexport function getSkillCount(): number {\n const skills = loadAllSkills()\n return skills.length\n}\n"],
5
+ "mappings": "AAgBA,SAAS,YAAY,cAAc,mBAA6B;AAChE,SAAS,MAAM,UAAU,eAAe;AACxC,SAAS,eAAe;AACxB,OAAO,YAAY;AAEnB,SAAS,sBAAsB;AAC/B,SAAS,cAAc;AAMvB,IAAI,cAA+C;AAMnD,MAAM,eAAe,oBAAI,IAAoB;AAM7C,SAAS,eACP,UACA,QACoB;AACpB,MAAI;AACF,QAAI,CAAC,WAAW,QAAQ,EAAG,QAAO;AAElC,UAAM,cAAc,aAAa,UAAU,OAAO;AAClD,UAAM,EAAE,MAAM,aAAa,QAAQ,IAAI,OAAO,WAAW;AAGzD,UAAM,WAAW,SAAS,UAAU,KAAK;AACzC,UAAM,OACJ,YAAY,SACX,aAAa,UAAU,SAAS,QAAQ,QAAQ,CAAC,IAAI;AAGxD,UAAM,SAAsB;AAAA,MAC1B;AAAA,MACA,aAAa,YAAY,eAAe;AAAA,MACxC,cAAc,YAAY,eAAe,KAAK,YAAY;AAAA,MAC1D,wBACE,YAAY,0BAA0B,KACtC,YAAY;AAAA,MACd,eACE,YAAY,gBAAgB,KAAK,YAAY,iBAAiB;AAAA,MAChE,cAAc;AAAA,QACZ,YAAY,eAAe,KAAK,YAAY;AAAA,MAC9C;AAAA,MACA,OAAO,YAAY;AAAA,MACnB,SAAS,YAAY;AAAA,MACrB,OAAO,YAAY;AAAA,MACnB,SAAS,QAAQ,KAAK;AAAA,IACxB;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAY;AAAA,MACZ;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,KAAK,8BAA8B,QAAQ,KAAK,KAAK;AAC7D,WAAO;AAAA,EACT;AACF;AAKA,SAAS,mBAAmB,OAAsC;AAChE,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,MAAM;AACjD,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,MAAM,MAAM,GAAG,EAAE,IAAI,OAAK,EAAE,KAAK,CAAC;AAAA,EAC3C;AACA,SAAO;AACT;AAQA,SAAS,8BACP,SACA,QACe;AACf,QAAM,SAAwB,CAAC;AAE/B,MAAI,CAAC,WAAW,OAAO,EAAG,QAAO;AAEjC,MAAI;AACF,UAAM,UAAU,YAAY,SAAS,EAAE,eAAe,KAAK,CAAC;AAE5D,eAAW,SAAS,SAAS;AAC3B,YAAM,YAAY,KAAK,SAAS,MAAM,IAAI;AAE1C,UAAI,MAAM,YAAY,GAAG;AAEvB,cAAM,cAAc,KAAK,WAAW,UAAU;AAC9C,YAAI,WAAW,WAAW,GAAG;AAC3B,gBAAM,QAAQ,eAAe,aAAa,MAAM;AAChD,cAAI,MAAO,QAAO,KAAK,KAAK;AAAA,QAC9B;AAAA,MACF,WAAW,MAAM,KAAK,SAAS,KAAK,KAAK,MAAM,SAAS,aAAa;AAEnE,cAAM,QAAQ,eAAe,WAAW,MAAM;AAC9C,YAAI,MAAO,QAAO,KAAK,KAAK;AAAA,MAC9B;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,KAAK,mCAAmC,OAAO,KAAK,KAAK;AAAA,EACnE;AAEA,SAAO;AACT;AAKA,SAAS,uBAAsC;AAC7C,QAAM,OAAO,QAAQ;AACrB,QAAM,MAAM,OAAO;AAGnB,QAAM,gBAAgB,KAAK,MAAM,WAAW,QAAQ;AACpD,QAAM,eAAe,KAAK,MAAM,UAAU,QAAQ;AAClD,QAAM,mBAAmB,KAAK,KAAK,WAAW,QAAQ;AACtD,QAAM,kBAAkB,KAAK,KAAK,UAAU,QAAQ;AAEpD,QAAM,mBAAmB,8BAA8B,eAAe,MAAM;AAC5E,QAAM,kBAAkB,8BAA8B,cAAc,MAAM;AAC1E,QAAM,sBAAsB;AAAA,IAC1B;AAAA,IACA;AAAA,EACF;AACA,QAAM,qBAAqB;AAAA,IACzB;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AACF;AASO,SAAS,gBAA+B;AAE7C,MAAI,gBAAgB,MAAM;AACxB,WAAO,MAAM,KAAK,YAAY,OAAO,CAAC;AAAA,EACxC;AAEA,QAAM,eAA8B,CAAC;AACrC,QAAM,UAAU,eAAe;AAG/B,aAAW,UAAU,SAAS;AAC5B,QAAI,CAAC,OAAO,QAAS;AAErB,eAAW,SAAS,OAAO,QAAQ;AAEjC,mBAAa,KAAK;AAAA,QAChB,GAAG;AAAA,QACH,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAAA,EACF;AAGA,QAAM,mBAAmB,qBAAqB;AAG9C,gBAAc,oBAAI,IAAI;AAGtB,aAAW,SAAS,cAAc;AAChC,gBAAY,IAAI,MAAM,MAAM,KAAK;AAAA,EACnC;AACA,aAAW,SAAS,kBAAkB;AACpC,gBAAY,IAAI,MAAM,MAAM,KAAK;AAAA,EACnC;AAEA,SAAO,MAAM,KAAK,YAAY,OAAO,CAAC;AACxC;AAMO,SAAS,SAAS,MAAuC;AAE9D,MAAI,gBAAgB,MAAM;AACxB,kBAAc;AAAA,EAChB;AAEA,SAAO,aAAa,IAAI,IAAI;AAC9B;AAMA,eAAsB,iBAAiB,OAAqC;AAE1E,MAAI,aAAa,IAAI,MAAM,IAAI,GAAG;AAChC,WAAO,aAAa,IAAI,MAAM,IAAI;AAAA,EACpC;AAGA,MAAI,MAAM,OAAO,SAAS;AACxB,iBAAa,IAAI,MAAM,MAAM,MAAM,OAAO,OAAO;AACjD,WAAO,MAAM,OAAO;AAAA,EACtB;AAGA,MAAI;AACF,UAAM,cAAc,aAAa,MAAM,UAAU,OAAO;AACxD,UAAM,EAAE,QAAQ,IAAI,OAAO,WAAW;AACtC,UAAM,iBAAiB,QAAQ,KAAK;AAGpC,iBAAa,IAAI,MAAM,MAAM,cAAc;AAG3C,UAAM,OAAO,UAAU;AAEvB,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,qCAAqC,MAAM,IAAI,MAC7C,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CACvD;AAAA,IACF;AAAA,EACF;AACF;AAMO,SAAS,aAAa,OAA8B;AACzD,QAAM,YAAY,cAAc;AAChC,QAAM,aAAa,MAAM,YAAY;AAErC,SAAO,UAAU;AAAA,IACf,WACE,MAAM,KAAK,YAAY,EAAE,SAAS,UAAU,KAC5C,MAAM,OAAO,YAAY,YAAY,EAAE,SAAS,UAAU,KAC1D,MAAM,WAAW,YAAY,EAAE,SAAS,UAAU;AAAA,EACtD;AACF;AAKO,SAAS,kBAAkB,YAAmC;AACnE,QAAM,YAAY,cAAc;AAChC,SAAO,UAAU,OAAO,WAAS,MAAM,eAAe,UAAU;AAClE;AAKO,SAAS,mBAAyB;AACvC,gBAAc;AACd,eAAa,MAAM;AACrB;AAKO,SAAS,gBAAwB;AACtC,QAAM,SAAS,cAAc;AAC7B,SAAO,OAAO;AAChB;",
6
6
  "names": []
7
7
  }
@@ -50,10 +50,7 @@ async function substituteVariables(content, args, context = {}) {
50
50
  }
51
51
  }
52
52
  } else {
53
- result = result.replace(
54
- /!\`([^`]+)\`/g,
55
- "(dynamic commands disabled)"
56
- );
53
+ result = result.replace(/!\`([^`]+)\`/g, "(dynamic commands disabled)");
57
54
  }
58
55
  return result;
59
56
  }
@@ -86,7 +83,9 @@ function extractSubstitutionPatterns(content) {
86
83
  for (const match of cmdMatches) {
87
84
  patterns.commands.push(match[1]);
88
85
  }
89
- patterns.indexedArgs = [...new Set(patterns.indexedArgs)].sort((a, b) => a - b);
86
+ patterns.indexedArgs = [...new Set(patterns.indexedArgs)].sort(
87
+ (a, b) => a - b
88
+ );
90
89
  patterns.variables = [...new Set(patterns.variables)];
91
90
  return patterns;
92
91
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/stringSubstitution.ts"],
4
- "sourcesContent": ["/**\n * String Substitution Engine\n *\n * Implements Claude Code skill specification for variable substitution.\n * Supports the following patterns:\n *\n * - $ARGUMENTS - All arguments as a single string\n * - $ARGUMENTS[N] - Indexed argument (0-based)\n * - $N - Shorthand for $ARGUMENTS[N]\n * - ${VARIABLE_NAME} - Environment variable or context variable\n * - !`command` - Dynamic command execution (with security controls)\n */\n\nimport { exec } from 'child_process'\nimport { promisify } from 'util'\n\nconst execAsync = promisify(exec)\n\n/**\n * Context for string substitution\n */\nexport interface SubstitutionContext {\n sessionId?: string\n cwd?: string\n env?: Record<string, string>\n /**\n * Allow dynamic command execution (!`command`)\n * Default: false (security consideration)\n */\n allowDynamicCommands?: boolean\n /**\n * Timeout for dynamic command execution in milliseconds\n * Default: 10000 (10 seconds)\n */\n commandTimeout?: number\n}\n\n/**\n * Default substitution context\n */\nconst DEFAULT_CONTEXT: Required<SubstitutionContext> = {\n sessionId: '',\n cwd: process.cwd(),\n env: {},\n allowDynamicCommands: false,\n commandTimeout: 10000,\n}\n\n/**\n * Substitute variables in a string\n *\n * @param content - The content to substitute variables in\n * @param args - Arguments string (space-separated)\n * @param context - Substitution context\n * @returns The content with variables substituted\n */\nexport async function substituteVariables(\n content: string,\n args: string,\n context: SubstitutionContext = {},\n): Promise<string> {\n const ctx = { ...DEFAULT_CONTEXT, ...context }\n let result = content\n const argValues = args.trim() ? args.trim().split(/\\s+/) : []\n\n // 1. $ARGUMENTS - All arguments as a single string\n // Use negative lookahead to avoid matching $ARGUMENTS[N]\n result = result.replace(/\\$ARGUMENTS(?!\\[)/g, args)\n\n // 2. $ARGUMENTS[N] - Indexed argument (0-based)\n result = result.replace(/\\$ARGUMENTS\\[(\\d+)\\]/g, (_, indexStr) => {\n const index = parseInt(indexStr, 10)\n return argValues[index] ?? ''\n })\n\n // 3. $N - Shorthand for indexed argument (0-based)\n // Use negative lookahead to avoid matching multi-digit or $ARGUMENTS\n result = result.replace(/\\$(\\d+)(?!\\d)/g, (_, indexStr) => {\n const index = parseInt(indexStr, 10)\n return argValues[index] ?? ''\n })\n\n // 4. ${VARIABLE_NAME} - Context and environment variables\n result = result.replace(/\\$\\{([A-Z_][A-Z0-9_]*)\\}/gi, (_, varName) => {\n // Built-in context variables (Claude Code spec)\n switch (varName.toUpperCase()) {\n case 'CLAUDE_SESSION_ID':\n case 'SESSION_ID':\n return ctx.sessionId || ''\n case 'CWD':\n case 'PWD':\n return ctx.cwd\n default:\n // Check custom context env first, then process.env\n return ctx.env[varName] ?? process.env[varName] ?? ''\n }\n })\n\n // 5. !`command` - Dynamic command execution\n // Only execute if explicitly allowed (security consideration)\n if (ctx.allowDynamicCommands) {\n const commandPattern = /!\\`([^`]+)\\`/g\n const matches = [...result.matchAll(commandPattern)]\n\n for (const match of matches) {\n const command = match[1]\n try {\n const { stdout } = await execAsync(command, {\n timeout: ctx.commandTimeout,\n cwd: ctx.cwd,\n })\n result = result.replace(match[0], stdout.trim())\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error)\n result = result.replace(match[0], `(error: ${errorMessage})`)\n }\n }\n } else {\n // Replace !`command` with placeholder when not allowed\n result = result.replace(\n /!\\`([^`]+)\\`/g,\n '(dynamic commands disabled)',\n )\n }\n\n return result\n}\n\n/**\n * Check if a string contains substitution patterns\n * Useful for optimization - skip substitution if no patterns present\n */\nexport function hasSubstitutionPatterns(content: string): boolean {\n return /\\$(?:ARGUMENTS|\\d+|\\{[A-Z_][A-Z0-9_]*\\})|!\\`/.test(content)\n}\n\n/**\n * Extract all substitution patterns from a string\n * Useful for validation and documentation\n */\nexport function extractSubstitutionPatterns(\n content: string,\n): {\n arguments: string[]\n indexedArgs: number[]\n variables: string[]\n commands: string[]\n} {\n const patterns = {\n arguments: [] as string[],\n indexedArgs: [] as number[],\n variables: [] as string[],\n commands: [] as string[],\n }\n\n // $ARGUMENTS\n if (/\\$ARGUMENTS(?!\\[)/.test(content)) {\n patterns.arguments.push('$ARGUMENTS')\n }\n\n // $ARGUMENTS[N]\n const argIndexMatches = content.matchAll(/\\$ARGUMENTS\\[(\\d+)\\]/g)\n for (const match of argIndexMatches) {\n patterns.indexedArgs.push(parseInt(match[1], 10))\n }\n\n // $N\n const shortIndexMatches = content.matchAll(/\\$(\\d+)(?!\\d)/g)\n for (const match of shortIndexMatches) {\n patterns.indexedArgs.push(parseInt(match[1], 10))\n }\n\n // ${VARIABLE}\n const varMatches = content.matchAll(/\\$\\{([A-Z_][A-Z0-9_]*)\\}/gi)\n for (const match of varMatches) {\n patterns.variables.push(match[1])\n }\n\n // !`command`\n const cmdMatches = content.matchAll(/!\\`([^`]+)\\`/g)\n for (const match of cmdMatches) {\n patterns.commands.push(match[1])\n }\n\n // Deduplicate indexed args\n patterns.indexedArgs = [...new Set(patterns.indexedArgs)].sort((a, b) => a - b)\n patterns.variables = [...new Set(patterns.variables)]\n\n return patterns\n}\n\n/**\n * Validate arguments against expected patterns\n * Returns error message if validation fails, null if valid\n */\nexport function validateArguments(\n args: string,\n patterns: ReturnType<typeof extractSubstitutionPatterns>,\n): string | null {\n const argValues = args.trim() ? args.trim().split(/\\s+/) : []\n const maxIndex = Math.max(...patterns.indexedArgs, -1)\n\n if (maxIndex >= 0 && argValues.length <= maxIndex) {\n return `Expected at least ${maxIndex + 1} argument(s), got ${argValues.length}`\n }\n\n return null\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * String Substitution Engine\n *\n * Implements Claude Code skill specification for variable substitution.\n * Supports the following patterns:\n *\n * - $ARGUMENTS - All arguments as a single string\n * - $ARGUMENTS[N] - Indexed argument (0-based)\n * - $N - Shorthand for $ARGUMENTS[N]\n * - ${VARIABLE_NAME} - Environment variable or context variable\n * - !`command` - Dynamic command execution (with security controls)\n */\n\nimport { exec } from 'child_process'\nimport { promisify } from 'util'\n\nconst execAsync = promisify(exec)\n\n/**\n * Context for string substitution\n */\nexport interface SubstitutionContext {\n sessionId?: string\n cwd?: string\n env?: Record<string, string>\n /**\n * Allow dynamic command execution (!`command`)\n * Default: false (security consideration)\n */\n allowDynamicCommands?: boolean\n /**\n * Timeout for dynamic command execution in milliseconds\n * Default: 10000 (10 seconds)\n */\n commandTimeout?: number\n}\n\n/**\n * Default substitution context\n */\nconst DEFAULT_CONTEXT: Required<SubstitutionContext> = {\n sessionId: '',\n cwd: process.cwd(),\n env: {},\n allowDynamicCommands: false,\n commandTimeout: 10000,\n}\n\n/**\n * Substitute variables in a string\n *\n * @param content - The content to substitute variables in\n * @param args - Arguments string (space-separated)\n * @param context - Substitution context\n * @returns The content with variables substituted\n */\nexport async function substituteVariables(\n content: string,\n args: string,\n context: SubstitutionContext = {},\n): Promise<string> {\n const ctx = { ...DEFAULT_CONTEXT, ...context }\n let result = content\n const argValues = args.trim() ? args.trim().split(/\\s+/) : []\n\n // 1. $ARGUMENTS - All arguments as a single string\n // Use negative lookahead to avoid matching $ARGUMENTS[N]\n result = result.replace(/\\$ARGUMENTS(?!\\[)/g, args)\n\n // 2. $ARGUMENTS[N] - Indexed argument (0-based)\n result = result.replace(/\\$ARGUMENTS\\[(\\d+)\\]/g, (_, indexStr) => {\n const index = parseInt(indexStr, 10)\n return argValues[index] ?? ''\n })\n\n // 3. $N - Shorthand for indexed argument (0-based)\n // Use negative lookahead to avoid matching multi-digit or $ARGUMENTS\n result = result.replace(/\\$(\\d+)(?!\\d)/g, (_, indexStr) => {\n const index = parseInt(indexStr, 10)\n return argValues[index] ?? ''\n })\n\n // 4. ${VARIABLE_NAME} - Context and environment variables\n result = result.replace(/\\$\\{([A-Z_][A-Z0-9_]*)\\}/gi, (_, varName) => {\n // Built-in context variables (Claude Code spec)\n switch (varName.toUpperCase()) {\n case 'CLAUDE_SESSION_ID':\n case 'SESSION_ID':\n return ctx.sessionId || ''\n case 'CWD':\n case 'PWD':\n return ctx.cwd\n default:\n // Check custom context env first, then process.env\n return ctx.env[varName] ?? process.env[varName] ?? ''\n }\n })\n\n // 5. !`command` - Dynamic command execution\n // Only execute if explicitly allowed (security consideration)\n if (ctx.allowDynamicCommands) {\n const commandPattern = /!\\`([^`]+)\\`/g\n const matches = [...result.matchAll(commandPattern)]\n\n for (const match of matches) {\n const command = match[1]\n try {\n const { stdout } = await execAsync(command, {\n timeout: ctx.commandTimeout,\n cwd: ctx.cwd,\n })\n result = result.replace(match[0], stdout.trim())\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error)\n result = result.replace(match[0], `(error: ${errorMessage})`)\n }\n }\n } else {\n // Replace !`command` with placeholder when not allowed\n result = result.replace(/!\\`([^`]+)\\`/g, '(dynamic commands disabled)')\n }\n\n return result\n}\n\n/**\n * Check if a string contains substitution patterns\n * Useful for optimization - skip substitution if no patterns present\n */\nexport function hasSubstitutionPatterns(content: string): boolean {\n return /\\$(?:ARGUMENTS|\\d+|\\{[A-Z_][A-Z0-9_]*\\})|!\\`/.test(content)\n}\n\n/**\n * Extract all substitution patterns from a string\n * Useful for validation and documentation\n */\nexport function extractSubstitutionPatterns(content: string): {\n arguments: string[]\n indexedArgs: number[]\n variables: string[]\n commands: string[]\n} {\n const patterns = {\n arguments: [] as string[],\n indexedArgs: [] as number[],\n variables: [] as string[],\n commands: [] as string[],\n }\n\n // $ARGUMENTS\n if (/\\$ARGUMENTS(?!\\[)/.test(content)) {\n patterns.arguments.push('$ARGUMENTS')\n }\n\n // $ARGUMENTS[N]\n const argIndexMatches = content.matchAll(/\\$ARGUMENTS\\[(\\d+)\\]/g)\n for (const match of argIndexMatches) {\n patterns.indexedArgs.push(parseInt(match[1], 10))\n }\n\n // $N\n const shortIndexMatches = content.matchAll(/\\$(\\d+)(?!\\d)/g)\n for (const match of shortIndexMatches) {\n patterns.indexedArgs.push(parseInt(match[1], 10))\n }\n\n // ${VARIABLE}\n const varMatches = content.matchAll(/\\$\\{([A-Z_][A-Z0-9_]*)\\}/gi)\n for (const match of varMatches) {\n patterns.variables.push(match[1])\n }\n\n // !`command`\n const cmdMatches = content.matchAll(/!\\`([^`]+)\\`/g)\n for (const match of cmdMatches) {\n patterns.commands.push(match[1])\n }\n\n // Deduplicate indexed args\n patterns.indexedArgs = [...new Set(patterns.indexedArgs)].sort(\n (a, b) => a - b,\n )\n patterns.variables = [...new Set(patterns.variables)]\n\n return patterns\n}\n\n/**\n * Validate arguments against expected patterns\n * Returns error message if validation fails, null if valid\n */\nexport function validateArguments(\n args: string,\n patterns: ReturnType<typeof extractSubstitutionPatterns>,\n): string | null {\n const argValues = args.trim() ? args.trim().split(/\\s+/) : []\n const maxIndex = Math.max(...patterns.indexedArgs, -1)\n\n if (maxIndex >= 0 && argValues.length <= maxIndex) {\n return `Expected at least ${maxIndex + 1} argument(s), got ${argValues.length}`\n }\n\n return null\n}\n"],
5
+ "mappings": "AAaA,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAE1B,MAAM,YAAY,UAAU,IAAI;AAwBhC,MAAM,kBAAiD;AAAA,EACrD,WAAW;AAAA,EACX,KAAK,QAAQ,IAAI;AAAA,EACjB,KAAK,CAAC;AAAA,EACN,sBAAsB;AAAA,EACtB,gBAAgB;AAClB;AAUA,eAAsB,oBACpB,SACA,MACA,UAA+B,CAAC,GACf;AACjB,QAAM,MAAM,EAAE,GAAG,iBAAiB,GAAG,QAAQ;AAC7C,MAAI,SAAS;AACb,QAAM,YAAY,KAAK,KAAK,IAAI,KAAK,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;AAI5D,WAAS,OAAO,QAAQ,sBAAsB,IAAI;AAGlD,WAAS,OAAO,QAAQ,yBAAyB,CAAC,GAAG,aAAa;AAChE,UAAM,QAAQ,SAAS,UAAU,EAAE;AACnC,WAAO,UAAU,KAAK,KAAK;AAAA,EAC7B,CAAC;AAID,WAAS,OAAO,QAAQ,kBAAkB,CAAC,GAAG,aAAa;AACzD,UAAM,QAAQ,SAAS,UAAU,EAAE;AACnC,WAAO,UAAU,KAAK,KAAK;AAAA,EAC7B,CAAC;AAGD,WAAS,OAAO,QAAQ,8BAA8B,CAAC,GAAG,YAAY;AAEpE,YAAQ,QAAQ,YAAY,GAAG;AAAA,MAC7B,KAAK;AAAA,MACL,KAAK;AACH,eAAO,IAAI,aAAa;AAAA,MAC1B,KAAK;AAAA,MACL,KAAK;AACH,eAAO,IAAI;AAAA,MACb;AAEE,eAAO,IAAI,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK;AAAA,IACvD;AAAA,EACF,CAAC;AAID,MAAI,IAAI,sBAAsB;AAC5B,UAAM,iBAAiB;AACvB,UAAM,UAAU,CAAC,GAAG,OAAO,SAAS,cAAc,CAAC;AAEnD,eAAW,SAAS,SAAS;AAC3B,YAAM,UAAU,MAAM,CAAC;AACvB,UAAI;AACF,cAAM,EAAE,OAAO,IAAI,MAAM,UAAU,SAAS;AAAA,UAC1C,SAAS,IAAI;AAAA,UACb,KAAK,IAAI;AAAA,QACX,CAAC;AACD,iBAAS,OAAO,QAAQ,MAAM,CAAC,GAAG,OAAO,KAAK,CAAC;AAAA,MACjD,SAAS,OAAO;AACd,cAAM,eACJ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACvD,iBAAS,OAAO,QAAQ,MAAM,CAAC,GAAG,WAAW,YAAY,GAAG;AAAA,MAC9D;AAAA,IACF;AAAA,EACF,OAAO;AAEL,aAAS,OAAO,QAAQ,iBAAiB,6BAA6B;AAAA,EACxE;AAEA,SAAO;AACT;AAMO,SAAS,wBAAwB,SAA0B;AAChE,SAAO,+CAA+C,KAAK,OAAO;AACpE;AAMO,SAAS,4BAA4B,SAK1C;AACA,QAAM,WAAW;AAAA,IACf,WAAW,CAAC;AAAA,IACZ,aAAa,CAAC;AAAA,IACd,WAAW,CAAC;AAAA,IACZ,UAAU,CAAC;AAAA,EACb;AAGA,MAAI,oBAAoB,KAAK,OAAO,GAAG;AACrC,aAAS,UAAU,KAAK,YAAY;AAAA,EACtC;AAGA,QAAM,kBAAkB,QAAQ,SAAS,uBAAuB;AAChE,aAAW,SAAS,iBAAiB;AACnC,aAAS,YAAY,KAAK,SAAS,MAAM,CAAC,GAAG,EAAE,CAAC;AAAA,EAClD;AAGA,QAAM,oBAAoB,QAAQ,SAAS,gBAAgB;AAC3D,aAAW,SAAS,mBAAmB;AACrC,aAAS,YAAY,KAAK,SAAS,MAAM,CAAC,GAAG,EAAE,CAAC;AAAA,EAClD;AAGA,QAAM,aAAa,QAAQ,SAAS,4BAA4B;AAChE,aAAW,SAAS,YAAY;AAC9B,aAAS,UAAU,KAAK,MAAM,CAAC,CAAC;AAAA,EAClC;AAGA,QAAM,aAAa,QAAQ,SAAS,eAAe;AACnD,aAAW,SAAS,YAAY;AAC9B,aAAS,SAAS,KAAK,MAAM,CAAC,CAAC;AAAA,EACjC;AAGA,WAAS,cAAc,CAAC,GAAG,IAAI,IAAI,SAAS,WAAW,CAAC,EAAE;AAAA,IACxD,CAAC,GAAG,MAAM,IAAI;AAAA,EAChB;AACA,WAAS,YAAY,CAAC,GAAG,IAAI,IAAI,SAAS,SAAS,CAAC;AAEpD,SAAO;AACT;AAMO,SAAS,kBACd,MACA,UACe;AACf,QAAM,YAAY,KAAK,KAAK,IAAI,KAAK,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;AAC5D,QAAM,WAAW,KAAK,IAAI,GAAG,SAAS,aAAa,EAAE;AAErD,MAAI,YAAY,KAAK,UAAU,UAAU,UAAU;AACjD,WAAO,qBAAqB,WAAW,CAAC,qBAAqB,UAAU,MAAM;AAAA,EAC/E;AAEA,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -288,16 +288,19 @@ async function installPlugins(pluginNames, targetDir) {
288
288
  const { join } = await import("path");
289
289
  const { homedir } = await import("os");
290
290
  const installDir = targetDir || join(homedir(), ".minto", "plugins");
291
- for (const pluginName of pluginNames) {
291
+ for (const pluginSpec of pluginNames) {
292
+ const atIndex = pluginSpec.indexOf("@");
293
+ const pluginName = atIndex !== -1 ? pluginSpec.slice(0, atIndex) : pluginSpec;
294
+ const marketplaceName = atIndex !== -1 ? pluginSpec.slice(atIndex + 1) : void 0;
292
295
  try {
293
296
  debugLogger.state("TEAM_CONFIG_INSTALL_PLUGIN", {
294
297
  pluginName,
298
+ marketplace: marketplaceName,
295
299
  targetDir: installDir
296
300
  });
297
301
  await installPluginFromMarketplace(
298
302
  pluginName,
299
- void 0,
300
- // Search all marketplaces
303
+ marketplaceName,
301
304
  join(installDir, pluginName)
302
305
  );
303
306
  installed++;
@@ -314,14 +317,14 @@ async function installPlugins(pluginNames, targetDir) {
314
317
  }
315
318
  return { installed, failed, errors };
316
319
  }
317
- async function installRemoteAgents(agentUrls) {
320
+ async function installRemoteAgents(agentUrls, targetDir) {
318
321
  let installed = 0;
319
322
  let failed = 0;
320
323
  const errors = [];
321
324
  const { join } = await import("path");
322
325
  const { homedir } = await import("os");
323
326
  const { writeFileSync, mkdirSync, existsSync: existsSync2 } = await import("fs");
324
- const agentsDir = join(homedir(), ".minto", "agents");
327
+ const agentsDir = targetDir || join(homedir(), ".minto", "agents");
325
328
  if (!existsSync2(agentsDir)) {
326
329
  mkdirSync(agentsDir, { recursive: true });
327
330
  }
@@ -353,14 +356,14 @@ async function installRemoteAgents(agentUrls) {
353
356
  }
354
357
  return { installed, failed, errors };
355
358
  }
356
- async function installInlineAgents(agents) {
359
+ async function installInlineAgents(agents, targetDir) {
357
360
  let installed = 0;
358
361
  let failed = 0;
359
362
  const errors = [];
360
363
  const { join } = await import("path");
361
364
  const { homedir } = await import("os");
362
365
  const { writeFileSync, mkdirSync, existsSync: existsSync2 } = await import("fs");
363
- const agentsDir = join(homedir(), ".minto", "agents");
366
+ const agentsDir = targetDir || join(homedir(), ".minto", "agents");
364
367
  if (!existsSync2(agentsDir)) {
365
368
  mkdirSync(agentsDir, { recursive: true });
366
369
  }
@@ -400,17 +403,17 @@ ${agent.systemPrompt}
400
403
  }
401
404
  return { installed, failed, errors };
402
405
  }
403
- async function installTeamHooks(hooks) {
404
- const { join } = await import("path");
406
+ async function installTeamHooks(hooks, targetPath) {
407
+ const { join, dirname } = await import("path");
405
408
  const { homedir } = await import("os");
406
409
  const { writeFileSync, mkdirSync, existsSync: existsSync2, readFileSync: readFileSync2 } = await import("fs");
407
410
  try {
408
411
  debugLogger.state("TEAM_CONFIG_INSTALL_HOOKS", {});
409
- const mintoDir = join(homedir(), ".minto");
410
- if (!existsSync2(mintoDir)) {
411
- mkdirSync(mintoDir, { recursive: true });
412
+ const hooksPath = targetPath || join(homedir(), ".minto", "hooks.json");
413
+ const hooksDir = dirname(hooksPath);
414
+ if (!existsSync2(hooksDir)) {
415
+ mkdirSync(hooksDir, { recursive: true });
412
416
  }
413
- const hooksPath = join(mintoDir, "hooks.json");
414
417
  const hooksConfig = {
415
418
  description: "Team-configured hooks",
416
419
  hooks: {}
@@ -461,6 +464,141 @@ async function installTeamHooks(hooks) {
461
464
  return { installed: false, error: errorMsg };
462
465
  }
463
466
  }
467
+ async function installInlineSkills(skills, targetDir) {
468
+ let installed = 0;
469
+ let failed = 0;
470
+ const errors = [];
471
+ const { join } = await import("path");
472
+ const { homedir } = await import("os");
473
+ const { writeFileSync, mkdirSync, existsSync: existsSync2 } = await import("fs");
474
+ const skillsDir = targetDir || join(homedir(), ".minto", "skills");
475
+ if (!existsSync2(skillsDir)) {
476
+ mkdirSync(skillsDir, { recursive: true });
477
+ }
478
+ for (const skill of skills) {
479
+ try {
480
+ debugLogger.state("TEAM_CONFIG_INSTALL_SKILL", { name: skill.name });
481
+ const frontmatterLines = [
482
+ `name: ${skill.name}`,
483
+ `description: "${skill.description.replace(/"/g, '\\"')}"`
484
+ ];
485
+ if (skill.argumentHint) {
486
+ frontmatterLines.push(`argument-hint: "${skill.argumentHint}"`);
487
+ }
488
+ if (skill.disableModelInvocation !== void 0) {
489
+ frontmatterLines.push(
490
+ `disable-model-invocation: ${skill.disableModelInvocation}`
491
+ );
492
+ }
493
+ if (skill.userInvocable !== void 0) {
494
+ frontmatterLines.push(`user-invocable: ${skill.userInvocable}`);
495
+ }
496
+ if (skill.allowedTools && skill.allowedTools.length > 0) {
497
+ frontmatterLines.push(
498
+ `allowed-tools: ${JSON.stringify(skill.allowedTools)}`
499
+ );
500
+ }
501
+ if (skill.model) {
502
+ frontmatterLines.push(`model: ${skill.model}`);
503
+ }
504
+ if (skill.context) {
505
+ frontmatterLines.push(`context: "${skill.context}"`);
506
+ }
507
+ if (skill.agent) {
508
+ frontmatterLines.push(`agent: "${skill.agent}"`);
509
+ }
510
+ const content = `---
511
+ ${frontmatterLines.join("\n")}
512
+ ---
513
+
514
+ ${skill.content}
515
+ `;
516
+ const filename = `${skill.name}.md`;
517
+ const targetPath = join(skillsDir, filename);
518
+ writeFileSync(targetPath, content, "utf-8");
519
+ installed++;
520
+ debugLogger.state("TEAM_CONFIG_INSTALL_SKILL_SUCCESS", {
521
+ name: skill.name,
522
+ targetPath
523
+ });
524
+ } catch (error) {
525
+ failed++;
526
+ const errorMsg = error instanceof Error ? error.message : String(error);
527
+ errors.push(`${skill.name}: ${errorMsg}`);
528
+ debugLogger.error("TEAM_CONFIG_INSTALL_SKILL_ERROR", {
529
+ name: skill.name,
530
+ error: errorMsg
531
+ });
532
+ }
533
+ }
534
+ return { installed, failed, errors };
535
+ }
536
+ async function installInlineCommands(commands, targetDir) {
537
+ let installed = 0;
538
+ let failed = 0;
539
+ const errors = [];
540
+ const { join } = await import("path");
541
+ const { homedir } = await import("os");
542
+ const { writeFileSync, mkdirSync, existsSync: existsSync2 } = await import("fs");
543
+ const commandsDir = targetDir || join(homedir(), ".minto", "commands");
544
+ if (!existsSync2(commandsDir)) {
545
+ mkdirSync(commandsDir, { recursive: true });
546
+ }
547
+ for (const cmd of commands) {
548
+ try {
549
+ debugLogger.state("TEAM_CONFIG_INSTALL_COMMAND", { name: cmd.name });
550
+ const frontmatterLines = [`name: ${cmd.name}`];
551
+ if (cmd.description) {
552
+ frontmatterLines.push(
553
+ `description: "${cmd.description.replace(/"/g, '\\"')}"`
554
+ );
555
+ }
556
+ if (cmd.aliases && cmd.aliases.length > 0) {
557
+ frontmatterLines.push(`aliases: ${JSON.stringify(cmd.aliases)}`);
558
+ }
559
+ if (cmd.enabled !== void 0) {
560
+ frontmatterLines.push(`enabled: ${cmd.enabled}`);
561
+ }
562
+ if (cmd.hidden !== void 0) {
563
+ frontmatterLines.push(`hidden: ${cmd.hidden}`);
564
+ }
565
+ if (cmd.progressMessage) {
566
+ frontmatterLines.push(`progressMessage: "${cmd.progressMessage}"`);
567
+ }
568
+ if (cmd.argNames && cmd.argNames.length > 0) {
569
+ frontmatterLines.push(`argNames: ${JSON.stringify(cmd.argNames)}`);
570
+ }
571
+ if (cmd.allowedTools && cmd.allowedTools.length > 0) {
572
+ frontmatterLines.push(
573
+ `allowed-tools: ${JSON.stringify(cmd.allowedTools)}`
574
+ );
575
+ }
576
+ const content = `---
577
+ ${frontmatterLines.join("\n")}
578
+ ---
579
+
580
+ ${cmd.content}
581
+ `;
582
+ const filename = `${cmd.name}.md`;
583
+ const targetPath = join(commandsDir, filename);
584
+ writeFileSync(targetPath, content, "utf-8");
585
+ installed++;
586
+ debugLogger.state("TEAM_CONFIG_INSTALL_COMMAND_SUCCESS", {
587
+ name: cmd.name,
588
+ targetPath
589
+ });
590
+ } catch (error) {
591
+ failed++;
592
+ const errorMsg = error instanceof Error ? error.message : String(error);
593
+ errors.push(`${cmd.name}: ${errorMsg}`);
594
+ debugLogger.error("TEAM_CONFIG_INSTALL_COMMAND_ERROR", {
595
+ name: cmd.name,
596
+ error: errorMsg
597
+ });
598
+ }
599
+ }
600
+ return { installed, failed, errors };
601
+ }
464
602
  export {
465
603
  addMarketplaces,
466
604
  applyTeamConfig,
@@ -468,6 +606,8 @@ export {
468
606
  convertTeamModelProfile,
469
607
  fetchTeamConfig,
470
608
  installInlineAgents,
609
+ installInlineCommands,
610
+ installInlineSkills,
471
611
  installPlugins,
472
612
  installRemoteAgents,
473
613
  installTeamHooks,