stenotype 0.1.1 → 0.3.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,479 @@
1
+ import express from 'express';
2
+ import session from 'express-session';
3
+ import bcrypt from 'bcryptjs';
4
+ import keytar from 'keytar';
5
+ import { DatabaseSync } from 'node:sqlite';
6
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
7
+ import path from 'node:path';
8
+ import os from 'node:os';
9
+ import { fileURLToPath } from 'node:url';
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+
13
+ const SERVICE_NAME = 'stenotype-dashboard';
14
+ const PORT = parseInt(process.env.DASHBOARD_PORT || '3737', 10);
15
+ const HOST = '0.0.0.0';
16
+
17
+ // Load credentials from OS keychain
18
+ let USERNAME, PASSWORD_HASH, SESSION_SECRET;
19
+ try {
20
+ [USERNAME, PASSWORD_HASH, SESSION_SECRET] = await Promise.all([
21
+ keytar.getPassword(SERVICE_NAME, 'username'),
22
+ keytar.getPassword(SERVICE_NAME, 'password_hash'),
23
+ keytar.getPassword(SERVICE_NAME, 'session_secret'),
24
+ ]);
25
+ } catch (err) {
26
+ console.error('Failed to load dashboard credentials from keychain:', err.message);
27
+ process.exit(1);
28
+ }
29
+
30
+ if (!USERNAME || !PASSWORD_HASH || !SESSION_SECRET) {
31
+ console.error('\n Dashboard credentials not configured.');
32
+ console.error(' Run: stenotype dashboard setup\n');
33
+ process.exit(1);
34
+ }
35
+
36
+ // Stenotype config paths
37
+ const STENO_DIR = path.join(os.homedir(), '.stenotype');
38
+ const CONFIG_PATH = path.join(STENO_DIR, 'stenotype.config.json');
39
+ const LICENSE_PATH = path.join(STENO_DIR, 'license.key');
40
+
41
+ // DB path
42
+ const DB_PATH = path.join(os.homedir(), '.stenotype', 'memories.db');
43
+
44
+ let db;
45
+ try {
46
+ db = new DatabaseSync(DB_PATH);
47
+ console.log(`Connected to DB: ${DB_PATH}`);
48
+ } catch (err) {
49
+ console.error(`Failed to open DB at ${DB_PATH}:`, err.message);
50
+ console.error('Server will start but API routes will fail until DB is available.');
51
+ }
52
+
53
+ const app = express();
54
+
55
+ app.use(express.json());
56
+ app.use(express.urlencoded({ extended: true }));
57
+
58
+ app.use(
59
+ session({
60
+ secret: SESSION_SECRET,
61
+ resave: false,
62
+ saveUninitialized: false,
63
+ cookie: {
64
+ secure: false,
65
+ httpOnly: true,
66
+ maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
67
+ },
68
+ })
69
+ );
70
+
71
+ // Serve static files
72
+ app.use(express.static(path.join(__dirname, 'public')));
73
+
74
+ // Root redirect
75
+ app.get('/', (req, res) => {
76
+ res.redirect('/login.html');
77
+ });
78
+
79
+ // Auth routes (no auth required)
80
+ app.post('/auth/login', (req, res) => {
81
+ const { username, password } = req.body;
82
+ if (!username || !password) {
83
+ return res.status(400).json({ error: 'Username and password required.' });
84
+ }
85
+ if (username !== USERNAME) {
86
+ return res.status(401).json({ error: 'Invalid credentials.' });
87
+ }
88
+ const valid = bcrypt.compareSync(password, PASSWORD_HASH);
89
+ if (!valid) {
90
+ return res.status(401).json({ error: 'Invalid credentials.' });
91
+ }
92
+ req.session.user = { username };
93
+ res.json({ ok: true, user: { username } });
94
+ });
95
+
96
+ app.post('/auth/logout', (req, res) => {
97
+ req.session.destroy(() => {
98
+ res.json({ ok: true });
99
+ });
100
+ });
101
+
102
+ app.get('/auth/me', (req, res) => {
103
+ if (req.session && req.session.user) {
104
+ return res.json({ user: req.session.user });
105
+ }
106
+ res.json({ user: null });
107
+ });
108
+
109
+ // Auth-only middleware (no db check) for setup/settings routes
110
+ function requireAuth(req, res, next) {
111
+ if (!req.session || !req.session.user) {
112
+ return res.status(401).json({ error: 'Unauthorized.' });
113
+ }
114
+ next();
115
+ }
116
+
117
+ // GET /api/setup-status
118
+ app.get('/api/setup-status', requireAuth, (req, res) => {
119
+ try {
120
+ if (!existsSync(CONFIG_PATH)) return res.json({ needsSetup: true });
121
+ const cfg = JSON.parse(readFileSync(CONFIG_PATH, 'utf8'));
122
+ res.json({ needsSetup: !cfg.setup_complete });
123
+ } catch {
124
+ res.json({ needsSetup: true });
125
+ }
126
+ });
127
+
128
+ // POST /api/setup
129
+ app.post('/api/setup', requireAuth, (req, res) => {
130
+ try {
131
+ const { licenseKey, geminiApiKey, embeddingChoice, platform } = req.body;
132
+ if (!licenseKey || !geminiApiKey) {
133
+ return res.status(400).json({ error: 'License key and Gemini API key are required.' });
134
+ }
135
+ mkdirSync(STENO_DIR, { recursive: true });
136
+ writeFileSync(LICENSE_PATH, licenseKey, { mode: 0o600 });
137
+
138
+ let existing = {};
139
+ if (existsSync(CONFIG_PATH)) {
140
+ try { existing = JSON.parse(readFileSync(CONFIG_PATH, 'utf8')); } catch {}
141
+ }
142
+
143
+ const config = {
144
+ ...existing,
145
+ database: DB_PATH,
146
+ extractionProvider: 'openai',
147
+ extractionModel: 'gemini-2.5-flash-lite',
148
+ extractionApiKey: geminiApiKey,
149
+ extractionApiBase: 'https://generativelanguage.googleapis.com/v1beta/openai/',
150
+ embeddingProvider: embeddingChoice || 'none',
151
+ embeddingModel: embeddingChoice === 'local' ? 'Qwen3-Embedding-0.6B-Q8_0' : '',
152
+ embeddingApiKey: '',
153
+ embeddingApiBase: '',
154
+ systemIntegration: platform || 'ductor',
155
+ logWatchPaths: [path.join(os.homedir(), '.claude', 'projects')],
156
+ ductorSessionsWatch: true,
157
+ setup_complete: true,
158
+ };
159
+
160
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
161
+ res.json({ ok: true });
162
+ } catch (err) {
163
+ res.status(500).json({ error: err.message });
164
+ }
165
+ });
166
+
167
+ // GET /api/settings
168
+ app.get('/api/settings', requireAuth, (req, res) => {
169
+ try {
170
+ if (!existsSync(CONFIG_PATH)) return res.json({ settings: null });
171
+ const cfg = JSON.parse(readFileSync(CONFIG_PATH, 'utf8'));
172
+ const masked = cfg.extractionApiKey
173
+ ? '••••••••' + cfg.extractionApiKey.slice(-4)
174
+ : '';
175
+ res.json({
176
+ settings: {
177
+ geminiApiKeyMasked: masked,
178
+ embeddingProvider: cfg.embeddingProvider || 'none',
179
+ systemIntegration: cfg.systemIntegration || 'ductor',
180
+ extractionModel: cfg.extractionModel || 'gemini-2.5-flash-lite',
181
+ },
182
+ });
183
+ } catch (err) {
184
+ res.status(500).json({ error: err.message });
185
+ }
186
+ });
187
+
188
+ // PUT /api/settings
189
+ app.put('/api/settings', requireAuth, (req, res) => {
190
+ try {
191
+ let existing = {};
192
+ if (existsSync(CONFIG_PATH)) {
193
+ try { existing = JSON.parse(readFileSync(CONFIG_PATH, 'utf8')); } catch {}
194
+ }
195
+
196
+ const { geminiApiKey, embeddingProvider, systemIntegration } = req.body;
197
+ if (geminiApiKey && geminiApiKey.length >= 10) {
198
+ existing.extractionApiKey = geminiApiKey;
199
+ }
200
+ if (embeddingProvider) {
201
+ existing.embeddingProvider = embeddingProvider;
202
+ existing.embeddingModel = embeddingProvider === 'local' ? 'Qwen3-Embedding-0.6B-Q8_0' : '';
203
+ }
204
+ if (systemIntegration) {
205
+ existing.systemIntegration = systemIntegration;
206
+ }
207
+
208
+ writeFileSync(CONFIG_PATH, JSON.stringify(existing, null, 2));
209
+ res.json({ ok: true });
210
+ } catch (err) {
211
+ res.status(500).json({ error: err.message });
212
+ }
213
+ });
214
+
215
+ // Auth middleware for /api routes
216
+ app.use('/api', (req, res, next) => {
217
+ if (!req.session || !req.session.user) {
218
+ return res.status(401).json({ error: 'Unauthorized.' });
219
+ }
220
+ if (!db) {
221
+ return res.status(503).json({ error: 'Database not available.' });
222
+ }
223
+ next();
224
+ });
225
+
226
+ // GET /api/stats
227
+ app.get('/api/stats', (req, res) => {
228
+ try {
229
+ const totalRow = db.prepare('SELECT COUNT(*) as count FROM memories').get();
230
+ const activeRow = db.prepare('SELECT COUNT(*) as count FROM memories WHERE is_active = 1').get();
231
+ const recentRow = db.prepare(
232
+ "SELECT COUNT(*) as count FROM memories WHERE is_active = 1 AND created_at >= datetime('now', '-7 days')"
233
+ ).get();
234
+
235
+ const byCategory = db
236
+ .prepare('SELECT category, COUNT(*) as count FROM memories WHERE is_active = 1 GROUP BY category ORDER BY count DESC')
237
+ .all();
238
+
239
+ const byTemperature = db
240
+ .prepare('SELECT temperature, COUNT(*) as count FROM memories WHERE is_active = 1 GROUP BY temperature ORDER BY count DESC')
241
+ .all();
242
+
243
+ const byAgent = db
244
+ .prepare('SELECT agent_id, COUNT(*) as count FROM memories WHERE is_active = 1 GROUP BY agent_id ORDER BY count DESC')
245
+ .all();
246
+
247
+ const archivedRow = db.prepare('SELECT COUNT(*) as count FROM memories WHERE is_active = 0').get();
248
+
249
+ res.json({
250
+ total: totalRow.count,
251
+ active: activeRow.count,
252
+ archived: archivedRow.count,
253
+ recentCount: recentRow.count,
254
+ byCategory,
255
+ byTemperature,
256
+ byAgent,
257
+ });
258
+ } catch (err) {
259
+ console.error('Stats error:', err);
260
+ res.status(500).json({ error: err.message });
261
+ }
262
+ });
263
+
264
+ // GET /api/memories
265
+ app.get('/api/memories', (req, res) => {
266
+ try {
267
+ const { category, temperature, agent_id, page = 1, limit = 20, q, archived, date_from } = req.query;
268
+ const isArchived = archived === 'true';
269
+ const activeFilter = isArchived ? 0 : 1;
270
+ const offset = (parseInt(page) - 1) * parseInt(limit);
271
+ const lim = parseInt(limit);
272
+
273
+ const SELECT_COLS = `
274
+ m.id, m.category, m.content, m.subject, m.confidence, m.importance,
275
+ m.tags, m.source_channel, m.agent_id, m.temperature, m.created_at,
276
+ m.updated_at, m.is_active, m.access_count, m.last_accessed_at,
277
+ m.supersedes_id, m.superseded_by, m.extracted_by
278
+ `;
279
+
280
+ if (q && q.trim()) {
281
+ // FTS search
282
+ const searchTerm = q.trim();
283
+ let ftsQuery = `
284
+ SELECT ${SELECT_COLS}
285
+ FROM memories m
286
+ JOIN memories_fts fts ON m.id = fts.rowid
287
+ WHERE memories_fts MATCH ? AND m.is_active = ?
288
+ `;
289
+ const params = [searchTerm, activeFilter];
290
+
291
+ if (category && category !== 'all') {
292
+ ftsQuery += ` AND m.category = ?`;
293
+ params.push(category);
294
+ }
295
+ if (temperature && temperature !== 'all') {
296
+ ftsQuery += ` AND m.temperature = ?`;
297
+ params.push(temperature);
298
+ }
299
+ if (agent_id && agent_id !== 'all') {
300
+ ftsQuery += ` AND m.agent_id = ?`;
301
+ params.push(agent_id);
302
+ }
303
+ if (date_from) {
304
+ ftsQuery += ` AND m.created_at >= ?`;
305
+ params.push(date_from);
306
+ }
307
+
308
+ ftsQuery += ` ORDER BY rank LIMIT ? OFFSET ?`;
309
+ params.push(lim, offset);
310
+
311
+ let memories;
312
+ try {
313
+ memories = db.prepare(ftsQuery).all(...params);
314
+ } catch (ftsErr) {
315
+ // FTS table might not exist or rowid join might fail - fall back to LIKE search
316
+ console.warn('FTS search failed, falling back to LIKE:', ftsErr.message);
317
+ const likePattern = `%${searchTerm}%`;
318
+ let fallbackQuery = `
319
+ SELECT ${SELECT_COLS}
320
+ FROM memories m
321
+ WHERE m.is_active = ? AND (m.content LIKE ? OR m.subject LIKE ? OR m.tags LIKE ?)
322
+ `;
323
+ const fbParams = [activeFilter, likePattern, likePattern, likePattern];
324
+ if (category && category !== 'all') { fallbackQuery += ` AND m.category = ?`; fbParams.push(category); }
325
+ if (temperature && temperature !== 'all') { fallbackQuery += ` AND m.temperature = ?`; fbParams.push(temperature); }
326
+ if (agent_id && agent_id !== 'all') { fallbackQuery += ` AND m.agent_id = ?`; fbParams.push(agent_id); }
327
+ if (date_from) { fallbackQuery += ` AND m.created_at >= ?`; fbParams.push(date_from); }
328
+ fallbackQuery += ` ORDER BY m.created_at DESC LIMIT ? OFFSET ?`;
329
+ fbParams.push(lim, offset);
330
+ memories = db.prepare(fallbackQuery).all(...fbParams);
331
+ }
332
+
333
+ // Count for pagination
334
+ let countQuery = `
335
+ SELECT COUNT(*) as count
336
+ FROM memories m
337
+ WHERE m.is_active = ? AND (m.content LIKE ? OR m.subject LIKE ? OR m.tags LIKE ?)
338
+ `;
339
+ const likePattern = `%${searchTerm}%`;
340
+ const countParams = [activeFilter, likePattern, likePattern, likePattern];
341
+ if (category && category !== 'all') { countQuery += ` AND m.category = ?`; countParams.push(category); }
342
+ if (temperature && temperature !== 'all') { countQuery += ` AND m.temperature = ?`; countParams.push(temperature); }
343
+ if (agent_id && agent_id !== 'all') { countQuery += ` AND m.agent_id = ?`; countParams.push(agent_id); }
344
+ if (date_from) { countQuery += ` AND m.created_at >= ?`; countParams.push(date_from); }
345
+ const countRow = db.prepare(countQuery).get(...countParams);
346
+
347
+ return res.json({ memories, total: countRow.count, page: parseInt(page) });
348
+ }
349
+
350
+ // Regular filtered query
351
+ let query = `SELECT ${SELECT_COLS} FROM memories m WHERE m.is_active = ?`;
352
+ const params = [activeFilter];
353
+
354
+ if (category && category !== 'all') {
355
+ query += ` AND m.category = ?`;
356
+ params.push(category);
357
+ }
358
+ if (temperature && temperature !== 'all') {
359
+ query += ` AND m.temperature = ?`;
360
+ params.push(temperature);
361
+ }
362
+ if (agent_id && agent_id !== 'all') {
363
+ query += ` AND m.agent_id = ?`;
364
+ params.push(agent_id);
365
+ }
366
+ if (date_from) {
367
+ query += ` AND m.created_at >= ?`;
368
+ params.push(date_from);
369
+ }
370
+
371
+ query += ` ORDER BY m.created_at DESC LIMIT ? OFFSET ?`;
372
+ params.push(lim, offset);
373
+
374
+ const memories = db.prepare(query).all(...params);
375
+
376
+ // Count
377
+ let countQuery = `SELECT COUNT(*) as count FROM memories m WHERE m.is_active = ?`;
378
+ const countParams = [activeFilter];
379
+ if (category && category !== 'all') { countQuery += ` AND m.category = ?`; countParams.push(category); }
380
+ if (temperature && temperature !== 'all') { countQuery += ` AND m.temperature = ?`; countParams.push(temperature); }
381
+ if (agent_id && agent_id !== 'all') { countQuery += ` AND m.agent_id = ?`; countParams.push(agent_id); }
382
+ if (date_from) { countQuery += ` AND m.created_at >= ?`; countParams.push(date_from); }
383
+
384
+ const countRow = db.prepare(countQuery).get(...countParams);
385
+
386
+ res.json({ memories, total: countRow.count, page: parseInt(page) });
387
+ } catch (err) {
388
+ console.error('Memories error:', err);
389
+ res.status(500).json({ error: err.message });
390
+ }
391
+ });
392
+
393
+ // GET /api/memories/:id
394
+ app.get('/api/memories/:id', (req, res) => {
395
+ try {
396
+ const memory = db.prepare(`
397
+ SELECT id, category, content, subject, confidence, importance,
398
+ tags, source_channel, agent_id, temperature, created_at,
399
+ updated_at, is_active, access_count, last_accessed_at,
400
+ supersedes_id, superseded_by, extracted_by
401
+ FROM memories WHERE id = ?
402
+ `).get(req.params.id);
403
+
404
+ if (!memory) {
405
+ return res.status(404).json({ error: 'Memory not found.' });
406
+ }
407
+ res.json(memory);
408
+ } catch (err) {
409
+ console.error('Get memory error:', err);
410
+ res.status(500).json({ error: err.message });
411
+ }
412
+ });
413
+
414
+ // DELETE /api/memories/:id (soft delete)
415
+ app.delete('/api/memories/:id', (req, res) => {
416
+ try {
417
+ const result = db.prepare(
418
+ `UPDATE memories SET is_active = 0, updated_at = datetime('now') WHERE id = ?`
419
+ ).run(req.params.id);
420
+
421
+ if (result.changes === 0) {
422
+ return res.status(404).json({ error: 'Memory not found.' });
423
+ }
424
+ res.json({ ok: true });
425
+ } catch (err) {
426
+ console.error('Delete memory error:', err);
427
+ res.status(500).json({ error: err.message });
428
+ }
429
+ });
430
+
431
+ // PUT /api/memories/:id/restore
432
+ app.put('/api/memories/:id/restore', (req, res) => {
433
+ try {
434
+ const result = db.prepare(
435
+ `UPDATE memories SET is_active = 1, temperature = 'hot',
436
+ last_accessed_at = datetime('now'), access_count = access_count + 1,
437
+ updated_at = datetime('now') WHERE id = ?`
438
+ ).run(req.params.id);
439
+ if (result.changes === 0) return res.status(404).json({ error: 'Memory not found.' });
440
+ res.json({ ok: true });
441
+ } catch (err) {
442
+ console.error('Restore error:', err);
443
+ res.status(500).json({ error: err.message });
444
+ }
445
+ });
446
+
447
+ // GET /api/pinned
448
+ app.get('/api/pinned', (req, res) => {
449
+ try {
450
+ const pinned = db.prepare(`
451
+ SELECT id, memory_id, content, category, agent_id, created_at, updated_at
452
+ FROM pinned_memories
453
+ ORDER BY created_at DESC
454
+ `).all();
455
+ res.json({ pinned });
456
+ } catch (err) {
457
+ console.error('Pinned error:', err);
458
+ res.status(500).json({ error: err.message });
459
+ }
460
+ });
461
+
462
+ // GET /api/commitments
463
+ app.get('/api/commitments', (req, res) => {
464
+ try {
465
+ const commitments = db.prepare(`
466
+ SELECT id, commitment, source, state, agent_id, created_at, updated_at
467
+ FROM commitment_ledger
468
+ ORDER BY created_at DESC
469
+ `).all();
470
+ res.json({ commitments });
471
+ } catch (err) {
472
+ console.error('Commitments error:', err);
473
+ res.status(500).json({ error: err.message });
474
+ }
475
+ });
476
+
477
+ app.listen(PORT, HOST, () => {
478
+ console.log(`Stenotype Dashboard running at http://localhost:${PORT}`);
479
+ });
Binary file
package/package.json CHANGED
@@ -1,62 +1,41 @@
1
1
  {
2
2
  "name": "stenotype",
3
- "version": "0.1.1",
4
- "description": "Automatic memory capture and recall for AI agent systems",
3
+ "version": "0.3.0",
4
+ "description": "Automatic memory capture and recall for AI agent workflows",
5
5
  "type": "module",
6
- "main": "dist/index.js",
6
+ "main": "dist/index.cjs",
7
7
  "bin": {
8
8
  "stenotype": "dist/index.cjs"
9
9
  },
10
10
  "files": [
11
11
  "dist/index.cjs",
12
- "dist/stenotype.jsc"
12
+ "dist/stenotype.jsc",
13
+ "dist/dashboard"
13
14
  ],
14
- "engines": {
15
- "node": ">=20.0.0"
16
- },
17
15
  "scripts": {
18
16
  "build": "tsc",
19
17
  "build:protected": "node scripts/build-protected.mjs",
20
- "start": "node dist/index.js",
21
- "status": "node dist/index.js status",
22
- "recall": "node dist/index.js recall",
23
- "test": "vitest run",
24
- "test:watch": "vitest",
25
- "test:recall-harness": "vitest run tests/recall-harness.test.ts --reporter=verbose",
26
- "typecheck": "tsc --noEmit",
27
- "baseline:report": "node scripts/generate-baseline-report.mjs main ./artifacts/baseline",
28
- "bench:local-vs-cloud": "node scripts/benchmark-local-vs-cloud.mjs --dataset ./bench/benchmark-dataset.json"
29
- },
30
- "openclaw": {
31
- "extensions": [
32
- "./index.ts"
33
- ]
18
+ "dev": "tsx src/index.ts",
19
+ "prepublishOnly": "node scripts/preflight-publish.mjs"
34
20
  },
35
21
  "dependencies": {
36
- "@modelcontextprotocol/sdk": "^1.29.0",
37
- "@types/express": "^5.0.6",
38
- "@types/pg": "^8.18.0",
39
- "better-sqlite3": "^11.9.1",
40
- "express": "^5.2.1",
41
- "jose": "^6.2.3",
22
+ "@anthropic-ai/sdk": "^0.90.0",
23
+ "bcryptjs": "^2.4.3",
24
+ "bytenode": "^1.5.7",
25
+ "express": "^4.18.2",
26
+ "express-session": "^1.17.3",
42
27
  "keytar": "^7.9.0",
43
- "node-llama-cpp": "^3.17.1",
44
- "openai": "^4.80.0",
45
- "pg": "^8.20.0",
46
- "pgvector": "^0.2.1",
47
- "prompts": "^2.4.2",
48
- "bytenode": "^1.5.7"
28
+ "openai": "^4.78.1",
29
+ "sqlite-vec": "^0.1.7",
30
+ "sqlite-vec-linux-x64": "^0.1.7"
49
31
  },
50
32
  "devDependencies": {
51
- "@sinclair/typebox": "^0.34.0",
52
- "@types/better-sqlite3": "^7.6.13",
53
- "@types/keytar": "^4.4.0",
54
- "@types/prompts": "^2.4.9",
55
- "@types/supertest": "^7.2.0",
56
- "bytenode": "^1.5.7",
57
- "esbuild": "^0.28.0",
58
- "supertest": "^7.2.2",
59
- "typescript": "^5.9.3",
60
- "vitest": "^4.0.18"
33
+ "@types/node": "^22.0.0",
34
+ "esbuild": "^0.24.0",
35
+ "tsx": "^4.19.0",
36
+ "typescript": "^5.7.0"
37
+ },
38
+ "engines": {
39
+ "node": ">=20.0.0"
61
40
  }
62
- }
41
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Stenotype contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
package/dist/index.js DELETED
@@ -1,10 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Stenotype standalone entrypoint (post-OpenClaw port).
4
- *
5
- * The original OpenClaw plugin registration lives in index.openclaw.ts.archive
6
- * for reference. That file depended on `openclaw/plugin-sdk` and assumed an
7
- * in-process runtime. Ductor runs Stenotype out-of-process as a CLI, so this
8
- * file simply delegates to the subcommand dispatcher in src/cli.ts.
9
- */
10
- import "./src/cli.js";