@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/skills/registry.ts
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs"
|
|
2
|
-
import * as path from "path"
|
|
3
|
-
import * as os from "os"
|
|
4
|
-
import { parseFrontmatter } from "./frontmatter"
|
|
5
|
-
import { substituteArguments } from "./args"
|
|
6
|
-
|
|
7
|
-
export interface Skill {
|
|
8
|
-
name: string
|
|
9
|
-
description: string
|
|
10
|
-
content: string
|
|
11
|
-
loadedFrom: "user" | "project" | "managed"
|
|
12
|
-
userInvocable: boolean
|
|
13
|
-
allowedTools: string[]
|
|
14
|
-
argNames: string[]
|
|
15
|
-
trustLevel: TrustLevel
|
|
16
|
-
skillRoot: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export type TrustLevel = "local" | "community" | "verified" | "signed"
|
|
20
|
-
|
|
21
|
-
export interface SkillExecution {
|
|
22
|
-
systemPrompt: string
|
|
23
|
-
userMessage: string
|
|
24
|
-
allowedTools?: string[]
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const SKILL_DIRS = () => {
|
|
28
|
-
const dirs: string[] = []
|
|
29
|
-
const primary = path.join(os.homedir(), ".llmtune", "skills")
|
|
30
|
-
dirs.push(primary)
|
|
31
|
-
return dirs
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export class SkillRegistry {
|
|
35
|
-
private skills: Map<string, Skill> = new Map()
|
|
36
|
-
|
|
37
|
-
loadAll(projectRoot?: string): void {
|
|
38
|
-
this.skills.clear()
|
|
39
|
-
|
|
40
|
-
for (const dir of SKILL_DIRS()) {
|
|
41
|
-
this.loadFromDir(dir, "user")
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const managedEnv = process.env.LLMTUNE_MANAGED_SKILLS_DIR
|
|
45
|
-
if (managedEnv) {
|
|
46
|
-
this.loadFromDir(managedEnv, "managed")
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (projectRoot) {
|
|
50
|
-
const projectDir = path.join(projectRoot, ".llmtune", "skills")
|
|
51
|
-
this.loadFromDir(projectDir, "project")
|
|
52
|
-
const compatDir = path.join(projectRoot, ".claude", "skills")
|
|
53
|
-
this.loadFromDir(compatDir, "project")
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
private loadFromDir(baseDir: string, loadedFrom: Skill["loadedFrom"]): void {
|
|
58
|
-
const resolved = path.resolve(baseDir)
|
|
59
|
-
if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) return
|
|
60
|
-
|
|
61
|
-
for (const entry of fs.readdirSync(resolved).sort()) {
|
|
62
|
-
const skillPath = path.join(resolved, entry)
|
|
63
|
-
if (!fs.statSync(skillPath).isDirectory()) continue
|
|
64
|
-
|
|
65
|
-
const mdPath = path.join(skillPath, "SKILL.md")
|
|
66
|
-
if (!fs.existsSync(mdPath)) continue
|
|
67
|
-
|
|
68
|
-
const raw = fs.readFileSync(mdPath, "utf-8")
|
|
69
|
-
const { frontmatter, body } = parseFrontmatter(raw)
|
|
70
|
-
|
|
71
|
-
const description = String(frontmatter.description ?? extractFirstLine(body) ?? `Skill: ${entry}`)
|
|
72
|
-
const userInvocable = frontmatter["user-invocable"] !== false
|
|
73
|
-
const allowedTools = asStrList(frontmatter["allowed-tools"])
|
|
74
|
-
const argNames = parseArgNames(frontmatter.arguments)
|
|
75
|
-
const trustLevel = resolveTrustLevel(frontmatter.trust, loadedFrom)
|
|
76
|
-
|
|
77
|
-
const skill: Skill = {
|
|
78
|
-
name: entry,
|
|
79
|
-
description,
|
|
80
|
-
content: body,
|
|
81
|
-
loadedFrom,
|
|
82
|
-
userInvocable,
|
|
83
|
-
allowedTools,
|
|
84
|
-
argNames,
|
|
85
|
-
trustLevel,
|
|
86
|
-
skillRoot: skillPath,
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (!this.skills.has(entry.toLowerCase())) {
|
|
90
|
-
this.skills.set(entry.toLowerCase(), skill)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
get(name: string): Skill | undefined {
|
|
96
|
-
return this.skills.get(name.toLowerCase())
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
list(): Skill[] {
|
|
100
|
-
return Array.from(this.skills.values())
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
listUserInvocable(): Skill[] {
|
|
104
|
-
return this.list().filter((s) => s.userInvocable)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
prepareExecution(name: string, args: string[]): SkillExecution | null {
|
|
108
|
-
const skill = this.get(name)
|
|
109
|
-
if (!skill) return null
|
|
110
|
-
|
|
111
|
-
const argMap: Record<string, string> = {}
|
|
112
|
-
for (let i = 0; i < Math.min(args.length, skill.argNames.length); i++) {
|
|
113
|
-
argMap[skill.argNames[i]] = args[i]
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const userMessage = substituteArguments(skill.content, argMap)
|
|
117
|
-
|
|
118
|
-
return {
|
|
119
|
-
systemPrompt: `You are executing the "${skill.name}" skill. ${skill.description}`,
|
|
120
|
-
userMessage,
|
|
121
|
-
allowedTools: skill.allowedTools.length > 0 ? skill.allowedTools : undefined,
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function resolveTrustLevel(raw: unknown, loadedFrom: Skill["loadedFrom"]): TrustLevel {
|
|
127
|
-
const str = String(raw ?? "").toLowerCase().trim()
|
|
128
|
-
if (str === "signed") return "signed"
|
|
129
|
-
if (str === "verified") return "verified"
|
|
130
|
-
if (str === "community") return "community"
|
|
131
|
-
if (loadedFrom === "user" || loadedFrom === "project") return "local"
|
|
132
|
-
return "community"
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function extractFirstLine(body: string): string | null {
|
|
136
|
-
for (const line of body.split("\n")) {
|
|
137
|
-
const stripped = line.trim()
|
|
138
|
-
if (!stripped || stripped.startsWith("#")) continue
|
|
139
|
-
return stripped.slice(0, 200)
|
|
140
|
-
}
|
|
141
|
-
return null
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function parseArgNames(raw: unknown): string[] {
|
|
145
|
-
return asStrList(raw)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function asStrList(val: unknown): string[] {
|
|
149
|
-
if (val == null) return []
|
|
150
|
-
if (Array.isArray(val)) return val.map(String).filter(Boolean)
|
|
151
|
-
const s = String(val).trim()
|
|
152
|
-
if (!s) return []
|
|
153
|
-
if (s.includes(",")) return s.split(",").map((x) => x.trim()).filter(Boolean)
|
|
154
|
-
return [s]
|
|
155
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs"
|
|
2
|
-
import * as path from "path"
|
|
3
|
-
import * as crypto from "crypto"
|
|
4
|
-
|
|
5
|
-
export interface SkillSignature {
|
|
6
|
-
algorithm: string
|
|
7
|
-
signature: string
|
|
8
|
-
publicKey: string
|
|
9
|
-
signedAt: string
|
|
10
|
-
skillName: string
|
|
11
|
-
contentHash: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const SIGNATURE_FILE = "SIGNATURE.json"
|
|
15
|
-
|
|
16
|
-
export function hashSkillContent(skillDir: string): string {
|
|
17
|
-
const mdPath = path.join(skillDir, "SKILL.md")
|
|
18
|
-
if (!fs.existsSync(mdPath)) {
|
|
19
|
-
throw new Error(`No SKILL.md found in ${skillDir}`)
|
|
20
|
-
}
|
|
21
|
-
const content = fs.readFileSync(mdPath, "utf-8")
|
|
22
|
-
return crypto.createHash("sha256").update(content).digest("hex")
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function signSkill(
|
|
26
|
-
skillDir: string,
|
|
27
|
-
privateKey: string,
|
|
28
|
-
publicKey: string,
|
|
29
|
-
): SkillSignature {
|
|
30
|
-
const contentHash = hashSkillContent(skillDir)
|
|
31
|
-
const skillName = path.basename(skillDir)
|
|
32
|
-
|
|
33
|
-
const signature = crypto
|
|
34
|
-
.createSign("sha256")
|
|
35
|
-
.update(contentHash)
|
|
36
|
-
.sign(privateKey, "base64")
|
|
37
|
-
|
|
38
|
-
const sig: SkillSignature = {
|
|
39
|
-
algorithm: "sha256-rsa",
|
|
40
|
-
signature,
|
|
41
|
-
publicKey,
|
|
42
|
-
signedAt: new Date().toISOString(),
|
|
43
|
-
skillName,
|
|
44
|
-
contentHash,
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const sigPath = path.join(skillDir, SIGNATURE_FILE)
|
|
48
|
-
fs.writeFileSync(sigPath, JSON.stringify(sig, null, 2), "utf-8")
|
|
49
|
-
|
|
50
|
-
return sig
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function verifySkill(
|
|
54
|
-
skillDir: string,
|
|
55
|
-
expectedPublicKey?: string,
|
|
56
|
-
): { valid: boolean; reason?: string } {
|
|
57
|
-
const sigPath = path.join(skillDir, SIGNATURE_FILE)
|
|
58
|
-
const mdPath = path.join(skillDir, "SKILL.md")
|
|
59
|
-
|
|
60
|
-
if (!fs.existsSync(sigPath)) {
|
|
61
|
-
return { valid: false, reason: "No signature file found" }
|
|
62
|
-
}
|
|
63
|
-
if (!fs.existsSync(mdPath)) {
|
|
64
|
-
return { valid: false, reason: "No SKILL.md found" }
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
const sig: SkillSignature = JSON.parse(fs.readFileSync(sigPath, "utf-8"))
|
|
69
|
-
const content = fs.readFileSync(mdPath, "utf-8")
|
|
70
|
-
const contentHash = crypto.createHash("sha256").update(content).digest("hex")
|
|
71
|
-
|
|
72
|
-
if (contentHash !== sig.contentHash) {
|
|
73
|
-
return { valid: false, reason: "Content hash mismatch - skill has been modified" }
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (expectedPublicKey && sig.publicKey !== expectedPublicKey) {
|
|
77
|
-
return { valid: false, reason: "Public key mismatch - signed by different author" }
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const verifier = crypto.createVerify("sha256")
|
|
81
|
-
verifier.update(sig.contentHash)
|
|
82
|
-
const isValid = verifier.verify(sig.publicKey, sig.signature, "base64")
|
|
83
|
-
|
|
84
|
-
if (!isValid) {
|
|
85
|
-
return { valid: false, reason: "Signature verification failed" }
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return { valid: true }
|
|
89
|
-
} catch (err) {
|
|
90
|
-
return { valid: false, reason: `Verification error: ${(err as Error).message}` }
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function generateKeyPair(): { publicKey: string; privateKey: string } {
|
|
95
|
-
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
|
|
96
|
-
modulusLength: 2048,
|
|
97
|
-
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
98
|
-
privateKeyEncoding: { type: "pkcs8", format: "pem" },
|
|
99
|
-
})
|
|
100
|
-
return { publicKey, privateKey }
|
|
101
|
-
}
|
package/src/skills/trust.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type { Skill } from "./registry"
|
|
2
|
-
|
|
3
|
-
export type TrustLevel = "local" | "community" | "verified" | "signed"
|
|
4
|
-
|
|
5
|
-
export interface TrustPolicy {
|
|
6
|
-
allowedTools: string[]
|
|
7
|
-
canExecuteBash: boolean
|
|
8
|
-
canWriteFiles: boolean
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const POLICIES: Record<TrustLevel, TrustPolicy> = {
|
|
12
|
-
local: {
|
|
13
|
-
allowedTools: ["read", "write", "edit", "bash", "glob", "grep"],
|
|
14
|
-
canExecuteBash: true,
|
|
15
|
-
canWriteFiles: true,
|
|
16
|
-
},
|
|
17
|
-
community: {
|
|
18
|
-
allowedTools: ["read", "glob", "grep"],
|
|
19
|
-
canExecuteBash: false,
|
|
20
|
-
canWriteFiles: false,
|
|
21
|
-
},
|
|
22
|
-
verified: {
|
|
23
|
-
allowedTools: ["read", "write", "edit", "bash", "glob", "grep"],
|
|
24
|
-
canExecuteBash: true,
|
|
25
|
-
canWriteFiles: true,
|
|
26
|
-
},
|
|
27
|
-
signed: {
|
|
28
|
-
allowedTools: ["read", "write", "edit", "bash", "glob", "grep"],
|
|
29
|
-
canExecuteBash: true,
|
|
30
|
-
canWriteFiles: true,
|
|
31
|
-
},
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function getTrustPolicy(level: TrustLevel): TrustPolicy {
|
|
35
|
-
return POLICIES[level]
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function resolveTrustLevel(skill: Skill): TrustLevel {
|
|
39
|
-
if (skill.loadedFrom === "project" || skill.loadedFrom === "user") return "local"
|
|
40
|
-
if (skill.trustLevel) return skill.trustLevel
|
|
41
|
-
return "community"
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function isToolAllowedForSkill(
|
|
45
|
-
toolName: string,
|
|
46
|
-
trustLevel: TrustLevel
|
|
47
|
-
): boolean {
|
|
48
|
-
const policy = POLICIES[trustLevel]
|
|
49
|
-
return policy.allowedTools.includes(toolName.toLowerCase())
|
|
50
|
-
}
|
package/src/telemetry/logger.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs"
|
|
2
|
-
import * as path from "path"
|
|
3
|
-
import * as os from "os"
|
|
4
|
-
import { getConfigDir } from "../auth/config"
|
|
5
|
-
|
|
6
|
-
export type TelemetryEvent =
|
|
7
|
-
| { event: "tool_call"; tool: string; latency_ms: number; input_preview?: string }
|
|
8
|
-
| { event: "llm_response"; tokens_in: number; tokens_out: number; cost: number; model: string; latency_ms: number }
|
|
9
|
-
| { event: "compaction"; tokens_saved: number; messages_before: number; messages_after: number; trigger: string }
|
|
10
|
-
| { event: "error"; source: string; error: string; tool?: string }
|
|
11
|
-
| { event: "session_start"; model: string; tools: string[]; cwd: string }
|
|
12
|
-
| { event: "session_end"; duration_ms: number; total_tool_calls: number; total_tokens: number }
|
|
13
|
-
|
|
14
|
-
interface SessionLog {
|
|
15
|
-
sessionId: string
|
|
16
|
-
startedAt: string
|
|
17
|
-
events: TelemetryEvent[]
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
let currentSession: SessionLog | null = null
|
|
21
|
-
let sessionStartTime = 0
|
|
22
|
-
|
|
23
|
-
function getLogsDir(): string {
|
|
24
|
-
const base = process.env.LLMTUNE_LOGS_DIR || path.join(getConfigDir(), "logs")
|
|
25
|
-
if (!fs.existsSync(base)) {
|
|
26
|
-
fs.mkdirSync(base, { recursive: true })
|
|
27
|
-
}
|
|
28
|
-
return base
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function getSessionLogPath(sessionId: string): string {
|
|
32
|
-
return path.join(getLogsDir(), `${sessionId}.jsonl`)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function startSessionLog(sessionId: string, meta: { model: string; tools: string[]; cwd: string }): void {
|
|
36
|
-
currentSession = {
|
|
37
|
-
sessionId,
|
|
38
|
-
startedAt: new Date().toISOString(),
|
|
39
|
-
events: [],
|
|
40
|
-
}
|
|
41
|
-
sessionStartTime = Date.now()
|
|
42
|
-
logEvent({ event: "session_start", model: meta.model, tools: meta.tools, cwd: meta.cwd })
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function logEvent(event: TelemetryEvent): void {
|
|
46
|
-
if (!currentSession) return
|
|
47
|
-
currentSession.events.push(event)
|
|
48
|
-
|
|
49
|
-
const entry = {
|
|
50
|
-
ts: new Date().toISOString(),
|
|
51
|
-
session_id: currentSession.sessionId,
|
|
52
|
-
...event,
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
const logPath = getSessionLogPath(currentSession.sessionId)
|
|
57
|
-
fs.appendFileSync(logPath, JSON.stringify(entry) + "\n", "utf-8")
|
|
58
|
-
} catch {
|
|
59
|
-
// Telemetry write failure is non-critical
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function endSessionLog(stats: { totalToolCalls: number; totalTokens: number }): void {
|
|
64
|
-
if (!currentSession) return
|
|
65
|
-
const durationMs = Date.now() - sessionStartTime
|
|
66
|
-
logEvent({
|
|
67
|
-
event: "session_end",
|
|
68
|
-
duration_ms: durationMs,
|
|
69
|
-
total_tool_calls: stats.totalToolCalls,
|
|
70
|
-
total_tokens: stats.totalTokens,
|
|
71
|
-
})
|
|
72
|
-
currentSession = null
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function getSessionLogs(sessionId: string): TelemetryEvent[] {
|
|
76
|
-
const logPath = getSessionLogPath(sessionId)
|
|
77
|
-
try {
|
|
78
|
-
if (!fs.existsSync(logPath)) return []
|
|
79
|
-
const lines = fs.readFileSync(logPath, "utf-8").split("\n").filter(Boolean)
|
|
80
|
-
return lines.map((line) => {
|
|
81
|
-
try { return JSON.parse(line) as TelemetryEvent }
|
|
82
|
-
catch { return null }
|
|
83
|
-
}).filter((e): e is TelemetryEvent => e !== null)
|
|
84
|
-
} catch {
|
|
85
|
-
return []
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function listSessionLogs(): Array<{ sessionId: string; size: number; modified: string }> {
|
|
90
|
-
const dir = getLogsDir()
|
|
91
|
-
try {
|
|
92
|
-
if (!fs.existsSync(dir)) return []
|
|
93
|
-
return fs.readdirSync(dir)
|
|
94
|
-
.filter((f) => f.endsWith(".jsonl"))
|
|
95
|
-
.map((f) => {
|
|
96
|
-
const fullPath = path.join(dir, f)
|
|
97
|
-
const stat = fs.statSync(fullPath)
|
|
98
|
-
return {
|
|
99
|
-
sessionId: f.replace(".jsonl", ""),
|
|
100
|
-
size: stat.size,
|
|
101
|
-
modified: stat.mtime.toISOString(),
|
|
102
|
-
}
|
|
103
|
-
})
|
|
104
|
-
.sort((a, b) => b.modified.localeCompare(a.modified))
|
|
105
|
-
} catch {
|
|
106
|
-
return []
|
|
107
|
-
}
|
|
108
|
-
}
|
package/src/tools/permissions.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import inquirer from "@inquirer/prompts"
|
|
2
|
-
import chalk from "chalk"
|
|
3
|
-
|
|
4
|
-
export type PermissionBehavior = "allow" | "deny" | "ask"
|
|
5
|
-
|
|
6
|
-
export interface PermissionCheckResult {
|
|
7
|
-
behavior: PermissionBehavior
|
|
8
|
-
message?: string
|
|
9
|
-
suggestion?: string
|
|
10
|
-
updatedInput?: Record<string, unknown>
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface PermissionConfig {
|
|
14
|
-
allowedTools: Set<string>
|
|
15
|
-
deniedTools: Set<string>
|
|
16
|
-
sessionTrust: Map<string, boolean>
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export class PermissionManager {
|
|
20
|
-
private config: PermissionConfig
|
|
21
|
-
|
|
22
|
-
constructor() {
|
|
23
|
-
this.config = {
|
|
24
|
-
allowedTools: new Set(),
|
|
25
|
-
deniedTools: new Set(),
|
|
26
|
-
sessionTrust: new Map(),
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
trustTool(toolName: string): void {
|
|
31
|
-
this.config.sessionTrust.set(toolName, true)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
isTrusted(toolName: string): boolean {
|
|
35
|
-
return this.config.sessionTrust.get(toolName) === true
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async check(
|
|
39
|
-
toolName: string,
|
|
40
|
-
input: Record<string, unknown>,
|
|
41
|
-
isDestructive: boolean
|
|
42
|
-
): Promise<PermissionCheckResult> {
|
|
43
|
-
if (this.isTrusted(toolName)) {
|
|
44
|
-
return { behavior: "allow" }
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (this.config.deniedTools.has(toolName)) {
|
|
48
|
-
return { behavior: "deny", message: `Tool '${toolName}' is denied` }
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (isDestructive) {
|
|
52
|
-
const command = toolName === "bash" ? (input.command as string) : ""
|
|
53
|
-
const preview = command
|
|
54
|
-
? command.slice(0, 80) + (command.length > 80 ? "..." : "")
|
|
55
|
-
: JSON.stringify(input).slice(0, 100)
|
|
56
|
-
|
|
57
|
-
console.log(chalk.yellow(`\n⚠ ${toolName} wants to execute:`))
|
|
58
|
-
console.log(chalk.dim(preview))
|
|
59
|
-
|
|
60
|
-
const confirmed = await inquirer.confirm({
|
|
61
|
-
message: `Allow ${toolName}? (y/N)`,
|
|
62
|
-
default: false,
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
if (!confirmed) {
|
|
66
|
-
return { behavior: "deny", message: "User denied" }
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const alwaysTrust = await inquirer.confirm({
|
|
70
|
-
message: `Trust ${toolName} for this session? (y/N)`,
|
|
71
|
-
default: false,
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
if (alwaysTrust) {
|
|
75
|
-
this.trustTool(toolName)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return { behavior: "allow" }
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return { behavior: "allow" }
|
|
82
|
-
}
|
|
83
|
-
}
|
package/src/tools/protocol.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export interface ToolSpec {
|
|
2
|
-
name: string
|
|
3
|
-
description: string
|
|
4
|
-
inputSchema: Record<string, unknown>
|
|
5
|
-
isReadOnly?: boolean
|
|
6
|
-
isDestructive?: boolean
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface ToolResult {
|
|
10
|
-
name: string
|
|
11
|
-
output: unknown
|
|
12
|
-
isError: boolean
|
|
13
|
-
toolUseId?: string
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface ToolContext {
|
|
17
|
-
cwd: string
|
|
18
|
-
workspaceRoot: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface Tool {
|
|
22
|
-
spec(): ToolSpec
|
|
23
|
-
run(input: Record<string, unknown>, ctx: ToolContext): ToolResult | Promise<ToolResult>
|
|
24
|
-
}
|
package/src/tools/registry.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import type { Tool, ToolSpec, ToolResult, ToolContext } from "./protocol"
|
|
2
|
-
import { validateJsonSchema } from "./validation"
|
|
3
|
-
|
|
4
|
-
export type { Tool, ToolSpec, ToolResult, ToolContext }
|
|
5
|
-
|
|
6
|
-
export interface ToolDefinition {
|
|
7
|
-
name: string
|
|
8
|
-
description: string
|
|
9
|
-
inputSchema: Record<string, unknown>
|
|
10
|
-
isReadOnly?: boolean
|
|
11
|
-
isDestructive?: boolean
|
|
12
|
-
run: (input: Record<string, unknown>, ctx: ToolContext) => ToolResult | Promise<ToolResult>
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
class ToolInstance implements Tool {
|
|
16
|
-
private def: ToolDefinition
|
|
17
|
-
constructor(def: ToolDefinition) { this.def = def }
|
|
18
|
-
spec(): ToolSpec {
|
|
19
|
-
return {
|
|
20
|
-
name: this.def.name,
|
|
21
|
-
description: this.def.description,
|
|
22
|
-
inputSchema: this.def.inputSchema,
|
|
23
|
-
isReadOnly: this.def.isReadOnly,
|
|
24
|
-
isDestructive: this.def.isDestructive,
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
run(input: Record<string, unknown>, ctx: ToolContext): ToolResult | Promise<ToolResult> {
|
|
28
|
-
return this.def.run(input, ctx)
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function createTool(def: ToolDefinition): Tool {
|
|
33
|
-
return new ToolInstance(def)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export class ToolRegistry {
|
|
37
|
-
private tools: Map<string, Tool> = new Map()
|
|
38
|
-
|
|
39
|
-
register(tool: Tool): void {
|
|
40
|
-
const spec = tool.spec()
|
|
41
|
-
const key = spec.name.toLowerCase()
|
|
42
|
-
if (this.tools.has(key)) {
|
|
43
|
-
throw new Error(`duplicate tool name: ${spec.name}`)
|
|
44
|
-
}
|
|
45
|
-
this.tools.set(key, tool)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
listSpecs(): ToolSpec[] {
|
|
49
|
-
return Array.from(this.tools.values()).map((t) => t.spec())
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
get(name: string): Tool | undefined {
|
|
53
|
-
return this.tools.get(name.toLowerCase())
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
dispatch(name: string, input: Record<string, unknown>, ctx: ToolContext): ToolResult {
|
|
57
|
-
const tool = this.get(name)
|
|
58
|
-
if (!tool) {
|
|
59
|
-
return { name, output: { error: `unknown tool: ${name}` }, isError: true }
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const spec = tool.spec()
|
|
63
|
-
try {
|
|
64
|
-
validateJsonSchema(input, spec.inputSchema)
|
|
65
|
-
} catch (err: unknown) {
|
|
66
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
67
|
-
return { name: spec.name, output: { error: msg }, isError: true }
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const result = tool.run(input, ctx)
|
|
71
|
-
if (result instanceof Promise) {
|
|
72
|
-
throw new Error("Async tools not supported in sync dispatch. Use dispatchAsync instead.")
|
|
73
|
-
}
|
|
74
|
-
return result
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async dispatchAsync(name: string, input: Record<string, unknown>, ctx: ToolContext): Promise<ToolResult> {
|
|
78
|
-
const tool = this.get(name)
|
|
79
|
-
if (!tool) {
|
|
80
|
-
return { name, output: { error: `unknown tool: ${name}` }, isError: true }
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const spec = tool.spec()
|
|
84
|
-
try {
|
|
85
|
-
validateJsonSchema(input, spec.inputSchema)
|
|
86
|
-
} catch (err: unknown) {
|
|
87
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
88
|
-
return { name: spec.name, output: { error: msg }, isError: true }
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return tool.run(input, ctx)
|
|
92
|
-
}
|
|
93
|
-
}
|