dude-claude-plugin 2026.2.18 → 2026.2.19
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/.claude-plugin/plugin.json +1 -1
- package/README.md +11 -1
- package/package.json +1 -1
- package/scripts/dump-local.js +67 -0
- package/src/db-libsql.js +55 -9
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
|
|
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
|
|
package/package.json
CHANGED
|
@@ -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
|
@@ -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,32 @@ export class LibsqlAdapter extends DbAdapter {
|
|
|
30
31
|
async init() {
|
|
31
32
|
if (this.db) return;
|
|
32
33
|
this._ensureDataDir();
|
|
33
|
-
this.
|
|
34
|
+
this._syncError = null;
|
|
35
|
+
|
|
36
|
+
if (this._hasSyncConfig()) {
|
|
37
|
+
try {
|
|
38
|
+
this.db = this._createClient(); // tries sync-enabled client
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.error(`[dude] Cloud sync connection failed: ${err.message}`);
|
|
41
|
+
console.error('[dude] Falling back to local-only mode.');
|
|
42
|
+
this._syncError = err.message;
|
|
43
|
+
this.db = this._createLocalOnlyClient();
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
this.db = this._createLocalOnlyClient();
|
|
47
|
+
}
|
|
48
|
+
|
|
34
49
|
await this._runSchema();
|
|
35
50
|
const projectName = this._detectProject();
|
|
36
51
|
this.currentProject = await this._upsertProject(projectName);
|
|
37
52
|
await this._migrateProjectNames(projectName);
|
|
53
|
+
|
|
38
54
|
const syncInfo = await this.syncStatus();
|
|
39
|
-
const syncMsg = syncInfo.enabled
|
|
55
|
+
const syncMsg = syncInfo.enabled
|
|
56
|
+
? ` | cloud sync → ${syncInfo.syncUrl}`
|
|
57
|
+
: syncInfo.error
|
|
58
|
+
? ' | cloud sync FAILED — local-only mode'
|
|
59
|
+
: '';
|
|
40
60
|
console.error(`[dude] LibSQL DB ready — project "${this.currentProject.name}" (id=${this.currentProject.id})${syncMsg}`);
|
|
41
61
|
}
|
|
42
62
|
|
|
@@ -46,6 +66,15 @@ export class LibsqlAdapter extends DbAdapter {
|
|
|
46
66
|
}
|
|
47
67
|
}
|
|
48
68
|
|
|
69
|
+
_hasSyncConfig() {
|
|
70
|
+
return !!(this.config.syncUrl || process.env.DUDE_TURSO_URL);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
_createLocalOnlyClient() {
|
|
74
|
+
const url = this.config.url || `file:${this.config.dbPath || DB_PATH}`;
|
|
75
|
+
return createClient({ url });
|
|
76
|
+
}
|
|
77
|
+
|
|
49
78
|
_createClient() {
|
|
50
79
|
const url = this.config.url
|
|
51
80
|
|| `file:${this.config.dbPath || DB_PATH}`;
|
|
@@ -55,8 +84,8 @@ export class LibsqlAdapter extends DbAdapter {
|
|
|
55
84
|
if (this.config.syncUrl || process.env.DUDE_TURSO_URL) {
|
|
56
85
|
opts.syncUrl = this.config.syncUrl || process.env.DUDE_TURSO_URL;
|
|
57
86
|
opts.authToken = this.config.authToken || process.env.DUDE_TURSO_TOKEN;
|
|
58
|
-
const interval = this.config.syncInterval || process.env.DUDE_SYNC_INTERVAL;
|
|
59
|
-
|
|
87
|
+
const interval = this.config.syncInterval || process.env.DUDE_SYNC_INTERVAL || 60000;
|
|
88
|
+
opts.syncInterval = parseInt(interval);
|
|
60
89
|
}
|
|
61
90
|
|
|
62
91
|
return createClient(opts);
|
|
@@ -419,22 +448,39 @@ export class LibsqlAdapter extends DbAdapter {
|
|
|
419
448
|
const syncUrl = this.config.syncUrl || process.env.DUDE_TURSO_URL || null;
|
|
420
449
|
const syncInterval = this.config.syncInterval
|
|
421
450
|
|| process.env.DUDE_SYNC_INTERVAL
|
|
422
|
-
|| null;
|
|
451
|
+
|| (syncUrl ? 60000 : null);
|
|
423
452
|
return {
|
|
424
|
-
enabled: !!syncUrl,
|
|
453
|
+
enabled: !!syncUrl && !this._syncError,
|
|
425
454
|
...(syncUrl ? { syncUrl } : {}),
|
|
426
455
|
...(syncInterval ? { syncInterval: parseInt(syncInterval) } : {}),
|
|
456
|
+
...(this._syncError ? { error: this._syncError, degraded: true } : {}),
|
|
427
457
|
};
|
|
428
458
|
}
|
|
429
459
|
|
|
430
460
|
async sync() {
|
|
431
|
-
|
|
432
|
-
if (!status.enabled) {
|
|
461
|
+
if (!this._hasSyncConfig()) {
|
|
433
462
|
return { synced: false, message: 'Cloud sync not configured. Set DUDE_TURSO_URL and DUDE_TURSO_TOKEN to enable.' };
|
|
434
463
|
}
|
|
464
|
+
|
|
465
|
+
// If degraded, attempt to reconnect with sync-enabled client
|
|
466
|
+
if (this._syncError) {
|
|
467
|
+
try {
|
|
468
|
+
const newClient = this._createClient();
|
|
469
|
+
// Run a test query to verify the connection works
|
|
470
|
+
await newClient.execute('SELECT 1');
|
|
471
|
+
this.db.close();
|
|
472
|
+
this.db = newClient;
|
|
473
|
+
this._syncError = null;
|
|
474
|
+
console.error('[dude] Cloud sync reconnected successfully.');
|
|
475
|
+
return { synced: true, message: `Reconnected and synced with ${this.config.syncUrl || process.env.DUDE_TURSO_URL}` };
|
|
476
|
+
} catch (err) {
|
|
477
|
+
return { synced: false, message: `Reconnection failed: ${err.message}` };
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
435
481
|
try {
|
|
436
482
|
await this.db.sync();
|
|
437
|
-
return { synced: true, message: `Synced with ${
|
|
483
|
+
return { synced: true, message: `Synced with ${this.config.syncUrl || process.env.DUDE_TURSO_URL}` };
|
|
438
484
|
} catch (err) {
|
|
439
485
|
return { synced: false, message: `Sync failed: ${err.message}` };
|
|
440
486
|
}
|