@llmtune/cli 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/README.md +1 -1
  2. package/dist/agent/conversation.d.ts +42 -0
  3. package/dist/agent/conversation.js +105 -0
  4. package/dist/agent/loop.d.ts +19 -0
  5. package/dist/agent/loop.js +185 -0
  6. package/dist/agent/planner.d.ts +8 -0
  7. package/dist/agent/planner.js +43 -0
  8. package/dist/auth/client.d.ts +4 -0
  9. package/dist/auth/client.js +24 -0
  10. package/dist/auth/config.d.ts +21 -0
  11. package/dist/auth/config.js +83 -0
  12. package/dist/commands/chat.d.ts +5 -0
  13. package/dist/commands/chat.js +27 -0
  14. package/dist/commands/config.d.ts +2 -0
  15. package/dist/commands/config.js +37 -0
  16. package/dist/commands/login.d.ts +2 -0
  17. package/dist/commands/login.js +93 -0
  18. package/dist/commands/marketplace.d.ts +6 -0
  19. package/dist/commands/marketplace.js +213 -0
  20. package/dist/commands/models.d.ts +2 -0
  21. package/dist/commands/models.js +53 -0
  22. package/dist/compact/history-store.d.ts +29 -0
  23. package/dist/compact/history-store.js +110 -0
  24. package/dist/compact/microcompact.d.ts +10 -0
  25. package/dist/compact/microcompact.js +43 -0
  26. package/dist/compact/service.d.ts +13 -0
  27. package/dist/compact/service.js +156 -0
  28. package/dist/context/analyzer.d.ts +26 -0
  29. package/dist/context/analyzer.js +99 -0
  30. package/dist/context/builder.d.ts +13 -0
  31. package/dist/context/builder.js +144 -0
  32. package/dist/context/cache.d.ts +6 -0
  33. package/dist/context/cache.js +8 -0
  34. package/dist/context/git-context.d.ts +9 -0
  35. package/dist/context/git-context.js +49 -0
  36. package/dist/context/llmtune-md.d.ts +6 -0
  37. package/dist/context/llmtune-md.js +73 -0
  38. package/dist/context/workspace.d.ts +11 -0
  39. package/dist/context/workspace.js +115 -0
  40. package/dist/index.d.ts +3 -0
  41. package/dist/index.js +1 -1
  42. package/dist/marketplace/client.d.ts +52 -0
  43. package/dist/marketplace/client.js +86 -0
  44. package/dist/memory/files.d.ts +14 -0
  45. package/dist/memory/files.js +116 -0
  46. package/dist/memory/service.d.ts +22 -0
  47. package/dist/memory/service.js +146 -0
  48. package/dist/repl/repl.d.ts +8 -0
  49. package/dist/repl/repl.js +374 -0
  50. package/dist/skills/args.d.ts +10 -0
  51. package/dist/skills/args.js +37 -0
  52. package/dist/skills/frontmatter.d.ts +6 -0
  53. package/dist/skills/frontmatter.js +44 -0
  54. package/dist/skills/loader.d.ts +5 -0
  55. package/dist/skills/loader.js +59 -0
  56. package/dist/skills/registry.d.ts +27 -0
  57. package/dist/skills/registry.js +162 -0
  58. package/dist/skills/signing/signer.d.ts +19 -0
  59. package/dist/skills/signing/signer.js +110 -0
  60. package/dist/skills/trust.d.ts +11 -0
  61. package/dist/skills/trust.js +42 -0
  62. package/dist/telemetry/logger.d.ts +51 -0
  63. package/dist/telemetry/logger.js +135 -0
  64. package/dist/tools/permissions.d.ts +20 -0
  65. package/dist/tools/permissions.js +58 -0
  66. package/dist/tools/protocol.d.ts +22 -0
  67. package/dist/tools/protocol.js +3 -0
  68. package/dist/tools/registry.d.ts +20 -0
  69. package/dist/tools/registry.js +77 -0
  70. package/dist/tools/sandbox/docker.d.ts +16 -0
  71. package/dist/tools/sandbox/docker.js +240 -0
  72. package/dist/tools/sandbox/index.d.ts +18 -0
  73. package/dist/tools/sandbox/index.js +80 -0
  74. package/dist/tools/tools/ask-user.d.ts +3 -0
  75. package/dist/tools/tools/ask-user.js +56 -0
  76. package/dist/tools/tools/bash.d.ts +3 -0
  77. package/dist/tools/tools/bash.js +85 -0
  78. package/dist/tools/tools/edit.d.ts +3 -0
  79. package/dist/tools/tools/edit.js +138 -0
  80. package/dist/tools/tools/glob.d.ts +3 -0
  81. package/dist/tools/tools/glob.js +63 -0
  82. package/dist/tools/tools/grep.d.ts +3 -0
  83. package/dist/tools/tools/grep.js +148 -0
  84. package/dist/tools/tools/read.d.ts +3 -0
  85. package/dist/tools/tools/read.js +85 -0
  86. package/dist/tools/tools/web-fetch.d.ts +3 -0
  87. package/dist/tools/tools/web-fetch.js +142 -0
  88. package/dist/tools/tools/write.d.ts +3 -0
  89. package/dist/tools/tools/write.js +84 -0
  90. package/dist/tools/validation.d.ts +13 -0
  91. package/dist/tools/validation.js +142 -0
  92. package/dist/utils/markdown.d.ts +9 -0
  93. package/dist/utils/markdown.js +89 -0
  94. package/dist/utils/streaming.d.ts +10 -0
  95. package/dist/utils/streaming.js +63 -0
  96. package/dist/utils/tokens.d.ts +12 -0
  97. package/dist/utils/tokens.js +44 -0
  98. package/package.json +2 -2
  99. package/dist/agent/conversation.d.ts.map +0 -1
  100. package/dist/agent/loop.d.ts.map +0 -1
  101. package/dist/agent/planner.d.ts.map +0 -1
  102. package/dist/auth/client.d.ts.map +0 -1
  103. package/dist/auth/config.d.ts.map +0 -1
  104. package/dist/commands/chat.d.ts.map +0 -1
  105. package/dist/commands/config.d.ts.map +0 -1
  106. package/dist/commands/login.d.ts.map +0 -1
  107. package/dist/commands/marketplace.d.ts.map +0 -1
  108. package/dist/commands/models.d.ts.map +0 -1
  109. package/dist/compact/history-store.d.ts.map +0 -1
  110. package/dist/compact/microcompact.d.ts.map +0 -1
  111. package/dist/compact/service.d.ts.map +0 -1
  112. package/dist/context/analyzer.d.ts.map +0 -1
  113. package/dist/context/builder.d.ts.map +0 -1
  114. package/dist/context/cache.d.ts.map +0 -1
  115. package/dist/context/git-context.d.ts.map +0 -1
  116. package/dist/context/llmtune-md.d.ts.map +0 -1
  117. package/dist/context/workspace.d.ts.map +0 -1
  118. package/dist/index.d.ts.map +0 -1
  119. package/dist/marketplace/client.d.ts.map +0 -1
  120. package/dist/memory/files.d.ts.map +0 -1
  121. package/dist/memory/service.d.ts.map +0 -1
  122. package/dist/repl/repl.d.ts.map +0 -1
  123. package/dist/skills/args.d.ts.map +0 -1
  124. package/dist/skills/frontmatter.d.ts.map +0 -1
  125. package/dist/skills/loader.d.ts.map +0 -1
  126. package/dist/skills/registry.d.ts.map +0 -1
  127. package/dist/skills/signing/signer.d.ts.map +0 -1
  128. package/dist/skills/trust.d.ts.map +0 -1
  129. package/dist/telemetry/logger.d.ts.map +0 -1
  130. package/dist/tools/permissions.d.ts.map +0 -1
  131. package/dist/tools/protocol.d.ts.map +0 -1
  132. package/dist/tools/registry.d.ts.map +0 -1
  133. package/dist/tools/sandbox/docker.d.ts.map +0 -1
  134. package/dist/tools/sandbox/index.d.ts.map +0 -1
  135. package/dist/tools/tools/ask-user.d.ts.map +0 -1
  136. package/dist/tools/tools/bash.d.ts.map +0 -1
  137. package/dist/tools/tools/edit.d.ts.map +0 -1
  138. package/dist/tools/tools/glob.d.ts.map +0 -1
  139. package/dist/tools/tools/grep.d.ts.map +0 -1
  140. package/dist/tools/tools/read.d.ts.map +0 -1
  141. package/dist/tools/tools/web-fetch.d.ts.map +0 -1
  142. package/dist/tools/tools/write.d.ts.map +0 -1
  143. package/dist/tools/validation.d.ts.map +0 -1
  144. package/dist/utils/markdown.d.ts.map +0 -1
  145. package/dist/utils/streaming.d.ts.map +0 -1
  146. package/dist/utils/tokens.d.ts.map +0 -1
  147. package/src/agent/conversation.ts +0 -140
  148. package/src/agent/loop.ts +0 -215
  149. package/src/agent/planner.ts +0 -55
  150. package/src/auth/client.ts +0 -19
  151. package/src/auth/config.ts +0 -89
  152. package/src/commands/chat.ts +0 -28
  153. package/src/commands/config.ts +0 -36
  154. package/src/commands/login.ts +0 -63
  155. package/src/commands/marketplace.ts +0 -190
  156. package/src/commands/models.ts +0 -74
  157. package/src/compact/history-store.ts +0 -101
  158. package/src/compact/microcompact.ts +0 -49
  159. package/src/compact/service.ts +0 -154
  160. package/src/context/analyzer.ts +0 -127
  161. package/src/context/builder.ts +0 -123
  162. package/src/context/cache.ts +0 -11
  163. package/src/context/git-context.ts +0 -58
  164. package/src/context/llmtune-md.ts +0 -48
  165. package/src/context/workspace.ts +0 -139
  166. package/src/index.ts +0 -100
  167. package/src/marketplace/client.ts +0 -118
  168. package/src/memory/files.ts +0 -81
  169. package/src/memory/service.ts +0 -124
  170. package/src/repl/repl.ts +0 -400
  171. package/src/skills/args.ts +0 -35
  172. package/src/skills/builtin/explain-code/SKILL.md +0 -30
  173. package/src/skills/frontmatter.ts +0 -47
  174. package/src/skills/loader.ts +0 -25
  175. package/src/skills/registry.ts +0 -155
  176. package/src/skills/signing/signer.ts +0 -101
  177. package/src/skills/trust.ts +0 -50
  178. package/src/telemetry/logger.ts +0 -108
  179. package/src/tools/permissions.ts +0 -83
  180. package/src/tools/protocol.ts +0 -24
  181. package/src/tools/registry.ts +0 -93
  182. package/src/tools/sandbox/docker.ts +0 -225
  183. package/src/tools/sandbox/index.ts +0 -91
  184. package/src/tools/tools/ask-user.ts +0 -60
  185. package/src/tools/tools/bash.ts +0 -97
  186. package/src/tools/tools/edit.ts +0 -111
  187. package/src/tools/tools/glob.ts +0 -68
  188. package/src/tools/tools/grep.ts +0 -121
  189. package/src/tools/tools/read.ts +0 -57
  190. package/src/tools/tools/web-fetch.ts +0 -158
  191. package/src/tools/tools/write.ts +0 -52
  192. package/src/tools/validation.ts +0 -164
  193. package/src/utils/markdown.ts +0 -96
  194. package/src/utils/streaming.ts +0 -63
  195. package/src/utils/tokens.ts +0 -41
  196. package/tsconfig.json +0 -20
