bare-agent 0.1.0 → 0.1.1

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,63 @@
1
+ 'use strict';
2
+
3
+ const { readFileSync, writeFileSync, existsSync } = require('node:fs');
4
+
5
+ /**
6
+ * JSON file memory store. Zero deps, case-insensitive substring search.
7
+ *
8
+ * Interface (implements Memory store contract):
9
+ * store(content, metadata) → id
10
+ * search(query, options) → [{ id, content, metadata, score }]
11
+ * get(id) → { content, metadata }
12
+ * delete(id) → void
13
+ */
14
+ class JsonFileStore {
15
+ /**
16
+ * @param {object} options
17
+ * @param {string} options.path - Path to JSON file (required).
18
+ * @throws {Error} `[JsonFileStore] requires options.path` — when path is missing.
19
+ */
20
+ constructor(options = {}) {
21
+ if (!options.path) throw new Error('[JsonFileStore] requires options.path');
22
+ this._path = options.path;
23
+ this._data = existsSync(this._path)
24
+ ? JSON.parse(readFileSync(this._path, 'utf8'))
25
+ : [];
26
+ this._nextId = this._data.length
27
+ ? Math.max(...this._data.map(d => d.id)) + 1
28
+ : 1;
29
+ }
30
+
31
+ _save() {
32
+ writeFileSync(this._path, JSON.stringify(this._data, null, 2));
33
+ }
34
+
35
+ store(content, metadata = {}) {
36
+ const id = this._nextId++;
37
+ this._data.push({ id, content, metadata, createdAt: new Date().toISOString() });
38
+ this._save();
39
+ return id;
40
+ }
41
+
42
+ search(query, options = {}) {
43
+ const limit = options.limit || 10;
44
+ const q = (query || '').toLowerCase();
45
+ if (!q) return this._data.slice(0, limit).map(d => ({ ...d, score: 1 }));
46
+ return this._data
47
+ .filter(d => d.content.toLowerCase().includes(q))
48
+ .slice(0, limit)
49
+ .map(d => ({ ...d, score: 1 }));
50
+ }
51
+
52
+ get(id) {
53
+ const item = this._data.find(d => d.id === id);
54
+ return item ? { id: item.id, content: item.content, metadata: item.metadata } : null;
55
+ }
56
+
57
+ delete(id) {
58
+ this._data = this._data.filter(d => d.id !== id);
59
+ this._save();
60
+ }
61
+ }
62
+
63
+ module.exports = { JsonFileStore };
@@ -0,0 +1,133 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * SQLite FTS5 memory store. Full-text search with BM25 ranking.
5
+ *
6
+ * Interface (implements Memory store contract):
7
+ * store(content, metadata) → id
8
+ * search(query, options) → [{ id, content, metadata, score }]
9
+ * get(id) → { content, metadata }
10
+ * delete(id) → void
11
+ *
12
+ * Requires peer dep: better-sqlite3
13
+ */
14
+ class SQLiteStore {
15
+ /**
16
+ * @param {object} options
17
+ * @param {string} options.path - Path to SQLite database file (required).
18
+ * @throws {Error} `[SQLiteStore] requires options.path` — when path is missing.
19
+ * @throws {Error} `[SQLiteStore] requires better-sqlite3` — when peer dep is not installed.
20
+ */
21
+ constructor(options = {}) {
22
+ if (!options.path) throw new Error('[SQLiteStore] requires options.path');
23
+
24
+ let Database;
25
+ try {
26
+ Database = require('better-sqlite3');
27
+ } catch {
28
+ throw new Error(
29
+ '[SQLiteStore] requires better-sqlite3. Install it: npm install better-sqlite3'
30
+ );
31
+ }
32
+
33
+ this._db = new Database(options.path);
34
+ this._db.pragma('journal_mode = WAL');
35
+ this._db.exec(`
36
+ CREATE TABLE IF NOT EXISTS chunks (
37
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
38
+ content TEXT NOT NULL,
39
+ metadata TEXT NOT NULL DEFAULT '{}',
40
+ created_at TEXT NOT NULL
41
+ );
42
+ CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
43
+ content,
44
+ content='chunks',
45
+ content_rowid='id',
46
+ tokenize='porter'
47
+ );
48
+ CREATE TRIGGER IF NOT EXISTS chunks_ai AFTER INSERT ON chunks BEGIN
49
+ INSERT INTO chunks_fts(rowid, content) VALUES (new.id, new.content);
50
+ END;
51
+ CREATE TRIGGER IF NOT EXISTS chunks_ad AFTER DELETE ON chunks BEGIN
52
+ INSERT INTO chunks_fts(chunks_fts, rowid, content) VALUES('delete', old.id, old.content);
53
+ END;
54
+ CREATE TRIGGER IF NOT EXISTS chunks_au AFTER UPDATE ON chunks BEGIN
55
+ INSERT INTO chunks_fts(chunks_fts, rowid, content) VALUES('delete', old.id, old.content);
56
+ INSERT INTO chunks_fts(rowid, content) VALUES (new.id, new.content);
57
+ END;
58
+ `);
59
+
60
+ this._insertStmt = this._db.prepare(
61
+ 'INSERT INTO chunks (content, metadata, created_at) VALUES (?, ?, ?)'
62
+ );
63
+ this._getStmt = this._db.prepare(
64
+ 'SELECT id, content, metadata FROM chunks WHERE id = ?'
65
+ );
66
+ this._deleteStmt = this._db.prepare('DELETE FROM chunks WHERE id = ?');
67
+ this._searchStmt = this._db.prepare(`
68
+ SELECT chunks.id, chunks.content, chunks.metadata, rank
69
+ FROM chunks_fts
70
+ JOIN chunks ON chunks.id = chunks_fts.rowid
71
+ WHERE chunks_fts MATCH ?
72
+ ORDER BY rank
73
+ LIMIT ?
74
+ `);
75
+ this._allStmt = this._db.prepare(
76
+ 'SELECT id, content, metadata FROM chunks ORDER BY id DESC LIMIT ?'
77
+ );
78
+ }
79
+
80
+ store(content, metadata = {}) {
81
+ const result = this._insertStmt.run(
82
+ content,
83
+ JSON.stringify(metadata),
84
+ new Date().toISOString()
85
+ );
86
+ return Number(result.lastInsertRowid);
87
+ }
88
+
89
+ search(query, options = {}) {
90
+ const limit = options.limit || 10;
91
+ if (!query) {
92
+ return this._allStmt.all(limit).map(row => ({
93
+ id: row.id,
94
+ content: row.content,
95
+ metadata: JSON.parse(row.metadata),
96
+ score: 1,
97
+ }));
98
+ }
99
+ // FTS5 query: wrap each word in quotes for phrase-safe matching
100
+ const ftsQuery = query
101
+ .split(/\s+/)
102
+ .filter(Boolean)
103
+ .map(w => `"${w.replace(/"/g, '""')}"`)
104
+ .join(' OR ');
105
+ try {
106
+ return this._searchStmt.all(ftsQuery, limit).map(row => ({
107
+ id: row.id,
108
+ content: row.content,
109
+ metadata: JSON.parse(row.metadata),
110
+ score: -row.rank, // FTS5 rank is negative (closer to 0 = better)
111
+ }));
112
+ } catch {
113
+ // If FTS query fails (e.g. special characters), return empty
114
+ return [];
115
+ }
116
+ }
117
+
118
+ get(id) {
119
+ const row = this._getStmt.get(id);
120
+ if (!row) return null;
121
+ return { id: row.id, content: row.content, metadata: JSON.parse(row.metadata) };
122
+ }
123
+
124
+ delete(id) {
125
+ this._deleteStmt.run(id);
126
+ }
127
+
128
+ close() {
129
+ this._db.close();
130
+ }
131
+ }
132
+
133
+ module.exports = { SQLiteStore };
package/src/stores.js ADDED
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ const { SQLiteStore } = require('./store-sqlite');
4
+ const { JsonFileStore } = require('./store-jsonfile');
5
+
6
+ module.exports = {
7
+ SQLite: SQLiteStore,
8
+ JsonFile: JsonFileStore,
9
+ };
package/src/stream.js ADDED
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Structured event streaming for observability and cross-process communication.
5
+ *
6
+ * Interface:
7
+ * emit(event) → void
8
+ * subscribe(callback) → unsubscribe function
9
+ *
10
+ * Event shape:
11
+ * { type, taskId, data, ts }
12
+ *
13
+ * Transport:
14
+ * Object with write(event) method — e.g. JsonlTransport
15
+ * null — disabled (subscribers only)
16
+ */
17
+ class Stream {
18
+ constructor(options = {}) {
19
+ this._transport = options.transport || null;
20
+ this._subscribers = [];
21
+ }
22
+
23
+ emit(event) {
24
+ const full = { ...event, ts: event.ts || new Date().toISOString() };
25
+ for (const fn of this._subscribers) {
26
+ try { fn(full); } catch {}
27
+ }
28
+ if (this._transport) {
29
+ this._transport.write(full);
30
+ }
31
+ }
32
+
33
+ subscribe(callback) {
34
+ this._subscribers.push(callback);
35
+ return () => {
36
+ this._subscribers = this._subscribers.filter(fn => fn !== callback);
37
+ };
38
+ }
39
+ }
40
+
41
+ module.exports = { Stream };
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * JSONL transport: one JSON object per line to a writable stream.
5
+ * Default: process.stdout. Pipe-friendly, parseable by any language.
6
+ *
7
+ * Debug output goes to stderr (never pollutes stdout).
8
+ */
9
+ class JsonlTransport {
10
+ constructor(options = {}) {
11
+ this._output = options.output || process.stdout;
12
+ }
13
+
14
+ write(event) {
15
+ this._output.write(JSON.stringify(event) + '\n');
16
+ }
17
+ }
18
+
19
+ module.exports = { JsonlTransport };