@llmtune/cli 0.1.3 → 0.1.6
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 +2 -2
- package/dist/agent/loop.d.ts +5 -0
- package/dist/agent/loop.js +163 -89
- package/dist/auth/client.js +9 -0
- package/dist/compact/auto-compact.d.ts +25 -0
- package/dist/compact/auto-compact.js +65 -0
- package/dist/compact/budget.d.ts +8 -0
- package/dist/compact/budget.js +36 -0
- package/dist/compact/service.d.ts +10 -2
- package/dist/compact/service.js +78 -19
- package/dist/context/agent-identity.js +2 -1
- package/dist/marketplace/client.js +18 -6
- package/dist/memory/service.d.ts +3 -0
- package/dist/memory/service.js +16 -0
- package/dist/repl/repl.js +15 -8
- package/llmtune-session-1780260929719.json +6 -0
- package/package.json +3 -2
- package/scripts/qa-full.js +440 -0
- package/scripts/smoke-test.js +142 -0
package/dist/compact/service.js
CHANGED
|
@@ -33,9 +33,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.extractActiveTask = extractActiveTask;
|
|
36
37
|
exports.compactConversation = compactConversation;
|
|
37
38
|
exports.uncompactConversation = uncompactConversation;
|
|
38
39
|
const tokens_1 = require("../utils/tokens");
|
|
40
|
+
const budget_1 = require("./budget");
|
|
41
|
+
const service_1 = require("../memory/service");
|
|
39
42
|
const fs = __importStar(require("fs"));
|
|
40
43
|
const path = __importStar(require("path"));
|
|
41
44
|
const os = __importStar(require("os"));
|
|
@@ -53,22 +56,55 @@ Include:
|
|
|
53
56
|
7. Pending Tasks: Tasks explicitly requested but not yet done
|
|
54
57
|
8. Current Work: What was being worked on immediately before this summary
|
|
55
58
|
|
|
59
|
+
The user's ACTIVE TASK must appear verbatim under "Current Work" and "Pending Tasks" if not finished.
|
|
60
|
+
|
|
56
61
|
Respond ONLY with plain text. No XML tags. No tool calls.`;
|
|
57
|
-
|
|
62
|
+
function extractActiveTask(messages) {
|
|
63
|
+
const userMsgs = messages
|
|
64
|
+
.filter((m) => m.role === "user")
|
|
65
|
+
.map((m) => (typeof m.content === "string" ? m.content.trim() : ""))
|
|
66
|
+
.filter(Boolean);
|
|
67
|
+
if (userMsgs.length === 0)
|
|
68
|
+
return "";
|
|
69
|
+
// Prefer the most recent substantive user message (skip one-word replies)
|
|
70
|
+
for (let i = userMsgs.length - 1; i >= 0; i--) {
|
|
71
|
+
const msg = userMsgs[i];
|
|
72
|
+
if (msg.length >= 12 && !msg.startsWith("/")) {
|
|
73
|
+
return msg.slice(0, 500);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return userMsgs[userMsgs.length - 1].slice(0, 500);
|
|
77
|
+
}
|
|
78
|
+
async function compactConversation(client, model, conversation, sessionsDir, options) {
|
|
79
|
+
const trigger = options?.trigger ?? "manual";
|
|
80
|
+
const keepTail = options?.keepTail ?? budget_1.KEEP_TAIL_MESSAGES;
|
|
58
81
|
const messages = conversation.messages;
|
|
82
|
+
const activeTask = options?.activeTask ?? extractActiveTask(messages);
|
|
83
|
+
if (activeTask) {
|
|
84
|
+
(0, service_1.saveActiveTask)(activeTask);
|
|
85
|
+
}
|
|
59
86
|
const preCompactTokens = (0, tokens_1.estimateTokens)(messages.map((m) => (typeof m.content === "string" ? m.content : JSON.stringify(m.content))).join(" "));
|
|
60
87
|
const preCompactCount = messages.length;
|
|
61
|
-
// Save raw history before compacting
|
|
62
88
|
saveRawHistory(conversation, sessionsDir);
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
|
|
89
|
+
const systemMessages = messages.filter((m) => m.role === "system");
|
|
90
|
+
const nonSystem = messages.filter((m) => m.role !== "system");
|
|
91
|
+
const tailMessages = nonSystem.slice(-keepTail);
|
|
92
|
+
const messagesToCompact = nonSystem.slice(0, -keepTail);
|
|
93
|
+
const summarySource = messagesToCompact.length > 0 ? messagesToCompact : nonSystem.slice(0, Math.max(0, nonSystem.length - 2));
|
|
94
|
+
const summaryRequestMessages = summarySource
|
|
95
|
+
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
96
|
+
.slice(-30)
|
|
67
97
|
.map((m) => ({
|
|
68
98
|
role: m.role,
|
|
69
|
-
content:
|
|
99
|
+
content: truncateForSummary(m),
|
|
70
100
|
}));
|
|
71
|
-
|
|
101
|
+
const taskBlock = activeTask
|
|
102
|
+
? `\n\nCRITICAL — preserve this active task in your summary:\n"${activeTask}"`
|
|
103
|
+
: "";
|
|
104
|
+
summaryRequestMessages.push({
|
|
105
|
+
role: "user",
|
|
106
|
+
content: `${COMPACT_SYSTEM_PROMPT}${taskBlock}`,
|
|
107
|
+
});
|
|
72
108
|
let summary = "";
|
|
73
109
|
try {
|
|
74
110
|
const response = await client.chat.completions.create({
|
|
@@ -80,24 +116,25 @@ async function compactConversation(client, model, conversation, sessionsDir) {
|
|
|
80
116
|
summary = response.choices[0]?.message?.content?.trim() ?? "";
|
|
81
117
|
}
|
|
82
118
|
catch {
|
|
83
|
-
summary = buildFallbackSummary(messages);
|
|
119
|
+
summary = buildFallbackSummary(messages, activeTask);
|
|
84
120
|
}
|
|
85
121
|
if (!summary) {
|
|
86
|
-
summary = buildFallbackSummary(messages);
|
|
122
|
+
summary = buildFallbackSummary(messages, activeTask);
|
|
123
|
+
}
|
|
124
|
+
if (activeTask) {
|
|
125
|
+
summary = `## Active Task\n${activeTask}\n\n${summary}`;
|
|
87
126
|
}
|
|
88
|
-
// Replace conversation with boundary marker + summary
|
|
89
127
|
const boundaryMsg = {
|
|
90
128
|
role: "system",
|
|
91
|
-
content: `[COMPACT BOUNDARY] Compacted at ${new Date().toISOString()}.
|
|
129
|
+
content: `[COMPACT BOUNDARY] ${trigger === "auto" ? "Auto-compacted" : "Compacted"} at ${new Date().toISOString()}. ` +
|
|
130
|
+
`${preCompactCount} messages summarized (${tailMessages.length} recent messages kept). Raw history preserved.`,
|
|
92
131
|
};
|
|
93
132
|
const summaryMsg = {
|
|
94
133
|
role: "system",
|
|
95
134
|
content: `## Conversation Summary\n\n${summary}`,
|
|
96
135
|
};
|
|
97
|
-
// Keep system messages + boundary + summary
|
|
98
|
-
const systemMessages = messages.filter((m) => m.role === "system" && m === messages[0]);
|
|
99
136
|
conversation.messages.length = 0;
|
|
100
|
-
conversation.messages.push(...systemMessages, boundaryMsg, summaryMsg);
|
|
137
|
+
conversation.messages.push(...systemMessages, boundaryMsg, summaryMsg, ...tailMessages);
|
|
101
138
|
const postCompactTokens = (0, tokens_1.estimateTokens)(conversation.messages.map((m) => (typeof m.content === "string" ? m.content : JSON.stringify(m.content))).join(" "));
|
|
102
139
|
const postCompactCount = conversation.messages.length;
|
|
103
140
|
return {
|
|
@@ -107,6 +144,8 @@ async function compactConversation(client, model, conversation, sessionsDir) {
|
|
|
107
144
|
preCompactMessages: preCompactCount,
|
|
108
145
|
postCompactMessages: postCompactCount,
|
|
109
146
|
summary,
|
|
147
|
+
activeTask: activeTask || undefined,
|
|
148
|
+
trigger,
|
|
110
149
|
};
|
|
111
150
|
}
|
|
112
151
|
function uncompactConversation(conversation, sessionsDir) {
|
|
@@ -125,6 +164,14 @@ function uncompactConversation(conversation, sessionsDir) {
|
|
|
125
164
|
return false;
|
|
126
165
|
}
|
|
127
166
|
}
|
|
167
|
+
function truncateForSummary(msg) {
|
|
168
|
+
let text = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
169
|
+
if (msg.toolCalls?.length) {
|
|
170
|
+
const tools = msg.toolCalls.map((tc) => tc.function.name).join(", ");
|
|
171
|
+
text = `[tools: ${tools}] ${text}`;
|
|
172
|
+
}
|
|
173
|
+
return text.length > 3000 ? text.slice(0, 3000) + "\n... (truncated)" : text;
|
|
174
|
+
}
|
|
128
175
|
function saveRawHistory(conversation, sessionsDir) {
|
|
129
176
|
const dir = sessionsDir ?? path.join(os.homedir(), ".llmtune", "sessions");
|
|
130
177
|
if (!fs.existsSync(dir)) {
|
|
@@ -135,7 +182,7 @@ function saveRawHistory(conversation, sessionsDir) {
|
|
|
135
182
|
fs.writeFileSync(rawPath, JSON.stringify({ messages: conversation.messages }, null, 2), "utf-8");
|
|
136
183
|
}
|
|
137
184
|
}
|
|
138
|
-
function buildFallbackSummary(messages) {
|
|
185
|
+
function buildFallbackSummary(messages, activeTask) {
|
|
139
186
|
const userMsgs = messages
|
|
140
187
|
.filter((m) => m.role === "user")
|
|
141
188
|
.map((m) => (typeof m.content === "string" ? m.content.slice(0, 200) : ""))
|
|
@@ -143,14 +190,26 @@ function buildFallbackSummary(messages) {
|
|
|
143
190
|
const toolNames = messages
|
|
144
191
|
.filter((m) => m.role === "assistant" && m.toolCalls)
|
|
145
192
|
.flatMap((m) => m.toolCalls?.map((tc) => tc.function.name) ?? []);
|
|
146
|
-
const parts = [
|
|
193
|
+
const parts = [];
|
|
194
|
+
if (activeTask) {
|
|
195
|
+
parts.push(`## Active Task\n${activeTask}`);
|
|
196
|
+
}
|
|
197
|
+
parts.push(`Conversation had ${messages.length} messages.`);
|
|
147
198
|
if (toolNames.length > 0) {
|
|
148
199
|
const unique = [...new Set(toolNames)];
|
|
149
200
|
parts.push(`Tools used: ${unique.join(", ")}`);
|
|
150
201
|
}
|
|
151
202
|
if (userMsgs.length > 0) {
|
|
152
|
-
parts.push(`
|
|
203
|
+
parts.push(`Recent user requests:\n${userMsgs.slice(-5).map((m) => `- ${m}`).join("\n")}`);
|
|
204
|
+
}
|
|
205
|
+
const allContent = messages
|
|
206
|
+
.map((m) => (typeof m.content === "string" ? m.content : ""))
|
|
207
|
+
.join(" ");
|
|
208
|
+
const fileMatches = allContent.match(/[\w/.\\:-]+\.(ts|tsx|js|jsx|py|rs|go|java|json|yaml|yml|md)/g);
|
|
209
|
+
if (fileMatches) {
|
|
210
|
+
const uniqueFiles = [...new Set(fileMatches)].slice(0, 15);
|
|
211
|
+
parts.push(`Files mentioned: ${uniqueFiles.join(", ")}`);
|
|
153
212
|
}
|
|
154
|
-
return parts.join("\n");
|
|
213
|
+
return parts.join("\n\n");
|
|
155
214
|
}
|
|
156
215
|
//# sourceMappingURL=service.js.map
|
|
@@ -27,7 +27,8 @@ function buildAgentIdentitySection(model) {
|
|
|
27
27
|
"## Behavior",
|
|
28
28
|
"- Be concise, direct, and helpful.",
|
|
29
29
|
"- Use tools to investigate and make changes — do not only describe what you would do.",
|
|
30
|
-
"- Stay on the user's task;
|
|
30
|
+
"- Stay on the user's task; continue from the conversation summary and active task in memory if present.",
|
|
31
|
+
"- If you see a [COMPACT BOUNDARY] message, treat the summary above it as prior context — do not ask the user to repeat their task unless the summary is empty.",
|
|
31
32
|
].join("\n");
|
|
32
33
|
}
|
|
33
34
|
//# sourceMappingURL=agent-identity.js.map
|
|
@@ -7,6 +7,10 @@ exports.publishSkill = publishSkill;
|
|
|
7
7
|
function getApiBase(config) {
|
|
8
8
|
return String(config.apiBase ?? "https://api.llmtune.io/api/agent/v1").replace(/\/$/, "");
|
|
9
9
|
}
|
|
10
|
+
/** Skills API lives at /api/agent/skills (not under /v1). */
|
|
11
|
+
function getAgentRoot(config) {
|
|
12
|
+
return getApiBase(config).replace(/\/v1$/, "");
|
|
13
|
+
}
|
|
10
14
|
function getHeaders(config) {
|
|
11
15
|
return {
|
|
12
16
|
"Content-Type": "application/json",
|
|
@@ -17,7 +21,7 @@ function getHeaders(config) {
|
|
|
17
21
|
* List all skills in the marketplace.
|
|
18
22
|
*/
|
|
19
23
|
async function listSkills(config, options) {
|
|
20
|
-
const base =
|
|
24
|
+
const base = getAgentRoot(config);
|
|
21
25
|
const params = new URLSearchParams();
|
|
22
26
|
if (options?.search)
|
|
23
27
|
params.set("search", options.search);
|
|
@@ -32,13 +36,21 @@ async function listSkills(config, options) {
|
|
|
32
36
|
if (!response.ok) {
|
|
33
37
|
throw new Error(`Failed to list skills: HTTP ${response.status}`);
|
|
34
38
|
}
|
|
35
|
-
|
|
39
|
+
const json = (await response.json());
|
|
40
|
+
if (Array.isArray(json.data)) {
|
|
41
|
+
return {
|
|
42
|
+
skills: json.data,
|
|
43
|
+
total: json.total ?? json.data.length,
|
|
44
|
+
page: options?.page ?? 1,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return json;
|
|
36
48
|
}
|
|
37
49
|
/**
|
|
38
50
|
* Get details for a specific skill.
|
|
39
51
|
*/
|
|
40
52
|
async function getSkillDetails(config, name) {
|
|
41
|
-
const base =
|
|
53
|
+
const base = getAgentRoot(config);
|
|
42
54
|
const url = `${base}/skills/${encodeURIComponent(name)}`;
|
|
43
55
|
const response = await fetch(url, { headers: getHeaders(config) });
|
|
44
56
|
if (!response.ok) {
|
|
@@ -52,12 +64,12 @@ async function getSkillDetails(config, name) {
|
|
|
52
64
|
* Install a skill from the marketplace to the local skills directory.
|
|
53
65
|
*/
|
|
54
66
|
async function installSkill(config, name) {
|
|
55
|
-
const base =
|
|
67
|
+
const base = getAgentRoot(config);
|
|
56
68
|
const url = `${base}/skills/install`;
|
|
57
69
|
const response = await fetch(url, {
|
|
58
70
|
method: "POST",
|
|
59
71
|
headers: getHeaders(config),
|
|
60
|
-
body: JSON.stringify({
|
|
72
|
+
body: JSON.stringify({ name, content: "" }),
|
|
61
73
|
});
|
|
62
74
|
if (!response.ok) {
|
|
63
75
|
const body = await response.text();
|
|
@@ -70,7 +82,7 @@ async function installSkill(config, name) {
|
|
|
70
82
|
* Publish a local skill to the marketplace.
|
|
71
83
|
*/
|
|
72
84
|
async function publishSkill(config, skill) {
|
|
73
|
-
const base =
|
|
85
|
+
const base = getAgentRoot(config);
|
|
74
86
|
const url = `${base}/skills`;
|
|
75
87
|
const response = await fetch(url, {
|
|
76
88
|
method: "POST",
|
package/dist/memory/service.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ declare const MEMORY_FILES: {
|
|
|
3
3
|
readonly "project-notes": "project-notes.md";
|
|
4
4
|
readonly decisions: "decisions.md";
|
|
5
5
|
readonly architecture: "architecture.md";
|
|
6
|
+
readonly "active-task": "active-task.md";
|
|
6
7
|
};
|
|
7
8
|
type MemoryCategory = keyof typeof MEMORY_FILES;
|
|
8
9
|
export interface MemoryEntry {
|
|
@@ -15,6 +16,8 @@ export declare function writeMemory(category: MemoryCategory, content: string):
|
|
|
15
16
|
export declare function appendMemory(category: MemoryCategory, line: string): void;
|
|
16
17
|
export declare function readAllMemory(): MemoryEntry[];
|
|
17
18
|
export declare function buildMemoryPrompt(): string;
|
|
19
|
+
export declare function saveActiveTask(task: string): void;
|
|
20
|
+
export declare function getActiveTask(): string;
|
|
18
21
|
export declare function clearMemory(category?: MemoryCategory): void;
|
|
19
22
|
export declare function getMemoryDir(): string;
|
|
20
23
|
export declare function initMemoryFiles(): void;
|
package/dist/memory/service.js
CHANGED
|
@@ -38,6 +38,8 @@ exports.writeMemory = writeMemory;
|
|
|
38
38
|
exports.appendMemory = appendMemory;
|
|
39
39
|
exports.readAllMemory = readAllMemory;
|
|
40
40
|
exports.buildMemoryPrompt = buildMemoryPrompt;
|
|
41
|
+
exports.saveActiveTask = saveActiveTask;
|
|
42
|
+
exports.getActiveTask = getActiveTask;
|
|
41
43
|
exports.clearMemory = clearMemory;
|
|
42
44
|
exports.getMemoryDir = getMemoryDir;
|
|
43
45
|
exports.initMemoryFiles = initMemoryFiles;
|
|
@@ -50,6 +52,7 @@ const MEMORY_FILES = {
|
|
|
50
52
|
"project-notes": "project-notes.md",
|
|
51
53
|
decisions: "decisions.md",
|
|
52
54
|
architecture: "architecture.md",
|
|
55
|
+
"active-task": "active-task.md",
|
|
53
56
|
};
|
|
54
57
|
function ensureMemoryDir() {
|
|
55
58
|
if (!fs.existsSync(MEMORY_DIR)) {
|
|
@@ -104,6 +107,18 @@ function buildMemoryPrompt() {
|
|
|
104
107
|
});
|
|
105
108
|
return "## User Memory\n\n" + sections.join("\n\n");
|
|
106
109
|
}
|
|
110
|
+
function saveActiveTask(task) {
|
|
111
|
+
const trimmed = task.trim();
|
|
112
|
+
if (!trimmed)
|
|
113
|
+
return;
|
|
114
|
+
writeMemory("active-task", `# Active Task\n\n${trimmed}`);
|
|
115
|
+
}
|
|
116
|
+
function getActiveTask() {
|
|
117
|
+
const raw = readMemory("active-task");
|
|
118
|
+
if (!raw)
|
|
119
|
+
return "";
|
|
120
|
+
return raw.replace(/^#\s*Active Task\s*\n+/i, "").trim();
|
|
121
|
+
}
|
|
107
122
|
function clearMemory(category) {
|
|
108
123
|
if (category) {
|
|
109
124
|
const filePath = getMemoryPath(category);
|
|
@@ -135,6 +150,7 @@ function initMemoryFiles() {
|
|
|
135
150
|
"project-notes": "# Project Notes\n# Key facts about the current project\n# Example: Auth uses JWT + bcrypt\n# Example: Database is Neon PostgreSQL via Prisma\n",
|
|
136
151
|
decisions: "# Architecture Decisions\n# Record important technical decisions\n# Example: Decided to use Prisma instead of Drizzle for ORM\n",
|
|
137
152
|
architecture: "# Architecture Overview\n# Describe the project structure\n# Example: Frontend: Next.js 16, Backend: Express 5, DB: Neon\n",
|
|
153
|
+
"active-task": "# Active Task\n# Updated automatically from your latest requests\n",
|
|
138
154
|
};
|
|
139
155
|
for (const [category, defaultContent] of Object.entries(defaults)) {
|
|
140
156
|
const filePath = getMemoryPath(category);
|
package/dist/repl/repl.js
CHANGED
|
@@ -41,6 +41,7 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
41
41
|
const readline_1 = require("readline");
|
|
42
42
|
const conversation_1 = require("../agent/conversation");
|
|
43
43
|
const registry_1 = require("../tools/registry");
|
|
44
|
+
const permissions_1 = require("../tools/permissions");
|
|
44
45
|
const loop_1 = require("../agent/loop");
|
|
45
46
|
const read_1 = require("../tools/tools/read");
|
|
46
47
|
const write_1 = require("../tools/tools/write");
|
|
@@ -51,6 +52,7 @@ const grep_1 = require("../tools/tools/grep");
|
|
|
51
52
|
const web_fetch_1 = require("../tools/tools/web-fetch");
|
|
52
53
|
const ask_user_1 = require("../tools/tools/ask-user");
|
|
53
54
|
const service_1 = require("../compact/service");
|
|
55
|
+
const auto_compact_1 = require("../compact/auto-compact");
|
|
54
56
|
const analyzer_1 = require("../context/analyzer");
|
|
55
57
|
const builder_1 = require("../context/builder");
|
|
56
58
|
const loader_1 = require("../skills/loader");
|
|
@@ -84,7 +86,7 @@ ${chalk_1.default.bold("LLMTune CLI - Commands:")}
|
|
|
84
86
|
async function startRepl(options) {
|
|
85
87
|
const registry = new registry_1.ToolRegistry();
|
|
86
88
|
const cwd = process.cwd();
|
|
87
|
-
const
|
|
89
|
+
const permissions = new permissions_1.PermissionManager();
|
|
88
90
|
registry.register(read_1.readTool);
|
|
89
91
|
registry.register(write_1.writeTool);
|
|
90
92
|
registry.register(edit_1.editTool);
|
|
@@ -151,16 +153,17 @@ async function startRepl(options) {
|
|
|
151
153
|
console.log(chalk_1.default.yellow(` Warning: tool "${toolName}" not allowed for skill trust level "${trustLevel}"`));
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
|
-
// Add skill system prompt and user message
|
|
155
156
|
conversation.addSystemMessage(execution.systemPrompt);
|
|
156
|
-
conversation.addUserMessage(execution.userMessage);
|
|
157
157
|
try {
|
|
158
158
|
const result = await (0, loop_1.runAgentLoop)(options.client, conversation, registry, execution.userMessage, {
|
|
159
159
|
model: currentModel,
|
|
160
160
|
maxTurns: 50,
|
|
161
161
|
verbose,
|
|
162
|
+
stream: streamMode,
|
|
162
163
|
cwd,
|
|
163
164
|
workspaceRoot: cwd,
|
|
165
|
+
permissions,
|
|
166
|
+
skipUserInput: false,
|
|
164
167
|
});
|
|
165
168
|
(0, logger_1.logEvent)({ event: "tool_call", tool: skillName, latency_ms: 0 });
|
|
166
169
|
}
|
|
@@ -179,7 +182,7 @@ async function startRepl(options) {
|
|
|
179
182
|
conversation,
|
|
180
183
|
registry,
|
|
181
184
|
skills,
|
|
182
|
-
|
|
185
|
+
permissions,
|
|
183
186
|
cwd,
|
|
184
187
|
client: options.client,
|
|
185
188
|
getModel: () => currentModel,
|
|
@@ -204,8 +207,10 @@ async function startRepl(options) {
|
|
|
204
207
|
model: currentModel,
|
|
205
208
|
maxTurns: 50,
|
|
206
209
|
verbose,
|
|
210
|
+
stream: streamMode,
|
|
207
211
|
cwd,
|
|
208
212
|
workspaceRoot: cwd,
|
|
213
|
+
permissions,
|
|
209
214
|
});
|
|
210
215
|
if (result.totalTokensIn > 0 || result.totalTokensOut > 0) {
|
|
211
216
|
const cost = estimateCostFromUsage(result.totalTokensIn, result.totalTokensOut);
|
|
@@ -279,10 +284,12 @@ async function handleCommand(input, ctx) {
|
|
|
279
284
|
}
|
|
280
285
|
console.log(chalk_1.default.dim("Compacting conversation..."));
|
|
281
286
|
try {
|
|
282
|
-
const result = await (0, service_1.compactConversation)(ctx.client, ctx.getModel(), ctx.conversation
|
|
283
|
-
|
|
287
|
+
const result = await (0, service_1.compactConversation)(ctx.client, ctx.getModel(), ctx.conversation, undefined, {
|
|
288
|
+
trigger: "manual",
|
|
289
|
+
});
|
|
290
|
+
(0, auto_compact_1.printCompactionNotice)(result, "manual", result.activeTask);
|
|
291
|
+
console.log(chalk_1.default.green(`Compacted: ${result.preCompactMessages} -> ${result.postCompactMessages} messages`));
|
|
284
292
|
console.log(chalk_1.default.dim(` Tokens saved: ~${result.tokensSaved.toLocaleString()}`));
|
|
285
|
-
console.log(chalk_1.default.dim(` Use /uncompact to restore full history.`));
|
|
286
293
|
(0, logger_1.logEvent)({ event: "compaction", tokens_saved: result.tokensSaved, messages_before: result.preCompactMessages, messages_after: result.postCompactMessages, trigger: "manual" });
|
|
287
294
|
}
|
|
288
295
|
catch (err) {
|
|
@@ -320,7 +327,7 @@ async function handleCommand(input, ctx) {
|
|
|
320
327
|
break;
|
|
321
328
|
case "/trust":
|
|
322
329
|
if (args[0]) {
|
|
323
|
-
ctx.
|
|
330
|
+
ctx.permissions.trustTool(args[0].toLowerCase());
|
|
324
331
|
console.log(chalk_1.default.green(`Trusting tool: ${args[0]} (no confirmation needed)`));
|
|
325
332
|
}
|
|
326
333
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llmtune/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "LLMTune CLI -AI CLI Agent powered by llmtune.io",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"build": "tsc",
|
|
11
11
|
"dev": "tsx src/index.ts",
|
|
12
12
|
"start": "node dist/index.js",
|
|
13
|
-
"lint": "tsc --noEmit"
|
|
13
|
+
"lint": "tsc --noEmit",
|
|
14
|
+
"test": "npm run build && node scripts/smoke-test.js"
|
|
14
15
|
},
|
|
15
16
|
"keywords": ["llmtune", "cli", "ai", "agent", "coding"],
|
|
16
17
|
"license": "MIT",
|