openhorizon-cli 1.0.5 → 1.0.7

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