@llmtune/cli 0.1.0 → 0.1.2

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 (198) 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 +39 -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 +3 -2
  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 +375 -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 +143 -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/dist/version.d.ts +2 -0
  99. package/dist/version.js +9 -0
  100. package/package.json +2 -2
  101. package/dist/agent/conversation.d.ts.map +0 -1
  102. package/dist/agent/loop.d.ts.map +0 -1
  103. package/dist/agent/planner.d.ts.map +0 -1
  104. package/dist/auth/client.d.ts.map +0 -1
  105. package/dist/auth/config.d.ts.map +0 -1
  106. package/dist/commands/chat.d.ts.map +0 -1
  107. package/dist/commands/config.d.ts.map +0 -1
  108. package/dist/commands/login.d.ts.map +0 -1
  109. package/dist/commands/marketplace.d.ts.map +0 -1
  110. package/dist/commands/models.d.ts.map +0 -1
  111. package/dist/compact/history-store.d.ts.map +0 -1
  112. package/dist/compact/microcompact.d.ts.map +0 -1
  113. package/dist/compact/service.d.ts.map +0 -1
  114. package/dist/context/analyzer.d.ts.map +0 -1
  115. package/dist/context/builder.d.ts.map +0 -1
  116. package/dist/context/cache.d.ts.map +0 -1
  117. package/dist/context/git-context.d.ts.map +0 -1
  118. package/dist/context/llmtune-md.d.ts.map +0 -1
  119. package/dist/context/workspace.d.ts.map +0 -1
  120. package/dist/index.d.ts.map +0 -1
  121. package/dist/marketplace/client.d.ts.map +0 -1
  122. package/dist/memory/files.d.ts.map +0 -1
  123. package/dist/memory/service.d.ts.map +0 -1
  124. package/dist/repl/repl.d.ts.map +0 -1
  125. package/dist/skills/args.d.ts.map +0 -1
  126. package/dist/skills/frontmatter.d.ts.map +0 -1
  127. package/dist/skills/loader.d.ts.map +0 -1
  128. package/dist/skills/registry.d.ts.map +0 -1
  129. package/dist/skills/signing/signer.d.ts.map +0 -1
  130. package/dist/skills/trust.d.ts.map +0 -1
  131. package/dist/telemetry/logger.d.ts.map +0 -1
  132. package/dist/tools/permissions.d.ts.map +0 -1
  133. package/dist/tools/protocol.d.ts.map +0 -1
  134. package/dist/tools/registry.d.ts.map +0 -1
  135. package/dist/tools/sandbox/docker.d.ts.map +0 -1
  136. package/dist/tools/sandbox/index.d.ts.map +0 -1
  137. package/dist/tools/tools/ask-user.d.ts.map +0 -1
  138. package/dist/tools/tools/bash.d.ts.map +0 -1
  139. package/dist/tools/tools/edit.d.ts.map +0 -1
  140. package/dist/tools/tools/glob.d.ts.map +0 -1
  141. package/dist/tools/tools/grep.d.ts.map +0 -1
  142. package/dist/tools/tools/read.d.ts.map +0 -1
  143. package/dist/tools/tools/web-fetch.d.ts.map +0 -1
  144. package/dist/tools/tools/write.d.ts.map +0 -1
  145. package/dist/tools/validation.d.ts.map +0 -1
  146. package/dist/utils/markdown.d.ts.map +0 -1
  147. package/dist/utils/streaming.d.ts.map +0 -1
  148. package/dist/utils/tokens.d.ts.map +0 -1
  149. package/src/agent/conversation.ts +0 -140
  150. package/src/agent/loop.ts +0 -215
  151. package/src/agent/planner.ts +0 -55
  152. package/src/auth/client.ts +0 -19
  153. package/src/auth/config.ts +0 -89
  154. package/src/commands/chat.ts +0 -28
  155. package/src/commands/config.ts +0 -36
  156. package/src/commands/login.ts +0 -63
  157. package/src/commands/marketplace.ts +0 -190
  158. package/src/commands/models.ts +0 -74
  159. package/src/compact/history-store.ts +0 -101
  160. package/src/compact/microcompact.ts +0 -49
  161. package/src/compact/service.ts +0 -154
  162. package/src/context/analyzer.ts +0 -127
  163. package/src/context/builder.ts +0 -123
  164. package/src/context/cache.ts +0 -11
  165. package/src/context/git-context.ts +0 -58
  166. package/src/context/llmtune-md.ts +0 -48
  167. package/src/context/workspace.ts +0 -139
  168. package/src/index.ts +0 -100
  169. package/src/marketplace/client.ts +0 -118
  170. package/src/memory/files.ts +0 -81
  171. package/src/memory/service.ts +0 -124
  172. package/src/repl/repl.ts +0 -400
  173. package/src/skills/args.ts +0 -35
  174. package/src/skills/builtin/explain-code/SKILL.md +0 -30
  175. package/src/skills/frontmatter.ts +0 -47
  176. package/src/skills/loader.ts +0 -25
  177. package/src/skills/registry.ts +0 -155
  178. package/src/skills/signing/signer.ts +0 -101
  179. package/src/skills/trust.ts +0 -50
  180. package/src/telemetry/logger.ts +0 -108
  181. package/src/tools/permissions.ts +0 -83
  182. package/src/tools/protocol.ts +0 -24
  183. package/src/tools/registry.ts +0 -93
  184. package/src/tools/sandbox/docker.ts +0 -225
  185. package/src/tools/sandbox/index.ts +0 -91
  186. package/src/tools/tools/ask-user.ts +0 -60
  187. package/src/tools/tools/bash.ts +0 -97
  188. package/src/tools/tools/edit.ts +0 -111
  189. package/src/tools/tools/glob.ts +0 -68
  190. package/src/tools/tools/grep.ts +0 -121
  191. package/src/tools/tools/read.ts +0 -57
  192. package/src/tools/tools/web-fetch.ts +0 -158
  193. package/src/tools/tools/write.ts +0 -52
  194. package/src/tools/validation.ts +0 -164
  195. package/src/utils/markdown.ts +0 -96
  196. package/src/utils/streaming.ts +0 -63
  197. package/src/utils/tokens.ts +0 -41
  198. package/tsconfig.json +0 -20
