openhorizon-cli 1.0.5 → 1.0.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.
Files changed (2) hide show
  1. package/dist/index.js +93 -80
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import chalk3 from "chalk";
5
+ import chalk4 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 chalk2 from "chalk";
40
+ import chalk3 from "chalk";
41
41
  import { input } from "@inquirer/prompts";
42
42
  import { createHash } from "crypto";
43
43
  import { marked } from "marked";
@@ -47,6 +47,7 @@ import TerminalRenderer from "marked-terminal";
47
47
  import fs2 from "fs";
48
48
  import path2 from "path";
49
49
  import os2 from "os";
50
+ import chalk from "chalk";
50
51
  var HISTORY_DIR = path2.join(os2.homedir(), ".openhorizon");
51
52
  var HISTORY_FILE = path2.join(HISTORY_DIR, "history.jsonl");
52
53
  function ensureDir() {
@@ -81,9 +82,42 @@ function getHistoryEntries() {
81
82
  return [];
82
83
  }
83
84
  }
85
+ function printHistoryEntries(entries, limit, isRaw = false) {
86
+ if (entries.length === 0) {
87
+ console.log(chalk.gray("\n No history found.\n"));
88
+ return;
89
+ }
90
+ if (isRaw) {
91
+ entries.slice(-limit).forEach((e) => console.log(JSON.stringify(e)));
92
+ return;
93
+ }
94
+ console.log(chalk.bold.blue(`
95
+ Recent Chat History (showing ${Math.min(entries.length, limit)})
96
+ `));
97
+ const subset = entries.slice(-limit);
98
+ subset.forEach((entry, i) => {
99
+ const date = new Date(entry.ts).toLocaleString([], {
100
+ month: "short",
101
+ day: "numeric",
102
+ hour: "2-digit",
103
+ minute: "2-digit"
104
+ });
105
+ const roleStr = entry.role === "user" ? chalk.green("\u276F You") : chalk.blue("\u276F Assistant");
106
+ const prevEntry = subset[i - 1];
107
+ if (!prevEntry || prevEntry.session !== entry.session) {
108
+ console.log(chalk.dim(` \u2500\u2500 Session: ${entry.session.slice(0, 8)}\u2026 \u2500\u2500`));
109
+ }
110
+ console.log(`${chalk.dim(date)} ${roleStr}`);
111
+ console.log(` ${entry.content.replace(/\n/g, "\n ")}`);
112
+ if (entry.usage) {
113
+ console.log(chalk.dim(` tokens: \u2191${entry.usage.promptTokens} \u2193${entry.usage.completionTokens} \xB7 ${entry.latencyMs}ms`));
114
+ }
115
+ console.log("");
116
+ });
117
+ }
84
118
 
85
119
  // src/update.ts
