@llmtune/cli 0.1.0
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 +181 -0
- package/dist/agent/conversation.d.ts.map +1 -0
- package/dist/agent/loop.d.ts.map +1 -0
- package/dist/agent/planner.d.ts.map +1 -0
- package/dist/auth/client.d.ts.map +1 -0
- package/dist/auth/config.d.ts.map +1 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/marketplace.d.ts.map +1 -0
- package/dist/commands/models.d.ts.map +1 -0
- package/dist/compact/history-store.d.ts.map +1 -0
- package/dist/compact/microcompact.d.ts.map +1 -0
- package/dist/compact/service.d.ts.map +1 -0
- package/dist/context/analyzer.d.ts.map +1 -0
- package/dist/context/builder.d.ts.map +1 -0
- package/dist/context/cache.d.ts.map +1 -0
- package/dist/context/git-context.d.ts.map +1 -0
- package/dist/context/llmtune-md.d.ts.map +1 -0
- package/dist/context/workspace.d.ts.map +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +125 -0
- package/dist/marketplace/client.d.ts.map +1 -0
- package/dist/memory/files.d.ts.map +1 -0
- package/dist/memory/service.d.ts.map +1 -0
- package/dist/repl/repl.d.ts.map +1 -0
- package/dist/skills/args.d.ts.map +1 -0
- package/dist/skills/frontmatter.d.ts.map +1 -0
- package/dist/skills/loader.d.ts.map +1 -0
- package/dist/skills/registry.d.ts.map +1 -0
- package/dist/skills/signing/signer.d.ts.map +1 -0
- package/dist/skills/trust.d.ts.map +1 -0
- package/dist/telemetry/logger.d.ts.map +1 -0
- package/dist/tools/permissions.d.ts.map +1 -0
- package/dist/tools/protocol.d.ts.map +1 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/sandbox/docker.d.ts.map +1 -0
- package/dist/tools/sandbox/index.d.ts.map +1 -0
- package/dist/tools/tools/ask-user.d.ts.map +1 -0
- package/dist/tools/tools/bash.d.ts.map +1 -0
- package/dist/tools/tools/edit.d.ts.map +1 -0
- package/dist/tools/tools/glob.d.ts.map +1 -0
- package/dist/tools/tools/grep.d.ts.map +1 -0
- package/dist/tools/tools/read.d.ts.map +1 -0
- package/dist/tools/tools/web-fetch.d.ts.map +1 -0
- package/dist/tools/tools/write.d.ts.map +1 -0
- package/dist/tools/validation.d.ts.map +1 -0
- package/dist/utils/markdown.d.ts.map +1 -0
- package/dist/utils/streaming.d.ts.map +1 -0
- package/dist/utils/tokens.d.ts.map +1 -0
- package/docs/SKILL_AUTHORING.md +175 -0
- package/package.json +38 -0
- package/src/agent/conversation.ts +140 -0
- package/src/agent/loop.ts +215 -0
- package/src/agent/planner.ts +55 -0
- package/src/auth/client.ts +19 -0
- package/src/auth/config.ts +89 -0
- package/src/commands/chat.ts +28 -0
- package/src/commands/config.ts +36 -0
- package/src/commands/login.ts +63 -0
- package/src/commands/marketplace.ts +190 -0
- package/src/commands/models.ts +74 -0
- package/src/compact/history-store.ts +101 -0
- package/src/compact/microcompact.ts +49 -0
- package/src/compact/service.ts +154 -0
- package/src/context/analyzer.ts +127 -0
- package/src/context/builder.ts +123 -0
- package/src/context/cache.ts +11 -0
- package/src/context/git-context.ts +58 -0
- package/src/context/llmtune-md.ts +48 -0
- package/src/context/workspace.ts +139 -0
- package/src/index.ts +100 -0
- package/src/marketplace/client.ts +118 -0
- package/src/memory/files.ts +81 -0
- package/src/memory/service.ts +124 -0
- package/src/repl/repl.ts +400 -0
- package/src/skills/args.ts +35 -0
- package/src/skills/builtin/explain-code/SKILL.md +30 -0
- package/src/skills/frontmatter.ts +47 -0
- package/src/skills/loader.ts +25 -0
- package/src/skills/registry.ts +155 -0
- package/src/skills/signing/signer.ts +101 -0
- package/src/skills/trust.ts +50 -0
- package/src/telemetry/logger.ts +108 -0
- package/src/tools/permissions.ts +83 -0
- package/src/tools/protocol.ts +24 -0
- package/src/tools/registry.ts +93 -0
- package/src/tools/sandbox/docker.ts +225 -0
- package/src/tools/sandbox/index.ts +91 -0
- package/src/tools/tools/ask-user.ts +60 -0
- package/src/tools/tools/bash.ts +97 -0
- package/src/tools/tools/edit.ts +111 -0
- package/src/tools/tools/glob.ts +68 -0
- package/src/tools/tools/grep.ts +121 -0
- package/src/tools/tools/read.ts +57 -0
- package/src/tools/tools/web-fetch.ts +158 -0
- package/src/tools/tools/write.ts +52 -0
- package/src/tools/validation.ts +164 -0
- package/src/utils/markdown.ts +96 -0
- package/src/utils/streaming.ts +63 -0
- package/src/utils/tokens.ts +41 -0
- package/tsconfig.json +20 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander"
|
|
3
|
+
import chalk from "chalk"
|
|
4
|
+
import { loadConfig, isLoggedIn } from "./auth/config"
|
|
5
|
+
import { createClient } from "./auth/client"
|
|
6
|
+
import { startRepl } from "./repl/repl"
|
|
7
|
+
|
|
8
|
+
const program = new Command()
|
|
9
|
+
|
|
10
|
+
program
|
|
11
|
+
.name("llmtune")
|
|
12
|
+
.description("LLMTune CLI - AI coding agent powered by api.llmtune.io")
|
|
13
|
+
.version("0.1.0")
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.command("login")
|
|
17
|
+
.description("Configure API key and settings")
|
|
18
|
+
.action(async () => {
|
|
19
|
+
const { loginCommand } = await import("./commands/login")
|
|
20
|
+
await loginCommand()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
.command("chat")
|
|
25
|
+
.description("Start interactive coding session")
|
|
26
|
+
.option("-m, --model <model>", "Model to use")
|
|
27
|
+
.option("--no-stream", "Disable streaming")
|
|
28
|
+
.action(async (options: { model?: string; stream?: boolean }) => {
|
|
29
|
+
if (!isLoggedIn()) {
|
|
30
|
+
console.log(chalk.red('Not logged in. Run "llmtune login" first.'))
|
|
31
|
+
process.exit(1)
|
|
32
|
+
}
|
|
33
|
+
const config = loadConfig()
|
|
34
|
+
const client = createClient()
|
|
35
|
+
const model = options.model ?? (config.defaultModel as string) ?? "z-ai/GLM-5.1"
|
|
36
|
+
const apiBase = (config.apiBase as string) ?? "https://api.llmtune.io/api/agent/v1"
|
|
37
|
+
|
|
38
|
+
console.log(chalk.cyan("\nLLMTune CLI"))
|
|
39
|
+
console.log(chalk.dim(`Connected to ${apiBase}`))
|
|
40
|
+
console.log(chalk.dim(`Model: ${model}`))
|
|
41
|
+
console.log(chalk.dim("Type /help for commands, /exit to quit\n"))
|
|
42
|
+
|
|
43
|
+
await startRepl({ client, model, stream: options.stream !== false })
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
program
|
|
47
|
+
.command("models")
|
|
48
|
+
.description("List available models")
|
|
49
|
+
.action(async () => {
|
|
50
|
+
const { modelsCommand } = await import("./commands/models")
|
|
51
|
+
await modelsCommand()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
program
|
|
55
|
+
.command("config")
|
|
56
|
+
.description("Show current configuration")
|
|
57
|
+
.action(async () => {
|
|
58
|
+
const { showConfig } = await import("./commands/config")
|
|
59
|
+
showConfig()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// Skills marketplace commands
|
|
63
|
+
const skillsCmd = program.command("skills").description("Manage skills (list, install, publish, sign)")
|
|
64
|
+
|
|
65
|
+
skillsCmd
|
|
66
|
+
.command("list")
|
|
67
|
+
.description("List available skills from marketplace")
|
|
68
|
+
.action(async () => {
|
|
69
|
+
const { listSkillsCommand } = await import("./commands/marketplace")
|
|
70
|
+
await listSkillsCommand()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
skillsCmd
|
|
74
|
+
.command("install")
|
|
75
|
+
.description("Install a skill from the marketplace")
|
|
76
|
+
.argument("<name>", "Skill name to install")
|
|
77
|
+
.action(async (name: string) => {
|
|
78
|
+
const { installSkillCommand } = await import("./commands/marketplace")
|
|
79
|
+
await installSkillCommand(name)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
skillsCmd
|
|
83
|
+
.command("publish")
|
|
84
|
+
.description("Publish a local skill to the marketplace")
|
|
85
|
+
.argument("<path>", "Path to skill directory")
|
|
86
|
+
.action(async (skillPath: string) => {
|
|
87
|
+
const { publishSkillCommand } = await import("./commands/marketplace")
|
|
88
|
+
await publishSkillCommand(skillPath)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
skillsCmd
|
|
92
|
+
.command("sign")
|
|
93
|
+
.description("Cryptographically sign a skill")
|
|
94
|
+
.argument("<path>", "Path to skill directory")
|
|
95
|
+
.action(async (skillPath: string) => {
|
|
96
|
+
const { signSkillCommand } = await import("./commands/marketplace")
|
|
97
|
+
await signSkillCommand(skillPath)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
program.parse()
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill marketplace client.
|
|
3
|
+
* Interacts with the infra's skill registry API to list, search, install, and publish skills.
|
|
4
|
+
*/
|
|
5
|
+
import type { AppConfig } from "../auth/config"
|
|
6
|
+
|
|
7
|
+
export interface MarketplaceSkill {
|
|
8
|
+
name: string
|
|
9
|
+
description: string
|
|
10
|
+
trustLevel: "local" | "community" | "verified" | "signed"
|
|
11
|
+
allowedTools: string[]
|
|
12
|
+
author?: string
|
|
13
|
+
version?: string
|
|
14
|
+
installs?: number
|
|
15
|
+
rating?: number
|
|
16
|
+
tags?: string[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SearchResult {
|
|
20
|
+
skills: MarketplaceSkill[]
|
|
21
|
+
total: number
|
|
22
|
+
page: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getApiBase(config: AppConfig): string {
|
|
26
|
+
return String(config.apiBase ?? "https://api.llmtune.io/api/agent/v1").replace(/\/$/, "")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getHeaders(config: AppConfig): Record<string, string> {
|
|
30
|
+
return {
|
|
31
|
+
"Content-Type": "application/json",
|
|
32
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* List all skills in the marketplace.
|
|
38
|
+
*/
|
|
39
|
+
export async function listSkills(
|
|
40
|
+
config: AppConfig,
|
|
41
|
+
options?: { search?: string; tag?: string; page?: number; limit?: number },
|
|
42
|
+
): Promise<SearchResult> {
|
|
43
|
+
const base = getApiBase(config)
|
|
44
|
+
const params = new URLSearchParams()
|
|
45
|
+
if (options?.search) params.set("search", options.search)
|
|
46
|
+
if (options?.tag) params.set("tag", options.tag)
|
|
47
|
+
if (options?.page) params.set("page", String(options.page))
|
|
48
|
+
if (options?.limit) params.set("limit", String(options.limit))
|
|
49
|
+
|
|
50
|
+
const url = `${base}/skills?${params.toString()}`
|
|
51
|
+
const response = await fetch(url, { headers: getHeaders(config) })
|
|
52
|
+
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
throw new Error(`Failed to list skills: HTTP ${response.status}`)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (await response.json()) as SearchResult
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get details for a specific skill.
|
|
62
|
+
*/
|
|
63
|
+
export async function getSkillDetails(config: AppConfig, name: string): Promise<MarketplaceSkill> {
|
|
64
|
+
const base = getApiBase(config)
|
|
65
|
+
const url = `${base}/skills/${encodeURIComponent(name)}`
|
|
66
|
+
const response = await fetch(url, { headers: getHeaders(config) })
|
|
67
|
+
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
if (response.status === 404) throw new Error(`Skill not found: ${name}`)
|
|
70
|
+
throw new Error(`Failed to get skill: HTTP ${response.status}`)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (await response.json()) as MarketplaceSkill
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Install a skill from the marketplace to the local skills directory.
|
|
78
|
+
*/
|
|
79
|
+
export async function installSkill(config: AppConfig, name: string): Promise<string> {
|
|
80
|
+
const base = getApiBase(config)
|
|
81
|
+
const url = `${base}/skills/install`
|
|
82
|
+
const response = await fetch(url, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: getHeaders(config),
|
|
85
|
+
body: JSON.stringify({ skillName: name }),
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
const body = await response.text()
|
|
90
|
+
throw new Error(`Failed to install skill: ${body}`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const data = (await response.json()) as { installPath: string; skillName: string }
|
|
94
|
+
return data.installPath
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Publish a local skill to the marketplace.
|
|
99
|
+
*/
|
|
100
|
+
export async function publishSkill(
|
|
101
|
+
config: AppConfig,
|
|
102
|
+
skill: { name: string; description: string; content: string; trustLevel: string; allowedTools: string[] },
|
|
103
|
+
): Promise<{ published: boolean; skillName: string }> {
|
|
104
|
+
const base = getApiBase(config)
|
|
105
|
+
const url = `${base}/skills`
|
|
106
|
+
const response = await fetch(url, {
|
|
107
|
+
method: "POST",
|
|
108
|
+
headers: getHeaders(config),
|
|
109
|
+
body: JSON.stringify(skill),
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
const body = await response.text()
|
|
114
|
+
throw new Error(`Failed to publish skill: ${body}`)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return (await response.json()) as { published: boolean; skillName: string }
|
|
118
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as fs from "fs"
|
|
2
|
+
import * as path from "path"
|
|
3
|
+
import { getConfigDir } from "../auth/config"
|
|
4
|
+
|
|
5
|
+
export interface MemoryCategory {
|
|
6
|
+
key: string
|
|
7
|
+
filename: string
|
|
8
|
+
description: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const MEMORY_CATEGORIES: MemoryCategory[] = [
|
|
12
|
+
{ key: "preferences", filename: "preferences.md", description: "User preferences (language, style, conventions)" },
|
|
13
|
+
{ key: "project", filename: "project-notes.md", description: "Project knowledge (architecture, patterns, key decisions)" },
|
|
14
|
+
{ key: "decisions", filename: "decisions.md", description: "Past decisions and rationale" },
|
|
15
|
+
{ key: "architecture", filename: "architecture.md", description: "System architecture notes" },
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
export function getMemoryDir(): string {
|
|
19
|
+
return path.join(getConfigDir(), "memory")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ensureMemoryDir(): void {
|
|
23
|
+
const dir = getMemoryDir()
|
|
24
|
+
if (!fs.existsSync(dir)) {
|
|
25
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
26
|
+
}
|
|
27
|
+
for (const cat of MEMORY_CATEGORIES) {
|
|
28
|
+
const filePath = path.join(dir, cat.filename)
|
|
29
|
+
if (!fs.existsSync(filePath)) {
|
|
30
|
+
fs.writeFileSync(filePath, `# ${cat.description}\n\n`, "utf-8")
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function readMemory(key: string): string {
|
|
36
|
+
const cat = MEMORY_CATEGORIES.find((c) => c.key === key)
|
|
37
|
+
if (!cat) return ""
|
|
38
|
+
const filePath = path.join(getMemoryDir(), cat.filename)
|
|
39
|
+
try {
|
|
40
|
+
return fs.readFileSync(filePath, "utf-8").trim()
|
|
41
|
+
} catch {
|
|
42
|
+
return ""
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function writeMemory(key: string, content: string): void {
|
|
47
|
+
const cat = MEMORY_CATEGORIES.find((c) => c.key === key)
|
|
48
|
+
if (!cat) return
|
|
49
|
+
ensureMemoryDir()
|
|
50
|
+
const filePath = path.join(getMemoryDir(), cat.filename)
|
|
51
|
+
fs.writeFileSync(filePath, content, "utf-8")
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function appendMemory(key: string, line: string): void {
|
|
55
|
+
const current = readMemory(key)
|
|
56
|
+
writeMemory(key, current + "\n" + line)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function readAllMemory(): Record<string, string> {
|
|
60
|
+
const result: Record<string, string> = {}
|
|
61
|
+
for (const cat of MEMORY_CATEGORIES) {
|
|
62
|
+
const content = readMemory(cat.key)
|
|
63
|
+
if (content) result[cat.key] = content
|
|
64
|
+
}
|
|
65
|
+
return result
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function buildMemoryPrompt(): string {
|
|
69
|
+
const memories = readAllMemory()
|
|
70
|
+
const entries = Object.entries(memories)
|
|
71
|
+
if (entries.length === 0) return ""
|
|
72
|
+
const lines = ["## User Memory"]
|
|
73
|
+
for (const [key, content] of entries) {
|
|
74
|
+
const cat = MEMORY_CATEGORIES.find((c) => c.key === key)
|
|
75
|
+
if (cat && content.trim()) {
|
|
76
|
+
lines.push(`### ${cat.description}`)
|
|
77
|
+
lines.push(content)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return lines.join("\n")
|
|
81
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
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
|
+
}
|