heyeric 1.3.0 → 1.4.0

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.
@@ -0,0 +1,77 @@
1
+ // src/update-check.ts
2
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
3
+ import { join, dirname } from "path";
4
+ import { fileURLToPath } from "url";
5
+ import { homedir } from "os";
6
+ var __dirname = dirname(fileURLToPath(import.meta.url));
7
+ function getCurrentVersion() {
8
+ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
9
+ return pkg.version;
10
+ }
11
+ function isNewer(latest, current) {
12
+ const a = latest.split(".").map(Number);
13
+ const b = current.split(".").map(Number);
14
+ for (let i = 0; i < 3; i++) {
15
+ if ((a[i] ?? 0) > (b[i] ?? 0)) return true;
16
+ if ((a[i] ?? 0) < (b[i] ?? 0)) return false;
17
+ }
18
+ return false;
19
+ }
20
+ var CACHE_DIR = join(homedir(), ".heyeric");
21
+ var CACHE_FILE = join(CACHE_DIR, "last-update-check");
22
+ var ONE_DAY_MS = 864e5;
23
+ function readCache() {
24
+ try {
25
+ const raw = readFileSync(CACHE_FILE, "utf-8").trim();
26
+ const sep = raw.indexOf(":");
27
+ if (sep === -1) return null;
28
+ const version = raw.slice(sep + 1);
29
+ if (!/^\d+\.\d+\.\d+$/.test(version)) return null;
30
+ return { timestamp: Number(raw.slice(0, sep)), version };
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+ function writeCache(version) {
36
+ try {
37
+ mkdirSync(CACHE_DIR, { recursive: true });
38
+ writeFileSync(CACHE_FILE, `${Date.now()}:${version}`);
39
+ } catch {
40
+ }
41
+ }
42
+ function sanitizeVersion(v) {
43
+ const match = v.match(/^\d+\.\d+\.\d+/);
44
+ return match ? match[0] : null;
45
+ }
46
+ async function fetchLatestVersion() {
47
+ const resp = await fetch("https://registry.npmjs.org/heyeric/latest", {
48
+ signal: AbortSignal.timeout(3e3)
49
+ });
50
+ if (!resp.ok) return null;
51
+ const data = await resp.json();
52
+ return data.version ? sanitizeVersion(data.version) : null;
53
+ }
54
+ async function checkForUpdate() {
55
+ try {
56
+ const current = getCurrentVersion();
57
+ const cache = readCache();
58
+ let latest = null;
59
+ if (cache && Date.now() - cache.timestamp < ONE_DAY_MS) {
60
+ latest = cache.version;
61
+ } else {
62
+ latest = await fetchLatestVersion();
63
+ if (latest) writeCache(latest);
64
+ }
65
+ if (!latest || !isNewer(latest, current)) return null;
66
+ return `
67
+ \x1B[33mUpdate available: ${current} \u2192 ${latest}\x1B[0m
68
+ \x1B[2mRun: npm i -g heyeric\x1B[0m
69
+ `;
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+
75
+ export {
76
+ checkForUpdate
77
+ };
package/dist/index.js CHANGED
@@ -2,6 +2,9 @@
2
2
  import {
3
3
  sanitize
4
4
  } from "./chunk-2CBZLAF5.js";
5
+ import {
6
+ checkForUpdate
7
+ } from "./chunk-3CJG52QJ.js";
5
8
 
6
9
  // src/index.ts
7
10
  import { execSync, spawn } from "child_process";
@@ -57,7 +60,14 @@ var history = getHistory();
57
60
  var systemPrompt = `You are a bash command generator.
58
61
  Convert the user's natural-language description into a single bash command.
59
62
  Prefer simple, well-known commands over complex one-liners when possible.
60
- Return ONLY the command. No explanation. No markdown. No backticks. No newlines.
63
+
64
+ If the user asks HOW to do something, generate the actual command to do it.
65
+ For example: "how to run spring boot" \u2192 ./mvnw spring-boot:run
66
+
67
+ If the query truly cannot be answered with a command (e.g., "what is the meaning of life"),
68
+ respond with exactly: NONE
69
+
70
+ Return ONLY the command (or NONE). No explanation. No markdown. No backticks. No newlines.
61
71
 
62
72
  OS: ${os}
63
73
  Shell: ${shell}
@@ -178,18 +188,29 @@ function prompt() {
178
188
  });
179
189
  }
180
190
  async function main() {
191
+ const updatePromise = checkForUpdate();
181
192
  const messages = [
182
193
  { role: "system", content: systemPrompt },
183
194
  { role: "user", content: query }
184
195
  ];
185
196
  let command = await askLLM(messages);
186
197
  if (!command) die("API returned empty command");
198
+ if (command === "NONE") {
199
+ process.stderr.write(`\r\x1B[K`);
200
+ process.stderr.write(`\x1B[2meric: I can only help with commands. Try rephrasing as an action.\x1B[0m
201
+ `);
202
+ const updateMsg = await updatePromise;
203
+ if (updateMsg) process.stderr.write(updateMsg);
204
+ process.exit(0);
205
+ }
187
206
  messages.push({ role: "assistant", content: command });
188
207
  while (true) {
189
208
  process.stderr.write(`\x1B[2mEnter to run \xB7 Type to refine \xB7 Ctrl+C to cancel\x1B[0m
190
209
  `);
191
210
  const answer = await prompt();
192
211
  if (!answer) {
212
+ const updateMsg = await updatePromise;
213
+ if (updateMsg) process.stderr.write(updateMsg);
193
214
  const child = spawn(command, { shell: true, stdio: "inherit" });
194
215
  child.on("close", (code) => process.exit(code ?? 0));
195
216
  return;
@@ -197,6 +218,14 @@ async function main() {
197
218
  messages.push({ role: "user", content: answer });
198
219
  command = await askLLM(messages, true);
199
220
  if (!command) die("API returned empty command");
221
+ if (command === "NONE") {
222
+ process.stderr.write(`\r\x1B[K`);
223
+ process.stderr.write(`\x1B[2meric: I can only help with commands. Try rephrasing as an action.\x1B[0m
224
+ `);
225
+ const updateMsg = await updatePromise;
226
+ if (updateMsg) process.stderr.write(updateMsg);
227
+ process.exit(0);
228
+ }
200
229
  messages.push({ role: "assistant", content: command });
201
230
  }
202
231
  }
@@ -0,0 +1,6 @@
1
+ import {
2
+ checkForUpdate
3
+ } from "./chunk-3CJG52QJ.js";
4
+ export {
5
+ checkForUpdate
6
+ };
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "heyeric",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Natural language to bash commands via LLM",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "eric": "./dist/index.js"
8
8
  },
9
9
  "scripts": {
10
- "build": "tsup src/index.ts src/sanitize.ts --format esm --clean",
10
+ "build": "tsup src/index.ts src/sanitize.ts src/update-check.ts --format esm --clean",
11
11
  "dev": "tsx src/index.ts",
12
12
  "test": "tsx --test src/index.test.ts"
13
13
  },