86
- import chalk from "chalk";
120
+ import chalk2 from "chalk";
87
121
  async function getLatestVersion() {
88
122
  try {
89
123
  const response = await fetch("https://registry.npmjs.org/openhorizon-cli/latest", {
@@ -102,8 +136,8 @@ async function checkForUpdate(currentVersion) {
102
136
  if (latest !== currentVersion) {
103
137
  console.log(
104
138
  boxen(
105
- `${chalk.yellow("Update available!")} ${chalk.dim(currentVersion)} \u2192 ${chalk.green(latest)}
106
- Run ${chalk.cyan("openhorizon update")} to update.`,
139
+ `${chalk2.yellow("Update available!")} ${chalk2.dim(currentVersion)} \u2192 ${chalk2.green(latest)}
140
+ Run ${chalk2.cyan("openhorizon update")} to update.`,
107
141
  { padding: 1, margin: 1, borderColor: "yellow", borderStyle: "round" }
108
142
  )
109
143
  );
@@ -126,26 +160,26 @@ function boxen(text, options) {
126
160
  return res;
127
161
  }
128
162
  async function runUpdate(currentVersion) {
129
- console.log(chalk.blue(`
163
+ console.log(chalk2.blue(`
130
164
  Checking for updates...`));
131
165
  const latest = await getLatestVersion();
132
166
  if (!latest) {
133
- console.error(chalk.red(" \u2717 Could not fetch the latest version from npm registry."));
167
+ console.error(chalk2.red(" \u2717 Could not fetch the latest version from npm registry."));
134
168
  return;
135
169
  }
136
170
  if (latest === currentVersion) {
137
- console.log(chalk.green(` \u2713 You are already on the latest version (${chalk.bold(currentVersion)}).
171
+ console.log(chalk2.green(` \u2713 You are already on the latest version (${chalk2.bold(currentVersion)}).
138
172
  `));
139
173
  return;
140
174
  }
141
- console.log(chalk.yellow(` ! A new version is available: ${chalk.bold(latest)}`));
142
- console.log(chalk.gray(` Current version: ${currentVersion}
175
+ console.log(chalk2.yellow(` ! A new version is available: ${chalk2.bold(latest)}`));
176
+ console.log(chalk2.gray(` Current version: ${currentVersion}
143
177
  `));
144
- console.log(chalk.white(` To update, run the following command:`));
145
- console.log(chalk.cyan(`
178
+ console.log(chalk2.white(` To update, run the following command:`));
179
+ console.log(chalk2.cyan(`
146
180
  npm install -g openhorizon-cli
147
181
  `));
148
- console.log(chalk.gray(` (Or use your preferred package manager like bun or pnpm)
182
+ console.log(chalk2.gray(` (Or use your preferred package manager like bun or pnpm)
149
183
  `));
150
184
  }
151
185
 
@@ -160,7 +194,7 @@ function createSpinner(label) {
160
194
  const interval = setInterval(() => {
161
195
  if (stopped) return;
162
196
  process.stdout.write(
163
- `\r${chalk2.cyan(SPINNER_FRAMES[i % SPINNER_FRAMES.length])} ${chalk2.dim(label)} `
197
+ `\r${chalk3.cyan(SPINNER_FRAMES[i % SPINNER_FRAMES.length])} ${chalk3.dim(label)} `
164
198
  );
165
199
  i++;
166
200
  }, 80);
@@ -188,7 +222,7 @@ async function* streamCompletion(baseUrl, apiKey, model, messages) {
188
222
  Authorization: `Bearer ${apiKey}`,
189
223
  "x-api-key": apiKey,
190
224
  "x-client": "openhorizon-cli",
191
- "x-client-version": "1.0.5"
225
+ "x-client-version": "1.0.6"
192
226
  },
193
227
  body: JSON.stringify({ model, messages, stream: true })
194
228
  });
@@ -232,13 +266,13 @@ async function runChatLoop(options) {
232
266
  const keyFingerprint = createHash("sha256").update(apiKey).digest("hex").slice(0, 8);
233
267
  const session = `${date}_${keyFingerprint}_${Math.random().toString(36).slice(2, 7)}`;
234
268
  console.log(
235
- "\n" + chalk2.green("\u2713 Connected") + chalk2.gray(" \xB7 model ") + chalk2.bold.white(model)
269
+ "\n" + chalk3.green("\u2713 Connected") + chalk3.gray(" \xB7 model ") + chalk3.bold.white(model)
236
270
  );
237
- console.log(chalk2.dim(` History \u2192 ${getHistoryPath()}`));
271
+ console.log(chalk3.dim(` History \u2192 ${getHistoryPath()}`));
238
272
  console.log(
239
- chalk2.gray(
273
+ chalk3.gray(
240
274
  `
241
- Commands: ${chalk2.white("/model")} ${chalk2.white("/clear")} ${chalk2.white("/help")} ${chalk2.white("/version")} ${chalk2.white("/update")} ${chalk2.white("exit")}
275
+ Commands: ${chalk3.white("/model")} ${chalk3.white("/clear")} ${chalk3.white("/help")} ${chalk3.white("/version")} ${chalk3.white("/update")} ${chalk3.white("exit")}
242
276
  `
243
277
  )
244
278
  );
@@ -246,10 +280,10 @@ async function runChatLoop(options) {
246
280
  while (true) {
247
281
  let userInput;
248
282
  try {
249
- userInput = await input({ message: chalk2.cyan("\u276F") });
283
+ userInput = await input({ message: chalk3.cyan("\u276F") });
250
284
  } catch (err) {
251
285
  if (err?.name === "ExitPromptError") {
252
- console.log(chalk2.gray("\nGoodbye!\n"));
286
+ console.log(chalk3.gray("\nGoodbye!\n"));
253
287
  process.exit(0);
254
288
  }
255
289
  throw err;
@@ -257,36 +291,46 @@ async function runChatLoop(options) {
257
291
  const trimmed = userInput.trim();
258
292
  if (!trimmed) continue;
259
293
  if (["exit", "quit", "q"].includes(trimmed.toLowerCase())) {
260
- console.log(chalk2.gray("\nGoodbye!\n"));
294
+ console.log(chalk3.gray("\nGoodbye!\n"));
261
295
  process.exit(0);
262
296
  }
263
297
  if (trimmed === "/help") {
264
- console.log(chalk2.gray("\n /model \u2013 show current model"));
265
- console.log(chalk2.gray(" /version \u2013 show current CLI version"));
266
- console.log(chalk2.gray(" /update \u2013 check for and install updates"));
267
- console.log(chalk2.gray(" /clear \u2013 clear conversation history"));
268
- console.log(chalk2.gray(" exit \u2013 quit\n"));
298
+ console.log(chalk3.gray("\n /model \u2013 show current model"));
299
+ console.log(chalk3.gray(" /version \u2013 show current CLI version"));
300
+ console.log(chalk3.gray(" /update \u2013 check for and install updates"));
301
+ console.log(chalk3.gray(" /history \u2013 show recent chat history (e.g. /history 10)"));
302
+ console.log(chalk3.gray(" /clear \u2013 clear conversation history"));
303
+ console.log(chalk3.gray(" exit \u2013 quit\n"));
304
+ console.log(chalk3.dim(" Tip: Use 'openhorizon history --help' for full history viewer.\n"));
269
305
  continue;
270
306
  }
271
307
  if (trimmed === "/version") {
272
- console.log(chalk2.blue(`
273
- OpenHorizon CLI version: ${chalk2.bold("1.0.5")}
308
+ console.log(chalk3.blue(`
309
+ OpenHorizon CLI version: ${chalk3.bold("1.0.6")}
274
310
  `));
275
311
  continue;
276
312
  }
277
313
  if (trimmed === "/update") {
278
- await runUpdate("1.0.5");
314
+ await runUpdate("1.0.6");
315
+ continue;
316
+ }
317
+ if (trimmed.startsWith("/history")) {
318
+ const args = trimmed.split(" ").slice(1);
319
+ const limit = args.find((a) => !isNaN(Number(a))) || 5;
320
+ const isRaw = args.includes("--raw");
321
+ const entries = getHistoryEntries();
322
+ printHistoryEntries(entries, Number(limit), isRaw);
279
323
  continue;
280
324
  }
281
325
  if (trimmed === "/model") {
282
- console.log(chalk2.gray(`
283
- Model: ${chalk2.bold.white(model)}
326
+ console.log(chalk3.gray(`
327
+ Model: ${chalk3.bold.white(model)}
284
328
  `));
285
329
  continue;
286
330
  }
287
331
  if (trimmed === "/clear") {
288
332
  messages.length = 0;
289
- console.log(chalk2.gray("\n \u2713 Conversation cleared.\n"));
333
+ console.log(chalk3.gray("\n \u2713 Conversation cleared.\n"));
290
334
  continue;
291
335
  }
292
336
  messages.push({ role: "user", content: trimmed });
@@ -303,12 +347,12 @@ async function runChatLoop(options) {
303
347
  const { promptTokens, completionTokens } = event.usage;
304
348
  spinner.stop();
305
349
  if (!fullResponse) {
306
- console.log(chalk2.red(" \u276F No response received.\n"));
350
+ console.log(chalk3.red(" \u276F No response received.\n"));
307
351
  break;
308
352
  }
309
353
  const rendered = String(await marked(fullResponse)).trim();
310
- process.stdout.write(chalk2.green("\u276F ") + rendered + "\n");
311
- const usageStr = promptTokens || completionTokens ? chalk2.dim(` \u2191${promptTokens} \u2193${completionTokens} tok \xB7 ${elapsed}s`) : chalk2.dim(` ${elapsed}s`);
354
+ process.stdout.write(chalk3.green("\u276F ") + rendered + "\n");
355
+ const usageStr = promptTokens || completionTokens ? chalk3.dim(` \u2191${promptTokens} \u2193${completionTokens} tok \xB7 ${elapsed}s`) : chalk3.dim(` ${elapsed}s`);
312
356
  process.stdout.write(usageStr + "\n\n");
313
357
  appendHistory({
314
358
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -331,15 +375,15 @@ async function runChatLoop(options) {
331
375
  spinner.stop();
332
376
  const msg = err?.message ?? String(err);
333
377
  if (msg.includes("404")) {
334
- console.log(chalk2.red(`
378
+ console.log(chalk3.red(`
335
379
  \u276F 404 \u2014 check your API URL (${baseUrl})
336
380
  `));
337
381
  } else if (msg.includes("401") || msg.includes("403")) {
338
- console.log(chalk2.red("\n \u276F Authentication failed \u2014 check your API key.\n"));
382
+ console.log(chalk3.red("\n \u276F Authentication failed \u2014 check your API key.\n"));
339
383
  } else if (msg.includes("429")) {
340
- console.log(chalk2.red("\n \u276F Rate limit reached \u2014 try again shortly.\n"));
384
+ console.log(chalk3.red("\n \u276F Rate limit reached \u2014 try again shortly.\n"));
341
385
  } else {
342
- console.log(chalk2.red(`
386
+ console.log(chalk3.red(`
343
387
  \u276F ${msg}
344
388
  `));
345
389
  }
@@ -351,58 +395,27 @@ async function runChatLoop(options) {
351
395
  // src/index.ts
352
396
  dotenv.config();
353
397
  var program = new Command();
354
- var version = "1.0.5";
398
+ var version = "1.0.6";
355
399
  program.name("openhorizon").description("CLI to interact with OpenHorizon AI Models").version(version, "-v, --version", "Output the current version");
356
400
  program.command("version").description("Show the current CLI version").action(() => {
357
- console.log(chalk3.blue(`OpenHorizon CLI version: ${chalk3.bold(version)}`));
401
+ console.log(chalk4.blue(`OpenHorizon CLI version: ${chalk4.bold(version)}`));
358
402
  });
359
403
  program.command("update").description("Check for and perform CLI updates").action(async () => {
360
404
  await runUpdate(version);
361
405
  });
362
406
  program.command("history").description("Show local chat history").option("-l, --limit <number>", "Limit the number of messages to show", "50").option("--raw", "Show raw JSONL history").action((options) => {
363
407
  const entries = getHistoryEntries();
364
- if (entries.length === 0) {
365
- console.log(chalk3.gray("\n No history found.\n"));
366
- return;
367
- }
368
- if (options.raw) {
369
- entries.slice(-Number(options.limit)).forEach((e) => console.log(JSON.stringify(e)));
370
- return;
371
- }
372
- console.log(chalk3.bold.blue(`
373
- Recent Chat History (last ${options.limit})
374
- `));
375
- const limit = Number(options.limit);
376
- const subset = entries.slice(-limit);
377
- subset.forEach((entry, i) => {
378
- const date = new Date(entry.ts).toLocaleString([], {
379
- month: "short",
380
- day: "numeric",
381
- hour: "2-digit",
382
- minute: "2-digit"
383
- });
384
- const roleStr = entry.role === "user" ? chalk3.green("\u276F You") : chalk3.blue("\u276F Assistant");
385
- const prevEntry = subset[i - 1];
386
- if (!prevEntry || prevEntry.session !== entry.session) {
387
- console.log(chalk3.dim(` \u2500\u2500 Session: ${entry.session.slice(0, 8)}\u2026 \u2500\u2500`));
388
- }
389
- console.log(`${chalk3.dim(date)} ${roleStr}`);
390
- console.log(` ${entry.content.replace(/\n/g, "\n ")}`);
391
- if (entry.usage) {
392
- console.log(chalk3.dim(` tokens: \u2191${entry.usage.promptTokens} \u2193${entry.usage.completionTokens} \xB7 ${entry.latencyMs}ms`));
393
- }
394
- console.log("");
395
- });
408
+ printHistoryEntries(entries, Number(options.limit), options.raw);
396
409
  });
397
410
  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) => {
398
411
  const config = getConfig();
399
412
  await checkForUpdate(version);
400
413
  const apiKey = process.env.OPENHORIZON_API_KEY || config.apiKey;
401
414
  if (!apiKey) {
402
- console.error(chalk3.red("\n\u2717 Missing API key."));
415
+ console.error(chalk4.red("\n\u2717 Missing API key."));
403
416
  console.log(
404
- chalk3.gray(
405
- " Run '" + chalk3.white("openhorizon login <api_key>") + chalk3.gray("' or set ") + chalk3.white("OPENHORIZON_API_KEY") + chalk3.gray(".\n")
417
+ chalk4.gray(
418
+ " Run '" + chalk4.white("openhorizon login <api_key>") + chalk4.gray("' or set ") + chalk4.white("OPENHORIZON_API_KEY") + chalk4.gray(".\n")
406
419
  )
407
420
  );
408
421
  process.exit(1);
@@ -415,9 +428,9 @@ program.command("model").description("Get or set the default AI model").argument
415
428
  const config = getConfig();
416
429
  if (modelName) {
417
430
  saveConfig({ defaultModel: modelName });
418
- console.log(chalk3.green(`\u2713 Default model set to: ${chalk3.bold(modelName)}`));
431
+ console.log(chalk4.green(`\u2713 Default model set to: ${chalk4.bold(modelName)}`));
419
432
  } else {
420
- console.log(chalk3.blue(`Current default model: ${chalk3.bold(config.defaultModel ?? "(not set)")}`));
433
+ console.log(chalk4.blue(`Current default model: ${chalk4.bold(config.defaultModel ?? "(not set)")}`));
421
434
  }
422
435
  });
423
436
  program.command("login").description("Save your OpenHorizon API Key securely to local config").argument("[apiKey]", "Your OpenHorizon API Key").action(async (apiKeyInput) => {
@@ -430,6 +443,6 @@ program.command("login").description("Save your OpenHorizon API Key securely to
430
443
  });
431
444
  }
432
445
  saveConfig({ apiKey: key });
433
- console.log(chalk3.green("\u2713 API Key saved successfully."));
446
+ console.log(chalk4.green("\u2713 API Key saved successfully."));
434
447
  });
435
448
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openhorizon-cli",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Official CLI for OpenHorizon — chat with AI models directly from your terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -50,7 +50,7 @@
50
50
  "chalk": "^5.6.2",
51
51
  "commander": "^14.0.3",
52
52
  "dotenv": "^17.4.0",
53
- "marked": "^17.0.5",
53
+ "marked": "^15.0.0",
54
54
  "marked-terminal": "^7.3.0"
55
55
  }
56
56
  }