dude-claude-plugin 2026.2.18 → 2026.2.20

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dude",
3
- "version": "2026.2.18",
3
+ "version": "2026.2.20",
4
4
  "description": "Ultra-minimal RAG and cross-project memory for Claude CLI",
5
5
  "author": {
6
6
  "name": "Fingerskier"
package/README.md CHANGED
@@ -63,7 +63,17 @@ npx dude-claude-plugin serve
63
63
  dude-claude serve
64
64
  ```
65
65
 
66
- Opens a local dashboard on port 3456 for browsing and editing projects, issues, and specifications.
66
+ Opens a local dashboard at `http://127.0.0.1:3456` (auto-opens in your browser). The UI has two panels: a sidebar listing records with filters, and a main panel for viewing and editing.
67
+
68
+ **What you can do:**
69
+
70
+ - **Project selector** — filter records by project or view all projects at once
71
+ - **Semantic search** — type in the search bar to find records by meaning, not just keywords (results ranked by similarity)
72
+ - **Filter by kind** — narrow to issues, specs, arch decisions, updates, or tests
73
+ - **Filter by status** — open, resolved, archived, active, or inactive
74
+ - **Create / edit / delete** — click "+ New" to add a record, click any record to edit it, or delete from the detail view
75
+
76
+ Set `DUDE_PORT` to change the default port (see [Configuration](#configuration)).
67
77
 
68
78
  ## Configuration
69
79
 
@@ -1,32 +1,43 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
+
3
+ process.on('uncaughtException', (err) => {
4
+ console.error(`[dude] Fatal error: ${err.message}`);
5
+ process.exit(1);
6
+ });
7
+
8
+ process.on('unhandledRejection', (err) => {
9
+ console.error(`[dude] Unhandled rejection: ${err?.message || err}`);
10
+ process.exit(1);
11
+ });
2
12
 
3
13
  const command = process.argv[2] || 'mcp';
4
14
 
5
- switch (command) {
6
- case 'mcp': {
7
- const { startServer } = await import('../src/server.js');
8
- await startServer();
9
- break;
10
- }
11
- case 'serve': {
12
- const { startWebServer } = await import('../src/web.js');
13
- await startWebServer();
14
- break;
15
- }
16
- case 'auto-retrieve': {
17
- await import('../hooks/auto-retrieve.js');
18
- break;
19
- }
20
- case 'auto-persist': {
21
- await import('../hooks/auto-persist.js');
22
- break;
23
- }
24
- case 'auto-persist-plan': {
25
- await import('../hooks/auto-persist-plan.js');
26
- break;
27
- }
28
- default:
29
- console.error(`Usage: dude-claude [mcp|serve|auto-retrieve|auto-persist|auto-persist-plan]
15
+ try {
16
+ switch (command) {
17
+ case 'mcp': {
18
+ const { startServer } = await import('../src/server.js');
19
+ await startServer();
20
+ break;
21
+ }
22
+ case 'serve': {
23
+ const { startWebServer } = await import('../src/web.js');
24
+ await startWebServer();
25
+ break;
26
+ }
27
+ case 'auto-retrieve': {
28
+ await import('../hooks/auto-retrieve.js');
29
+ break;
30
+ }
31
+ case 'auto-persist': {
32
+ await import('../hooks/auto-persist.js');
33
+ break;
34
+ }
35
+ case 'auto-persist-plan': {
36
+ await import('../hooks/auto-persist-plan.js');
37
+ break;
38
+ }
39
+ default:
40
+ console.error(`Usage: dude-claude [mcp|serve|auto-retrieve|auto-persist|auto-persist-plan]
30
41
 
31
42
  Commands:
32
43
  mcp Start the MCP stdio server (default)
@@ -34,5 +45,9 @@ Commands:
34
45
  auto-retrieve Run the auto-retrieve hook (reads prompt from stdin)
35
46
  auto-persist Run the auto-persist utility (reads classification JSON from stdin)
36
47
  auto-persist-plan Run the auto-persist-plan utility (reads classification JSON from stdin)`);
37
- process.exit(1);
48
+ process.exit(1);
49
+ }
50
+ } catch (err) {
51
+ console.error(`[dude] Startup failed: ${err.message}`);
52
+ process.exit(1);
38
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dude-claude-plugin",
3
- "version": "2026.2.18",
3
+ "version": "2026.2.20",
4
4
  "description": "Ultra-minimal RAG and cross-project memory for Claude CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Export the local libsql database as SQL INSERT statements.
4
+ *
5
+ * Usage:
6
+ * node scripts/dump-local.js > dump.sql
7
+ * turso db shell <dbname> < dump.sql
8
+ *
9
+ * Reads from ~/.dude-claude/dude-libsql.db (or DUDE_DB_PATH env override).
10
+ * Outputs projects first, then records (with vector embeddings), to stdout.
11
+ */
12
+
13
+ import { createClient } from '@libsql/client';
14
+ import { join } from 'node:path';
15
+ import { homedir } from 'node:os';
16
+
17
+ const DATA_DIR = join(homedir(), '.dude-claude');
18
+ const DB_PATH = process.env.DUDE_DB_PATH || join(DATA_DIR, 'dude-libsql.db');
19
+
20
+ function esc(str) {
21
+ if (str == null) return 'NULL';
22
+ return "'" + String(str).replace(/'/g, "''") + "'";
23
+ }
24
+
25
+ function parseEmbedding(blob) {
26
+ if (!blob) return null;
27
+ if (blob instanceof Float32Array) return blob;
28
+ if (blob instanceof ArrayBuffer) return new Float32Array(blob);
29
+ if (ArrayBuffer.isView(blob)) {
30
+ return new Float32Array(blob.buffer, blob.byteOffset, blob.byteLength / 4);
31
+ }
32
+ if (typeof blob === 'string') return new Float32Array(JSON.parse(blob));
33
+ return null;
34
+ }
35
+
36
+ async function main() {
37
+ const db = createClient({ url: `file:${DB_PATH}` });
38
+
39
+ // -- Projects --
40
+ const projects = await db.execute('SELECT * FROM project ORDER BY id');
41
+ for (const p of projects.rows) {
42
+ console.log(
43
+ `INSERT INTO project (id, name, created_at, updated_at) VALUES (${p.id}, ${esc(p.name)}, ${esc(p.created_at)}, ${esc(p.updated_at)});`
44
+ );
45
+ }
46
+
47
+ // -- Records (with embeddings) --
48
+ const records = await db.execute('SELECT * FROM record ORDER BY id');
49
+ for (const r of records.rows) {
50
+ const emb = parseEmbedding(r.embedding);
51
+ const embExpr = emb
52
+ ? `vector('${JSON.stringify(Array.from(emb))}')`
53
+ : 'NULL';
54
+
55
+ console.log(
56
+ `INSERT INTO record (id, project_id, kind, title, body, status, embedding, created_at, updated_at) VALUES (${r.id}, ${r.project_id}, ${esc(r.kind)}, ${esc(r.title)}, ${esc(r.body)}, ${esc(r.status)}, ${embExpr}, ${esc(r.created_at)}, ${esc(r.updated_at)});`
57
+ );
58
+ }
59
+
60
+ db.close();
61
+ console.error(`[dump] Exported ${projects.rows.length} projects, ${records.rows.length} records`);
62
+ }
63
+
64
+ main().catch(err => {
65
+ console.error('dump-local failed:', err);
66
+ process.exit(1);
67
+ });
package/src/db-libsql.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createClient } from '@libsql/client';
2
2
  import { execSync } from 'node:child_process';
3
- import { existsSync, mkdirSync } from 'node:fs';
3
+ import { appendFileSync, existsSync, mkdirSync } from 'node:fs';
4
4
  import { join, basename } from 'node:path';
5
5
  import { homedir } from 'node:os';
6
6
  import { DbAdapter } from './db-adapter.js';
@@ -21,6 +21,7 @@ export class LibsqlAdapter extends DbAdapter {
21
21
  this.config = config;
22
22
  this.db = null;
23
23
  this.currentProject = null;
24
+ this._syncError = null;
24
25
  }
25
26
 
26
27
  // ---------------------------------------------------------------------------
@@ -30,13 +31,38 @@ export class LibsqlAdapter extends DbAdapter {
30
31
  async init() {
31
32
  if (this.db) return;
32
33
  this._ensureDataDir();
33
- this.db = this._createClient();
34
- await this._runSchema();
34
+ this._syncError = null;
35
+
36
+ if (this._hasSyncConfig()) {
37
+ try {
38
+ this.db = this._createClient(); // tries sync-enabled client
39
+ await this._runSchema();
40
+ } catch (err) {
41
+ // Close the bad client if it was created
42
+ if (this.db) try { this.db.close(); } catch {}
43
+ this._syncError = err.message;
44
+ this._logToFile(`Cloud sync failed: ${err.message}\n${err.stack}`);
45
+ console.error(`[dude] Cloud sync failed: ${err.message}`);
46
+ console.error('[dude] Falling back to local-only mode. Details in ~/.dude-claude/dude.log');
47
+ // Recreate with local-only client
48
+ this.db = this._createLocalOnlyClient();
49
+ await this._runSchema();
50
+ }
51
+ } else {
52
+ this.db = this._createLocalOnlyClient();
53
+ await this._runSchema();
54
+ }
55
+
35
56
  const projectName = this._detectProject();
36
57
  this.currentProject = await this._upsertProject(projectName);
37
58
  await this._migrateProjectNames(projectName);
59
+
38
60
  const syncInfo = await this.syncStatus();
39
- const syncMsg = syncInfo.enabled ? ` | cloud sync → ${syncInfo.syncUrl}` : '';
61
+ const syncMsg = syncInfo.enabled
62
+ ? ` | cloud sync → ${syncInfo.syncUrl}`
63
+ : syncInfo.error
64
+ ? ' | cloud sync FAILED — local-only mode'
65
+ : '';
40
66
  console.error(`[dude] LibSQL DB ready — project "${this.currentProject.name}" (id=${this.currentProject.id})${syncMsg}`);
41
67
  }
42
68
 
@@ -46,6 +72,23 @@ export class LibsqlAdapter extends DbAdapter {
46
72
  }
47
73
  }
48
74
 
75
+ _logToFile(message) {
76
+ try {
77
+ const logPath = join(DATA_DIR, 'dude.log');
78
+ const timestamp = new Date().toISOString();
79
+ appendFileSync(logPath, `[${timestamp}] ${message}\n`);
80
+ } catch { /* never let logging crash the server */ }
81
+ }
82
+
83
+ _hasSyncConfig() {
84
+ return !!(this.config.syncUrl || process.env.DUDE_TURSO_URL);
85
+ }
86
+
87
+ _createLocalOnlyClient() {
88
+ const url = this.config.url || `file:${this.config.dbPath || DB_PATH}`;
89
+ return createClient({ url });
90
+ }
91
+
49
92
  _createClient() {
50
93
  const url = this.config.url
51
94
  || `file:${this.config.dbPath || DB_PATH}`;
@@ -55,8 +98,8 @@ export class LibsqlAdapter extends DbAdapter {
55
98
  if (this.config.syncUrl || process.env.DUDE_TURSO_URL) {
56
99
  opts.syncUrl = this.config.syncUrl || process.env.DUDE_TURSO_URL;
57
100
  opts.authToken = this.config.authToken || process.env.DUDE_TURSO_TOKEN;
58
- const interval = this.config.syncInterval || process.env.DUDE_SYNC_INTERVAL;
59
- if (interval) opts.syncInterval = parseInt(interval);
101
+ const interval = this.config.syncInterval || process.env.DUDE_SYNC_INTERVAL || 60000;
102
+ opts.syncInterval = parseInt(interval);
60
103
  }
61
104
 
62
105
  return createClient(opts);
@@ -419,22 +462,39 @@ export class LibsqlAdapter extends DbAdapter {
419
462
  const syncUrl = this.config.syncUrl || process.env.DUDE_TURSO_URL || null;
420
463
  const syncInterval = this.config.syncInterval
421
464
  || process.env.DUDE_SYNC_INTERVAL
422
- || null;
465
+ || (syncUrl ? 60000 : null);
423
466
  return {
424
- enabled: !!syncUrl,
467
+ enabled: !!syncUrl && !this._syncError,
425
468
  ...(syncUrl ? { syncUrl } : {}),
426
469
  ...(syncInterval ? { syncInterval: parseInt(syncInterval) } : {}),
470
+ ...(this._syncError ? { error: this._syncError, degraded: true } : {}),
427
471
  };
428
472
  }
429
473
 
430
474
  async sync() {
431
- const status = await this.syncStatus();
432
- if (!status.enabled) {
475
+ if (!this._hasSyncConfig()) {
433
476
  return { synced: false, message: 'Cloud sync not configured. Set DUDE_TURSO_URL and DUDE_TURSO_TOKEN to enable.' };
434
477
  }
478
+
479
+ // If degraded, attempt to reconnect with sync-enabled client
480
+ if (this._syncError) {
481
+ try {
482
+ const newClient = this._createClient();
483
+ // Run a test query to verify the connection works
484
+ await newClient.execute('SELECT 1');
485
+ this.db.close();
486
+ this.db = newClient;
487
+ this._syncError = null;
488
+ console.error('[dude] Cloud sync reconnected successfully.');
489
+ return { synced: true, message: `Reconnected and synced with ${this.config.syncUrl || process.env.DUDE_TURSO_URL}` };
490
+ } catch (err) {
491
+ return { synced: false, message: `Reconnection failed: ${err.message}` };
492
+ }
493
+ }
494
+
435
495
  try {
436
496
  await this.db.sync();
437
- return { synced: true, message: `Synced with ${status.syncUrl}` };
497
+ return { synced: true, message: `Synced with ${this.config.syncUrl || process.env.DUDE_TURSO_URL}` };
438
498
  } catch (err) {
439
499
  return { synced: false, message: `Sync failed: ${err.message}` };
440
500
  }