openhorizon-cli 1.0.2 → 1.0.4

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 (2) hide show
  1. package/dist/index.js +119 -33
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import chalk2 from "chalk";
5
+ import chalk3 from "chalk";
6
6
  import dotenv from "dotenv";
7
7
 
8
8
  // src/config.ts
@@ -37,7 +37,7 @@ function saveConfig(updates) {
37
37
  }
38
38
 
39
39
  // src/chat.ts
40
- import chalk from "chalk";
40
+ import chalk2 from "chalk";
41
41
  import { input } from "@inquirer/prompts";
42
42
  import { createHash } from "crypto";
43
43
  import { marked } from "marked";
@@ -65,6 +65,73 @@ function getHistoryPath() {
65
65
  return HISTORY_FILE;
66
66
  }
67
67
 
68
+ // src/update.ts
69
+ import chalk from "chalk";
70
+ async function getLatestVersion() {
71
+ try {
72
+ const response = await fetch("https://registry.npmjs.org/openhorizon-cli/latest", {
73
+ signal: AbortSignal.timeout(2e3)
74
+ });
75
+ if (!response.ok) return null;
76
+ const data = await response.json();
77
+ return data.version;
78
+ } catch {
79
+ return null;
80
+ }
81
+ }
82
+ async function checkForUpdate(currentVersion) {
83
+ const latest = await getLatestVersion();
84
+ if (!latest) return;
85
+ if (latest !== currentVersion) {
86
+ console.log(
87
+ boxen(
88
+ `${chalk.yellow("Update available!")} ${chalk.dim(currentVersion)} \u2192 ${chalk.green(latest)}
89
+ Run ${chalk.cyan("openhorizon update")} to update.`,
90
+ { padding: 1, margin: 1, borderColor: "yellow", borderStyle: "round" }
91
+ )
92
+ );
93
+ }
94
+ }
95
+ function boxen(text, options) {
96
+ const lines = text.split("\n");
97
+ const width = Math.max(...lines.map((l) => l.replace(/\u001b\[[0-9;]*m/g, "").length));
98
+ const horizontalLine = "\u2500".repeat(width + 2);
99
+ let res = `
100
+ \u256D${horizontalLine}\u256E
101
+ `;
102
+ for (const line of lines) {
103
+ const padding = " ".repeat(width - line.replace(/\u001b\[[0-9;]*m/g, "").length);
104
+ res += ` \u2502 ${line}${padding} \u2502
105
+ `;
106
+ }
107
+ res += ` \u2570${horizontalLine}\u256F
108
+ `;
109
+ return res;
110
+ }
111
+ async function runUpdate(currentVersion) {
112
+ console.log(chalk.blue(`
113
+ Checking for updates...`));
114
+ const latest = await getLatestVersion();
115
+ if (!latest) {
116
+ console.error(chalk.red(" \u2717 Could not fetch the latest version from npm registry."));
117
+ return;
118
+ }
119
+ if (latest === currentVersion) {
120
+ console.log(chalk.green(` \u2713 You are already on the latest version (${chalk.bold(currentVersion)}).
121
+ `));
122
+ return;
123
+ }
124
+ console.log(chalk.yellow(` ! A new version is available: ${chalk.bold(latest)}`));
125
+ console.log(chalk.gray(` Current version: ${currentVersion}
126
+ `));
127
+ console.log(chalk.white(` To update, run the following command:`));
128
+ console.log(chalk.cyan(`
129
+ npm install -g openhorizon-cli
130
+ `));
131
+ console.log(chalk.gray(` (Or use your preferred package manager like bun or pnpm)
132
+ `));
133
+ }
134
+
68
135
  // src/chat.ts
69
136
  marked.setOptions({
70
137
  renderer: new TerminalRenderer()
@@ -76,7 +143,7 @@ function createSpinner(label) {
76
143
  const interval = setInterval(() => {
77
144
  if (stopped) return;
78
145
  process.stdout.write(
79
- `\r${chalk.cyan(SPINNER_FRAMES[i % SPINNER_FRAMES.length])} ${chalk.dim(label)} `
146
+ `\r${chalk2.cyan(SPINNER_FRAMES[i % SPINNER_FRAMES.length])} ${chalk2.dim(label)} `
80
147
  );
81
148
  i++;
82
149
  }, 80);
@@ -104,7 +171,7 @@ async function* streamCompletion(baseUrl, apiKey, model, messages) {
104
171
  Authorization: `Bearer ${apiKey}`,
105
172
  "x-api-key": apiKey,
106
173
  "x-client": "openhorizon-cli",
107
- "x-client-version": "1.0.2"
174
+ "x-client-version": "1.0.4"
108
175
  },
109
176
  body: JSON.stringify({ model, messages, stream: true })
110
177
  });
@@ -148,13 +215,13 @@ async function runChatLoop(options) {
148
215
  const keyFingerprint = createHash("sha256").update(apiKey).digest("hex").slice(0, 8);
149
216
  const session = `${date}_${keyFingerprint}_${Math.random().toString(36).slice(2, 7)}`;
150
217
  console.log(
151
- "\n" + chalk.green("\u2713 Connected") + chalk.gray(" \xB7 model ") + chalk.bold.white(model)
218
+ "\n" + chalk2.green("\u2713 Connected") + chalk2.gray(" \xB7 model ") + chalk2.bold.white(model)
152
219
  );
153
- console.log(chalk.dim(` History \u2192 ${getHistoryPath()}`));
220
+ console.log(chalk2.dim(` History \u2192 ${getHistoryPath()}`));
154
221
  console.log(
155
- chalk.gray(
222
+ chalk2.gray(
156
223
  `
157
- Commands: ${chalk.white("/model")} ${chalk.white("/clear")} ${chalk.white("/help")} ${chalk.white("exit")}
224
+ Commands: ${chalk2.white("/model")} ${chalk2.white("/clear")} ${chalk2.white("/help")} ${chalk2.white("/version")} ${chalk2.white("exit")}
158
225
  `
159
226
  )
160
227
  );
@@ -162,10 +229,10 @@ async function runChatLoop(options) {
162
229
  while (true) {
163
230
  let userInput;
164
231
  try {
165
- userInput = await input({ message: chalk.cyan("\u276F") });
232
+ userInput = await input({ message: chalk2.cyan("\u276F") });
166
233
  } catch (err) {
167
234
  if (err?.name === "ExitPromptError") {
168
- console.log(chalk.gray("\nGoodbye!\n"));
235
+ console.log(chalk2.gray("\nGoodbye!\n"));
169
236
  process.exit(0);
170
237
  }
171
238
  throw err;
@@ -173,24 +240,36 @@ async function runChatLoop(options) {
173
240
  const trimmed = userInput.trim();
174
241
  if (!trimmed) continue;
175
242
  if (["exit", "quit", "q"].includes(trimmed.toLowerCase())) {
176
- console.log(chalk.gray("\nGoodbye!\n"));
243
+ console.log(chalk2.gray("\nGoodbye!\n"));
177
244
  process.exit(0);
178
245
  }
179
246
  if (trimmed === "/help") {
180
- console.log(chalk.gray("\n /model \u2013 show current model"));
181
- console.log(chalk.gray(" /clear \u2013 clear conversation history"));
182
- console.log(chalk.gray(" exit \u2013 quit\n"));
247
+ console.log(chalk2.gray("\n /model \u2013 show current model"));
248
+ console.log(chalk2.gray(" /version \u2013 show current CLI version"));
249
+ console.log(chalk2.gray(" /update \u2013 check for and install updates"));
250
+ console.log(chalk2.gray(" /clear \u2013 clear conversation history"));
251
+ console.log(chalk2.gray(" exit \u2013 quit\n"));
252
+ continue;
253
+ }
254
+ if (trimmed === "/version") {
255
+ console.log(chalk2.blue(`
256
+ OpenHorizon CLI version: ${chalk2.bold("1.0.4")}
257
+ `));
258
+ continue;
259
+ }
260
+ if (trimmed === "/update") {
261
+ await runUpdate("1.0.4");
183
262
  continue;
184
263
  }
185
264
  if (trimmed === "/model") {
186
- console.log(chalk.gray(`
187
- Model: ${chalk.bold.white(model)}
265
+ console.log(chalk2.gray(`
266
+ Model: ${chalk2.bold.white(model)}
188
267
  `));
189
268
  continue;
190
269
  }
191
270
  if (trimmed === "/clear") {
192
271
  messages.length = 0;
193
- console.log(chalk.gray("\n \u2713 Conversation cleared.\n"));
272
+ console.log(chalk2.gray("\n \u2713 Conversation cleared.\n"));
194
273
  continue;
195
274
  }
196
275
  messages.push({ role: "user", content: trimmed });
@@ -207,13 +286,12 @@ async function runChatLoop(options) {
207
286
  const { promptTokens, completionTokens } = event.usage;
208
287
  spinner.stop();
209
288
  if (!fullResponse) {
210
- console.log(chalk.red(" \u276F No response received.\n"));
289
+ console.log(chalk2.red(" \u276F No response received.\n"));
211
290
  break;
212
291
  }
213
- const rendered = String(await marked(fullResponse)).trimEnd();
214
- process.stdout.write(chalk.green("\u276F ") + "\n");
215
- process.stdout.write(rendered + "\n");
216
- const usageStr = promptTokens || completionTokens ? chalk.dim(` \u2191${promptTokens} \u2193${completionTokens} tok \xB7 ${elapsed}s`) : chalk.dim(` ${elapsed}s`);
292
+ const rendered = String(await marked(fullResponse)).trim();
293
+ process.stdout.write(chalk2.green("\u276F ") + rendered + "\n");
294
+ const usageStr = promptTokens || completionTokens ? chalk2.dim(` \u2191${promptTokens} \u2193${completionTokens} tok \xB7 ${elapsed}s`) : chalk2.dim(` ${elapsed}s`);
217
295
  process.stdout.write(usageStr + "\n\n");
218
296
  appendHistory({
219
297
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -236,15 +314,15 @@ async function runChatLoop(options) {
236
314
  spinner.stop();
237
315
  const msg = err?.message ?? String(err);
238
316
  if (msg.includes("404")) {
239
- console.log(chalk.red(`
317
+ console.log(chalk2.red(`
240
318
  \u276F 404 \u2014 check your API URL (${baseUrl})
241
319
  `));
242
320
  } else if (msg.includes("401") || msg.includes("403")) {
243
- console.log(chalk.red("\n \u276F Authentication failed \u2014 check your API key.\n"));
321
+ console.log(chalk2.red("\n \u276F Authentication failed \u2014 check your API key.\n"));
244
322
  } else if (msg.includes("429")) {
245
- console.log(chalk.red("\n \u276F Rate limit reached \u2014 try again shortly.\n"));
323
+ console.log(chalk2.red("\n \u276F Rate limit reached \u2014 try again shortly.\n"));
246
324
  } else {
247
- console.log(chalk.red(`
325
+ console.log(chalk2.red(`
248
326
  \u276F ${msg}
249
327
  `));
250
328
  }
@@ -256,15 +334,23 @@ async function runChatLoop(options) {
256
334
  // src/index.ts
257
335
  dotenv.config();
258
336
  var program = new Command();
259
- program.name("openhorizon").description("CLI to interact with OpenHorizon AI Models").version("1.0.0");
337
+ var version = "1.0.4";
338
+ program.name("openhorizon").description("CLI to interact with OpenHorizon AI Models").version(version, "-v, --version", "Output the current version");
339
+ program.command("version").description("Show the current CLI version").action(() => {
340
+ console.log(chalk3.blue(`OpenHorizon CLI version: ${chalk3.bold(version)}`));
341
+ });
342
+ program.command("update").description("Check for and perform CLI updates").action(async () => {
343
+ await runUpdate(version);
344
+ });
260
345
  program.command("chat", { isDefault: true }).description("Start an interactive chat session with the AI").option("-m, --model <model>", "Specify a model to use for this session").option("-b, --base-url <url>", "Base API URL (e.g. https://api.openhorizon.devwtf.in/v1)").action(async (options) => {
261
346
  const config = getConfig();
347
+ await checkForUpdate(version);
262
348
  const apiKey = process.env.OPENHORIZON_API_KEY || config.apiKey;
263
349
  if (!apiKey) {
264
- console.error(chalk2.red("\n\u2717 Missing API key."));
350
+ console.error(chalk3.red("\n\u2717 Missing API key."));
265
351
  console.log(
266
- chalk2.gray(
267
- " Run '" + chalk2.white("openhorizon login <api_key>") + chalk2.gray("' or set ") + chalk2.white("OPENHORIZON_API_KEY") + chalk2.gray(".\n")
352
+ chalk3.gray(
353
+ " Run '" + chalk3.white("openhorizon login <api_key>") + chalk3.gray("' or set ") + chalk3.white("OPENHORIZON_API_KEY") + chalk3.gray(".\n")
268
354
  )
269
355
  );
270
356
  process.exit(1);
@@ -277,9 +363,9 @@ program.command("model").description("Get or set the default AI model").argument
277
363
  const config = getConfig();
278
364
  if (modelName) {
279
365
  saveConfig({ defaultModel: modelName });
280
- console.log(chalk2.green(`\u2713 Default model set to: ${chalk2.bold(modelName)}`));
366
+ console.log(chalk3.green(`\u2713 Default model set to: ${chalk3.bold(modelName)}`));
281
367
  } else {
282
- console.log(chalk2.blue(`Current default model: ${chalk2.bold(config.defaultModel ?? "(not set)")}`));
368
+ console.log(chalk3.blue(`Current default model: ${chalk3.bold(config.defaultModel ?? "(not set)")}`));
283
369
  }
284
370
  });
285
371
  program.command("login").description("Save your OpenHorizon API Key securely to local config").argument("[apiKey]", "Your OpenHorizon API Key").action(async (apiKeyInput) => {
@@ -292,6 +378,6 @@ program.command("login").description("Save your OpenHorizon API Key securely to
292
378
  });
293
379
  }
294
380
  saveConfig({ apiKey: key });
295
- console.log(chalk2.green("\u2713 API Key saved successfully."));
381
+ console.log(chalk3.green("\u2713 API Key saved successfully."));
296
382
  });
297
383
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openhorizon-cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Official CLI for OpenHorizon — chat with AI models directly from your terminal",
5
5
  "type": "module",
6
6
  "bin": {