sotashimozono_obsidian-remo.../plugin/scripts/cdp-tail.mjs
Souta 21bb70a5d0 chore: monorepo restructure (plugin/ + server/ + proto/)
Move the entire Obsidian plugin tree under plugin/ and add an empty
server/ tree (Go) and proto/ directory so the upcoming
obsidian-remote-server daemon has a home in the same repo. The plugin
remains the only thing that builds today; the server is a hello-world
skeleton that prints "not implemented yet" and exits non-zero, with a
Makefile that knows how to cross-compile for linux/darwin × amd64/arm64
once real code lands.

Background:
  Approach A (vault-adapter monkey-patch) hit structural ceilings —
  vault index frozen at startup, third-party plugins bypass DataAdapter,
  empty-remote .obsidian read failures cascade through Templater/Kanban
  etc. We are pivoting to the VS Code Remote-SSH model: a small Go
  daemon on the remote host, JSON-RPC over a SSH-tunnelled WebSocket,
  fsnotify-driven push events, attachment serving over HTTP on the same
  socket. This restructure is the first mechanical step before the
  protocol and transport land.

Mechanical changes:
- git mv all plugin sources, manifest, configs, scripts, and tests
  into plugin/.
- plugin/scripts/dev-install.mjs walks one extra parent (`pluginRoot`
  → `repoRoot` → `..` for the dev vault) so REMOTE_SSH_DEV_VAULT
  defaulting still resolves to ../SelfArchive-dev relative to the
  repo, not to plugin/.
- CI: ci.yml runs lint/test/build with `working-directory: plugin`
  and a server job that builds + tests the Go module. release.yml
  builds out of plugin/ and uploads plugin/{main.js,manifest.json,
  styles.css}.
- Dependabot: npm directory becomes /plugin; new gomod directory /server.
- Labeler: paths re-anchored to plugin/, with new plugin/server/proto
  area labels.

Verification:
  cd plugin && npm install && npx tsc --noEmit && npm test (76/76)
  cd plugin && npm run build:install (367 KB; dev vault refreshed).
  Go side waits on CI (no Go toolchain on the dev host).
2026-04-25 10:52:52 +09:00

103 lines
3.3 KiB
JavaScript

#!/usr/bin/env node
/*
* CDP tail — connect to Obsidian's Chrome DevTools Protocol endpoint
* and stream Runtime.consoleAPICalled / Runtime.exceptionThrown events
* to stdout AND to ./cdp-console.log.
*
* Usage:
* 1. Launch Obsidian with `--remote-debugging-port=9222`
* (Windows: edit the shortcut "Target" field; macOS/Linux: pass via argv)
* 2. node scripts/cdp-tail.mjs
*
* Requires Node 22+ (built-in WebSocket).
*/
import * as fs from 'node:fs';
import * as path from 'node:path';
const CDP_HOST = process.env.CDP_HOST ?? '127.0.0.1';
const CDP_PORT = process.env.CDP_PORT ?? '9222';
const OUT_FILE = path.resolve(process.cwd(), 'cdp-console.log');
if (typeof globalThis.WebSocket !== 'function') {
console.error('Node 22+ is required (built-in WebSocket). Current:', process.version);
process.exit(1);
}
async function fetchTargets() {
const res = await fetch(`http://${CDP_HOST}:${CDP_PORT}/json`);
if (!res.ok) throw new Error(`CDP /json returned ${res.status}`);
return res.json();
}
function pickTarget(targets) {
// Prefer "page" type targets that are obsidian app windows; skip extensions.
const pages = targets.filter(t => t.type === 'page' && !/^chrome-extension:/.test(t.url));
if (pages.length === 0) {
throw new Error('No suitable CDP target found. Make sure Obsidian is running with --remote-debugging-port=' + CDP_PORT);
}
return pages[0];
}
function appendOut(line) {
fs.appendFile(OUT_FILE, line + '\n', () => {});
}
function fmtArg(arg) {
if (arg.value !== undefined) return JSON.stringify(arg.value);
if (arg.unserializableValue !== undefined) return arg.unserializableValue;
if (arg.description) return arg.description;
return arg.type ?? '<unknown>';
}
async function main() {
const targets = await fetchTargets();
const target = pickTarget(targets);
const wsUrl = target.webSocketDebuggerUrl;
console.error(`[cdp-tail] connecting to ${target.title || target.url}`);
console.error(`[cdp-tail] writing to ${OUT_FILE}`);
const ws = new WebSocket(wsUrl);
let nextId = 1;
ws.addEventListener('open', () => {
ws.send(JSON.stringify({ id: nextId++, method: 'Runtime.enable' }));
console.error('[cdp-tail] connected');
});
ws.addEventListener('message', (ev) => {
let msg;
try { msg = JSON.parse(typeof ev.data === 'string' ? ev.data : ev.data.toString()); }
catch { return; }
if (msg.method === 'Runtime.consoleAPICalled') {
const { type, args, timestamp } = msg.params;
const ts = new Date(timestamp).toISOString();
const text = (args ?? []).map(fmtArg).join(' ');
const line = `[${ts}] [console.${type}] ${text}`;
console.log(line);
appendOut(line);
} else if (msg.method === 'Runtime.exceptionThrown') {
const e = msg.params.exceptionDetails;
const ts = new Date(msg.params.timestamp).toISOString();
const desc = e?.exception?.description ?? e?.text ?? '<unknown exception>';
const line = `[${ts}] [exception] ${desc}`;
console.log(line);
appendOut(line);
}
});
ws.addEventListener('close', () => {
console.error('[cdp-tail] connection closed');
process.exit(0);
});
ws.addEventListener('error', (e) => {
console.error('[cdp-tail] websocket error:', e.message ?? e);
});
}
main().catch(err => {
console.error('[cdp-tail] fatal:', err.message ?? err);
process.exit(1);
});