@@ -1,127 +0,0 @@
1
- import type { ToolSpec } from "../tools/protocol"
2
- import { estimateTokens } from "../utils/tokens"
3
-
4
- export interface ContextCategory {
5
- name: string
6
- tokens: number
7
- }
8
-
9
- export interface ContextAnalysis {
10
- categories: ContextCategory[]
11
- totalTokens: number
12
- maxTokens: number
13
- percentage: number
14
- model: string
15
- }
16
-
17
- const MODEL_CONTEXT_WINDOWS: Record<string, number> = {
18
- "gpt-4o": 128_000,
19
- "gpt-4o-mini": 128_000,
20
- "gpt-4-turbo": 128_000,
21
- "glm-5.1": 128_000,
22
- "glm-5-turbo": 128_000,
23
- "glm-4.7-flash": 128_000,
24
- "claude-sonnet": 200_000,
25
- "claude-opus": 200_000,
26
- "deepseek-r1": 128_000,
27
- "qwen3": 128_000,
28
- }
29
-
30
- const DEFAULT_CONTEXT_WINDOW = 128_000
31
-
32
- export function getContextWindowForModel(model: string): number {
33
- const lower = model.toLowerCase()
34
- for (const [name, window] of Object.entries(MODEL_CONTEXT_WINDOWS)) {
35
- if (lower.includes(name)) return window
36
- }
37
- const kMatch = lower.match(/(\d+)k/)
38
- if (kMatch) return parseInt(kMatch[1], 10) * 1_000
39
- if (lower.includes("1m") || lower.includes("million")) return 1_000_000
40
- return DEFAULT_CONTEXT_WINDOW
41
- }
42
-
43
- export function analyzeContextUsage(options: {
44
- systemPrompt: string
45
- toolSpecs: ToolSpec[]
46
- messages: Array<{ role: string; content: unknown }>
47
- skillsContent?: string
48
- memoryContent?: string
49
- model: string
50
- }): ContextAnalysis {
51
- const categories: ContextCategory[] = []
52
-
53
- const systemTokens = estimateTokens(options.systemPrompt)
54
- if (systemTokens > 0) {
55
- categories.push({ name: "System prompt", tokens: systemTokens })
56
- }
57
-
58
- let toolTokens = 0
59
- for (const spec of options.toolSpecs) {
60
- toolTokens += estimateTokens(spec.description)
61
- toolTokens += estimateTokens(JSON.stringify(spec.inputSchema))
62
- }
63
- if (toolTokens > 0) {
64
- categories.push({ name: "Tool definitions", tokens: toolTokens })
65
- }
66
-
67
- if (options.skillsContent) {
68
- const skillsTokens = estimateTokens(options.skillsContent)
69
- if (skillsTokens > 0) {
70
- categories.push({ name: "Skills", tokens: skillsTokens })
71
- }
72
- }
73
-
74
- if (options.memoryContent) {
75
- const memTokens = estimateTokens(options.memoryContent)
76
- if (memTokens > 0) {
77
- categories.push({ name: "Memory", tokens: memTokens })
78
- }
79
- }
80
-
81
- let messageTokens = 0
82
- for (const msg of options.messages) {
83
- const text = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content ?? "")
84
- messageTokens += estimateTokens(text)
85
- }
86
- if (messageTokens > 0) {
87
- categories.push({ name: "Messages", tokens: messageTokens })
88
- }
89
-
90
- const totalTokens = categories.reduce((sum, c) => sum + c.tokens, 0)
91
- const maxTokens = getContextWindowForModel(options.model)
92
- const percentage = maxTokens > 0 ? Math.round((totalTokens / maxTokens) * 1000) / 10 : 0
93
-
94
- const freeTokens = Math.max(0, maxTokens - totalTokens)
95
- categories.push({ name: "Free space", tokens: freeTokens })
96
-
97
- return { categories, totalTokens, maxTokens, percentage, model: options.model }
98
- }
99
-
100
- export function formatContextAnalysis(analysis: ContextAnalysis): string {
101
- const lines: string[] = [
102
- "## Context Usage",
103
- "",
104
- `**Model:** ${analysis.model}`,
105
- `**Tokens:** ${analysis.totalTokens.toLocaleString()} / ${analysis.maxTokens.toLocaleString()} (${analysis.percentage}%)`,
106
- "",
107
- ]
108
-
109
- const visible = analysis.categories.filter((c) => c.tokens > 0 && c.name !== "Free space")
110
-
111
- if (visible.length > 0) {
112
- lines.push("| Category | Tokens | Percentage |")
113
- lines.push("|----------|--------|------------|")
114
- for (const cat of visible) {
115
- const pct = analysis.maxTokens > 0 ? ((cat.tokens / analysis.maxTokens) * 100).toFixed(1) : "0.0"
116
- lines.push(`| ${cat.name} | ${cat.tokens.toLocaleString()} | ${pct}% |`)
117
- }
118
- const free = analysis.categories.find((c) => c.name === "Free space")
119
- if (free && free.tokens > 0) {
120
- const pct = analysis.maxTokens > 0 ? ((free.tokens / analysis.maxTokens) * 100).toFixed(1) : "0.0"
121
- lines.push(`| Free space | ${free.tokens.toLocaleString()} | ${pct}% |`)
122
- }
123
- lines.push("")
124
- }
125
-
126
- return lines.join("\n")
127
- }
@@ -1,123 +0,0 @@
1
- import * as path from "path"
2
- import * as crypto from "crypto"
3
- import * as fs from "fs"
4
- import { collectGitContext, type GitContext } from "./git-context"
5
- import { buildWorkspaceSnapshot, renderWorkspaceSection, type WorkspaceSnapshot } from "./workspace"
6
- import { loadProjectInstructions } from "./llmtune-md"
7
-
8
- export interface ContextResult {
9
- prompt: string
10
- cacheKey: string
11
- cacheHit: boolean
12
- sections: string[]
13
- }
14
-
15
- const CACHE_DIR = () => {
16
- const base = process.env.LLMTUNE_CACHE_DIR || path.join(process.env.HOME || process.env.USERPROFILE || "~", ".llmtune", "cache")
17
- return base
18
- }
19
-
20
- function getCachePath(cacheKey: string): string {
21
- return path.join(CACHE_DIR(), `${cacheKey}.json`)
22
- }
23
-
24
- export function computeCacheKey(workspaceRoot: string, cwd: string): string {
25
- const git = collectGitContext(workspaceRoot)
26
- const base = `${workspaceRoot}:${cwd}`
27
- if (git.available && git.recentCommit) {
28
- return crypto.createHash("sha256").update(`${base}:${git.recentCommit}`).digest("hex").slice(0, 16)
29
- }
30
- return crypto.createHash("sha256").update(base).digest("hex").slice(0, 16)
31
- }
32
-
33
- export function readCache(cacheKey: string): ContextResult | null {
34
- try {
35
- const cachePath = getCachePath(cacheKey)
36
- if (fs.existsSync(cachePath)) {
37
- const stat = fs.statSync(cachePath)
38
- const ageMs = Date.now() - stat.mtimeMs
39
- if (ageMs > 30 * 60 * 1000) return null
40
- const data = JSON.parse(fs.readFileSync(cachePath, "utf-8"))
41
- return { ...data, cacheHit: true }
42
- }
43
- } catch {}
44
- return null
45
- }
46
-
47
- export function writeCache(cacheKey: string, result: ContextResult): void {
48
- try {
49
- const cacheDir = CACHE_DIR()
50
- if (!fs.existsSync(cacheDir)) {
51
- fs.mkdirSync(cacheDir, { recursive: true })
52
- }
53
- const cachePath = getCachePath(cacheKey)
54
- fs.writeFileSync(cachePath, JSON.stringify({ ...result, cacheHit: false }, null, 2))
55
- } catch {}
56
- }
57
-
58
- export async function buildContextPrompt(
59
- workspaceRoot: string,
60
- cwd: string,
61
- options?: { useCache?: boolean }
62
- ): Promise<ContextResult> {
63
- const cacheKey = computeCacheKey(workspaceRoot, cwd)
64
-
65
- if (options?.useCache !== false) {
66
- const cached = readCache(cacheKey)
67
- if (cached) return cached
68
- }
69
-
70
- const sections: string[] = []
71
-
72
- // Workspace section (async)
73
- const workspace = await buildWorkspaceSnapshot(workspaceRoot, cwd)
74
- const workspaceSection = renderWorkspaceSection(workspace)
75
- if (workspaceSection) sections.push(workspaceSection)
76
-
77
- // Git section
78
- const git = collectGitContext(workspaceRoot)
79
- const gitSection = renderGitSection(git)
80
- if (gitSection) sections.push(gitSection)
81
-
82
- // LLMTUNE.md / CLAUDE.md section
83
- const mdFiles = loadProjectInstructions(workspaceRoot, cwd)
84
- const mdSection = renderMdSection(mdFiles, workspaceRoot)
85
- if (mdSection) sections.push(mdSection)
86
-
87
- const prompt = sections.join("\n\n")
88
-
89
- const result: ContextResult = {
90
- prompt,
91
- cacheKey,
92
- cacheHit: false,
93
- sections,
94
- }
95
-
96
- writeCache(cacheKey, result)
97
- return result
98
- }
99
-
100
- function renderGitSection(git: GitContext): string {
101
- if (!git.available) return ""
102
- const lines = ["## Git Context"]
103
- if (git.branch) lines.push(`- Current branch: ${git.branch}`)
104
- if (git.recentCommit) lines.push(`- Latest commit: ${git.recentCommit}`)
105
- if (git.status) {
106
- lines.push("- Git status snapshot:", "```text", git.status.slice(0, 2000), "```")
107
- }
108
- return lines.join("\n")
109
- }
110
-
111
- function renderMdSection(
112
- files: Array<{ path: string; content: string }>,
113
- workspaceRoot: string
114
- ): string {
115
- if (files.length === 0) return ""
116
- const lines = ["## Project Instructions"]
117
- for (const f of files) {
118
- const relative = path.relative(workspaceRoot, f.path)
119
- const label = relative ? `./${relative}` : f.path
120
- lines.push(`### ${label}`, "```md", f.content.slice(0, 4000), "```")
121
- }
122
- return lines.join("\n")
123
- }
@@ -1,11 +0,0 @@
1
- import { buildContextPrompt, type ContextResult } from "./builder"
2
-
3
- export type { ContextResult }
4
-
5
- export async function getContext(
6
- workspaceRoot: string,
7
- cwd: string,
8
- options?: { useCache?: boolean }
9
- ): Promise<ContextResult> {
10
- return buildContextPrompt(workspaceRoot, cwd, options)
11
- }
@@ -1,58 +0,0 @@
1
- import { execSync } from "child_process";
2
- import { join } from "path";
3
-
4
- export interface GitContext {
5
- available: boolean;
6
- repoRoot: string | null;
7
- branch: string | null;
8
- recentCommit: string | null;
9
- status: string | null;
10
- }
11
-
12
- export function collectGitContext(workspaceRoot: string): GitContext {
13
- try {
14
- const gitDir = execSync("git rev-parse --show-toplevel 2>/dev/null", {
15
- cwd: workspaceRoot,
16
- encoding: "utf-8",
17
- timeout: 5000,
18
- }).trim();
19
-
20
- if (!gitDir) {
21
- return { available: false, repoRoot: null, branch: null, recentCommit: null, status: null };
22
- }
23
-
24
- const branch = execSync("git rev-parse --abbrev-ref HEAD 2>/dev/null", {
25
- cwd: gitDir,
26
- encoding: "utf-8",
27
- timeout: 5000,
28
- }).trim();
29
-
30
- const recentCommit = execSync("git log -1 --oneline 2>/dev/null", {
31
- cwd: gitDir,
32
- encoding: "utf-8",
33
- timeout: 5000,
34
- }).trim();
35
-
36
- let status: string | null = null;
37
- try {
38
- const raw = execSync("git status --short 2>/dev/null", {
39
- cwd: gitDir,
40
- encoding: "utf-8",
41
- timeout: 5000,
42
- }).trim();
43
- if (raw) {
44
- const lines = raw.split("\n").slice(0, 30);
45
- status = lines.join("\n");
46
- if (raw.split("\n").length > 30) {
47
- status += `\n... ${raw.split("\n").length - 30} more files`;
48
- }
49
- }
50
- } catch {
51
- // status unavailable, non-critical
52
- }
53
-
54
- return { available: true, repoRoot: gitDir, branch, recentCommit, status };
55
- } catch {
56
- return { available: false, repoRoot: null, branch: null, recentCommit: null, status: null };
57
- }
58
- }
@@ -1,48 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
-
4
- export interface MarkdownFile {
5
- path: string;
6
- content: string;
7
- }
8
-
9
- const FILENAMES = ["LLMTUNE.md", "CLAUDE.md", ".llmtune", ".claude"];
10
-
11
- export function loadProjectInstructions(
12
- workspaceRoot: string,
13
- cwd: string
14
- ): MarkdownFile[] {
15
- const files: MarkdownFile[] = [];
16
- const seen = new Set<string>();
17
-
18
- const dirsToCheck = [cwd];
19
- let current = cwd;
20
- while (current !== workspaceRoot && current !== path.dirname(current)) {
21
- current = path.dirname(current);
22
- dirsToCheck.push(current);
23
- }
24
- dirsToCheck.push(workspaceRoot);
25
-
26
- for (const dir of dirsToCheck) {
27
- for (const filename of FILENAMES) {
28
- const filePath = path.join(dir, filename);
29
- const resolved = path.resolve(filePath);
30
- if (seen.has(resolved)) continue;
31
-
32
- try {
33
- const stat = fs.statSync(filePath);
34
- if (stat.isFile()) {
35
- const content = fs.readFileSync(filePath, "utf-8");
36
- if (content.trim()) {
37
- seen.add(resolved);
38
- files.push({ path: resolved, content: content.trim() });
39
- }
40
- }
41
- } catch {
42
- // file doesn't exist, skip
43
- }
44
- }
45
- }
46
-
47
- return files;
48
- }
@@ -1,139 +0,0 @@
1
- import { readdir, stat } from "fs/promises";
2
- import { join, relative, extname } from "path";
3
-
4
- export interface WorkspaceSnapshot {
5
- workspace_root: string;
6
- current_directory: string;
7
- top_level_entries: string[];
8
- key_files: string[];
9
- total_files: number;
10
- file_type_counts: Record<string, number>;
11
- }
12
-
13
- const KEY_FILE_NAMES = new Set([
14
- "package.json",
15
- "tsconfig.json",
16
- "Cargo.toml",
17
- "go.mod",
18
- "pyproject.toml",
19
- "requirements.txt",
20
- "Makefile",
21
- "Dockerfile",
22
- "docker-compose.yml",
23
- "README.md",
24
- "CLAUDE.md",
25
- "LLMTUNE.md",
26
- ".env.example",
27
- ".gitignore",
28
- "prisma",
29
- "schema.prisma",
30
- ]);
31
-
32
- const IGNORED_DIRS = new Set([
33
- "node_modules",
34
- ".git",
35
- "__pycache__",
36
- ".next",
37
- "dist",
38
- "build",
39
- ".venv",
40
- "venv",
41
- ".tox",
42
- "target",
43
- ".cache",
44
- ".turbo",
45
- "coverage",
46
- ]);
47
-
48
- export async function buildWorkspaceSnapshot(
49
- workspaceRoot: string,
50
- cwd?: string,
51
- ): Promise<WorkspaceSnapshot> {
52
- const root = workspaceRoot;
53
- const current = cwd ?? workspaceRoot;
54
-
55
- const topEntries = await safeReaddir(root);
56
- const topLevelEntries = topEntries.map((e) => e.name);
57
-
58
- const keyFiles: string[] = [];
59
- const fileTypeCounts: Record<string, number> = {};
60
- let totalFiles = 0;
61
-
62
- await walkDir(root, "", async (name, relPath, isDir) => {
63
- if (isDir) return;
64
- totalFiles++;
65
- const ext = extname(name).toLowerCase();
66
- fileTypeCounts[ext] = (fileTypeCounts[ext] ?? 0) + 1;
67
- if (KEY_FILE_NAMES.has(name)) {
68
- keyFiles.push(relative(root, join(root, relPath, name)));
69
- }
70
- });
71
-
72
- return {
73
- workspace_root: root,
74
- current_directory: current,
75
- top_level_entries: topLevelEntries.slice(0, 30),
76
- key_files: keyFiles.slice(0, 20),
77
- total_files: totalFiles,
78
- file_type_counts: fileTypeCounts,
79
- };
80
- }
81
-
82
- export function renderWorkspaceSection(snapshot: WorkspaceSnapshot): string {
83
- const today = new Date().toISOString().split("T")[0];
84
- const lines = [
85
- "## Runtime Context",
86
- `- Today's date: ${today}`,
87
- `- Workspace root: ${snapshot.workspace_root}`,
88
- `- Current directory: ${snapshot.current_directory}`,
89
- `- Total files: ${snapshot.total_files}`,
90
- ];
91
-
92
- const topExts = Object.entries(snapshot.file_type_counts)
93
- .sort((a, b) => b[1] - a[1])
94
- .slice(0, 5)
95
- .map(([ext, count]) => `${ext || "none"}: ${count}`);
96
- if (topExts.length > 0) {
97
- lines.push(`- Top file types: ${topExts.join(", ")}`);
98
- }
99
-
100
- if (snapshot.key_files.length > 0) {
101
- lines.push(`- Key files: ${snapshot.key_files.join(", ")}`);
102
- }
103
-
104
- if (snapshot.top_level_entries.length > 0) {
105
- lines.push(
106
- `- Top-level entries: ${snapshot.top_level_entries.slice(0, 15).join(", ")}`,
107
- );
108
- }
109
-
110
- return lines.join("\n");
111
- }
112
-
113
- async function walkDir(
114
- root: string,
115
- relDir: string,
116
- visitor: (name: string, relPath: string, isDir: boolean) => Promise<void>,
117
- depth = 0,
118
- ): Promise<void> {
119
- if (depth > 3) return;
120
- const entries = await safeReaddir(join(root, relDir));
121
- for (const entry of entries) {
122
- if (IGNORED_DIRS.has(entry.name)) continue;
123
- const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
124
- const isDir = entry.isDirectory();
125
- await visitor(entry.name, relDir, isDir);
126
- if (isDir && depth < 3) {
127
- await walkDir(root, relPath, visitor, depth + 1);
128
- }
129
- }
130
- }
131
-
132
- async function safeReaddir(dirPath: string) {
133
- try {
134
- const entries = await readdir(dirPath, { withFileTypes: true });
135
- return entries;
136
- } catch {
137
- return [];
138
- }
139
- }
package/src/index.ts DELETED
@@ -1,100 +0,0 @@
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()