@llmtune/cli 0.1.7 → 0.1.9

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.
@@ -9,6 +9,8 @@ const auto_compact_1 = require("../compact/auto-compact");
9
9
  const service_1 = require("../memory/service");
10
10
  const tokens_1 = require("../utils/tokens");
11
11
  const chalk_1 = __importDefault(require("chalk"));
12
+ const markdown_1 = require("../utils/markdown");
13
+ const streaming_1 = require("../utils/streaming");
12
14
  async function runAgentLoop(client, conversation, registry, userInput, config, onTextChunk) {
13
15
  const model = config.model ?? "z-ai/GLM-5.1";
14
16
  const maxTurns = config.maxTurns ?? 20;
@@ -48,6 +50,9 @@ async function runAgentLoop(client, conversation, registry, userInput, config, o
48
50
  let finalText = "";
49
51
  for (let turn = 0; turn < maxTurns; turn++) {
50
52
  const allMessages = buildApiMessages(conversation, contextPrompt);
53
+ // Clear the "thinking" indicator on subsequent turns
54
+ if (turn > 0)
55
+ process.stdout.write("\r \r");
51
56
  const turnResult = useStream
52
57
  ? await runStreamingTurn(client, model, allMessages, openaiTools, onTextChunk)
53
58
  : await runBufferedTurn(client, model, allMessages, openaiTools);
@@ -141,6 +146,10 @@ function buildApiMessages(conversation, contextPrompt) {
141
146
  }),
142
147
  ];
143
148
  }
