obsidian-tc 1.0.2 → 1.3.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.
- package/LICENSE +661 -201
- package/README.md +10 -2
- package/dist/cli.js +482 -57331
- package/dist/cli.js.map +738 -0
- package/dist/index.js +145 -39852
- package/dist/index.js.map +531 -0
- package/dist/migrations/20260519_002_entity_unique.sql +51 -0
- package/dist/migrations/20260626_001_experiential_init.sql +61 -0
- package/dist/migrations/20260626_001_vault_edges.sql +31 -0
- package/dist/migrations/20260626_002_plane.sql +69 -0
- package/dist/migrations/20260702_001_notes.sql +17 -0
- package/dist/plugin/main.js +5 -0
- package/dist/plugin/manifest.json +11 -0
- package/dist/schema.sql +8 -2
- package/package.json +28 -15
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
-- 20260519_002_entity_unique — F4: enforce the entity natural key
|
|
2
|
+
-- (vault_id, entity_type, name) at the DB level to close the create_entity
|
|
3
|
+
-- read-then-insert race. Pre-existing duplicates are merged into the earliest
|
|
4
|
+
-- (MIN rowid) row. Relations are repointed onto the survivor BEFORE the dedup
|
|
5
|
+
-- DELETE, so the ON DELETE CASCADE FKs do not drop edges that belonged to a
|
|
6
|
+
-- duplicate (review #5); then the unique index is created.
|
|
7
|
+
|
|
8
|
+
-- Repoint outgoing edges of a duplicate onto its surviving sibling. UPDATE OR IGNORE
|
|
9
|
+
-- drops a repoint that would collide with an existing (source,target,type) edge.
|
|
10
|
+
UPDATE OR IGNORE memory_relations
|
|
11
|
+
SET source_id = (
|
|
12
|
+
SELECT keep.id
|
|
13
|
+
FROM memory_entities dup
|
|
14
|
+
JOIN memory_entities keep
|
|
15
|
+
ON keep.vault_id = dup.vault_id
|
|
16
|
+
AND keep.entity_type = dup.entity_type
|
|
17
|
+
AND keep.name = dup.name
|
|
18
|
+
WHERE dup.id = memory_relations.source_id
|
|
19
|
+
ORDER BY keep.rowid
|
|
20
|
+
LIMIT 1
|
|
21
|
+
)
|
|
22
|
+
WHERE source_id IN (
|
|
23
|
+
SELECT id FROM memory_entities
|
|
24
|
+
WHERE rowid NOT IN (SELECT MIN(rowid) FROM memory_entities GROUP BY vault_id, entity_type, name)
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
-- Repoint incoming edges likewise.
|
|
28
|
+
UPDATE OR IGNORE memory_relations
|
|
29
|
+
SET target_id = (
|
|
30
|
+
SELECT keep.id
|
|
31
|
+
FROM memory_entities dup
|
|
32
|
+
JOIN memory_entities keep
|
|
33
|
+
ON keep.vault_id = dup.vault_id
|
|
34
|
+
AND keep.entity_type = dup.entity_type
|
|
35
|
+
AND keep.name = dup.name
|
|
36
|
+
WHERE dup.id = memory_relations.target_id
|
|
37
|
+
ORDER BY keep.rowid
|
|
38
|
+
LIMIT 1
|
|
39
|
+
)
|
|
40
|
+
WHERE target_id IN (
|
|
41
|
+
SELECT id FROM memory_entities
|
|
42
|
+
WHERE rowid NOT IN (SELECT MIN(rowid) FROM memory_entities GROUP BY vault_id, entity_type, name)
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
DELETE FROM memory_entities
|
|
46
|
+
WHERE rowid NOT IN (
|
|
47
|
+
SELECT MIN(rowid) FROM memory_entities GROUP BY vault_id, entity_type, name
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
CREATE UNIQUE INDEX idx_memory_entities_natural_key
|
|
51
|
+
ON memory_entities(vault_id, entity_type, name);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
-- 20260626_001_experiential_init.sql
|
|
2
|
+
-- THE-233 (W-SCHEMA): the experiential tier -- a PHYSICALLY SEPARATE store (experiential.db),
|
|
3
|
+
-- NOT a partition of cache.db. The membrane (see decision
|
|
4
|
+
-- 2026-06-26-experiential-tier-write-on-gate): low-trust per-retrieval state lives in its
|
|
5
|
+
-- own file so an injected / auto-captured episode can never FK into an authored atom,
|
|
6
|
+
-- poisoning blast radius is capped at the store boundary, and a reset is a file truncate
|
|
7
|
+
-- rather than a filtered delete. Because chunks live in cache.db, object_id / chunk_id
|
|
8
|
+
-- reference chunk ids BY VALUE (no cross-file foreign key -- this is the membrane).
|
|
9
|
+
--
|
|
10
|
+
-- Holds only NON-RECONSTRUCTABLE keep-state: engram activation history + retrieval feedback.
|
|
11
|
+
-- Authored chunks are re-indexed from the vault, never imported (thin-merge decision).
|
|
12
|
+
-- Behavior-preserving port of KMS vault_object_state (migration 017) + chunk_retrievals (018).
|
|
13
|
+
--
|
|
14
|
+
-- PRAGMAs (foreign_keys, journal_mode = WAL) are set per-connection by the runtime.
|
|
15
|
+
-- The migration runner pre-creates schema_migrations; it is not declared here.
|
|
16
|
+
--
|
|
17
|
+
-- TRIPWIRE: valid_from / valid_until are the EXISTING KMS 2-timestamp validity, loaded
|
|
18
|
+
-- as-is. This is NOT the engine-build 4-timestamp bi-temporal substrate; no claim atoms,
|
|
19
|
+
-- authoritative_claims, or derives_from (THE-235, downstream).
|
|
20
|
+
|
|
21
|
+
-- ACT-R / engram activation state, one row per chunk (object).
|
|
22
|
+
CREATE TABLE vault_object_state (
|
|
23
|
+
object_id TEXT PRIMARY KEY, -- chunk id, by value (membrane: no cross-file FK)
|
|
24
|
+
retrieval_strength REAL,
|
|
25
|
+
storage_strength REAL,
|
|
26
|
+
frequency INTEGER NOT NULL DEFAULT 0,
|
|
27
|
+
last_accessed INTEGER,
|
|
28
|
+
learned_at INTEGER,
|
|
29
|
+
valid_from INTEGER, -- existing KMS 2-timestamp validity (loaded as-is)
|
|
30
|
+
valid_until INTEGER,
|
|
31
|
+
emotional_weight REAL NOT NULL DEFAULT 5, -- KMS default 5 (1-10 modulates ACT-R decay)
|
|
32
|
+
confidence REAL,
|
|
33
|
+
injections INTEGER NOT NULL DEFAULT 0,
|
|
34
|
+
hits INTEGER NOT NULL DEFAULT 0,
|
|
35
|
+
misses INTEGER NOT NULL DEFAULT 0,
|
|
36
|
+
last_hit_at INTEGER,
|
|
37
|
+
cached_activation_score REAL,
|
|
38
|
+
last_computed_at INTEGER
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
CREATE INDEX idx_vault_object_state_activation
|
|
42
|
+
ON vault_object_state(cached_activation_score)
|
|
43
|
+
WHERE cached_activation_score IS NOT NULL;
|
|
44
|
+
|
|
45
|
+
-- Append-only retrieval event log; feeds the nightly activation recompute (sleep-time plane).
|
|
46
|
+
CREATE TABLE chunk_retrievals (
|
|
47
|
+
id TEXT PRIMARY KEY, -- retrieval event id, by value
|
|
48
|
+
chunk_id TEXT NOT NULL, -- chunk id, by value (membrane: no cross-file FK)
|
|
49
|
+
retrieved_at INTEGER NOT NULL,
|
|
50
|
+
session_id TEXT,
|
|
51
|
+
surface_type TEXT, -- which surface / client retrieved it
|
|
52
|
+
query_text TEXT,
|
|
53
|
+
rank_in_results INTEGER,
|
|
54
|
+
rerank_score REAL, -- rerank passthrough score (D1); was KMS cohere_score
|
|
55
|
+
cited_in_response INTEGER, -- nullable 0/1
|
|
56
|
+
citation_score REAL,
|
|
57
|
+
feedback INTEGER -- -1 | 0 | +1
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
CREATE INDEX idx_chunk_retrievals_chunk ON chunk_retrievals(chunk_id, retrieved_at DESC);
|
|
61
|
+
CREATE INDEX idx_chunk_retrievals_session ON chunk_retrievals(session_id) WHERE session_id IS NOT NULL;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
-- 20260626_001_vault_edges.sql
|
|
2
|
+
-- THE-233 (W-SCHEMA): wikilink edge graph for GraphRAG.
|
|
3
|
+
--
|
|
4
|
+
-- The ported vault_graph_expand walks this edge table (KMS migrations 005/011/013).
|
|
5
|
+
-- Edges are authored / derived from vault wikilinks, so they live in the main cache.db
|
|
6
|
+
-- alongside chunks. Path-based (a wikilink is note -> note); the graph walk joins edges
|
|
7
|
+
-- to chunks by path. Behavior-preserving port of the KMS vault_edges shape (Postgres)
|
|
8
|
+
-- onto SQLite. SQLite has full recursive CTEs, so the expand walk ports directly; only
|
|
9
|
+
-- the pgvector distance operator becomes a sqlite-vec / in-process call (retrieval slice).
|
|
10
|
+
--
|
|
11
|
+
-- PRAGMAs (foreign_keys, journal_mode = WAL) are set per-connection by the runtime.
|
|
12
|
+
--
|
|
13
|
+
-- TRIPWIRE: plain graph structure only. No claim atoms, authoritative_claims,
|
|
14
|
+
-- derives_from, or bi-temporal columns -- that is the engine-build typed-atom substrate
|
|
15
|
+
-- (THE-235), downstream of this merge.
|
|
16
|
+
|
|
17
|
+
CREATE TABLE vault_edges (
|
|
18
|
+
source_path TEXT NOT NULL, -- vault-relative note path (edge tail)
|
|
19
|
+
target_path TEXT NOT NULL, -- vault-relative note path (edge head)
|
|
20
|
+
edge_type TEXT NOT NULL, -- relation verb, e.g. "links_to" | "embed" | "tag"
|
|
21
|
+
edge_kind TEXT NOT NULL DEFAULT 'literal', -- 'literal' (wikilink) | 'virtual' (cosine neighbor)
|
|
22
|
+
provenance TEXT, -- how the edge was derived, e.g. "wikilink" | "graph_expand"
|
|
23
|
+
created_at INTEGER NOT NULL,
|
|
24
|
+
updated_at INTEGER NOT NULL
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
-- Dedup identical edges regardless of insert order (mirrors KMS unique(source, target, type)).
|
|
28
|
+
CREATE UNIQUE INDEX idx_vault_edges_unique ON vault_edges(source_path, target_path, edge_type);
|
|
29
|
+
CREATE INDEX idx_vault_edges_source ON vault_edges(source_path);
|
|
30
|
+
CREATE INDEX idx_vault_edges_target ON vault_edges(target_path);
|
|
31
|
+
CREATE INDEX idx_vault_edges_kind ON vault_edges(edge_kind);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
-- 20260626_002_plane.sql
|
|
2
|
+
-- THE-233 W-WORKERS: sleep-time consolidation plane state tables. Committed here but NOT yet
|
|
3
|
+
-- wired into the cli.ts migrate chain — that registration lands in the integration slice
|
|
4
|
+
-- (Batch 4), alongside W-SCHEMA's vault_edges/experiential migrations. The plane jobs operate
|
|
5
|
+
-- on these tables; the unit tests provision them directly.
|
|
6
|
+
--
|
|
7
|
+
-- PRAGMAs (foreign_keys, journal_mode = WAL) are set per-connection by the runtime.
|
|
8
|
+
-- TRIPWIRE: lifecycle/state tables only. No claim atoms, authoritative_claims, derives_from,
|
|
9
|
+
-- or 4-timestamp bi-temporal columns (detected_at/resolved_at is the existing KMS 2-timestamp
|
|
10
|
+
-- flag lifecycle, not the engine-build substrate).
|
|
11
|
+
|
|
12
|
+
-- Contradiction flags (contradiction job; was KMS contradictions table). Flag-only lifecycle.
|
|
13
|
+
CREATE TABLE contradictions (
|
|
14
|
+
id TEXT PRIMARY KEY,
|
|
15
|
+
source_chunk_id TEXT NOT NULL,
|
|
16
|
+
source_path TEXT NOT NULL,
|
|
17
|
+
conflict_chunk_id TEXT NOT NULL,
|
|
18
|
+
conflict_path TEXT NOT NULL,
|
|
19
|
+
source_content_sha TEXT NOT NULL,
|
|
20
|
+
conflict_content_sha TEXT NOT NULL,
|
|
21
|
+
cosine_similarity REAL,
|
|
22
|
+
judge_verdict TEXT NOT NULL, -- 'contradiction' | 'tension'
|
|
23
|
+
judge_rationale TEXT,
|
|
24
|
+
judge_model TEXT, -- resolved gateway model (attestation)
|
|
25
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
26
|
+
detected_at INTEGER NOT NULL,
|
|
27
|
+
resolved_at INTEGER
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
-- Dedup a pair regardless of which side was the new chunk (mirrors KMS unique constraint).
|
|
31
|
+
CREATE UNIQUE INDEX idx_contradictions_pair ON contradictions(source_content_sha, conflict_content_sha);
|
|
32
|
+
CREATE INDEX idx_contradictions_status ON contradictions(status, detected_at DESC);
|
|
33
|
+
|
|
34
|
+
-- Weekly synthesis records (synthesis job; was KMS syntheses table). One row per ISO week.
|
|
35
|
+
CREATE TABLE syntheses (
|
|
36
|
+
iso_year INTEGER NOT NULL,
|
|
37
|
+
iso_week INTEGER NOT NULL,
|
|
38
|
+
generated_at INTEGER NOT NULL,
|
|
39
|
+
cluster_count INTEGER NOT NULL,
|
|
40
|
+
pattern_count INTEGER NOT NULL,
|
|
41
|
+
clusters TEXT NOT NULL, -- JSON
|
|
42
|
+
patterns TEXT NOT NULL, -- JSON
|
|
43
|
+
judge_model TEXT,
|
|
44
|
+
PRIMARY KEY (iso_year, iso_week)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
-- Health audit reports (audit job; was KMS audit_reports/audit_flags collapsed).
|
|
48
|
+
CREATE TABLE audit_reports (
|
|
49
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
50
|
+
report_type TEXT NOT NULL,
|
|
51
|
+
created_at INTEGER NOT NULL,
|
|
52
|
+
has_issues INTEGER NOT NULL,
|
|
53
|
+
summary TEXT,
|
|
54
|
+
report TEXT NOT NULL -- JSON
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
CREATE INDEX idx_audit_reports_created ON audit_reports(created_at DESC);
|
|
58
|
+
|
|
59
|
+
-- Plane run log (observability for the local job runner).
|
|
60
|
+
CREATE TABLE job_runs (
|
|
61
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
62
|
+
job TEXT NOT NULL,
|
|
63
|
+
started_at INTEGER NOT NULL,
|
|
64
|
+
finished_at INTEGER,
|
|
65
|
+
ok INTEGER NOT NULL,
|
|
66
|
+
detail TEXT -- JSON
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
CREATE INDEX idx_job_runs_job ON job_runs(job, started_at DESC);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- THE-291: per-note metadata backing the lexical/metadata index (list_tags, list_properties,
|
|
2
|
+
-- find_notes_by_*, search_text acceleration). notes_fts (FTS5, trigram) is runtime-provisioned
|
|
3
|
+
-- by ensureNotesFts (search/fts.ts) exactly like vec_chunks — deliberately NOT in this chain,
|
|
4
|
+
-- so the versioned migrations apply unchanged on an adapter built without FTS5.
|
|
5
|
+
CREATE TABLE IF NOT EXISTS notes (
|
|
6
|
+
vault_id TEXT NOT NULL,
|
|
7
|
+
path TEXT NOT NULL,
|
|
8
|
+
title TEXT NOT NULL,
|
|
9
|
+
tags TEXT NOT NULL, -- JSON array (noteTags(raw).all)
|
|
10
|
+
frontmatter TEXT, -- JSON object; NULL when absent or unserializable
|
|
11
|
+
content_hash TEXT NOT NULL, -- contentHash(raw) — backfill/staleness key
|
|
12
|
+
mtime INTEGER NOT NULL,
|
|
13
|
+
size INTEGER NOT NULL,
|
|
14
|
+
indexed_at INTEGER NOT NULL,
|
|
15
|
+
PRIMARY KEY (vault_id, path)
|
|
16
|
+
);
|
|
17
|
+
CREATE INDEX IF NOT EXISTS idx_notes_hash ON notes(content_hash);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";var k=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var B=Object.prototype.hasOwnProperty;var E=(l,s)=>{for(var o in s)k(l,o,{get:s[o],enumerable:!0})},L=(l,s,o,n)=>{if(s&&typeof s=="object"||typeof s=="function")for(let i of T(s))!B.call(l,i)&&i!==o&&k(l,i,{get:()=>s[i],enumerable:!(n=C(s,i))||n.enumerable});return l};var S=l=>L(k({},"__esModule",{value:!0}),l);var O={};E(O,{default:()=>b});module.exports=S(O);var q=require("obsidian");var _=require("obsidian"),j="1",R={excalidraw:"obsidian-excalidraw-plugin",dataview:"dataview",tasks:"obsidian-tasks-plugin",templater:"templater-obsidian",quickadd:"quickadd","text-extractor":"obsidian-text-extractor","make-md":"make-md"},c=(l,s)=>{l.status(200).json({ok:!0,result:s})},u=(l,s,o,n)=>{l.status(200).json({ok:!1,code:s,message:o,...n?{details:n}:{}})},m=l=>typeof l.body=="object"&&l.body!==null?l.body:{},g=(l,s)=>typeof l[s]=="string"?l[s]:void 0;function w(l,s){let o=R[s];return o?l.plugins?.plugins?.[o]:void 0}function h(l,s,o){let n=w(l,o);return n?n.api?n.api:(u(s,"plugin_unreachable",`${o} exposes no programmatic API`,{plugin:o}),null):(u(s,"plugin_missing",`${o} is not installed`,{plugin:o}),null)}function F(l,s,o){let n={};for(let[i,e]of Object.entries(R)){let t=l.plugins?.plugins?.[e];n[i]=t?{installed:!0,...t.manifest?.version?{version:t.manifest.version}:{}}:{installed:!1}}return{plugin_version:s,obsidian_version:_.apiVersion,obsidianTcApiVersion:j,vault_path:l.vault.getName(),capabilities:n,shape_ok:o.length===0,...o.length?{shape_warnings:o}:{}}}function x(l,s){let o=l.vault.getAbstractFileByPath((0,_.normalizePath)(s));return o&&"extension"in o?o:null}function A(l,s,o=[]){let n=l;return[{method:"get",path:"/probe",handler:(i,e)=>c(e,F(n,s,o))},{method:"post",path:"/commands/list",handler:(i,e)=>{let t=g(m(i),"filter")?.toLowerCase(),a=(n.commands?.listCommands()??[]).map(r=>({id:r.id,name:r.name})).filter(r=>!t||r.id.toLowerCase().includes(t)||r.name.toLowerCase().includes(t));c(e,{items:a,total:a.length})}},{method:"post",path:"/commands/execute",handler:(i,e)=>{let t=g(m(i),"command_id");if(!t)return u(e,"invalid_input","command_id is required");if(!(n.commands?.executeCommandById(t)??!1))return u(e,"invalid_input","command not found or did not run",{command_id:t});c(e,{command_id:t,fired_at:new Date().toISOString()})}},{method:"post",path:"/dataview/dql",handler:async(i,e)=>{let t=h(n,e,"dataview");if(!t)return;if(!t.query)return u(e,"plugin_unreachable","dataview query API unavailable",{plugin:"dataview"});let a=await t.query(g(m(i),"dql")??"");if(!a.successful)return u(e,"dql_error",a.error??"DQL query failed");c(e,{headers:a.value?.headers??[],rows:a.value?.values??[],note_paths:[]})}},{method:"post",path:"/dataview/validate",handler:async(i,e)=>{let t=h(n,e,"dataview");if(!t)return;if(!t.query)return u(e,"plugin_unreachable","dataview query API unavailable",{plugin:"dataview"});let a=await t.query(g(m(i),"dql")??"");c(e,{valid:a.successful,...a.successful?{}:{error:{message:a.error??"parse error"}}})}},{method:"post",path:"/dataview/eval",handler:async(i,e)=>{let t=h(n,e,"dataview");if(!t)return;if(!t.evaluate)return u(e,"plugin_unreachable","dataview evaluate API unavailable",{plugin:"dataview"});let a=m(i),r=await t.evaluate(g(a,"expression")??"",g(a,"path"));if(!r.successful)return u(e,"dql_error",r.error??"evaluation failed");c(e,{value:r.value,type:typeof r.value})}},{method:"post",path:"/quickadd/actions",handler:(i,e)=>{let t=w(n,"quickadd");if(!t)return u(e,"plugin_missing","quickadd is not installed",{plugin:"quickadd"});let r=(t.settings?.choices??[]).map(d=>({name:d.name,type:d.type??"unknown"}));c(e,{items:r})}},{method:"post",path:"/quickadd/trigger",handler:async(i,e)=>{let t=h(n,e,"quickadd");if(!t)return;if(!t.executeChoice)return u(e,"plugin_unreachable","quickadd execute API unavailable",{plugin:"quickadd"});let a=m(i),r=g(a,"action_name");if(!r)return u(e,"invalid_input","action_name is required");let d=typeof a.args=="object"&&a.args!==null?a.args:void 0;await t.executeChoice(r,d),c(e,{action_name:r,fired_at:new Date().toISOString()})}},{method:"post",path:"/ocr/attachment",handler:async(i,e)=>{let t=h(n,e,"text-extractor");if(!t)return;if(!t.extractText)return u(e,"plugin_unreachable","text-extractor API unavailable",{plugin:"text-extractor"});let a=g(m(i),"path")??"",r=x(n,a);if(!r)return u(e,"note_not_found","attachment not found",{path:a});let d=await t.isInCache?.(r)??!1,p=Date.now(),f=await t.extractText(r);c(e,{path:a,text:f,cached:d,duration_ms:Date.now()-p})}},{method:"post",path:"/ocr/bulk",handler:async(i,e)=>{let t=h(n,e,"text-extractor");if(!t)return;if(!t.extractText)return u(e,"plugin_unreachable","text-extractor API unavailable",{plugin:"text-extractor"});let a=m(i).paths,r=Array.isArray(a)?a:[],d=Date.now(),p=[];for(let f of r){let y=x(n,f);if(!y){p.push({path:f,ok:!1,error:"not found"});continue}try{p.push({path:f,ok:!0,text:await t.extractText(y)})}catch(v){p.push({path:f,ok:!1,error:v.message})}}c(e,{processed:p.length,results:p,total_duration_ms:Date.now()-d})}},{method:"post",path:"/excalidraw/read",handler:async(i,e)=>{if(!w(n,"excalidraw"))return u(e,"plugin_missing","excalidraw is not installed",{plugin:"excalidraw"});let t=g(m(i),"path")??"",a=x(n,t);if(!a)return u(e,"note_not_found","drawing not found",{path:t});let r=await n.vault.read(a);c(e,{path:t,text_content:r})}},{method:"post",path:"/excalidraw/write",handler:async(i,e)=>{if(!w(n,"excalidraw"))return u(e,"plugin_missing","excalidraw is not installed",{plugin:"excalidraw"});let t=m(i),a=g(t,"path")??"",r=x(n,a);if(t.mode==="create"&&r&&t.overwrite!==!0)return u(e,"note_exists","drawing already exists",{path:a});let d=`---
|
|
2
|
+
excalidraw-plugin: parsed
|
|
3
|
+
---
|
|
4
|
+
# Excalidraw Data
|
|
5
|
+
`;r?await n.vault.modify(r,d):await n.vault.create((0,_.normalizePath)(a),d),c(e,{path:a,plugin_used:!0})}},{method:"post",path:"/makemd/spaces",handler:(i,e)=>{let t=h(n,e,"make-md");t&&c(e,{spaces:typeof t.spaces=="function"?t.spaces():[]})}},{method:"post",path:"/makemd/query",handler:(i,e)=>{let t=h(n,e,"make-md");if(!t)return;if(!t.query)return u(e,"plugin_unreachable","make-md query API unavailable",{plugin:"make-md"});let a=m(i),r=t.query(g(a,"space_id")??"",a.filter);c(e,{items:Array.isArray(r)?r:[],total:Array.isArray(r)?r.length:0})}},{method:"post",path:"/templater/list",handler:(i,e)=>{let t=w(n,"templater");if(!t)return u(e,"plugin_missing","templater is not installed",{plugin:"templater"});let r=t.settings?.templates_folder,d=r?n.vault.getMarkdownFiles().filter(p=>p.path.startsWith(`${r}/`)).map(p=>({path:p.path,name:p.basename})):[];c(e,{items:d})}},{method:"post",path:"/templater/execute",handler:async(i,e)=>{let t=w(n,"templater");if(!t)return u(e,"plugin_missing","templater is not installed",{plugin:"templater"});if(!t.templater?.create_new_note_from_template)return u(e,"plugin_unreachable","templater expansion API unavailable",{plugin:"templater"});let a=m(i),r=g(a,"template")??"",d=g(a,"target")??"",p=x(n,r);if(!p)return u(e,"note_not_found","template not found",{path:r});let f=d.endsWith(".md")?d:`${d}.md`;if(a.overwrite!==!0&&x(n,f))return u(e,"note_exists","target already exists; set overwrite",{path:f});let y=t.templater.create_new_note_from_template,v=d.lastIndexOf("/"),P=v>0?d.slice(0,v):"",I=(v>=0?d.slice(v+1):d).replace(/\.md$/,""),D=await y(p,P,I,!1);c(e,{template:r,target:D?.path??d,created_at:new Date().toISOString()})}},{method:"post",path:"/tasks/filter",handler:(i,e)=>{if(!w(n,"tasks"))return u(e,"plugin_missing","tasks is not installed",{plugin:"tasks"});u(e,"plugin_unreachable","Tasks exposes no programmatic filter API; use list_tasks",{plugin:"tasks"})}}]}var $="obsidian-local-rest-api",b=class extends q.Plugin{async onload(){let s=[],o=this.app;typeof o.commands?.listCommands!="function"&&s.push("app.commands.listCommands is not a function"),(typeof o.plugins?.plugins!="object"||o.plugins?.plugins===null)&&s.push("app.plugins.plugins is not an object");let n=A(this.app,this.manifest.version,s),i=this.registerBridgeRoutes(n);i===null?console.warn("[obsidian-tc] Local REST API plugin not found (or no extension API); bridge routes not registered. Install/enable the Local REST API plugin."):(s.length&&console.warn(`[obsidian-tc] degraded: ${s.join("; ")} \u2014 Obsidian internals may have moved; some bridges will degrade.`),console.info(`[obsidian-tc] registered ${i} bridge routes under /obsidian-tc/v1`))}registerBridgeRoutes(s){let o=this.app.plugins?.plugins?.[$];if(!o)return null;let n=o.requestHandler?.apiExtensionRouter;if(n){for(let e of s)n[e.method](e.path,e.handler);return s.length}let i=o.api?.addRoute;if(i){for(let e of s)i.call(o.api,e.path)[e.method](e.handler);return s.length}return null}};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "obsidian-tc",
|
|
3
|
+
"name": "Obsidian Turbocharged",
|
|
4
|
+
"version": "1.0.2",
|
|
5
|
+
"minAppVersion": "1.7.0",
|
|
6
|
+
"description": "Companion plugin for the obsidian-tc MCP server. Extends Local REST API with command dispatch and plugin firepower bridges.",
|
|
7
|
+
"author": "The 40 Thieves",
|
|
8
|
+
"authorUrl": "https://github.com/The-40-Thieves",
|
|
9
|
+
"fundingUrl": "",
|
|
10
|
+
"isDesktopOnly": false
|
|
11
|
+
}
|
package/dist/schema.sql
CHANGED
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
-- PRAGMA foreign_keys = ON;
|
|
8
8
|
-- PRAGMA journal_mode = WAL;
|
|
9
9
|
--
|
|
10
|
-
-- One DB
|
|
10
|
+
-- One SHARED cache DB for the whole server, at <config.cacheDir>/cache.db (vault isolation
|
|
11
|
+
-- is logical via vault_id columns — see ARCHITECTURE.md component 10; per-vault DB files are
|
|
12
|
+
-- the planned V2 storage rewrite). A sibling
|
|
13
|
+
-- experiential.db holds the low-trust membrane tier, provisioned on its own migration chain.
|
|
11
14
|
|
|
12
15
|
-- ============================================================================
|
|
13
16
|
-- Migrations tracking
|
|
@@ -139,6 +142,9 @@ CREATE TABLE memory_entities (
|
|
|
139
142
|
|
|
140
143
|
CREATE INDEX idx_memory_entities_vault_type ON memory_entities(vault_id, entity_type);
|
|
141
144
|
CREATE INDEX idx_memory_entities_name ON memory_entities(vault_id, name);
|
|
145
|
+
-- Natural key (F4): (vault_id, entity_type, name) is unique — closes the create_entity
|
|
146
|
+
-- read-then-insert race. Migration 20260519_002 dedups + adds this on existing DBs.
|
|
147
|
+
CREATE UNIQUE INDEX idx_memory_entities_natural_key ON memory_entities(vault_id, entity_type, name);
|
|
142
148
|
|
|
143
149
|
CREATE TABLE memory_relations (
|
|
144
150
|
source_id TEXT NOT NULL,
|
|
@@ -169,7 +175,7 @@ CREATE INDEX idx_memory_relations_target ON memory_relations(target_id);
|
|
|
169
175
|
-- 3. Replay reads existing row if completed_at IS NOT NULL.
|
|
170
176
|
-- 4. Concurrent INSERT with same (vault_id, key) and completed_at IS NULL
|
|
171
177
|
-- raises idempotency_in_flight.
|
|
172
|
-
-- 5. Sweep reaps rows where started_at + 60000 < now AND
|
|
178
|
+
-- 5. Sweep reaps rows where started_at + the configured reclaim window (idempotencyReclaimSeconds x 1000 ms, default 60000) < now AND
|
|
173
179
|
-- completed_at IS NULL (process crashed mid-execution).
|
|
174
180
|
-- ============================================================================
|
|
175
181
|
|
package/package.json
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "obsidian-tc",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "MCP server for Obsidian — comprehensive tool surface for humans and autonomous agents",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "AGPL-3.0-only",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
8
8
|
"bin": {
|
|
9
9
|
"obsidian-tc": "./dist/cli.js"
|
|
10
10
|
},
|
|
11
|
-
"files": [
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
12
16
|
"scripts": {
|
|
13
|
-
"build": "bun build ./src/index.ts ./src/cli.ts --outdir ./dist --target node && bun scripts/copy-assets.mjs",
|
|
17
|
+
"build": "bun build ./src/index.ts ./src/cli.ts --outdir ./dist --target node --external better-sqlite3 --minify --sourcemap=linked && bun scripts/copy-assets.mjs",
|
|
14
18
|
"typecheck": "tsc --noEmit",
|
|
15
19
|
"test": "vitest run",
|
|
16
20
|
"test:coverage": "vitest run --coverage",
|
|
@@ -19,30 +23,36 @@
|
|
|
19
23
|
"dependencies": {
|
|
20
24
|
"@hono/node-server": "^2.0.5",
|
|
21
25
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
22
|
-
"@the-40-thieves/obsidian-tc-native": "1.
|
|
23
|
-
"@the-40-thieves/obsidian-tc-shared": "1.
|
|
26
|
+
"@the-40-thieves/obsidian-tc-native": "1.3.1",
|
|
27
|
+
"@the-40-thieves/obsidian-tc-shared": "1.3.1",
|
|
24
28
|
"@opentelemetry/api": "^1.9.1",
|
|
25
29
|
"@opentelemetry/exporter-trace-otlp-http": "^0.219.0",
|
|
26
30
|
"@opentelemetry/resources": "^2.8.0",
|
|
27
31
|
"@opentelemetry/sdk-trace-node": "^2.8.0",
|
|
28
32
|
"@opentelemetry/semantic-conventions": "^1.41.1",
|
|
29
|
-
"better-sqlite3": "^11.
|
|
33
|
+
"better-sqlite3": "^12.11.1",
|
|
30
34
|
"fetch-to-node": "^2.1.0",
|
|
31
35
|
"hono": "^4.6.0",
|
|
32
36
|
"jose": "^6.2.3",
|
|
33
37
|
"prom-client": "^15.1.3",
|
|
34
38
|
"sqlite-vec": "^0.1.9",
|
|
35
|
-
"yaml": "^2.
|
|
36
|
-
"zod": "^
|
|
37
|
-
"zod-to-json-schema": "^3.24.0"
|
|
39
|
+
"yaml": "^2.9.0",
|
|
40
|
+
"zod": "^4.4.3"
|
|
38
41
|
},
|
|
39
42
|
"devDependencies": {
|
|
40
|
-
"@types/better-sqlite3": "^7.6.
|
|
41
|
-
"@types/node": "^
|
|
42
|
-
"@vitest/coverage-v8": "^2.
|
|
43
|
-
"vitest": "^2.
|
|
43
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
44
|
+
"@types/node": "^24.0.0",
|
|
45
|
+
"@vitest/coverage-v8": "^3.2.6",
|
|
46
|
+
"vitest": "^3.2.6"
|
|
44
47
|
},
|
|
45
|
-
"keywords": [
|
|
48
|
+
"keywords": [
|
|
49
|
+
"obsidian",
|
|
50
|
+
"mcp",
|
|
51
|
+
"model-context-protocol",
|
|
52
|
+
"knowledge-management",
|
|
53
|
+
"agent",
|
|
54
|
+
"ai"
|
|
55
|
+
],
|
|
46
56
|
"repository": {
|
|
47
57
|
"type": "git",
|
|
48
58
|
"url": "https://github.com/The-40-Thieves/obsidian-tc.git",
|
|
@@ -51,5 +61,8 @@
|
|
|
51
61
|
"exports": {
|
|
52
62
|
".": "./dist/index.js",
|
|
53
63
|
"./package.json": "./package.json"
|
|
64
|
+
},
|
|
65
|
+
"engines": {
|
|
66
|
+
"node": ">=24"
|
|
54
67
|
}
|
|
55
68
|
}
|