@@ -1,124 +0,0 @@
1
- import * as fs from "fs"
2
- import * as path from "path"
3
- import * as os from "os"
4
-
5
- const MEMORY_DIR = path.join(os.homedir(), ".llmtune", "memory")
6
-
7
- const MEMORY_FILES = {
8
- preferences: "preferences.md",
9
- "project-notes": "project-notes.md",
10
- decisions: "decisions.md",
11
- architecture: "architecture.md",
12
- } as const
13
-
14
- type MemoryCategory = keyof typeof MEMORY_FILES
15
-
16
- export interface MemoryEntry {
17
- category: MemoryCategory
18
- content: string
19
- path: string
20
- }
21
-
22
- function ensureMemoryDir(): void {
23
- if (!fs.existsSync(MEMORY_DIR)) {
24
- fs.mkdirSync(MEMORY_DIR, { recursive: true })
25
- }
26
- }
27
-
28
- function getMemoryPath(category: MemoryCategory): string {
29
- return path.join(MEMORY_DIR, MEMORY_FILES[category])
30
- }
31
-
32
- export function readMemory(category: MemoryCategory): string {
33
- const filePath = getMemoryPath(category)
34
- try {
35
- return fs.readFileSync(filePath, "utf-8").trim()
36
- } catch {
37
- return ""
38
- }
39
- }
40
-
41
- export function writeMemory(category: MemoryCategory, content: string): void {
42
- ensureMemoryDir()
43
- const filePath = getMemoryPath(category)
44
- fs.writeFileSync(filePath, content.trim() + "\n", "utf-8")
45
- }
46
-
47
- export function appendMemory(category: MemoryCategory, line: string): void {
48
- ensureMemoryDir()
49
- const existing = readMemory(category)
50
- const updated = existing ? `${existing}\n${line}` : line
51
- writeMemory(category, updated)
52
- }
53
-
54
- export function readAllMemory(): MemoryEntry[] {
55
- ensureMemoryDir()
56
- const entries: MemoryEntry[] = []
57
- for (const [category, filename] of Object.entries(MEMORY_FILES)) {
58
- const content = readMemory(category as MemoryCategory)
59
- if (content) {
60
- entries.push({
61
- category: category as MemoryCategory,
62
- content,
63
- path: path.join(MEMORY_DIR, filename),
64
- })
65
- }
66
- }
67
- return entries
68
- }
69
-
70
- export function buildMemoryPrompt(): string {
71
- const entries = readAllMemory()
72
- if (entries.length === 0) return ""
73
-
74
- const sections = entries.map((entry) => {
75
- const label = entry.category.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())
76
- return `### ${label}\n${entry.content}`
77
- })
78
-
79
- return "## User Memory\n\n" + sections.join("\n\n")
80
- }
81
-
82
- export function clearMemory(category?: MemoryCategory): void {
83
- if (category) {
84
- const filePath = getMemoryPath(category)
85
- try {
86
- fs.unlinkSync(filePath)
87
- } catch {
88
- // file doesn't exist
89
- }
90
- } else {
91
- for (const filename of Object.values(MEMORY_FILES)) {
92
- try {
93
- fs.unlinkSync(path.join(MEMORY_DIR, filename))
94
- } catch {
95
- // skip
96
- }
97
- }
98
- }
99
- }
100
-
101
- export function getMemoryDir(): string {
102
- return MEMORY_DIR
103
- }
104
-
105
- export function initMemoryFiles(): void {
106
- ensureMemoryDir()
107
- const defaults: Record<MemoryCategory, string> = {
108
- preferences:
109
- "# User Preferences\n# Add your coding preferences here (one per line)\n# Example: I prefer TypeScript over JavaScript\n# Example: I use 2-space indentation\n",
110
- "project-notes":
111
- "# Project Notes\n# Key facts about the current project\n# Example: Auth uses JWT + bcrypt\n# Example: Database is Neon PostgreSQL via Prisma\n",
112
- decisions:
113
- "# Architecture Decisions\n# Record important technical decisions\n# Example: Decided to use Prisma instead of Drizzle for ORM\n",
114
- architecture:
115
- "# Architecture Overview\n# Describe the project structure\n# Example: Frontend: Next.js 16, Backend: Express 5, DB: Neon\n",
116
- }
117
-
118
- for (const [category, defaultContent] of Object.entries(defaults)) {
119
- const filePath = getMemoryPath(category as MemoryCategory)
120
- if (!fs.existsSync(filePath)) {
121
- fs.writeFileSync(filePath, defaultContent, "utf-8")
122
- }
123
- }
124
- }
package/src/repl/repl.ts DELETED
@@ -1,400 +0,0 @@
1
- import chalk from "chalk"
2
- import { createInterface } from "readline"
3
- import OpenAI from "openai"
4
- import { Conversation } from "../agent/conversation"
5
- import { ToolRegistry } from "../tools/registry"
6
- import { runAgentLoop } from "../agent/loop"
7
- import { estimateTokens } from "../utils/tokens"
8
- import { readTool } from "../tools/tools/read"
9
- import { writeTool } from "../tools/tools/write"
10
- import { editTool } from "../tools/tools/edit"
11
- import { bashTool } from "../tools/tools/bash"
12
- import { globTool } from "../tools/tools/glob"
13
- import { grepTool } from "../tools/tools/grep"
14
- import { webFetchTool } from "../tools/tools/web-fetch"
15
- import { askUserTool } from "../tools/tools/ask-user"
16
- import { compactConversation, uncompactConversation, type CompactResult } from "../compact/service"
17
- import { analyzeContextUsage, formatContextAnalysis } from "../context/analyzer"
18
- import { SkillRegistry, type TrustLevel } from "../skills/registry"
19
- import { loadAllSkills } from "../skills/loader"
20
- import { isToolAllowedForSkill, resolveTrustLevel } from "../skills/trust"
21
- import { initMemoryFiles, buildMemoryPrompt, readAllMemory, type MemoryEntry } from "../memory/service"
22
- import { startSessionLog, logEvent, endSessionLog, type TelemetryEvent } from "../telemetry/logger"
23
- import { loadConfig } from "../auth/config"
24
- import { getConfigDir } from "../auth/config"
25
- import * as fs from "fs"
26
- import * as path from "path"
27
-
28
- export interface ReplOptions {
29
- client: OpenAI
30
- model: string
31
- stream: boolean
32
- }
33
-
34
- const HELP_TEXT = `
35
- ${chalk.bold("LLMTune CLI - Commands:")}
36
-
37
- ${chalk.cyan("/help")} Show this help
38
- ${chalk.cyan("/clear")} Clear conversation history
39
- ${chalk.cyan("/context")} Show detailed context usage breakdown
40
- ${chalk.cyan("/compact")} Compact conversation (LLM summary)
41
- ${chalk.cyan("/uncompact")} Restore conversation from before compaction
42
- ${chalk.cyan("/model <name>")} Switch model
43
- ${chalk.cyan("/stream")} Toggle streaming mode
44
- ${chalk.cyan("/verbose")} Toggle verbose tool output
45
- ${chalk.cyan("/trust <tool>")} Trust a tool (skip confirmations)
46
- ${chalk.cyan("/skills")} List available skills
47
- ${chalk.cyan("/memory")} Show memory contents
48
- ${chalk.cyan("/save")} Save conversation to file
49
- ${chalk.cyan("/exit")} Exit REPL
50
-
51
- ${chalk.gray("Type your message and press Enter to chat.")}
52
- ${chalk.gray("Multi-line: end a line with '\\' to continue.")}
53
- `.trim()
54
-
55
- export async function startRepl(options: ReplOptions): Promise<void> {
56
- const registry = new ToolRegistry()
57
- const cwd = process.cwd()
58
- const trustedTools = new Set<string>()
59
-
60
- registry.register(readTool)
61
- registry.register(writeTool)
62
- registry.register(editTool)
63
- registry.register(bashTool)
64
- registry.register(globTool)
65
- registry.register(grepTool)
66
- registry.register(webFetchTool)
67
- registry.register(askUserTool)
68
-
69
- const conversation = new Conversation(options.model)
70
-
71
- // Load skills
72
- const skills = loadAllSkills(cwd)
73
- const skillList = skills.listUserInvocable()
74
-
75
- // Initialize memory
76
- initMemoryFiles()
77
-
78
- // Start telemetry
79
- startSessionLog(conversation.id, {
80
- model: options.model,
81
- tools: registry.listSpecs().map((s) => s.name),
82
- cwd,
83
- })
84
-
85
- let currentModel = options.model
86
- let streamMode = options.stream
87
- let verbose = false
88
-
89
- console.log(chalk.cyan(`\nLLMTune CLI v0.1.0`))
90
- console.log(chalk.dim(`Model: ${currentModel}`))
91
- console.log(chalk.dim(`Tools: ${registry.listSpecs().map((s) => s.name).join(", ")}`))
92
- if (skillList.length > 0) {
93
- console.log(chalk.dim(`Skills: ${skillList.map((s) => s.name).join(", ")}`))
94
- }
95
- console.log(chalk.dim(`Type /help for commands, /exit to quit.\n`))
96
-
97
- const rl = createInterface({
98
- input: process.stdin,
99
- output: process.stdout,
100
- prompt: chalk.blue("> "),
101
- })
102
-
103
- rl.prompt()
104
-
105
- let multiLineBuffer = ""
106
-
107
- rl.on("line", async (line: string) => {
108
- const trimmed = line.trim()
109
-
110
- if (trimmed.endsWith("\\")) {
111
- multiLineBuffer += trimmed.slice(0, -1) + "\n"
112
- process.stdout.write(chalk.dim("... "))
113
- return
114
- }
115
-
116
- const fullInput = multiLineBuffer + trimmed
117
- multiLineBuffer = ""
118
-
119
- if (!fullInput) {
120
- rl.prompt()
121
- return
122
- }
123
-
124
- // Check for skill execution: /skill-name [args]
125
- if (fullInput.startsWith("/") && !fullInput.startsWith("/help") && !fullInput.startsWith("/exit") && !fullInput.startsWith("/quit") && !fullInput.startsWith("/clear") && !fullInput.startsWith("/context") && !fullInput.startsWith("/compact") && !fullInput.startsWith("/uncompact") && !fullInput.startsWith("/model") && !fullInput.startsWith("/stream") && !fullInput.startsWith("/verbose") && !fullInput.startsWith("/trust") && !fullInput.startsWith("/skills") && !fullInput.startsWith("/memory") && !fullInput.startsWith("/save")) {
126
- const parts = fullInput.slice(1).split(/\s+/)
127
- const skillName = parts[0]
128
- const skillArgs = parts.slice(1)
129
-
130
- const execution = skills.prepareExecution(skillName, skillArgs)
131
- if (execution) {
132
- const skill = skills.get(skillName)!
133
- console.log(chalk.magenta(`Executing skill: ${skill.name}`))
134
-
135
- const trustLevel = resolveTrustLevel(skill)
136
- const toolNames = registry.listSpecs().map((s) => s.name)
137
- for (const toolName of toolNames) {
138
- if (!isToolAllowedForSkill(toolName, trustLevel)) {
139
- console.log(chalk.yellow(` Warning: tool "${toolName}" not allowed for skill trust level "${trustLevel}"`))
140
- }
141
- }
142
-
143
- // Add skill system prompt and user message
144
- conversation.addSystemMessage(execution.systemPrompt)
145
- conversation.addUserMessage(execution.userMessage)
146
-
147
- try {
148
- const result = await runAgentLoop(options.client, conversation, registry, execution.userMessage, {
149
- model: currentModel,
150
- maxTurns: 50,
151
- verbose,
152
- cwd,
153
- workspaceRoot: cwd,
154
- })
155
- logEvent({ event: "tool_call", tool: skillName, latency_ms: 0 })
156
- } catch (err: unknown) {
157
- const msg = err instanceof Error ? err.message : String(err)
158
- console.log(chalk.red(`\nSkill error: ${msg}`))
159
- }
160
-
161
- console.log("")
162
- rl.prompt()
163
- return
164
- }
165
- }
166
-
167
- if (fullInput.startsWith("/")) {
168
- await handleCommand(fullInput, {
169
- rl,
170
- conversation,
171
- registry,
172
- skills,
173
- trustedTools,
174
- cwd,
175
- client: options.client,
176
- getModel: () => currentModel,
177
- setModel: (m: string) => {
178
- currentModel = m
179
- },
180
- getStream: () => streamMode,
181
- setStream: (s: boolean) => {
182
- streamMode = s
183
- },
184
- getVerbose: () => verbose,
185
- setVerbose: (v: boolean) => {
186
- verbose = v
187
- },
188
- })
189
- rl.prompt()
190
- return
191
- }
192
-
193
- // Normal chat
194
- try {
195
- const result = await runAgentLoop(options.client, conversation, registry, fullInput, {
196
- model: currentModel,
197
- maxTurns: 50,
198
- verbose,
199
- cwd,
200
- workspaceRoot: cwd,
201
- })
202
-
203
- if (result.totalTokensIn > 0 || result.totalTokensOut > 0) {
204
- const cost = estimateCostFromUsage(result.totalTokensIn, result.totalTokensOut)
205
- console.log(
206
- chalk.dim(
207
- ` [${result.turns} turn${result.turns !== 1 ? "s" : ""} | ` +
208
- `${result.totalToolCalls} tool calls | ` +
209
- `${result.totalTokensIn + result.totalTokensOut} tokens | ` +
210
- `~$${cost.toFixed(4)}]`,
211
- ),
212
- )
213
- logEvent({
214
- event: "llm_response",
215
- model: currentModel,
216
- tokens_in: result.totalTokensIn,
217
- tokens_out: result.totalTokensOut,
218
- cost,
219
- latency_ms: 0,
220
- })
221
- }
222
- } catch (err: unknown) {
223
- const msg = err instanceof Error ? err.message : String(err)
224
- console.log(chalk.red(`\nError: ${msg}`))
225
- logEvent({ event: "error", source: "agent_loop", error: msg } as TelemetryEvent)
226
- }
227
-
228
- console.log("")
229
- rl.prompt()
230
- })
231
-
232
- rl.on("close", () => {
233
- endSessionLog({ totalToolCalls: 0, totalTokens: 0 })
234
- console.log(chalk.dim("\nGoodbye!"))
235
- process.exit(0)
236
- })
237
- }
238
-
239
- interface CommandContext {
240
- rl: ReturnType<typeof createInterface>
241
- conversation: Conversation
242
- registry: ToolRegistry
243
- skills: SkillRegistry
244
- trustedTools: Set<string>
245
- cwd: string
246
- client: OpenAI
247
- getModel: () => string
248
- setModel: (m: string) => void
249
- getStream: () => boolean
250
- setStream: (s: boolean) => void
251
- getVerbose: () => boolean
252
- setVerbose: (v: boolean) => void
253
- }
254
-
255
- async function handleCommand(input: string, ctx: CommandContext): Promise<void> {
256
- const parts = input.split(/\s+/)
257
- const cmd = parts[0].toLowerCase()
258
- const args = parts.slice(1)
259
-
260
- switch (cmd) {
261
- case "/help":
262
- case "/h":
263
- case "/?":
264
- console.log(HELP_TEXT)
265
- break
266
-
267
- case "/exit":
268
- case "/quit":
269
- case "/q":
270
- console.log(chalk.dim("Goodbye!"))
271
- process.exit(0)
272
-
273
- case "/clear":
274
- ctx.conversation.messages.length = 0
275
- console.log(chalk.green("Conversation cleared."))
276
- break
277
-
278
- case "/context": {
279
- const analysis = analyzeContextUsage({
280
- systemPrompt: "LLMTune coding agent",
281
- toolSpecs: ctx.registry.listSpecs(),
282
- messages: ctx.conversation.getApiMessages().map((m) => ({
283
- role: m.role,
284
- content: m.content,
285
- })),
286
- skillsContent: ctx.skills.list().map((s) => s.content).join("\n") || undefined,
287
- memoryContent: buildMemoryPrompt() || undefined,
288
- model: ctx.getModel(),
289
- })
290
-
291
- console.log(formatContextAnalysis(analysis))
292
- break
293
- }
294
-
295
- case "/compact": {
296
- if (ctx.conversation.messages.length < 3) {
297
- console.log(chalk.yellow("Not enough messages to compact (need at least 3)."))
298
- break
299
- }
300
- console.log(chalk.dim("Compacting conversation..."))
301
- try {
302
- const result = await compactConversation(ctx.client, ctx.getModel(), ctx.conversation)
303
- console.log(chalk.green(`\nCompacted: ${result.preCompactMessages} -> ${result.postCompactMessages} messages`))
304
- console.log(chalk.dim(` Tokens saved: ~${result.tokensSaved.toLocaleString()}`))
305
- console.log(chalk.dim(` Use /uncompact to restore full history.`))
306
- logEvent({ event: "compaction", tokens_saved: result.tokensSaved, messages_before: result.preCompactMessages, messages_after: result.postCompactMessages, trigger: "manual" })
307
- } catch (err: unknown) {
308
- const msg = err instanceof Error ? err.message : String(err)
309
- console.log(chalk.red(`Compaction failed: ${msg}`))
310
- }
311
- break
312
- }
313
-
314
- case "/uncompact": {
315
- const restored = uncompactConversation(ctx.conversation)
316
- if (restored) {
317
- console.log(chalk.green(`Restored conversation from before compaction (${ctx.conversation.messages.length} messages).`))
318
- } else {
319
- console.log(chalk.yellow("No raw history found to restore."))
320
- }
321
- break
322
- }
323
-
324
- case "/model":
325
- if (args[0]) {
326
- ctx.setModel(args[0])
327
- console.log(chalk.green(`Model set to: ${args[0]}`))
328
- } else {
329
- console.log(chalk.dim(`Current model: ${ctx.getModel()}`))
330
- }
331
- break
332
-
333
- case "/stream":
334
- ctx.setStream(!ctx.getStream())
335
- console.log(chalk.green(`Streaming: ${ctx.getStream() ? "on" : "off"}`))
336
- break
337
-
338
- case "/verbose":
339
- ctx.setVerbose(!ctx.getVerbose())
340
- console.log(chalk.green(`Verbose: ${ctx.getVerbose() ? "on" : "off"}`))
341
- break
342
-
343
- case "/trust":
344
- if (args[0]) {
345
- ctx.trustedTools.add(args[0].toLowerCase())
346
- console.log(chalk.green(`Trusting tool: ${args[0]} (no confirmation needed)`))
347
- } else {
348
- console.log(chalk.dim("Usage: /trust <tool-name>"))
349
- }
350
- break
351
-
352
- case "/skills": {
353
- const allSkills = ctx.skills.listUserInvocable()
354
- if (allSkills.length === 0) {
355
- console.log(chalk.dim("No skills loaded. Create skills in ~/.llmtune/skills/ or .llmtune/skills/"))
356
- console.log(chalk.dim("Each skill is a directory with a SKILL.md file."))
357
- } else {
358
- console.log(chalk.bold("\nAvailable Skills:"))
359
- for (const skill of allSkills) {
360
- const trustLabel = skill.trustLevel
361
- console.log(` ${chalk.cyan(`/${skill.name}`)} - ${skill.description} ${chalk.dim(`[${trustLabel}]`)}`)
362
- }
363
- console.log("")
364
- }
365
- break
366
- }
367
-
368
- case "/memory": {
369
- const entries = readAllMemory()
370
- if (entries.length === 0) {
371
- console.log(chalk.dim("\nNo memory entries. The agent will auto-populate memory as you chat."))
372
- console.log(chalk.dim(`Memory directory: ${getConfigDir()}/memory/\n`))
373
- } else {
374
- console.log(chalk.bold("\nMemory:"))
375
- for (const entry of entries) {
376
- if (entry.content.trim()) {
377
- const preview = entry.content.slice(0, 100).replace(/\n/g, " ")
378
- console.log(` ${chalk.cyan(entry.category)}: ${chalk.dim(preview)}${entry.content.length > 100 ? "..." : ""}`)
379
- }
380
- }
381
- console.log("")
382
- }
383
- break
384
- }
385
-
386
- case "/save": {
387
- const savePath = path.join(process.cwd(), `llmtune-session-${Date.now()}.json`)
388
- fs.writeFileSync(savePath, JSON.stringify(ctx.conversation.getApiMessages(), null, 2), "utf-8")
389
- console.log(chalk.green(`Session saved to: ${savePath}`))
390
- break
391
- }
392
-
393
- default:
394
- console.log(chalk.yellow(`Unknown command: ${cmd}. Type /help for available commands.`))
395
- }
396
- }
397
-
398
- function estimateCostFromUsage(inputTokens: number, outputTokens: number): number {
399
- return (inputTokens * 3 + outputTokens * 15) / 1_000_000
400
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * Parse argument names from a skill's frontmatter "arguments" field.
3
- * Supports: "arg1 arg2", "arg1, arg2", "$arg1 $arg2"
4
- */
5
- export function parseArgumentNames(raw: unknown): string[] {
6
- if (!raw) return []
7
- const str = String(raw).trim()
8
- if (!str) return []
9
-
10
- // Split by comma or space, strip $ prefixes
11
- return str
12
- .split(/[,\s]+/)
13
- .map((s) => s.trim().replace(/^\$/, ""))
14
- .filter(Boolean)
15
- }
16
-
17
- /**
18
- * Substitute {{arg}} placeholders in skill content with provided values.
19
- */
20
- export function substituteArguments(
21
- content: string,
22
- args: Record<string, string>
23
- ): string {
24
- let result = content
25
- for (const [key, value] of Object.entries(args)) {
26
- const patterns = [
27
- new RegExp(`\\{\\{${key}\\}\\}`, "g"),
28
- new RegExp(`\\$\\{${key}\\}`, "g"),
29
- ]
30
- for (const pattern of patterns) {
31
- result = result.replace(pattern, value)
32
- }
33
- }
34
- return result
35
- }
@@ -1,30 +0,0 @@
1
- ---
2
- description: "Explain a code file or code snippet with clear analysis"
3
- user-invocable: true
4
- when_to_use: "Use when the user asks to explain, analyze, or understand code"
5
- allowed-tools:
6
- - read
7
- - glob
8
- - grep
9
- arguments:
10
- - file_path
11
- ---
12
-
13
- You are a code analysis expert. Your task is to explain the given code clearly and thoroughly.
14
-
15
- ## Instructions
16
-
17
- 1. If a `file_path` argument is provided, read the file first using the `read` tool
18
- 2. Provide a structured explanation covering:
19
- - **Purpose**: What does this code do?
20
- - **Key Components**: Main functions, classes, or modules
21
- - **Data Flow**: How data moves through the code
22
- - **Dependencies**: What external modules or services are used
23
- - **Notable Patterns**: Design patterns, architectural decisions
24
- 3. Use clear, concise language
25
- 4. Include relevant code snippets in your explanation
26
- 5. If the code has potential issues or improvements, mention them
27
-
28
- ## Output Format
29
-
30
- Provide the explanation in markdown format with proper headings and code blocks.
@@ -1,47 +0,0 @@
1
- export interface FrontmatterResult {
2
- frontmatter: Record<string, unknown>
3
- body: string
4
- }
5
-
6
- export function parseFrontmatter(content: string): FrontmatterResult {
7
- const trimmed = content.trimStart()
8
-
9
- if (!trimmed.startsWith("---")) {
10
- return { frontmatter: {}, body: content }
11
- }
12
-
13
- const endMatch = trimmed.indexOf("---", 3)
14
- if (endMatch === -1 || endMatch < 4) {
15
- return { frontmatter: {}, body: content }
16
- }
17
-
18
- const frontmatterStr = trimmed.slice(3, endMatch).trim()
19
- const body = trimmed.slice(endMatch + 3).trimStart()
20
-
21
- const frontmatter: Record<string, unknown> = {}
22
- for (const line of frontmatterStr.split("\n")) {
23
- const colonIdx = line.indexOf(":")
24
- if (colonIdx === -1) continue
25
- const key = line.slice(0, colonIdx).trim()
26
- let value: unknown = line.slice(colonIdx + 1).trim()
27
-
28
- if (typeof value === "string") {
29
- if (value === "true") value = true
30
- else if (value === "false") value = false
31
- else if (/^\d+$/.test(value)) value = Number(value)
32
- else if (value.startsWith("[") && value.endsWith("]")) {
33
- value = value
34
- .slice(1, -1)
35
- .split(",")
36
- .map((s) => s.trim())
37
- .filter(Boolean)
38
- } else {
39
- value = value.replace(/^["']|["']$/g, "")
40
- }
41
- }
42
-
43
- frontmatter[key] = value
44
- }
45
-
46
- return { frontmatter, body }
47
- }
@@ -1,25 +0,0 @@
1
- import * as path from "path"
2
- import * as os from "os"
3
- import { SkillRegistry } from "./registry"
4
-
5
- export function loadAllSkills(projectRoot?: string): SkillRegistry {
6
- const registry = new SkillRegistry()
7
- registry.loadAll(projectRoot)
8
- return registry
9
- }
10
-
11
- export function getDefaultSkillsDirs(): string[] {
12
- const dirs: string[] = []
13
- const home = os.homedir()
14
-
15
- const envDir = process.env.LLMTUNE_SKILLS_DIR
16
- if (envDir) dirs.push(path.resolve(envDir))
17
-
18
- dirs.push(path.join(home, ".llmtune", "skills"))
19
-
20
- return dirs
21
- }
22
-
23
- export function getProjectSkillsDir(projectRoot: string): string {
24
- return path.join(projectRoot, ".llmtune", "skills")
25
- }