datanotes 1.1.0 → 1.2.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.
package/README.md CHANGED
@@ -4,30 +4,14 @@ The engine of your second brain: Markdown notes as database tables, for you and
4
4
 
5
5
  A row is any note whose `table` property names a table; tables are defined in
6
6
  `Database/database.yaml`. Every write is checked against its table, journaled and undoable. Agents
7
- use it through MCP (the separate `datanotes-mcp` server); programs use its HTTP API or the
8
- `datanotes/client` library; it also runs inside Obsidian as a plugin.
7
+ programs use its HTTP API or the `datanotes/client` library, and agents use the separate MCP
8
+ server `datanotes-mcp`.
9
9
 
10
10
  **Documentation: [datanotes.dev](https://datanotes.dev)**
11
11
 
12
12
  ## Quick start
13
13
 
14
- Requires Node.js 20 or newer. Connect an MCP client to a vault (here Claude Code):
15
-
16
- ```bash
17
- claude mcp add datanotes -- npx -y datanotes-mcp --vault /path/to/vault
18
- ```
19
-
20
- Other clients use the same command:
21
-
22
- ```json
23
- {
24
- "mcpServers": {
25
- "datanotes": { "command": "npx", "args": ["-y", "datanotes-mcp", "--vault", "/path/to/vault"] }
26
- }
27
- }
28
- ```
29
-
30
- To keep the engine running for several clients and apps:
14
+ Requires Node.js 20 or newer. Start the engine on a vault:
31
15
 
32
16
  ```bash
33
17
  npx -y datanotes serve --vault /path/to/vault
@@ -43,14 +27,17 @@ const db = new DatanotesClient({ url: "http://127.0.0.1:27150", token });
43
27
  const { value } = await db.call("select_rows", { table: "book", where: { pages: { gt: 300 } } });
44
28
  ```
45
29
 
30
+ AI agents connect through [`datanotes-mcp`](https://www.npmjs.com/package/datanotes-mcp), the MCP
31
+ server of Datanotes, a separate package built on this API.
32
+
46
33
  ## Learn more
47
34
 
48
35
  - [How it works](https://datanotes.dev/concepts/how-it-works/)
49
- - [Connect agents](https://datanotes.dev/start/connect-agents/)
50
- - [Operations and MCP tools](https://datanotes.dev/reference/tools/)
36
+ - [Operations](https://datanotes.dev/reference/tools/)
37
+ - [Connect agents (datanotes-mcp)](https://datanotes.dev/start/connect-agents/)
51
38
  - [JavaScript client](https://datanotes.dev/guides/client/)
52
39
  - [Apps](https://datanotes.dev/guides/apps/)
53
- - [Obsidian plugin](https://datanotes.dev/guides/obsidian/)
40
+ - [Obsidian client](https://datanotes.dev/guides/obsidian/)
54
41
 
55
42
  ## Security
56
43
 
@@ -40,8 +40,7 @@ var import_node_os = __toESM(require("node:os"), 1);
40
40
  var import_node_path = __toESM(require("node:path"), 1);
41
41
  var LOCK_STALE_MS = 3e4;
42
42
  function vaultSettingsPath(vault) {
43
- const plugin = import_node_path.default.join(vault, ".obsidian", "plugins", "datanotes", "data.json");
44
- return import_node_fs.default.existsSync(plugin) ? plugin : import_node_path.default.join(vault, ".datanotes", "service.json");
43
+ return import_node_path.default.join(vault, ".datanotes", "service.json");
45
44
  }
46
45
  function readJson(file) {
47
46
  try {
@@ -4,7 +4,7 @@
4
4
  */
5
5
  /** The lock an engine writes in its store folder (`<vault>/.datanotes/engine.lock`). */
6
6
  export interface EngineLock {
7
- kind: "service" | "plugin";
7
+ kind: "service";
8
8
  pid: number;
9
9
  host: string;
10
10
  port: number;
@@ -18,7 +18,7 @@ export interface LocalEngine {
18
18
  token: string;
19
19
  lock: EngineLock;
20
20
  }
21
- /** The settings file of a vault: the Obsidian plugin's when present, else the service's. */
21
+ /** The settings file of a vault's engine. */
22
22
  export declare function vaultSettingsPath(vault: string): string;
23
23
  /**
24
24
  * The engine holding `vault`, or null when none runs. Throws when the lock is fresh but held on
package/dist/api/local.js CHANGED
@@ -4,8 +4,7 @@ import os from "node:os";
4
4
  import path from "node:path";
5
5
  var LOCK_STALE_MS = 3e4;
6
6
  function vaultSettingsPath(vault) {
7
- const plugin = path.join(vault, ".obsidian", "plugins", "datanotes", "data.json");
8
- return fs.existsSync(plugin) ? plugin : path.join(vault, ".datanotes", "service.json");
7
+ return path.join(vault, ".datanotes", "service.json");
9
8
  }
10
9
  function readJson(file) {
11
10
  try {
@@ -7424,19 +7424,16 @@ var DEFAULT_SETTINGS = {
7424
7424
  tableProperty: "table",
7425
7425
  telemetryEnabled: false,
7426
7426
  telemetryEndpoint: "http://127.0.0.1:4318",
7427
- telemetryServiceName: "obsidian-datanotes",
7428
- restApiEnabled: false,
7427
+ telemetryServiceName: "datanotes",
7429
7428
  autoRecompute: true,
7430
7429
  viewNotes: true,
7431
7430
  semanticEnabled: false,
7432
7431
  semanticProvider: "ollama",
7433
7432
  semanticBaseUrl: "",
7434
7433
  semanticModel: "",
7435
- semanticApiKeySecret: "",
7436
7434
  semanticScope: "vault",
7437
7435
  semanticExclude: [],
7438
7436
  semanticBatchSize: 32,
7439
- httpServerEnabled: false,
7440
7437
  httpServerPort: 27150,
7441
7438
  httpServerToken: ""
7442
7439
  };
@@ -7453,18 +7450,15 @@ var KEYS = [
7453
7450
  "telemetryEnabled",
7454
7451
  "telemetryEndpoint",
7455
7452
  "telemetryServiceName",
7456
- "restApiEnabled",
7457
7453
  "autoRecompute",
7458
7454
  "viewNotes",
7459
7455
  "semanticEnabled",
7460
7456
  "semanticProvider",
7461
7457
  "semanticBaseUrl",
7462
7458
  "semanticModel",
7463
- "semanticApiKeySecret",
7464
7459
  "semanticScope",
7465
7460
  "semanticExclude",
7466
7461
  "semanticBatchSize",
7467
- "httpServerEnabled",
7468
7462
  "httpServerPort",
7469
7463
  "httpServerToken"
7470
7464
  ];
@@ -7548,9 +7542,8 @@ function mergeWithDefaults(loaded) {
7548
7542
  if (typeof raw === "string" && (raw.trim() === "" || /^https?:\/\/\S+$/.test(raw.trim()))) out.semanticBaseUrl = raw.trim().replace(/\/+$/, "");
7549
7543
  break;
7550
7544
  }
7551
- case "semanticModel":
7552
- case "semanticApiKeySecret": {
7553
- if (typeof raw === "string") out[key] = raw.trim();
7545
+ case "semanticModel": {
7546
+ if (typeof raw === "string") out.semanticModel = raw.trim();
7554
7547
  break;
7555
7548
  }
7556
7549
  case "semanticScope": {
@@ -7574,9 +7567,7 @@ function mergeWithDefaults(loaded) {
7574
7567
  break;
7575
7568
  }
7576
7569
  case "telemetryEnabled":
7577
- case "httpServerEnabled":
7578
7570
  case "fileLogEnabled":
7579
- case "restApiEnabled":
7580
7571
  case "semanticEnabled":
7581
7572
  case "autoRecompute":
7582
7573
  case "viewNotes": {
@@ -7802,7 +7793,6 @@ var TeeTelemetry = class {
7802
7793
 
7803
7794
  // src/server/engine-lock.ts
7804
7795
  var LOCK_PATH_SUFFIX = "engine.lock";
7805
- var LOCK_STALE_MS = 3e4;
7806
7796
  var LOCK_HEARTBEAT_MS = 1e4;
7807
7797
  function lockPath(storeFolder) {
7808
7798
  return `${storeFolder.replace(/\/+$/, "")}/${LOCK_PATH_SUFFIX}`;
@@ -7815,9 +7805,6 @@ async function readLock(adapter2, storeFolder) {
7815
7805
  return null;
7816
7806
  }
7817
7807
  }
7818
- function isFresh(lock, now = Date.now()) {
7819
- return !!lock && now - Date.parse(lock.heartbeat) < LOCK_STALE_MS;
7820
- }
7821
7808
  var LockHolder = class {
7822
7809
  constructor(adapter2, storeFolder, info) {
7823
7810
  this.adapter = adapter2;
@@ -7833,10 +7820,6 @@ var LockHolder = class {
7833
7820
  async write() {
7834
7821
  this.info.heartbeat = (/* @__PURE__ */ new Date()).toISOString();
7835
7822
  try {
7836
- if (this.info.kind === "plugin") {
7837
- const current2 = await readLock(this.adapter, this.storeFolder);
7838
- if (current2 && current2.kind === "service" && isFresh(current2)) return;
7839
- }
7840
7823
  await this.adapter.mkdir(this.storeFolder);
7841
7824
  await this.adapter.write(lockPath(this.storeFolder), JSON.stringify(this.info));
7842
7825
  } catch {
@@ -15650,13 +15633,13 @@ function createHandlers(app, getSettings, saveSettings = async () => {
15650
15633
  },
15651
15634
  "/tables/describe/": async (body) => ({ status: 200, json: await describeTable(app, s(), str(body, "table")) }),
15652
15635
  "/ddl/": ddl,
15653
- // The plugin API itself, method by method (camelCase inputs as in public-api.ts): what the
15654
- // Obsidian plugin uses in client mode, when the headless service owns the vault.
15636
+ // The engine's API itself, method by method (camelCase inputs as in public-api.ts), for the
15637
+ // owner: `remote-api.ts` is its client.
15655
15638
  "/api/": async (body) => {
15656
15639
  const method = str(body, "method");
15657
15640
  const args = list(body, "args") ?? [];
15658
15641
  const fn = pub[method];
15659
- if (typeof fn !== "function" || API_EXCLUDED.has(method)) throw new BadRequest(`"method" must be a method of the plugin API`);
15642
+ if (typeof fn !== "function" || API_EXCLUDED.has(method)) throw new BadRequest(`"method" must be a method of the engine API`);
15660
15643
  const result = await fn.apply(pub, args);
15661
15644
  return { status: 200, json: { result: result === void 0 ? null : result } };
15662
15645
  },
@@ -16214,7 +16197,7 @@ async function simpleSearch(app, query, contextLength = 100) {
16214
16197
  }
16215
16198
  return out.sort((a, b) => b.score - a.score || a.filename.localeCompare(b.filename)).slice(0, 200);
16216
16199
  }
16217
- var PluginHttpServer = class {
16200
+ var HttpApiServer = class {
16218
16201
  constructor(opts) {
16219
16202
  this.opts = opts;
16220
16203
  this.server = null;
@@ -20775,7 +20758,7 @@ var EngineRuntime = class {
20775
20758
  transport: async (url, body) => this.o.postLogs(url, body),
20776
20759
  resource: { "service.version": this.o.version, "host.name": this.o.hostName }
20777
20760
  });
20778
- const source = this.o.hostName === "obsidian" ? void 0 : this.o.hostName;
20761
+ const source = this.o.hostName;
20779
20762
  const fileLog = new FileLogger({ enabled: () => s().fileLogEnabled, write: (day, lines) => appendLog(this.o.app, s(), day, lines, source) });
20780
20763
  const tee = new TeeTelemetry([logger, fileLog]);
20781
20764
  setTelemetry(tee);
@@ -20813,7 +20796,6 @@ var EngineRuntime = class {
20813
20796
  this.httpServer = null;
20814
20797
  this.httpServerError = null;
20815
20798
  const s = this.o.getSettings();
20816
- if (!s.httpServerEnabled) return;
20817
20799
  if (!this.o.canServeHttp) {
20818
20800
  this.httpServerError = "the HTTP server runs on desktop only";
20819
20801
  return;
@@ -20824,7 +20806,7 @@ var EngineRuntime = class {
20824
20806
  }
20825
20807
  const handlers = this.handlers();
20826
20808
  const guarded = this.apps.guard(dispatch);
20827
- const server = new PluginHttpServer({
20809
+ const server = new HttpApiServer({
20828
20810
  app: this.o.app,
20829
20811
  handlers,
20830
20812
  token: s.httpServerToken,
@@ -22080,7 +22062,7 @@ var NodeWatcher = class {
22080
22062
  };
22081
22063
 
22082
22064
  // src/node/service.ts
22083
- var VERSION = true ? "1.1.0" : "dev";
22065
+ var VERSION = true ? "1.2.0" : "dev";
22084
22066
  function parseArgs(argv, env = process.env) {
22085
22067
  const get = (name) => {
22086
22068
  const i = argv.indexOf(`--${name}`);
@@ -22103,8 +22085,6 @@ function parseArgs(argv, env = process.env) {
22103
22085
  }
22104
22086
  function settingsPath(o) {
22105
22087
  if (o.settingsFile) return import_node_path5.default.resolve(o.settingsFile);
22106
- const plugin = import_node_path5.default.join(o.vault, ".obsidian", "plugins", "datanotes", "data.json");
22107
- if (import_node_fs5.default.existsSync(plugin)) return plugin;
22108
22088
  return import_node_path5.default.join(o.vault, ".datanotes", "service.json");
22109
22089
  }
22110
22090
  async function startService(o, env = process.env) {
@@ -22123,13 +22103,9 @@ async function startService(o, env = process.env) {
22123
22103
  } catch {
22124
22104
  current2 = {};
22125
22105
  }
22126
- const { httpServerEnabled: _forced, ...mine } = settings;
22127
22106
  import_node_fs5.default.mkdirSync(import_node_path5.default.dirname(file), { recursive: true });
22128
- import_node_fs5.default.writeFileSync(file, JSON.stringify({ ...current2, ...mine }, null, 2));
22107
+ import_node_fs5.default.writeFileSync(file, JSON.stringify({ ...current2, ...settings }, null, 2));
22129
22108
  };
22130
- const view = new Proxy(settings, {
22131
- get: (t, k) => k === "httpServerEnabled" ? true : t[k]
22132
- });
22133
22109
  const host = openNodeVault(o.vault);
22134
22110
  const watcher = new NodeWatcher(host.vault, { mode: o.watch, intervalMs: o.pollMs });
22135
22111
  const keyFromFile = () => {
@@ -22143,7 +22119,7 @@ async function startService(o, env = process.env) {
22143
22119
  const runtime = new EngineRuntime({
22144
22120
  ...o.host ? { httpHost: o.host } : {},
22145
22121
  app: host.app,
22146
- getSettings: () => view,
22122
+ getSettings: () => settings,
22147
22123
  saveSettings: save,
22148
22124
  transport: async (req) => {
22149
22125
  const res = await fetch(req.url, { method: req.method, headers: req.headers, body: req.body });
@@ -22251,7 +22227,7 @@ Docs: https://datanotes.dev`;
22251
22227
  }
22252
22228
 
22253
22229
  // src/node/cli.ts
22254
- var VERSION2 = true ? "1.1.0" : "dev";
22230
+ var VERSION2 = true ? "1.2.0" : "dev";
22255
22231
  async function main2(argv = process.argv.slice(2)) {
22256
22232
  const [command, ...rest] = argv;
22257
22233
  if (command === "--version" || command === "-v" || command === "version") return void process.stdout.write(`${VERSION2}
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "datanotes",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "The engine of your second brain: Markdown notes as database tables, for you and your agents.",
5
5
  "keywords": [
6
- "obsidian",
7
6
  "markdown",
8
7
  "database",
9
8
  "mcp",
10
9
  "model-context-protocol",
11
10
  "second-brain",
12
11
  "agents",
13
- "frontmatter"
12
+ "frontmatter",
13
+ "obsidian-vault"
14
14
  ],
15
15
  "homepage": "https://datanotes.dev",
16
16
  "bugs": {
@@ -56,8 +56,6 @@
56
56
  "node": ">=20"
57
57
  },
58
58
  "scripts": {
59
- "dev": "node esbuild.config.mjs",
60
- "build": "node scripts/check-layers.mjs && tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
61
59
  "test": "vitest run",
62
60
  "test:coverage": "vitest run --coverage",
63
61
  "test:watch": "vitest",
@@ -65,16 +63,14 @@
65
63
  "check:layers": "node scripts/check-layers.mjs",
66
64
  "test:node": "vitest run --config vitest.node.config.ts",
67
65
  "build:service": "node scripts/check-layers.mjs && tsc -noEmit -skipLibCheck && node esbuild.service.mjs && npm run build:api",
68
- "build:client": "node scripts/check-layers.mjs && tsc -noEmit -skipLibCheck && node esbuild.client.mjs",
69
66
  "prepack": "npm run build:service",
70
- "build:api": "tsc -p tsconfig.api.json && node esbuild.api.mjs"
67
+ "build:api": "tsc -p tsconfig.api.json && node esbuild.api.mjs",
68
+ "build": "npm run build:service"
71
69
  },
72
70
  "devDependencies": {
73
71
  "@types/node": "^22.10.0",
74
72
  "@vitest/coverage-v8": "^2.1.9",
75
73
  "esbuild": "0.25.5",
76
- "happy-dom": "^20.9.0",
77
- "obsidian": "1.7.2",
78
74
  "tslib": "2.8.1",
79
75
  "typescript": "^5.8.3",
80
76
  "vitest": "^2.1.9",