@within-7/minto 0.3.5 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/{cli.js → cli.cjs} +25 -23
  2. package/dist/commands/language.js +137 -0
  3. package/dist/commands/language.js.map +7 -0
  4. package/dist/commands/new.js +56 -0
  5. package/dist/commands/new.js.map +7 -0
  6. package/dist/commands/resume.js +251 -16
  7. package/dist/commands/resume.js.map +2 -2
  8. package/dist/commands/sessions.js +224 -0
  9. package/dist/commands/sessions.js.map +7 -0
  10. package/dist/commands/setup.js +3 -2
  11. package/dist/commands/setup.js.map +2 -2
  12. package/dist/commands/stats.js +235 -0
  13. package/dist/commands/stats.js.map +7 -0
  14. package/dist/commands/status.js +11 -5
  15. package/dist/commands/status.js.map +2 -2
  16. package/dist/commands/undo.js +26 -16
  17. package/dist/commands/undo.js.map +2 -2
  18. package/dist/commands.js +6 -0
  19. package/dist/commands.js.map +2 -2
  20. package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js +3 -2
  21. package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js.map +2 -2
  22. package/dist/components/Config.js +9 -8
  23. package/dist/components/Config.js.map +2 -2
  24. package/dist/components/HeaderBar.js +2 -1
  25. package/dist/components/HeaderBar.js.map +2 -2
  26. package/dist/components/Help.js +2 -1
  27. package/dist/components/Help.js.map +2 -2
  28. package/dist/components/HotkeyHelpPanel.js +46 -44
  29. package/dist/components/HotkeyHelpPanel.js.map +2 -2
  30. package/dist/components/Logo.js +5 -2
  31. package/dist/components/Logo.js.map +2 -2
  32. package/dist/components/MCPServerApprovalDialog.js +6 -5
  33. package/dist/components/MCPServerApprovalDialog.js.map +2 -2
  34. package/dist/components/MCPServerMultiselectDialog.js +5 -4
  35. package/dist/components/MCPServerMultiselectDialog.js.map +2 -2
  36. package/dist/components/MessageSelector.js +4 -3
  37. package/dist/components/MessageSelector.js.map +2 -2
  38. package/dist/components/ModelConfig.js +13 -12
  39. package/dist/components/ModelConfig.js.map +2 -2
  40. package/dist/components/ModelListManager.js +4 -3
  41. package/dist/components/ModelListManager.js.map +2 -2
  42. package/dist/components/PromptInput.js +72 -39
  43. package/dist/components/PromptInput.js.map +2 -2
  44. package/dist/components/SensitiveFileWarning.js +12 -8
  45. package/dist/components/SensitiveFileWarning.js.map +2 -2
  46. package/dist/components/TabbedListView/ScrollableList.js +91 -0
  47. package/dist/components/TabbedListView/ScrollableList.js.map +7 -0
  48. package/dist/components/TabbedListView/SearchInput.js +23 -0
  49. package/dist/components/TabbedListView/SearchInput.js.map +7 -0
  50. package/dist/components/TabbedListView/TabBar.js +20 -0
  51. package/dist/components/TabbedListView/TabBar.js.map +7 -0
  52. package/dist/components/TabbedListView/TabbedListView.js +171 -0
  53. package/dist/components/TabbedListView/TabbedListView.js.map +7 -0
  54. package/dist/components/TabbedListView/index.js +11 -0
  55. package/dist/components/TabbedListView/index.js.map +7 -0
  56. package/dist/components/TabbedListView/types.js +1 -0
  57. package/dist/components/TabbedListView/types.js.map +7 -0
  58. package/dist/components/TodoChangeBlock.js +6 -5
  59. package/dist/components/TodoChangeBlock.js.map +3 -3
  60. package/dist/components/TodoPanel.js +6 -3
  61. package/dist/components/TodoPanel.js.map +3 -3
  62. package/dist/components/TrustDialog.js +6 -5
  63. package/dist/components/TrustDialog.js.map +2 -2
  64. package/dist/components/messages/UserToolResultMessage/UserToolCanceledMessage.js +2 -1
  65. package/dist/components/messages/UserToolResultMessage/UserToolCanceledMessage.js.map +2 -2
  66. package/dist/constants/macros.js +1 -1
  67. package/dist/constants/macros.js.map +1 -1
  68. package/dist/constants/product.js +2 -2
  69. package/dist/constants/product.js.map +1 -1
  70. package/dist/constants/prompts.js +17 -0
  71. package/dist/constants/prompts.js.map +2 -2
  72. package/dist/constants/toolInputExamples.js +5 -1
  73. package/dist/constants/toolInputExamples.js.map +2 -2
  74. package/dist/core/tokenStatsManager.js +5 -0
  75. package/dist/core/tokenStatsManager.js.map +2 -2
  76. package/dist/entrypoints/bootstrap.js +54 -0
  77. package/dist/entrypoints/bootstrap.js.map +7 -0
  78. package/dist/entrypoints/cli.js +132 -23
  79. package/dist/entrypoints/cli.js.map +3 -3
  80. package/dist/history.js +75 -15
  81. package/dist/history.js.map +2 -2
  82. package/dist/i18n/index.js +2 -2
  83. package/dist/i18n/index.js.map +2 -2
  84. package/dist/i18n/locales/en.js +283 -1
  85. package/dist/i18n/locales/en.js.map +2 -2
  86. package/dist/i18n/locales/zh-CN.js +283 -1
  87. package/dist/i18n/locales/zh-CN.js.map +2 -2
  88. package/dist/i18n/types.js.map +1 -1
  89. package/dist/index.js +1 -1
  90. package/dist/index.js.map +2 -2
  91. package/dist/messages.js +11 -0
  92. package/dist/messages.js.map +2 -2
  93. package/dist/permissions.js.map +2 -2
  94. package/dist/query.js +9 -0
  95. package/dist/query.js.map +2 -2
  96. package/dist/screens/REPL.js +45 -7
  97. package/dist/screens/REPL.js.map +2 -2
  98. package/dist/services/customCommands.js +14 -8
  99. package/dist/services/customCommands.js.map +2 -2
  100. package/dist/tools/TaskTool/TaskTool.js +176 -1
  101. package/dist/tools/TaskTool/TaskTool.js.map +2 -2
  102. package/dist/tools/TodoWriteTool/prompt.js +21 -0
  103. package/dist/tools/TodoWriteTool/prompt.js.map +2 -2
  104. package/dist/tools/URLFetcherTool/prompt.js +14 -9
  105. package/dist/tools/URLFetcherTool/prompt.js.map +2 -2
  106. package/dist/tools/WebSearchTool/prompt.js +12 -6
  107. package/dist/tools/WebSearchTool/prompt.js.map +2 -2
  108. package/dist/types/PermissionMode.js +30 -1
  109. package/dist/types/PermissionMode.js.map +2 -2
  110. package/dist/types/plugin.js.map +2 -2
  111. package/dist/utils/agentHookExecutor.js +106 -0
  112. package/dist/utils/agentHookExecutor.js.map +7 -0
  113. package/dist/utils/agentLoader.js +212 -26
  114. package/dist/utils/agentLoader.js.map +2 -2
  115. package/dist/utils/agentMemory.js +134 -0
  116. package/dist/utils/agentMemory.js.map +7 -0
  117. package/dist/utils/config.js +51 -1
  118. package/dist/utils/config.js.map +2 -2
  119. package/dist/utils/configPaths.js +199 -0
  120. package/dist/utils/configPaths.js.map +7 -0
  121. package/dist/utils/historyManager.js +234 -0
  122. package/dist/utils/historyManager.js.map +7 -0
  123. package/dist/utils/messages.js +13 -8
  124. package/dist/utils/messages.js.map +2 -2
  125. package/dist/utils/migration/index.js +37 -0
  126. package/dist/utils/migration/index.js.map +7 -0
  127. package/dist/utils/migration/migrateHistory.js +273 -0
  128. package/dist/utils/migration/migrateHistory.js.map +7 -0
  129. package/dist/utils/migration/migrateTodos.js +323 -0
  130. package/dist/utils/migration/migrateTodos.js.map +7 -0
  131. package/dist/utils/pasteCache.js +309 -0
  132. package/dist/utils/pasteCache.js.map +7 -0
  133. package/dist/utils/pluginLoader.js +6 -3
  134. package/dist/utils/pluginLoader.js.map +2 -2
  135. package/dist/utils/sessionIndex.js +192 -0
  136. package/dist/utils/sessionIndex.js.map +7 -0
  137. package/dist/utils/sessionTracker.js +170 -0
  138. package/dist/utils/sessionTracker.js.map +7 -0
  139. package/dist/utils/skillLoader.js +91 -5
  140. package/dist/utils/skillLoader.js.map +2 -2
  141. package/dist/utils/stats.js +417 -0
  142. package/dist/utils/stats.js.map +7 -0
  143. package/dist/utils/stringSubstitution.js +107 -0
  144. package/dist/utils/stringSubstitution.js.map +7 -0
  145. package/dist/utils/teamConfig.js +3 -1
  146. package/dist/utils/teamConfig.js.map +2 -2
  147. package/dist/utils/todoStorage.js +51 -19
  148. package/dist/utils/todoStorage.js.map +2 -2
  149. package/dist/utils/tooling/safeRender.js.map +2 -2
  150. package/dist/version.js +2 -2
  151. package/dist/version.js.map +1 -1
  152. package/package.json +71 -28
  153. package/scripts/{postinstall.js → postinstall.cjs} +1 -1
