openhorizon-cli 1.0.1 → 1.0.3

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 +118 -31
  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.0"
174
+ "x-client-version": "1.0.3"
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("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.3")}
257
+ `));
258
+ continue;
259
+ }
260
+ if (trimmed === "/update") {
261
+ await runUpdate("1.0.3");
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,13 @@ 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
292
  const rendered = String(await marked(fullResponse)).trimEnd();
214
- process.stdout.write(chalk.green("\u276F ") + "\n");
293
+ process.stdout.write(chalk2.green("\u276F ") + "\n");
215
294
  process.stdout.write(rendered + "\n");
216
- const usageStr = promptTokens || completionTokens ? chalk.dim(` \u2191${promptTokens} \u2193${completionTokens} tok \xB7 ${elapsed}s`) : chalk.dim(` ${elapsed}s`);
295
+ const usageStr = promptTokens || completionTokens ? chalk2.dim(` \u2191${promptTokens} \u2193${completionTokens} tok \xB7 ${elapsed}s`) : chalk2.dim(` ${elapsed}s`);
217
296
  process.stdout.write(usageStr + "\n\n");
218
297
  appendHistory({
219
298
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -236,15 +315,15 @@ async function runChatLoop(options) {
236
315
  spinner.stop();
237
316
  const msg = err?.message ?? String(err);
238
317
  if (msg.includes("404")) {
239
- console.log(chalk.red(`
318
+ console.log(chalk2.red(`
240
319
  \u276F 404 \u2014 check your API URL (${baseUrl})
241
320
  `));
242
321
  } else if (msg.includes("401") || msg.includes("403")) {
243
- console.log(chalk.red("\n \u276F Authentication failed \u2014 check your API key.\n"));
322
+ console.log(chalk2.red("\n \u276F Authentication failed \u2014 check your API key.\n"));
244
323
  } else if (msg.includes("429")) {
245
- console.log(chalk.red("\n \u276F Rate limit reached \u2014 try again shortly.\n"));
324
+ console.log(chalk2.red("\n \u276F Rate limit reached \u2014 try again shortly.\n"));
246
325
  } else {
247
- console.log(chalk.red(`
326
+ console.log(chalk2.red(`
248
327
  \u276F ${msg}
249
328
  `));
250
329
  }
@@ -256,15 +335,23 @@ async function runChatLoop(options) {
256
335
  // src/index.ts
257
336
  dotenv.config();
258
337
  var program = new Command();
259
- program.name("openhorizon").description("CLI to interact with OpenHorizon AI Models").version("1.0.0");
338
+ var version = "1.0.3";
339
+ program.name("openhorizon").description("CLI to interact with OpenHorizon AI Models").version(version, "-v, --version", "Output the current version");
340
+ program.command("version").description("Show the current CLI version").action(() => {
341
+ console.log(chalk3.blue(`OpenHorizon CLI version: ${chalk3.bold(version)}`));
342
+ });
343
+ program.command("update").description("Check for and perform CLI updates").action(async () => {
344
+ await runUpdate(version);
345
+ });
260
346
  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
347
  const config = getConfig();
348
+ await checkForUpdate(version);
262
349
  const apiKey = process.env.OPENHORIZON_API_KEY || config.apiKey;
263
350
  if (!apiKey) {
264
- console.error(chalk2.red("\n\u2717 Missing API key."));
351
+ console.error(chalk3.red("\n\u2717 Missing API key."));
265
352
  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")
353
+ chalk3.gray(
354
+ " Run '" + chalk3.white("openhorizon login <api_key>") + chalk3.gray("' or set ") + chalk3.white("OPENHORIZON_API_KEY") + chalk3.gray(".\n")
268
355
  )
269
356
  );
270
357
  process.exit(1);
@@ -277,9 +364,9 @@ program.command("model").description("Get or set the default AI model").argument
277
364
  const config = getConfig();
278
365
  if (modelName) {
279
366
  saveConfig({ defaultModel: modelName });
280
- console.log(chalk2.green(`\u2713 Default model set to: ${chalk2.bold(modelName)}`));
367
+ console.log(chalk3.green(`\u2713 Default model set to: ${chalk3.bold(modelName)}`));
281
368
  } else {
282
- console.log(chalk2.blue(`Current default model: ${chalk2.bold(config.defaultModel ?? "(not set)")}`));
369
+ console.log(chalk3.blue(`Current default model: ${chalk3.bold(config.defaultModel ?? "(not set)")}`));
283
370
  }
284
371
  });
285
372
  program.command("login").description("Save your OpenHorizon API Key securely to local config").argument("[apiKey]", "Your OpenHorizon API Key").action(async (apiKeyInput) => {
@@ -292,6 +379,6 @@ program.command("login").description("Save your OpenHorizon API Key securely to
292
379
  });
293
380
  }
294
381
  saveConfig({ apiKey: key });
295
- console.log(chalk2.green("\u2713 API Key saved successfully."));
382
+ console.log(chalk3.green("\u2713 API Key saved successfully."));
296
383
  });
297
384
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openhorizon-cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Official CLI for OpenHorizon — chat with AI models directly from your terminal",
5
5
  "type": "module",
6
6
  "bin": {