@hasna/terminal 0.5.0 → 0.5.2

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.
package/dist/App.js CHANGED
@@ -69,7 +69,7 @@ export default function App() {
69
69
  const [activeTab, setActiveTab] = useState(0);
70
70
  const abortRef = useRef(null);
71
71
  let nextTabId = useRef(2);
72
- const sessionIdRef = useRef(createSession(process.cwd()));
72
+ const sessionIdRef = useRef("");
73
73
  const interactionIdRef = useRef(0);
74
74
  const tab = tabs[activeTab];
75
75
  const allNl = [...nlHistory, ...tab.sessionNl];
@@ -158,6 +158,10 @@ export default function App() {
158
158
  // ── translate + run ─────────────────────────────────────────────────────────
159
159
  const translateAndRun = async (nl, raw) => {
160
160
  updateTab(t => ({ ...t, sessionNl: [...t.sessionNl, nl] }));
161
+ // Lazy session creation — only when user actually types something
162
+ if (!sessionIdRef.current) {
163
+ sessionIdRef.current = createSession(process.cwd());
164
+ }
161
165
  // Log interaction start
162
166
  const startTime = Date.now();
163
167
  interactionIdRef.current = logInteraction(sessionIdRef.current, { nl });
package/dist/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { render } from "ink";
4
4
  const args = process.argv.slice(2);
@@ -45,6 +45,20 @@ export function getRecipe(name, projectPath) {
45
45
  export function createRecipe(opts) {
46
46
  const filePath = opts.project ? projectFile(opts.project) : GLOBAL_FILE;
47
47
  const store = loadStore(filePath);
48
+ // Prevent duplicates — update existing if same name
49
+ const existingIdx = store.recipes.findIndex(r => r.name === opts.name);
50
+ if (existingIdx >= 0) {
51
+ store.recipes[existingIdx].command = opts.command;
52
+ store.recipes[existingIdx].updatedAt = Date.now();
53
+ if (opts.description)
54
+ store.recipes[existingIdx].description = opts.description;
55
+ if (opts.tags)
56
+ store.recipes[existingIdx].tags = opts.tags;
57
+ if (opts.collection)
58
+ store.recipes[existingIdx].collection = opts.collection;
59
+ saveStore(filePath, store);
60
+ return store.recipes[existingIdx];
61
+ }
48
62
  // Auto-detect variables from command if not explicitly provided
49
63
  const detectedVars = extractVariables(opts.command);
50
64
  const variables = opts.variables ?? detectedVars.map(name => ({ name, required: true }));
@@ -98,6 +112,10 @@ export function listCollections(projectPath) {
98
112
  export function createCollection(opts) {
99
113
  const filePath = opts.project ? projectFile(opts.project) : GLOBAL_FILE;
100
114
  const store = loadStore(filePath);
115
+ // Prevent duplicates — return existing if same name
116
+ const existing = store.collections.find(c => c.name === opts.name);
117
+ if (existing)
118
+ return existing;
101
119
  const collection = {
102
120
  id: genId(),
103
121
  name: opts.name,
@@ -1,5 +1,6 @@
1
1
  // SQLite session database — tracks every terminal interaction
2
- import Database from "better-sqlite3";
2
+ // @ts-ignore bun:sqlite is a bun built-in
3
+ import { Database } from "bun:sqlite";
3
4
  import { existsSync, mkdirSync } from "fs";
4
5
  import { homedir } from "os";
5
6
  import { join } from "path";
@@ -13,7 +14,7 @@ function getDb() {
13
14
  if (!existsSync(DIR))
14
15
  mkdirSync(DIR, { recursive: true });
15
16
  db = new Database(DB_PATH);
16
- db.pragma("journal_mode = WAL");
17
+ db.exec("PRAGMA journal_mode = WAL");
17
18
  db.exec(`
18
19
  CREATE TABLE IF NOT EXISTS sessions (
19
20
  id TEXT PRIMARY KEY,
@@ -62,7 +63,9 @@ export function getSession(id) {
62
63
  export function logInteraction(sessionId, data) {
63
64
  const result = getDb().prepare(`INSERT INTO interactions (session_id, nl, command, output, exit_code, tokens_used, tokens_saved, duration_ms, model, cached, created_at)
64
65
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(sessionId, data.nl, data.command ?? null, data.output ? data.output.slice(0, 500) : null, data.exitCode ?? null, data.tokensUsed ?? 0, data.tokensSaved ?? 0, data.durationMs ?? null, data.model ?? null, data.cached ? 1 : 0, Date.now());
65
- return result.lastInsertRowid;
66
+ // bun:sqlite — lastInsertRowid is a property on the statement after run()
67
+ const lastId = getDb().prepare("SELECT last_insert_rowid() as id").get();
68
+ return lastId?.id ?? 0;
66
69
  }
67
70
  export function updateInteraction(id, data) {
68
71
  const sets = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/terminal",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Smart terminal wrapper for AI agents and humans — structured output, token compression, MCP server, natural language",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,7 +17,6 @@
17
17
  "@anthropic-ai/sdk": "^0.39.0",
18
18
  "@modelcontextprotocol/sdk": "^1.27.1",
19
19
  "@typescript/vfs": "^1.6.4",
20
- "better-sqlite3": "^12.8.0",
21
20
  "ink": "^5.0.1",
22
21
  "react": "^18.2.0",
23
22
  "zod": "^4.3.6"
@@ -31,7 +30,6 @@
31
30
  "url": "git+https://github.com/hasna/terminal.git"
32
31
  },
33
32
  "devDependencies": {
34
- "@types/better-sqlite3": "^7.6.13",
35
33
  "@types/node": "^20.0.0",
36
34
  "@types/react": "^18.2.0",
37
35
  "tsx": "^4.0.0",
package/src/App.tsx CHANGED
@@ -110,7 +110,7 @@ export default function App() {
110
110
  const [activeTab, setActiveTab] = useState(0);
111
111
  const abortRef = useRef<AbortController | null>(null);
112
112
  let nextTabId = useRef(2);
113
- const sessionIdRef = useRef<string>(createSession(process.cwd()));
113
+ const sessionIdRef = useRef<string>("");
114
114
  const interactionIdRef = useRef<number>(0);
115
115
 
116
116
  const tab = tabs[activeTab];
@@ -217,6 +217,10 @@ export default function App() {
217
217
  const translateAndRun = async (nl: string, raw: boolean) => {
218
218
  updateTab(t => ({ ...t, sessionNl: [...t.sessionNl, nl] }));
219
219
 
220
+ // Lazy session creation — only when user actually types something
221
+ if (!sessionIdRef.current) {
222
+ sessionIdRef.current = createSession(process.cwd());
223
+ }
220
224
  // Log interaction start
221
225
  const startTime = Date.now();
222
226
  interactionIdRef.current = logInteraction(sessionIdRef.current, { nl });
package/src/cli.tsx CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
2
  import React from "react";
3
3
  import { render } from "ink";
4
4
 
@@ -61,6 +61,18 @@ export function createRecipe(opts: {
61
61
  const filePath = opts.project ? projectFile(opts.project) : GLOBAL_FILE;
62
62
  const store = loadStore(filePath);
63
63
 
64
+ // Prevent duplicates — update existing if same name
65
+ const existingIdx = store.recipes.findIndex(r => r.name === opts.name);
66
+ if (existingIdx >= 0) {
67
+ store.recipes[existingIdx].command = opts.command;
68
+ store.recipes[existingIdx].updatedAt = Date.now();
69
+ if (opts.description) store.recipes[existingIdx].description = opts.description;
70
+ if (opts.tags) store.recipes[existingIdx].tags = opts.tags;
71
+ if (opts.collection) store.recipes[existingIdx].collection = opts.collection;
72
+ saveStore(filePath, store);
73
+ return store.recipes[existingIdx];
74
+ }
75
+
64
76
  // Auto-detect variables from command if not explicitly provided
65
77
  const detectedVars = extractVariables(opts.command);
66
78
  const variables = opts.variables ?? detectedVars.map(name => ({ name, required: true }));
@@ -120,6 +132,10 @@ export function createCollection(opts: { name: string; description?: string; pro
120
132
  const filePath = opts.project ? projectFile(opts.project) : GLOBAL_FILE;
121
133
  const store = loadStore(filePath);
122
134
 
135
+ // Prevent duplicates — return existing if same name
136
+ const existing = store.collections.find(c => c.name === opts.name);
137
+ if (existing) return existing;
138
+
123
139
  const collection: Collection = {
124
140
  id: genId(),
125
141
  name: opts.name,
@@ -1,6 +1,7 @@
1
1
  // SQLite session database — tracks every terminal interaction
2
2
 
3
- import Database from "better-sqlite3";
3
+ // @ts-ignore bun:sqlite is a bun built-in
4
+ import { Database } from "bun:sqlite";
4
5
  import { existsSync, mkdirSync } from "fs";
5
6
  import { homedir } from "os";
6
7
  import { join } from "path";
@@ -9,13 +10,13 @@ import { randomUUID } from "crypto";
9
10
  const DIR = join(homedir(), ".terminal");
10
11
  const DB_PATH = join(DIR, "sessions.db");
11
12
 
12
- let db: Database.Database | null = null;
13
+ let db: Database | null = null;
13
14
 
14
- function getDb(): Database.Database {
15
+ function getDb(): Database {
15
16
  if (db) return db;
16
17
  if (!existsSync(DIR)) mkdirSync(DIR, { recursive: true });
17
18
  db = new Database(DB_PATH);
18
- db.pragma("journal_mode = WAL");
19
+ db.exec("PRAGMA journal_mode = WAL");
19
20
 
20
21
  db.exec(`
21
22
  CREATE TABLE IF NOT EXISTS sessions (
@@ -126,7 +127,9 @@ export function logInteraction(sessionId: string, data: {
126
127
  data.cached ? 1 : 0,
127
128
  Date.now()
128
129
  );
129
- return result.lastInsertRowid as number;
130
+ // bun:sqlite — lastInsertRowid is a property on the statement after run()
131
+ const lastId = getDb().prepare("SELECT last_insert_rowid() as id").get() as any;
132
+ return lastId?.id ?? 0;
130
133
  }
131
134
 
132
135
  export function updateInteraction(id: number, data: {