@@ -0,0 +1,170 @@
1
+ import { randomUUID } from "crypto";
2
+ import { getCwd } from "./state.js";
3
+ import { getGlobalConfig } from "./config.js";
4
+ import { ModelManager } from "./model.js";
5
+ import {
6
+ createSessionEntry,
7
+ updateSessionEntry,
8
+ getSessionEntry
9
+ } from "./sessionIndex.js";
10
+ import {
11
+ recordSessionStart as statsRecordSessionStart,
12
+ recordSessionEnd as statsRecordSessionEnd
13
+ } from "./stats.js";
14
+ let trackerState = null;
15
+ function initSessionTracker(projectPath, model) {
16
+ const sessionId = randomUUID();
17
+ const resolvedProjectPath = projectPath || getCwd();
18
+ let resolvedModel = model;
19
+ if (!resolvedModel) {
20
+ try {
21
+ const config = getGlobalConfig();
22
+ const modelManager = new ModelManager(config);
23
+ resolvedModel = modelManager.getModelName("main") || "unknown";
24
+ } catch {
25
+ resolvedModel = "unknown";
26
+ }
27
+ }
28
+ trackerState = {
29
+ sessionId,
30
+ entry: null,
31
+ startTime: Date.now(),
32
+ hasRecordedFirstPrompt: false
33
+ };
34
+ trackerState._projectPath = resolvedProjectPath;
35
+ trackerState._model = resolvedModel;
36
+ try {
37
+ statsRecordSessionStart();
38
+ } catch {
39
+ }
40
+ return sessionId;
41
+ }
42
+ function recordFirstPrompt(prompt) {
43
+ if (!trackerState) {
44
+ return;
45
+ }
46
+ if (trackerState.hasRecordedFirstPrompt) {
47
+ return;
48
+ }
49
+ const projectPath = trackerState._projectPath;
50
+ const model = trackerState._model;
51
+ trackerState.entry = createSessionEntry(
52
+ trackerState.sessionId,
53
+ projectPath,
54
+ prompt,
55
+ model
56
+ );
57
+ trackerState.entry.messageCount = 1;
58
+ trackerState.hasRecordedFirstPrompt = true;
59
+ updateSessionEntry(trackerState.entry);
60
+ }
61
+ function getSessionId() {
62
+ return trackerState?.sessionId;
63
+ }
64
+ function getCurrentSessionEntry() {
65
+ return trackerState?.entry ?? void 0;
66
+ }
67
+ function isSessionTrackerActive() {
68
+ return trackerState !== null;
69
+ }
70
+ function incrementMessageCount(count = 1) {
71
+ if (!trackerState?.entry) {
72
+ return;
73
+ }
74
+ trackerState.entry.messageCount += count;
75
+ }
76
+ function incrementToolCallCount(count = 1) {
77
+ if (!trackerState?.entry) {
78
+ return;
79
+ }
80
+ trackerState.entry.toolCallCount += count;
81
+ }
82
+ function setSessionSummary(summary) {
83
+ if (!trackerState?.entry) {
84
+ return;
85
+ }
86
+ trackerState.entry.summary = summary;
87
+ updateSessionEntry(trackerState.entry);
88
+ }
89
+ function flushSessionStats() {
90
+ if (!trackerState?.entry) {
91
+ return;
92
+ }
93
+ trackerState.entry.durationMs = Date.now() - trackerState.startTime;
94
+ trackerState.entry.modified = (/* @__PURE__ */ new Date()).toISOString();
95
+ updateSessionEntry(trackerState.entry);
96
+ }
97
+ function endSession(reason = "other") {
98
+ if (!trackerState) {
99
+ return;
100
+ }
101
+ const sessionDuration = Date.now() - trackerState.startTime;
102
+ if (trackerState.entry) {
103
+ trackerState.entry.durationMs = sessionDuration;
104
+ trackerState.entry.modified = (/* @__PURE__ */ new Date()).toISOString();
105
+ updateSessionEntry(trackerState.entry);
106
+ }
107
+ try {
108
+ statsRecordSessionEnd(sessionDuration);
109
+ } catch {
110
+ }
111
+ trackerState = null;
112
+ }
113
+ function resumeSession(sessionId) {
114
+ const entry = getSessionEntry(sessionId);
115
+ if (!entry) {
116
+ return false;
117
+ }
118
+ trackerState = {
119
+ sessionId,
120
+ entry,
121
+ startTime: Date.now(),
122
+ // New activity period starts now
123
+ hasRecordedFirstPrompt: true
124
+ // Already has first prompt from previous session
125
+ };
126
+ return true;
127
+ }
128
+ function syncCountsFromMessages(messages) {
129
+ if (!trackerState?.entry) {
130
+ return;
131
+ }
132
+ let messageCount = 0;
133
+ let toolCallCount = 0;
134
+ for (const msg of messages) {
135
+ if (msg.type === "user" || msg.type === "assistant") {
136
+ messageCount++;
137
+ }
138
+ }
139
+ trackerState.entry.messageCount = messageCount;
140
+ }
141
+ function getSessionDuration() {
142
+ if (!trackerState) {
143
+ return 0;
144
+ }
145
+ return Date.now() - trackerState.startTime;
146
+ }
147
+ function updateSessionModel(model) {
148
+ if (!trackerState?.entry) {
149
+ return;
150
+ }
151
+ trackerState.entry.model = model;
152
+ updateSessionEntry(trackerState.entry);
153
+ }
154
+ export {
155
+ endSession,
156
+ flushSessionStats,
157
+ getCurrentSessionEntry,
158
+ getSessionDuration,
159
+ getSessionId,
160
+ incrementMessageCount,
161
+ incrementToolCallCount,
162
+ initSessionTracker,
163
+ isSessionTrackerActive,
164
+ recordFirstPrompt,
165
+ resumeSession,
166
+ setSessionSummary,
167
+ syncCountsFromMessages,
168
+ updateSessionModel
169
+ };
170
+ //# sourceMappingURL=sessionTracker.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/utils/sessionTracker.ts"],
4
+ "sourcesContent": ["/**\n * Session Tracker\n *\n * Tracks session lifecycle and updates session index.\n * Integrates with REPL to automatically capture session metadata.\n *\n * Usage:\n * 1. Initialize at session start: initSessionTracker(projectPath, model)\n * 2. Record first prompt: recordFirstPrompt(prompt)\n * 3. Update stats on message add: incrementMessageCount() / incrementToolCallCount()\n * 4. End session: endSession()\n */\n\nimport { randomUUID } from 'crypto'\nimport { getCwd } from './state'\nimport { getGlobalConfig } from './config'\nimport { ModelManager } from './model'\nimport {\n createSessionEntry,\n updateSessionEntry,\n getSessionEntry,\n type SessionEntry,\n} from './sessionIndex'\nimport {\n recordSessionStart as statsRecordSessionStart,\n recordSessionEnd as statsRecordSessionEnd,\n} from './stats'\n\n/**\n * Session tracker state\n */\ninterface SessionTrackerState {\n /** Current session ID */\n sessionId: string\n /** Session entry object */\n entry: SessionEntry | null\n /** Session start timestamp */\n startTime: number\n /** Whether first prompt has been recorded */\n hasRecordedFirstPrompt: boolean\n}\n\n/** Global session tracker state */\nlet trackerState: SessionTrackerState | null = null\n\n/**\n * Initialize the session tracker\n * Should be called when REPL starts\n *\n * @param projectPath - Project directory path (defaults to cwd)\n * @param model - Primary model name (auto-detected if not provided)\n * @returns Session ID\n */\nexport function initSessionTracker(\n projectPath?: string,\n model?: string,\n): string {\n const sessionId = randomUUID()\n const resolvedProjectPath = projectPath || getCwd()\n\n // Auto-detect model if not provided\n let resolvedModel = model\n if (!resolvedModel) {\n try {\n const config = getGlobalConfig()\n const modelManager = new ModelManager(config)\n resolvedModel = modelManager.getModelName('main') || 'unknown'\n } catch {\n resolvedModel = 'unknown'\n }\n }\n\n // Initialize tracker state (don't create entry yet - wait for first prompt)\n trackerState = {\n sessionId,\n entry: null,\n startTime: Date.now(),\n hasRecordedFirstPrompt: false,\n }\n\n // Store project path and model for later use\n ;(trackerState as any)._projectPath = resolvedProjectPath\n ;(trackerState as any)._model = resolvedModel\n\n // Also record to persistent stats manager (fire-and-forget)\n try {\n statsRecordSessionStart()\n } catch {\n // Stats recording is non-critical\n }\n\n return sessionId\n}\n\n/**\n * Record the first prompt and create the session entry\n * Should be called when user submits their first message\n *\n * @param prompt - First user prompt\n */\nexport function recordFirstPrompt(prompt: string): void {\n if (!trackerState) {\n return\n }\n\n if (trackerState.hasRecordedFirstPrompt) {\n return // Already recorded\n }\n\n // Create the session entry now that we have the first prompt\n const projectPath = (trackerState as any)._projectPath as string\n const model = (trackerState as any)._model as string\n\n trackerState.entry = createSessionEntry(\n trackerState.sessionId,\n projectPath,\n prompt,\n model,\n )\n trackerState.entry.messageCount = 1 // First message\n trackerState.hasRecordedFirstPrompt = true\n\n // Save immediately\n updateSessionEntry(trackerState.entry)\n}\n\n/**\n * Get the current session ID\n *\n * @returns Current session ID or undefined if not initialized\n */\nexport function getSessionId(): string | undefined {\n return trackerState?.sessionId\n}\n\n/**\n * Get the current session entry\n *\n * @returns Current session entry or undefined if not initialized\n */\nexport function getCurrentSessionEntry(): SessionEntry | undefined {\n return trackerState?.entry ?? undefined\n}\n\n/**\n * Check if session tracker is active\n *\n * @returns true if tracker is initialized\n */\nexport function isSessionTrackerActive(): boolean {\n return trackerState !== null\n}\n\n/**\n * Increment the message count\n * Should be called each time a message is added to the conversation\n *\n * @param count - Number to increment by (default: 1)\n */\nexport function incrementMessageCount(count: number = 1): void {\n if (!trackerState?.entry) {\n return\n }\n\n trackerState.entry.messageCount += count\n // Don't save on every increment to avoid too many writes\n}\n\n/**\n * Increment the tool call count\n * Should be called each time a tool is invoked\n *\n * @param count - Number to increment by (default: 1)\n */\nexport function incrementToolCallCount(count: number = 1): void {\n if (!trackerState?.entry) {\n return\n }\n\n trackerState.entry.toolCallCount += count\n // Don't save on every increment to avoid too many writes\n}\n\n/**\n * Set the session summary\n *\n * @param summary - AI-generated session summary\n */\nexport function setSessionSummary(summary: string): void {\n if (!trackerState?.entry) {\n return\n }\n\n trackerState.entry.summary = summary\n updateSessionEntry(trackerState.entry)\n}\n\n/**\n * Update session statistics\n * Call periodically or after significant events\n */\nexport function flushSessionStats(): void {\n if (!trackerState?.entry) {\n return\n }\n\n // Update duration\n trackerState.entry.durationMs = Date.now() - trackerState.startTime\n trackerState.entry.modified = new Date().toISOString()\n\n // Save to index\n updateSessionEntry(trackerState.entry)\n}\n\n/**\n * End the session and save final stats\n * Should be called when REPL exits or clears conversation\n *\n * @param reason - Reason for ending the session\n */\nexport function endSession(\n reason: 'clear' | 'logout' | 'exit' | 'other' = 'other',\n): void {\n if (!trackerState) {\n return\n }\n\n const sessionDuration = Date.now() - trackerState.startTime\n\n // Only save if we have an entry (i.e., user sent at least one message)\n if (trackerState.entry) {\n // Update final stats\n trackerState.entry.durationMs = sessionDuration\n trackerState.entry.modified = new Date().toISOString()\n\n // Save to index\n updateSessionEntry(trackerState.entry)\n }\n\n // Also record to persistent stats manager (fire-and-forget)\n try {\n statsRecordSessionEnd(sessionDuration)\n } catch {\n // Stats recording is non-critical\n }\n\n // Clear state\n trackerState = null\n}\n\n/**\n * Resume an existing session\n * Used when REPL resumes a previous conversation\n *\n * @param sessionId - Session ID to resume\n * @returns true if session was found and resumed\n */\nexport function resumeSession(sessionId: string): boolean {\n const entry = getSessionEntry(sessionId)\n\n if (!entry) {\n return false\n }\n\n trackerState = {\n sessionId,\n entry,\n startTime: Date.now(), // New activity period starts now\n hasRecordedFirstPrompt: true, // Already has first prompt from previous session\n }\n\n return true\n}\n\n/**\n * Update message and tool counts from actual message array\n * Useful for syncing after resuming or when counts might be out of sync\n *\n * @param messages - Array of messages to count\n */\nexport function syncCountsFromMessages(\n messages: Array<{ type: string }>,\n): void {\n if (!trackerState?.entry) {\n return\n }\n\n let messageCount = 0\n let toolCallCount = 0\n\n for (const msg of messages) {\n if (msg.type === 'user' || msg.type === 'assistant') {\n messageCount++\n }\n // Note: Tool calls are tracked separately as they occur\n // This is a rough sync - for accurate tool counts, use incrementToolCallCount\n }\n\n trackerState.entry.messageCount = messageCount\n}\n\n/**\n * Get session duration so far\n *\n * @returns Duration in milliseconds, or 0 if not tracking\n */\nexport function getSessionDuration(): number {\n if (!trackerState) {\n return 0\n }\n\n return Date.now() - trackerState.startTime\n}\n\n/**\n * Update the primary model used in the session\n *\n * @param model - New model name\n */\nexport function updateSessionModel(model: string): void {\n if (!trackerState?.entry) {\n return\n }\n\n trackerState.entry.model = model\n updateSessionEntry(trackerState.entry)\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,25 +1,111 @@
1
- import { readFileSync } from "fs";
1
+ import { existsSync, readFileSync, readdirSync } from "fs";
2
+ import { join, basename, dirname } from "path";
3
+ import { homedir } from "os";
2
4
  import matter from "gray-matter";
3
5
  import { loadAllPlugins } from "./pluginLoader.js";
6
+ import { getCwd } from "./state.js";
4
7
  let skillsCache = null;
5
8
  const contentCache = /* @__PURE__ */ new Map();
9
+ function parseSkillFile(filePath, source) {
10
+ try {
11
+ if (!existsSync(filePath)) return null;
12
+ const fileContent = readFileSync(filePath, "utf-8");
13
+ const { data: frontmatter, content } = matter(fileContent);
14
+ const fileName = basename(filePath, ".md");
15
+ const name = frontmatter.name || (fileName === "SKILL" ? basename(dirname(filePath)) : fileName);
16
+ const config = {
17
+ name,
18
+ description: frontmatter.description || "",
19
+ argumentHint: frontmatter["argument-hint"] || frontmatter.argumentHint,
20
+ disableModelInvocation: frontmatter["disable-model-invocation"] ?? frontmatter.disableModelInvocation,
21
+ userInvocable: frontmatter["user-invocable"] ?? frontmatter.userInvocable ?? true,
22
+ allowedTools: parseStringOrArray(
23
+ frontmatter["allowed-tools"] || frontmatter.allowedTools
24
+ ),
25
+ model: frontmatter.model,
26
+ context: frontmatter.context,
27
+ agent: frontmatter.agent,
28
+ content: content.trim()
29
+ };
30
+ return {
31
+ name,
32
+ filePath,
33
+ config,
34
+ pluginName: "standalone",
35
+ source
36
+ };
37
+ } catch (error) {
38
+ console.warn(`Failed to parse skill file ${filePath}:`, error);
39
+ return null;
40
+ }
41
+ }
42
+ function parseStringOrArray(value) {
43
+ if (!value) return void 0;
44
+ if (Array.isArray(value)) return value.map(String);
45
+ if (typeof value === "string") {
46
+ return value.split(",").map((s) => s.trim());
47
+ }
48
+ return void 0;
49
+ }
50
+ function scanStandaloneSkillsDirectory(dirPath, source) {
51
+ const skills = [];
52
+ if (!existsSync(dirPath)) return skills;
53
+ try {
54
+ const entries = readdirSync(dirPath, { withFileTypes: true });
55
+ for (const entry of entries) {
56
+ const entryPath = join(dirPath, entry.name);
57
+ if (entry.isDirectory()) {
58
+ const skillMdPath = join(entryPath, "SKILL.md");
59
+ if (existsSync(skillMdPath)) {
60
+ const skill = parseSkillFile(skillMdPath, source);
61
+ if (skill) skills.push(skill);
62
+ }
63
+ } else if (entry.name.endsWith(".md") && entry.name !== "README.md") {
64
+ const skill = parseSkillFile(entryPath, source);
65
+ if (skill) skills.push(skill);
66
+ }
67
+ }
68
+ } catch (error) {
69
+ console.warn(`Failed to scan skills directory ${dirPath}:`, error);
70
+ }
71
+ return skills;
72
+ }
73
+ function loadStandaloneSkills() {
74
+ const home = homedir();
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,
81
+ "project"
82
+ );
83
+ return [...userSkills, ...projectSkills];
84
+ }
6
85
  function loadAllSkills() {
7
86
  if (skillsCache !== null) {
8
87
  return Array.from(skillsCache.values());
9
88
  }
10
- const skills = [];
89
+ const pluginSkills = [];
11
90
  const plugins = loadAllPlugins();
12
91
  for (const plugin of plugins) {
13
92
  if (!plugin.enabled) continue;
14
93
  for (const skill of plugin.skills) {
15
- skills.push(skill);
94
+ pluginSkills.push({
95
+ ...skill,
96
+ source: "plugin"
97
+ });
16
98
  }
17
99
  }
100
+ const standaloneSkills = loadStandaloneSkills();
18
101
  skillsCache = /* @__PURE__ */ new Map();
19
- for (const skill of skills) {
102
+ for (const skill of pluginSkills) {
20
103
  skillsCache.set(skill.name, skill);
21
104
  }
22
- return skills;
105
+ for (const skill of standaloneSkills) {
106
+ skillsCache.set(skill.name, skill);
107
+ }
108
+ return Array.from(skillsCache.values());
23
109
  }
24
110
  function getSkill(name) {
25
111
  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.\n * Loads metadata first (name, description) for performance,\n * then lazy-loads full content when needed.\n */\n\nimport { readFileSync } from 'fs'\nimport matter from 'gray-matter'\nimport { LoadedSkill } from '../types/plugin'\nimport { loadAllPlugins } from './pluginLoader'\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 * Load all skills from all installed plugins\n * Uses progressive disclosure - only loads metadata (name, description)\n * Content is lazy-loaded when needed\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 skills: 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 skills.push(skill)\n }\n }\n\n // Build cache map for fast lookups\n skillsCache = new Map()\n for (const skill of skills) {\n skillsCache.set(skill.name, skill)\n }\n\n return skills\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": "AAQA,SAAS,oBAAoB;AAC7B,OAAO,YAAY;AAEnB,SAAS,sBAAsB;AAM/B,IAAI,cAA+C;AAMnD,MAAM,eAAe,oBAAI,IAAoB;AAOtC,SAAS,gBAA+B;AAE7C,MAAI,gBAAgB,MAAM;AACxB,WAAO,MAAM,KAAK,YAAY,OAAO,CAAC;AAAA,EACxC;AAEA,QAAM,SAAwB,CAAC;AAC/B,QAAM,UAAU,eAAe;AAG/B,aAAW,UAAU,SAAS;AAC5B,QAAI,CAAC,OAAO,QAAS;AAErB,eAAW,SAAS,OAAO,QAAQ;AACjC,aAAO,KAAK,KAAK;AAAA,IACnB;AAAA,EACF;AAGA,gBAAc,oBAAI,IAAI;AACtB,aAAW,SAAS,QAAQ;AAC1B,gBAAY,IAAI,MAAM,MAAM,KAAK;AAAA,EACnC;AAEA,SAAO;AACT;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;",
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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",
6
6
  "names": []
7
7
  }