@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.
- package/README.md +1 -1
- package/dist/agent/conversation.d.ts +42 -0
- package/dist/agent/conversation.js +105 -0
- package/dist/agent/loop.d.ts +19 -0
- package/dist/agent/loop.js +185 -0
- package/dist/agent/planner.d.ts +8 -0
- package/dist/agent/planner.js +43 -0
- package/dist/auth/client.d.ts +4 -0
- package/dist/auth/client.js +24 -0
- package/dist/auth/config.d.ts +21 -0
- package/dist/auth/config.js +83 -0
- package/dist/commands/chat.d.ts +5 -0
- package/dist/commands/chat.js +27 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +37 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +93 -0
- package/dist/commands/marketplace.d.ts +6 -0
- package/dist/commands/marketplace.js +213 -0
- package/dist/commands/models.d.ts +2 -0
- package/dist/commands/models.js +53 -0
- package/dist/compact/history-store.d.ts +29 -0
- package/dist/compact/history-store.js +110 -0
- package/dist/compact/microcompact.d.ts +10 -0
- package/dist/compact/microcompact.js +43 -0
- package/dist/compact/service.d.ts +13 -0
- package/dist/compact/service.js +156 -0
- package/dist/context/analyzer.d.ts +26 -0
- package/dist/context/analyzer.js +99 -0
- package/dist/context/builder.d.ts +13 -0
- package/dist/context/builder.js +144 -0
- package/dist/context/cache.d.ts +6 -0
- package/dist/context/cache.js +8 -0
- package/dist/context/git-context.d.ts +9 -0
- package/dist/context/git-context.js +49 -0
- package/dist/context/llmtune-md.d.ts +6 -0
- package/dist/context/llmtune-md.js +73 -0
- package/dist/context/workspace.d.ts +11 -0
- package/dist/context/workspace.js +115 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -1
- package/dist/marketplace/client.d.ts +52 -0
- package/dist/marketplace/client.js +86 -0
- package/dist/memory/files.d.ts +14 -0
- package/dist/memory/files.js +116 -0
- package/dist/memory/service.d.ts +22 -0
- package/dist/memory/service.js +146 -0
- package/dist/repl/repl.d.ts +8 -0
- package/dist/repl/repl.js +374 -0
- package/dist/skills/args.d.ts +10 -0
- package/dist/skills/args.js +37 -0
- package/dist/skills/frontmatter.d.ts +6 -0
- package/dist/skills/frontmatter.js +44 -0
- package/dist/skills/loader.d.ts +5 -0
- package/dist/skills/loader.js +59 -0
- package/dist/skills/registry.d.ts +27 -0
- package/dist/skills/registry.js +162 -0
- package/dist/skills/signing/signer.d.ts +19 -0
- package/dist/skills/signing/signer.js +110 -0
- package/dist/skills/trust.d.ts +11 -0
- package/dist/skills/trust.js +42 -0
- package/dist/telemetry/logger.d.ts +51 -0
- package/dist/telemetry/logger.js +135 -0
- package/dist/tools/permissions.d.ts +20 -0
- package/dist/tools/permissions.js +58 -0
- package/dist/tools/protocol.d.ts +22 -0
- package/dist/tools/protocol.js +3 -0
- package/dist/tools/registry.d.ts +20 -0
- package/dist/tools/registry.js +77 -0
- package/dist/tools/sandbox/docker.d.ts +16 -0
- package/dist/tools/sandbox/docker.js +240 -0
- package/dist/tools/sandbox/index.d.ts +18 -0
- package/dist/tools/sandbox/index.js +80 -0
- package/dist/tools/tools/ask-user.d.ts +3 -0
- package/dist/tools/tools/ask-user.js +56 -0
- package/dist/tools/tools/bash.d.ts +3 -0
- package/dist/tools/tools/bash.js +85 -0
- package/dist/tools/tools/edit.d.ts +3 -0
- package/dist/tools/tools/edit.js +138 -0
- package/dist/tools/tools/glob.d.ts +3 -0
- package/dist/tools/tools/glob.js +63 -0
- package/dist/tools/tools/grep.d.ts +3 -0
- package/dist/tools/tools/grep.js +148 -0
- package/dist/tools/tools/read.d.ts +3 -0
- package/dist/tools/tools/read.js +85 -0
- package/dist/tools/tools/web-fetch.d.ts +3 -0
- package/dist/tools/tools/web-fetch.js +142 -0
- package/dist/tools/tools/write.d.ts +3 -0
- package/dist/tools/tools/write.js +84 -0
- package/dist/tools/validation.d.ts +13 -0
- package/dist/tools/validation.js +142 -0
- package/dist/utils/markdown.d.ts +9 -0
- package/dist/utils/markdown.js +89 -0
- package/dist/utils/streaming.d.ts +10 -0
- package/dist/utils/streaming.js +63 -0
- package/dist/utils/tokens.d.ts +12 -0
- package/dist/utils/tokens.js +44 -0
- package/package.json +2 -2
- package/dist/agent/conversation.d.ts.map +0 -1
- package/dist/agent/loop.d.ts.map +0 -1
- package/dist/agent/planner.d.ts.map +0 -1
- package/dist/auth/client.d.ts.map +0 -1
- package/dist/auth/config.d.ts.map +0 -1
- package/dist/commands/chat.d.ts.map +0 -1
- package/dist/commands/config.d.ts.map +0 -1
- package/dist/commands/login.d.ts.map +0 -1
- package/dist/commands/marketplace.d.ts.map +0 -1
- package/dist/commands/models.d.ts.map +0 -1
- package/dist/compact/history-store.d.ts.map +0 -1
- package/dist/compact/microcompact.d.ts.map +0 -1
- package/dist/compact/service.d.ts.map +0 -1
- package/dist/context/analyzer.d.ts.map +0 -1
- package/dist/context/builder.d.ts.map +0 -1
- package/dist/context/cache.d.ts.map +0 -1
- package/dist/context/git-context.d.ts.map +0 -1
- package/dist/context/llmtune-md.d.ts.map +0 -1
- package/dist/context/workspace.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/marketplace/client.d.ts.map +0 -1
- package/dist/memory/files.d.ts.map +0 -1
- package/dist/memory/service.d.ts.map +0 -1
- package/dist/repl/repl.d.ts.map +0 -1
- package/dist/skills/args.d.ts.map +0 -1
- package/dist/skills/frontmatter.d.ts.map +0 -1
- package/dist/skills/loader.d.ts.map +0 -1
- package/dist/skills/registry.d.ts.map +0 -1
- package/dist/skills/signing/signer.d.ts.map +0 -1
- package/dist/skills/trust.d.ts.map +0 -1
- package/dist/telemetry/logger.d.ts.map +0 -1
- package/dist/tools/permissions.d.ts.map +0 -1
- package/dist/tools/protocol.d.ts.map +0 -1
- package/dist/tools/registry.d.ts.map +0 -1
- package/dist/tools/sandbox/docker.d.ts.map +0 -1
- package/dist/tools/sandbox/index.d.ts.map +0 -1
- package/dist/tools/tools/ask-user.d.ts.map +0 -1
- package/dist/tools/tools/bash.d.ts.map +0 -1
- package/dist/tools/tools/edit.d.ts.map +0 -1
- package/dist/tools/tools/glob.d.ts.map +0 -1
- package/dist/tools/tools/grep.d.ts.map +0 -1
- package/dist/tools/tools/read.d.ts.map +0 -1
- package/dist/tools/tools/web-fetch.d.ts.map +0 -1
- package/dist/tools/tools/write.d.ts.map +0 -1
- package/dist/tools/validation.d.ts.map +0 -1
- package/dist/utils/markdown.d.ts.map +0 -1
- package/dist/utils/streaming.d.ts.map +0 -1
- package/dist/utils/tokens.d.ts.map +0 -1
- package/src/agent/conversation.ts +0 -140
- package/src/agent/loop.ts +0 -215
- package/src/agent/planner.ts +0 -55
- package/src/auth/client.ts +0 -19
- package/src/auth/config.ts +0 -89
- package/src/commands/chat.ts +0 -28
- package/src/commands/config.ts +0 -36
- package/src/commands/login.ts +0 -63
- package/src/commands/marketplace.ts +0 -190
- package/src/commands/models.ts +0 -74
- package/src/compact/history-store.ts +0 -101
- package/src/compact/microcompact.ts +0 -49
- package/src/compact/service.ts +0 -154
- package/src/context/analyzer.ts +0 -127
- package/src/context/builder.ts +0 -123
- package/src/context/cache.ts +0 -11
- package/src/context/git-context.ts +0 -58
- package/src/context/llmtune-md.ts +0 -48
- package/src/context/workspace.ts +0 -139
- package/src/index.ts +0 -100
- package/src/marketplace/client.ts +0 -118
- package/src/memory/files.ts +0 -81
- package/src/memory/service.ts +0 -124
- package/src/repl/repl.ts +0 -400
- package/src/skills/args.ts +0 -35
- package/src/skills/builtin/explain-code/SKILL.md +0 -30
- package/src/skills/frontmatter.ts +0 -47
- package/src/skills/loader.ts +0 -25
- package/src/skills/registry.ts +0 -155
- package/src/skills/signing/signer.ts +0 -101
- package/src/skills/trust.ts +0 -50
- package/src/telemetry/logger.ts +0 -108
- package/src/tools/permissions.ts +0 -83
- package/src/tools/protocol.ts +0 -24
- package/src/tools/registry.ts +0 -93
- package/src/tools/sandbox/docker.ts +0 -225
- package/src/tools/sandbox/index.ts +0 -91
- package/src/tools/tools/ask-user.ts +0 -60
- package/src/tools/tools/bash.ts +0 -97
- package/src/tools/tools/edit.ts +0 -111
- package/src/tools/tools/glob.ts +0 -68
- package/src/tools/tools/grep.ts +0 -121
- package/src/tools/tools/read.ts +0 -57
- package/src/tools/tools/web-fetch.ts +0 -158
- package/src/tools/tools/write.ts +0 -52
- package/src/tools/validation.ts +0 -164
- package/src/utils/markdown.ts +0 -96
- package/src/utils/streaming.ts +0 -63
- package/src/utils/tokens.ts +0 -41
- package/tsconfig.json +0 -20
package/src/memory/service.ts
DELETED
|
@@ -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
|
-
}
|
package/src/skills/args.ts
DELETED
|
@@ -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
|
-
}
|
package/src/skills/loader.ts
DELETED
|
@@ -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
|
-
}
|