149
+ /**
150
+ * Streaming turn — prints tokens live to the terminal as they arrive.
151
+ * After stream completes, final rendered markdown is shown.
152
+ */
144
153
  async function runStreamingTurn(client, model, messages, openaiTools, onTextChunk) {
145
154
  const stream = await client.chat.completions.create({
146
155
  model,
@@ -155,16 +164,18 @@ async function runStreamingTurn(client, model, messages, openaiTools, onTextChun
155
164
  let currentToolCall = null;
156
165
  let tokensIn = 0;
157
166
  let tokensOut = 0;
167
+ // Create streaming display for live output
168
+ const display = (0, streaming_1.createStreamingDisplay)();
158
169
  for await (const chunk of stream) {
159
170
  const delta = chunk.choices[0]?.delta;
160
171
  if (!delta)
161
172
  continue;
162
173
  if (delta.content) {
163
174
  assistantContent += delta.content;
175
+ // Show live output to the user
176
+ display.onToken(delta.content);
164
177
  if (onTextChunk)
165
178
  onTextChunk(delta.content);
166
- else
167
- process.stdout.write(delta.content);
168
179
  }
169
180
  if (delta.tool_calls) {
170
181
  for (const tc of delta.tool_calls) {
@@ -193,11 +204,18 @@ async function runStreamingTurn(client, model, messages, openaiTools, onTextChun
193
204
  tokensOut += chunk.usage.completion_tokens ?? 0;
194
205
  }
195
206
  }
196
- if (!onTextChunk)
197
- console.log();
207
+ // Flush any remaining buffered tokens
208
+ display.flush();
209
+ // If no external handler and we have content, render it as markdown
210
+ if (!onTextChunk && assistantContent) {
211
+ // Content was already streamed raw via display, add a newline for spacing
212
+ console.log("");
213
+ }
198
214
  return { assistantContent, toolCalls, tokensIn, tokensOut };
199
215
  }
200
216
  async function runBufferedTurn(client, model, messages, openaiTools) {
217
+ // Clear the "thinking" indicator
218
+ process.stdout.write("\r \r");
201
219
  const response = await client.chat.completions.create({
202
220
  model,
203
221
  messages,
@@ -218,8 +236,7 @@ async function runBufferedTurn(client, model, messages, openaiTools) {
218
236
  },
219
237
  }));
220
238
  if (assistantContent) {
221
- process.stdout.write(assistantContent);
222
- console.log();
239
+ console.log((0, markdown_1.renderMarkdown)(assistantContent));
223
240
  }
224
241
  return {
225
242
  assistantContent,
@@ -0,0 +1,2 @@
1
+ export declare function balanceCommand(): Promise<void>;
2
+ //# sourceMappingURL=balance.d.ts.map
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.balanceCommand = balanceCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const config_1 = require("../auth/config");
9
+ async function balanceCommand() {
10
+ const apiKey = (0, config_1.getApiKey)();
11
+ if (!apiKey) {
12
+ console.log(chalk_1.default.red('Not authenticated. Run "llmtune login" first.'));
13
+ process.exit(1);
14
+ }
15
+ const config = (0, config_1.loadConfig)();
16
+ const apiBase = config.apiBase || "https://api.llmtune.io/api/agent/v1";
17
+ // Derive the base URL (strip /api/agent/v1 → root)
18
+ const baseUrl = apiBase.replace(/\/api\/agent\/v1\/?$/, "");
19
+ const url = `${baseUrl}/api/usage/balance-bar`;
20
+ try {
21
+ const res = await fetch(url, {
22
+ headers: {
23
+ Authorization: `Bearer ${apiKey}`,
24
+ "Content-Type": "application/json",
25
+ },
26
+ });
27
+ if (!res.ok) {
28
+ const text = await res.text();
29
+ console.log(chalk_1.default.red(`Failed to fetch balance (${res.status}): ${text}`));
30
+ return;
31
+ }
32
+ const data = (await res.json());
33
+ console.log("");
34
+ console.log(chalk_1.default.bold(" Account Balance"));
35
+ console.log("");
36
+ // Balance display
37
+ const balance = data.balanceUsd;
38
+ const balanceStr = balance < 0
39
+ ? chalk_1.default.red(`-$${Math.abs(balance).toFixed(2)}`)
40
+ : balance < 1
41
+ ? chalk_1.default.yellow(`$${balance.toFixed(4)}`)
42
+ : chalk_1.default.green(`$${balance.toFixed(2)}`);
43
+ console.log(` Balance: ${balanceStr}`);
44
+ // Token usage (30d)
45
+ if (data.totalTokens !== undefined) {
46
+ const tokens = data.totalTokens;
47
+ const formatted = tokens >= 1_000_000
48
+ ? `${(tokens / 1_000_000).toFixed(1)}M`
49
+ : tokens >= 1_000
50
+ ? `${(tokens / 1_000).toFixed(1)}K`
51
+ : `${tokens}`;
52
+ console.log(` Usage (30d): ${chalk_1.default.dim(formatted + " tokens")}`);
53
+ }
54
+ console.log("");
55
+ // Low balance warning
56
+ if (balance > 0 && balance < 0.50) {
57
+ console.log(chalk_1.default.yellow(" ⚠ Low balance — consider topping up at llmtune.io"));
58
+ console.log("");
59
+ }
60
+ }
61
+ catch (err) {
62
+ console.log(chalk_1.default.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
63
+ }
64
+ }
65
+ //# sourceMappingURL=balance.js.map
@@ -5,23 +5,27 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.chatCommand = chatCommand;
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
- const config_js_1 = require("../auth/config.js");
9
- const client_js_1 = require("../auth/client.js");
10
- const repl_js_1 = require("../repl/repl.js");
8
+ const config_1 = require("../auth/config");
9
+ const client_1 = require("../auth/client");
10
+ const repl_1 = require("../repl/repl");
11
+ const banner_1 = require("../ui/banner");
11
12
  async function chatCommand(options) {
12
- const config = (0, config_js_1.loadConfig)();
13
+ const config = (0, config_1.loadConfig)();
13
14
  if (!config.apiKey) {
14
15
  console.log(chalk_1.default.red('No API key configured. Run "llmtune login" first.'));
15
16
  process.exit(1);
16
17
  }
17
- const client = (0, client_js_1.createClient)();
18
+ const client = (0, client_1.createClient)();
18
19
  const model = options.model || config.defaultModel || "z-ai/GLM-5.1";
19
20
  const stream = options.stream !== false;
20
- console.log(chalk_1.default.cyan(`\nLLMTune CLI`));
21
- console.log(chalk_1.default.dim(`Connected to ${(0, config_js_1.getApiBase)()}`));
22
- console.log(chalk_1.default.dim(`Model: ${model}`));
23
- console.log(chalk_1.default.dim(`Stream: ${stream}`));
24
- console.log(chalk_1.default.dim(`Type /help for commands, /exit to quit\n`));
25
- await (0, repl_js_1.startRepl)({ client, model, stream });
21
+ // Render the LLMTune ASCII art banner
22
+ console.log((0, banner_1.renderBanner)());
23
+ // Connection info
24
+ console.log(chalk_1.default.dim(` Connected to ${(0, config_1.getApiBase)()}`));
25
+ console.log(chalk_1.default.dim(` Model: ${model}`));
26
+ console.log(chalk_1.default.dim(` Stream: ${stream}`));
27
+ console.log(chalk_1.default.dim(` Type /help for commands, /exit to quit`));
28
+ console.log("");
29
+ await (0, repl_1.startRepl)({ client, model, stream });
26
30
  }
27
31
  //# sourceMappingURL=chat.js.map
@@ -0,0 +1,2 @@
1
+ export declare function doctorCommand(): Promise<void>;
2
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.doctorCommand = doctorCommand;
40
+ const chalk_1 = __importDefault(require("chalk"));
41
+ const fs = __importStar(require("fs"));
42
+ const os = __importStar(require("os"));
43
+ const path = __importStar(require("path"));
44
+ const child_process_1 = require("child_process");
45
+ const config_1 = require("../auth/config");
46
+ const version_1 = require("../version");
47
+ async function doctorCommand() {
48
+ const checks = [];
49
+ console.log(chalk_1.default.bold("\n🩺 LLMTune CLI Doctor\n"));
50
+ // 1. Node.js version
51
+ const nodeVersion = process.version;
52
+ const major = parseInt(nodeVersion.slice(1).split(".")[0], 10);
53
+ if (major >= 18) {
54
+ checks.push({ name: "Node.js", status: "pass", message: `v${nodeVersion.slice(1)}` });
55
+ }
56
+ else {
57
+ checks.push({
58
+ name: "Node.js",
59
+ status: "fail",
60
+ message: `v${nodeVersion.slice(1)} — requires >= 18.0.0`,
61
+ fix: "Upgrade Node.js: https://nodejs.org/",
62
+ });
63
+ }
64
+ // 2. CLI version
65
+ checks.push({ name: "CLI version", status: "pass", message: `v${version_1.CLI_VERSION}` });
66
+ // 3. Config file
67
+ const configPath = (0, config_1.getConfigPath)();
68
+ if (fs.existsSync(configPath)) {
69
+ checks.push({ name: "Config file", status: "pass", message: configPath });
70
+ }
71
+ else {
72
+ checks.push({
73
+ name: "Config file",
74
+ status: "warn",
75
+ message: "Not found",
76
+ fix: "Run: llmtune login",
77
+ });
78
+ }
79
+ // 4. API key
80
+ if ((0, config_1.isLoggedIn)()) {
81
+ const config = (0, config_1.loadConfig)();
82
+ const key = config.apiKey;
83
+ const masked = key && key.length > 12
84
+ ? `${key.slice(0, 6)}...${key.slice(-4)}`
85
+ : key ? "(set)" : "(not set)";
86
+ checks.push({ name: "API key", status: "pass", message: masked });
87
+ }
88
+ else {
89
+ checks.push({
90
+ name: "API key",
91
+ status: "fail",
92
+ message: "Not configured",
93
+ fix: "Run: llmtune login",
94
+ });
95
+ }
96
+ // 5. API connectivity — use native fetch instead of curl (Windows compatible)
97
+ if ((0, config_1.isLoggedIn)()) {
98
+ try {
99
+ const config = (0, config_1.loadConfig)();
100
+ const apiBase = config.apiBase || "https://api.llmtune.io/api/agent/v1";
101
+ const apiKey = config.apiKey;
102
+ const controller = new AbortController();
103
+ const timeout = setTimeout(() => controller.abort(), 10000);
104
+ const response = await fetch(`${apiBase}/models`, {
105
+ headers: { Authorization: `Bearer ${apiKey}` },
106
+ signal: controller.signal,
107
+ });
108
+ clearTimeout(timeout);
109
+ if (response.ok) {
110
+ checks.push({ name: "API connection", status: "pass", message: `${apiBase} (HTTP ${response.status})` });
111
+ }
112
+ else {
113
+ checks.push({ name: "API connection", status: "warn", message: `${apiBase} (HTTP ${response.status})` });
114
+ }
115
+ }
116
+ catch (err) {
117
+ checks.push({
118
+ name: "API connection",
119
+ status: "fail",
120
+ message: "Cannot reach API",
121
+ fix: "Check your network connection and API base URL",
122
+ });
123
+ }
124
+ }
125
+ // 6. Config dir
126
+ const configDir = path.join(os.homedir(), ".llmtune");
127
+ if (fs.existsSync(configDir)) {
128
+ checks.push({ name: "Config dir", status: "pass", message: configDir });
129
+ }
130
+ else {
131
+ checks.push({
132
+ name: "Config dir",
133
+ status: "warn",
134
+ message: "Not created yet",
135
+ fix: "Will be created on first login",
136
+ });
137
+ }
138
+ // 7. Git
139
+ try {
140
+ const gitVersion = (0, child_process_1.execSync)("git --version", { encoding: "utf-8", timeout: 5000 }).trim();
141
+ checks.push({ name: "Git", status: "pass", message: gitVersion });
142
+ }
143
+ catch {
144
+ checks.push({
145
+ name: "Git",
146
+ status: "warn",
147
+ message: "Not found",
148
+ fix: "Install Git for full workspace context features",
149
+ });
150
+ }
151
+ // 8. Docker (optional)
152
+ try {
153
+ const dockerVersion = (0, child_process_1.execSync)("docker --version", { encoding: "utf-8", timeout: 5000 }).trim();
154
+ checks.push({ name: "Docker", status: "pass", message: `${dockerVersion} (sandbox available)` });
155
+ }
156
+ catch {
157
+ checks.push({ name: "Docker", status: "warn", message: "Not found (sandbox mode unavailable)" });
158
+ }
159
+ // 9. Memory dir
160
+ const memoryDir = path.join(os.homedir(), ".llmtune", "memory");
161
+ if (fs.existsSync(memoryDir)) {
162
+ const files = fs.readdirSync(memoryDir).filter(f => f.endsWith(".md"));
163
+ checks.push({ name: "Memory", status: "pass", message: `${files.length} memory files` });
164
+ }
165
+ else {
166
+ checks.push({ name: "Memory", status: "warn", message: "Not initialized" });
167
+ }
168
+ // 10. Check for updates
169
+ try {
170
+ const latest = (0, child_process_1.execSync)("npm view @llmtune/cli version", {
171
+ encoding: "utf-8",
172
+ timeout: 10000,
173
+ }).trim();
174
+ if (latest === version_1.CLI_VERSION) {
175
+ checks.push({ name: "Update", status: "pass", message: "Up to date" });
176
+ }
177
+ else {
178
+ checks.push({
179
+ name: "Update",
180
+ status: "warn",
181
+ message: `Latest: v${latest} (you have v${version_1.CLI_VERSION})`,
182
+ fix: "Run: npm update -g @llmtune/cli",
183
+ });
184
+ }
185
+ }
186
+ catch {
187
+ checks.push({ name: "Update", status: "pass", message: "Could not check (offline?)" });
188
+ }
189
+ // Print results
190
+ for (const check of checks) {
191
+ const icon = check.status === "pass" ? chalk_1.default.green("✓")
192
+ : check.status === "warn" ? chalk_1.default.yellow("⚠")
193
+ : chalk_1.default.red("✗");
194
+ const label = chalk_1.default.bold(check.name.padEnd(16));
195
+ console.log(` ${icon} ${label} ${chalk_1.default.dim(check.message)}`);
196
+ if (check.fix && check.status !== "pass") {
197
+ console.log(`${" ".repeat(22)}${chalk_1.default.dim("→ " + check.fix)}`);
198
+ }
199
+ }
200
+ const fails = checks.filter(c => c.status === "fail").length;
201
+ const warns = checks.filter(c => c.status === "warn").length;
202
+ const passes = checks.filter(c => c.status === "pass").length;
203
+ console.log("");
204
+ if (fails === 0) {
205
+ console.log(chalk_1.default.green(` ${passes} passed, ${warns} warnings`));
206
+ }
207
+ else {
208
+ console.log(chalk_1.default.red(` ${passes} passed, ${warns} warnings, ${fails} failed`));
209
+ }
210
+ console.log("");
211
+ }
212
+ //# sourceMappingURL=doctor.js.map
@@ -79,12 +79,14 @@ async function loginCommand() {
79
79
  }
80
80
  const apiBase = await ask("API base URL", existingUrl);
81
81
  const model = await ask("Default model (leave empty for auto)", existingModel || undefined);
82
+ // FIX: Merge with existing config to preserve other keys (providers, preferences, etc.)
82
83
  (0, config_1.saveConfig)({
84
+ ...existing,
83
85
  apiKey: key,
84
86
  apiBase: apiBase.replace(/\/$/, ""),
85
87
  defaultModel: model || undefined,
86
88
  });
87
- console.log(chalk_1.default.green("\nConfiguration saved!"));
89
+ console.log(chalk_1.default.green("\n✓ Configuration saved!"));
88
90
  console.log(` API key: ${maskKey(key)}`);
89
91
  console.log(` API base: ${apiBase}`);
90
92
  console.log(` Model: ${model || "auto"}`);
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.modelsCommand = modelsCommand;
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
- const cli_table3_1 = __importDefault(require("cli-table3"));
8
+ const markdown_1 = require("../utils/markdown");
9
9
  const config_1 = require("../auth/config");
10
10
  async function modelsCommand() {
11
11
  const apiKey = (0, config_1.getApiKey)();
@@ -28,22 +28,16 @@ async function modelsCommand() {
28
28
  process.exit(1);
29
29
  }
30
30
  const data = (await response.json());
31
+ console.log("");
31
32
  if (data.subscription) {
32
- console.log(chalk_1.default.cyan(`\nSubscription: ${data.subscription.planName}`));
33
- console.log(chalk_1.default.dim(`Daily quota: ${data.subscription.quotaDaily} requests\n`));
33
+ console.log(chalk_1.default.cyan(` Subscription: ${data.subscription.planName}`));
34
+ console.log(chalk_1.default.dim(` Daily quota: ${data.subscription.quotaDaily} requests`));
35
+ console.log("");
34
36
  }
35
- const table = new cli_table3_1.default({
36
- head: [
37
- chalk_1.default.cyan("Model ID"),
38
- chalk_1.default.cyan("Provider"),
39
- ],
40
- colWidths: [50, 20],
41
- });
42
- for (const model of data.data) {
43
- table.push([model.id, model.owned_by]);
44
- }
45
- console.log(table.toString());
46
- console.log(chalk_1.default.dim(`\n${data.data.length} models available`));
37
+ const headers = ["Model ID", "Provider"];
38
+ const rows = data.data.map((model) => [model.id, model.owned_by]);
39
+ console.log((0, markdown_1.renderTable)(headers, rows));
40
+ console.log(chalk_1.default.dim(`\n ${data.data.length} models available\n`));
47
41
  }
48
42
  catch (err) {
49
43
  console.log(chalk_1.default.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
@@ -0,0 +1,6 @@
1
+ export declare function updateCommand(): Promise<void>;
2
+ /**
3
+ * Check for updates (cached, runs once per day). Returns latest version or null.
4
+ */
5
+ export declare function checkForUpdate(): string | null;
6
+ //# sourceMappingURL=update.d.ts.map
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.updateCommand = updateCommand;
40
+ exports.checkForUpdate = checkForUpdate;
41
+ const chalk_1 = __importDefault(require("chalk"));
42
+ const child_process_1 = require("child_process");
43
+ const fs = __importStar(require("fs"));
44
+ const path = __importStar(require("path"));
45
+ const os = __importStar(require("os"));
46
+ const version_1 = require("../version");
47
+ const CACHE_FILE = path.join(os.homedir(), ".llmtune", "cache", "update-check.json");
48
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // Once per day
49
+ async function updateCommand() {
50
+ console.log(chalk_1.default.dim("Checking for updates..."));
51
+ const current = version_1.CLI_VERSION;
52
+ const latest = fetchLatestVersion();
53
+ if (!latest) {
54
+ console.log(chalk_1.default.yellow("Could not check for updates. Are you online?"));
55
+ return;
56
+ }
57
+ if (latest === current) {
58
+ console.log(chalk_1.default.green(`\n Already on the latest version (v${current})\n`));
59
+ return;
60
+ }
61
+ console.log(chalk_1.default.yellow(`\n Update available: v${current} → v${latest}`));
62
+ console.log(chalk_1.default.dim(" Installing..."));
63
+ try {
64
+ (0, child_process_1.execSync)("npm install -g @llmtune/cli@latest", {
65
+ stdio: "inherit",
66
+ timeout: 120_000,
67
+ });
68
+ console.log(chalk_1.default.green(`\n Updated to v${latest}\n`));
69
+ }
70
+ catch {
71
+ console.log(chalk_1.default.red("\n Update failed. Try manually:"));
72
+ console.log(chalk_1.default.bold(" npm install -g @llmtune/cli@latest\n"));
73
+ }
74
+ }
75
+ /**
76
+ * Check for updates (cached, runs once per day). Returns latest version or null.
77
+ */
78
+ function checkForUpdate() {
79
+ try {
80
+ // Check cache first
81
+ try {
82
+ if (fs.existsSync(CACHE_FILE)) {
83
+ const cached = JSON.parse(fs.readFileSync(CACHE_FILE, "utf-8"));
84
+ if (Date.now() - cached.lastCheck < CHECK_INTERVAL_MS) {
85
+ return cached.latestVersion !== version_1.CLI_VERSION ? cached.latestVersion : null;
86
+ }
87
+ }
88
+ }
89
+ catch { /* ignore */ }
90
+ const latest = fetchLatestVersion();
91
+ if (!latest)
92
+ return null;
93
+ // Save to cache
94
+ const dir = path.dirname(CACHE_FILE);
95
+ if (!fs.existsSync(dir))
96
+ fs.mkdirSync(dir, { recursive: true });
97
+ fs.writeFileSync(CACHE_FILE, JSON.stringify({ lastCheck: Date.now(), latestVersion: latest }), "utf-8");
98
+ return latest !== version_1.CLI_VERSION ? latest : null;
99
+ }
100
+ catch {
101
+ return null;
102
+ }
103
+ }
104
+ function fetchLatestVersion() {
105
+ try {
106
+ return (0, child_process_1.execSync)("npm view @llmtune/cli version", {
107
+ encoding: "utf-8",
108
+ timeout: 10_000,
109
+ }).trim();
110
+ }
111
+ catch {
112
+ return null;
113
+ }
114
+ }
115
+ //# sourceMappingURL=update.js.map