dsh-tiddlywiki 0.1.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/LICENSE +21 -0
- package/README.md +172 -0
- package/cordis.patch.yml +13 -0
- package/lib/client.bundle.js +1417 -0
- package/lib/client.js +1427 -0
- package/lib/index.js +2153 -0
- package/lib/index.js.map +1 -0
- package/package.json +63 -0
- package/src/client/editor-popup.ts +121 -0
- package/src/client/index.ts +76 -0
- package/src/client/note-widget.ts +210 -0
- package/src/client/panel.ts +304 -0
- package/src/client/settings-page.ts +397 -0
- package/src/client/sidebar-entry.ts +148 -0
- package/src/client/state.ts +39 -0
- package/src/client/styles.ts +235 -0
- package/src/client/toast.ts +22 -0
- package/src/host/admin.ts +408 -0
- package/src/host/config.ts +86 -0
- package/src/host/git.ts +218 -0
- package/src/host/routes.ts +233 -0
- package/src/host/seed-notes.ts +62 -0
- package/src/host/tools.ts +254 -0
- package/src/host/tw-api.ts +157 -0
- package/src/host/wiki.ts +287 -0
- package/src/index.ts +331 -0
- package/src/sdk.ts +198 -0
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["readBody","json"],"sources":["../src/host/git.ts","../src/host/wiki.ts","../src/host/routes.ts","../src/host/config.ts","../src/host/admin.ts","../src/host/seed-notes.ts","../src/host/tw-api.ts","../src/sdk.ts","../src/host/tools.ts","../src/index.ts"],"sourcesContent":["/**\n * Git face (design doc §7, D11) — the ONLY place dsh-tiddlywiki shells out to\n * git. The wiki folder itself is the repository; the folder is pure text\n * (FileSystemAdaptor writes one file per tiddler), so git is a natural sync /\n * backup channel.\n *\n * Sync model is the single-thread alternating one:\n * 1. start of work: `git pull --rebase --autostash`\n * 2. end of work: `git add -A && git commit && git push`\n * 3. auto-commit: debounced 60s commit after wiki writes (AutoCommitter)\n *\n * Conflict policy (user-confirmed, no complex handling): a rebase conflict\n * (only reachable by \"forgot to pull before writing\") → `git rebase --abort`\n * + report the unmerged files. Never auto-merge data.\n *\n * @module dsh-tiddlywiki/host/git\n */\nimport { execFile } from 'node:child_process'\nimport { promisify } from 'node:util'\n\nconst execFileP = promisify(execFile)\n\n/** Timeout for quick read-only queries. */\nconst QUICK_TIMEOUT_MS = 5_000\n\n/** Timeout for structural/network operations. */\nconst HEAVY_TIMEOUT_MS = 60_000\n\nexport interface ExecResult { ok: boolean; stdout: string; stderr: string }\nexport type ExecFn = (args: string[], options: { cwd?: string; timeout?: number }) => Promise<ExecResult>\n\n/** Default exec layer: run `git <args>` under a cwd with a timeout. */\nconst defaultExec: ExecFn = async (args, options) => {\n try {\n const { stdout, stderr } = await execFileP('git', args, {\n cwd: options.cwd,\n timeout: options.timeout ?? QUICK_TIMEOUT_MS,\n windowsHide: true,\n encoding: 'utf8',\n maxBuffer: 32 * 1024 * 1024,\n })\n return { ok: true, stdout, stderr }\n } catch (err) {\n const e = err as { stdout?: string; stderr?: string; message?: string }\n return { ok: false, stdout: e.stdout ?? '', stderr: e.stderr ?? String(e.message ?? err) }\n }\n}\n\nexport interface GitStatusView {\n exists: boolean\n branch: string\n dirty: boolean\n dirtyFiles: string[]\n remote: string\n lastCommit?: string\n ahead?: number\n behind?: number\n}\n\nexport interface GitActionResult { ok: boolean; message: string; conflictFiles?: string[] }\n\nfunction parseCount(line: string, re: RegExp): number | undefined {\n const m = line.match(re)\n return m === null ? undefined : Number(m[1])\n}\n\nexport class GitFace {\n constructor(private readonly exec: ExecFn = defaultExec) {}\n\n async isRepo(dir: string): Promise<boolean> {\n const r = await this.exec(['rev-parse', '--is-inside-work-tree'], { cwd: dir, timeout: 2_000 })\n return r.ok && r.stdout.trim() === 'true'\n }\n\n async init(dir: string, branch = 'main'): Promise<boolean> {\n const r = await this.exec(['init', '-b', branch], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return r.ok\n }\n\n /** Initial commit for a fresh repo (tolerates an empty index). */\n async initialCommit(dir: string): Promise<boolean> {\n await this.exec(['add', '-A'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n const r = await this.exec([...identity(), 'commit', '-m', 'chore(dsh-tiddlywiki): initial commit'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return r.ok || /nothing to commit/.test(r.stderr + r.stdout)\n }\n\n /**\n * Stage everything and commit; a local identity is always provided so the\n * plugin never depends on the machine's global git config. Returns whether\n * a commit actually happened.\n */\n async commit(dir: string, message: string): Promise<{ committed: boolean; message: string }> {\n await this.exec(['add', '-A'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n const staged = await this.exec(['diff', '--cached', '--quiet'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n // `diff --cached --quiet` exits 0 when nothing is staged → nothing to commit.\n if (staged.ok) return { committed: false, message: 'nothing to commit' }\n const r = await this.exec([...identity(), 'commit', '-m', message], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return r.ok\n ? { committed: true, message }\n : { committed: false, message: `commit failed: ${(r.stderr.trim() || r.stdout.trim()).slice(0, 500)}` }\n }\n\n async status(dir: string): Promise<GitStatusView> {\n const empty: GitStatusView = { exists: false, branch: '', dirty: false, dirtyFiles: [], remote: '' }\n const r = await this.exec(['status', '--porcelain', '-b'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n if (!r.ok) return empty\n const lines = r.stdout.split('\\n').filter((l) => l.length > 0)\n const branchLine = lines.find((l) => l.startsWith('## '))\n const branch = branchLine === undefined ? '' : branchLine.slice(3).split('...')[0] ?? ''\n const ahead = branchLine === undefined ? undefined : parseCount(branchLine, /ahead (\\d+)/)\n const behind = branchLine === undefined ? undefined : parseCount(branchLine, /behind (\\d+)/)\n const dirty = lines.some((l) => !l.startsWith('## '))\n const dirtyFiles = lines.filter((l) => !l.startsWith('## ')).map((l) => l.slice(3).trim()).filter(Boolean)\n const remoteR = await this.exec(['remote', '-v'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n const remote = remoteR.ok ? remoteR.stdout.split('\\n').map((l) => l.trim()).find(Boolean) ?? '' : ''\n const lastR = await this.exec(['log', '-1', '--format=%h %s'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n const lastCommit = lastR.ok && lastR.stdout.trim().length > 0 ? lastR.stdout.trim() : undefined\n return { exists: true, branch, dirty, dirtyFiles, remote, ...(lastCommit !== undefined ? { lastCommit } : {}), ...(ahead !== undefined ? { ahead } : {}), ...(behind !== undefined ? { behind } : {}) }\n }\n\n /** `git pull --rebase --autostash`; on conflict: abort + report files. */\n async pull(dir: string): Promise<GitActionResult> {\n const r = await this.exec(['pull', '--rebase', '--autostash'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n if (r.ok) return { ok: true, message: r.stdout.trim() || 'pull ok' }\n const conflictFiles = await this.unmergedFiles(dir)\n await this.exec(['rebase', '--abort'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n const reason = (r.stderr.trim() || r.stdout.trim()).slice(0, 500)\n return { ok: false, message: conflictFiles.length > 0 ? `conflict in ${conflictFiles.join(', ')} (rebase aborted): ${reason}` : `pull failed: ${reason}`, ...(conflictFiles.length > 0 ? { conflictFiles } : {}) }\n }\n\n async push(dir: string): Promise<GitActionResult> {\n const r = await this.exec(['push'], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return r.ok\n ? { ok: true, message: r.stdout.trim() || 'push ok' }\n : { ok: false, message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500) }\n }\n\n /** First push with upstream tracking (called once after a remote is set). */\n async firstPush(dir: string): Promise<GitActionResult> {\n const branch = (await this.status(dir)).branch || 'main'\n const r = await this.exec(['push', '-u', 'origin', branch], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return r.ok\n ? { ok: true, message: `pushed ${branch} to origin` }\n : { ok: false, message: (r.stderr.trim() || r.stdout.trim()).slice(0, 500) }\n }\n\n /** Ensure `origin` points at `url` (add or set-url). */\n async ensureRemote(dir: string, url: string): Promise<GitActionResult> {\n const cur = await this.exec(['remote', 'get-url', 'origin'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n if (cur.ok) {\n if (cur.stdout.trim() === url) return { ok: true, message: 'remote origin already set' }\n const set = await this.exec(['remote', 'set-url', 'origin', url], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return set.ok ? { ok: true, message: `remote origin → ${url}` } : { ok: false, message: set.stderr.trim() || 'remote set-url failed' }\n }\n const add = await this.exec(['remote', 'add', 'origin', url], { cwd: dir, timeout: HEAVY_TIMEOUT_MS })\n return add.ok ? { ok: true, message: `remote origin → ${url}` } : { ok: false, message: add.stderr.trim() || 'remote add failed' }\n }\n\n private async unmergedFiles(dir: string): Promise<string[]> {\n const r = await this.exec(['diff', '--name-only', '--diff-filter=U'], { cwd: dir, timeout: QUICK_TIMEOUT_MS })\n return r.ok ? r.stdout.split('\\n').map((l) => l.trim()).filter(Boolean) : []\n }\n}\n\n/** Always-on local identity so commits never depend on global git config. */\nfunction identity(): string[] {\n return ['-c', 'user.name=dsh-tiddlywiki', '-c', 'user.email=dsh-tiddlywiki@local']\n}\n\nexport interface AutoCommitterOptions {\n git: GitFace\n dir: string\n enabled: boolean\n debounceMs: number\n message: () => string\n onError?: (err: unknown) => void\n onCommit?: (info: { committed: boolean; message: string }) => void\n}\n\n/**\n * Debounced auto-committer: every wiki write calls `touch()`; the commit\n * fires once writes settle for `debounceMs`. Disable with git.autoCommit.\n */\nexport class AutoCommitter {\n private timer: NodeJS.Timeout | undefined\n private disposed = false\n\n constructor(private readonly options: AutoCommitterOptions) {}\n\n touch(): void {\n if (!this.options.enabled || this.disposed) return\n if (this.timer !== undefined) clearTimeout(this.timer)\n this.timer = setTimeout(() => { void this.flush() }, this.options.debounceMs)\n }\n\n /** Run a commit now (also cancels the pending debounce). */\n async flush(): Promise<void> {\n if (this.timer !== undefined) {\n clearTimeout(this.timer)\n this.timer = undefined\n }\n if (!this.options.enabled || this.disposed) return\n try {\n const result = await this.options.git.commit(this.options.dir, this.options.message())\n this.options.onCommit?.(result)\n } catch (err) {\n this.options.onError?.(err)\n }\n }\n\n dispose(): void {\n this.disposed = true\n if (this.timer !== undefined) {\n clearTimeout(this.timer)\n this.timer = undefined\n }\n }\n}\n","/**\n * WikiServer — the TiddlyWiki 5 child-process lifecycle (design doc §9, D3).\n *\n * Zero-friction rules:\n * - ensure the wiki folder exists (scaffold with `--init server` once)\n * - git bootstrap is NOT this class's job (index.ts owns the GitFace)\n * - auto-detect a free loopback port unless one is pinned in config\n * - spawn `node <tw>/tiddlywiki.js <wiki> --listen host=127.0.0.1 ...`\n * and poll /status until it answers 200\n * - the TW child serves at the ROOT of its own dedicated loopback port (no\n * `path-prefix`): TW's browser frontend builds its API URLs from\n * `$protocol$//$host$/` only, so any path-prefix makes every frontend call\n * ../../status → 404 (verified against tiddlywiki 5.4.1). Namespacing lives\n * on the DSH webserver side (/dsh-tiddlywiki/* routes), never in TW itself.\n * - crash → restart with exponential backoff (1s,2s,4s… cap 30s), reset on\n * a successful readiness\n * - stop() is deterministic: SIGTERM, escalate to SIGKILL after a grace\n * period, and never leave a timer that would respawn during teardown\n *\n * @module dsh-tiddlywiki/host/wiki\n */\nimport { spawn, execFile, type ChildProcessByStdio } from 'node:child_process'\nimport { existsSync } from 'node:fs'\nimport { mkdir } from 'node:fs/promises'\nimport { createRequire } from 'node:module'\nimport { createServer } from 'node:net'\nimport { join, resolve } from 'node:path'\nimport type { Readable } from 'node:stream'\n\n/** The DSH webserver route prefix (NOT a TW path-prefix; see module header). */\nexport const PATH_PREFIX = '/dsh-tiddlywiki'\n\n/** How long to wait for the wiki to answer /status. */\nconst READY_TIMEOUT_MS = 20_000\n\n/** Poll cadence while waiting for readiness. */\nconst READY_POLL_MS = 500\n\n/** Backoff ceiling for crash restarts. */\nconst MAX_RESTART_BACKOFF_MS = 30_000\n\n/** SIGTERM → SIGKILL escalation grace. */\nconst KILL_GRACE_MS = 3_000\n\n/** Ring-buffer cap for the stdout/stderr log. */\nconst LOG_BUFFER_LIMIT = 200\n\n/** One-shot scaffold timeout for `--init server`. */\nconst INIT_TIMEOUT_MS = 30_000\n\nexport interface WikiServerOptions {\n /** Root that holds one folder per wiki (default $DSH_HOME/tiddlywiki). */\n wikiRoot: string\n /** Wiki folder name under wikiRoot (default \"main\"). */\n wiki: string\n /** Port; 0 = auto-detect a free loopback port. */\n port: number\n /** Optional Basic Auth (loopback anonymous by default). */\n username?: string\n password?: string\n logBufferLimit?: number\n}\n\nexport type WikiHealth = 'starting' | 'running' | 'stopped' | 'failed'\n\nexport interface WikiStatusView {\n status: WikiHealth\n url?: string\n port?: number\n wikiPath: string\n pid?: number\n lastStartedAt?: number\n error?: string\n logs: string[]\n}\n\n/** Resolve the absolute entry of the installed `tiddlywiki` package. */\nfunction resolveTwEntry(): string {\n const require = createRequire(import.meta.url)\n return require.resolve('tiddlywiki/tiddlywiki.js')\n}\n\nexport class WikiServer {\n private child: ChildProcessByStdio<null, Readable, Readable> | undefined\n private readonly wikiPath: string\n private readonly logs: string[] = []\n private readonly logLimit: number\n private health: WikiHealth = 'stopped'\n private port: number | undefined\n private stopping = false\n private restartTimer: NodeJS.Timeout | undefined\n private restartDelay = 1_000\n private lastStartedAt: number | undefined\n private error: string | undefined\n\n constructor(private readonly options: WikiServerOptions) {\n this.wikiPath = resolve(options.wikiRoot, options.wiki)\n this.logLimit = options.logBufferLimit ?? LOG_BUFFER_LIMIT\n }\n\n /** Base URL of the TW service, once a port is bound (root, no path prefix). */\n get url(): string | undefined {\n return this.port === undefined ? undefined : `http://127.0.0.1:${this.port}`\n }\n\n /** The currently bound port (undefined until first spawn). */\n get currentPort(): number | undefined {\n return this.port\n }\n\n private log(line: string): void {\n const ts = new Date().toISOString()\n this.logs.push(`[${ts}] ${line}`)\n if (this.logs.length > this.logLimit) this.logs.splice(0, this.logs.length - this.logLimit)\n }\n\n /** Scaffold the wiki folder with `--init server` when it is absent. */\n async ensureWiki(): Promise<void> {\n await mkdir(this.wikiPath, { recursive: true })\n if (existsSync(join(this.wikiPath, 'tiddlywiki.info'))) return\n const tw = resolveTwEntry()\n this.log(`init: ${process.execPath} ${tw} ${this.wikiPath} --init server`)\n await new Promise<void>((resolveP, rejectP) => {\n execFile(process.execPath, [tw, this.wikiPath, '--init', 'server'], { timeout: INIT_TIMEOUT_MS, windowsHide: true }, (err) => {\n if (err) rejectP(err as Error)\n else resolveP()\n })\n })\n }\n\n /** Probe a free loopback port. */\n private async findFreePort(): Promise<number> {\n return new Promise<number>((resolveP, rejectP) => {\n const server = createServer()\n server.unref()\n server.once('error', rejectP)\n server.listen(0, '127.0.0.1', () => {\n const address = server.address()\n if (address === null || typeof address === 'string') {\n server.close()\n rejectP(new Error('cannot resolve a free port'))\n return\n }\n const port = address.port\n server.close(() => resolveP(port))\n })\n })\n }\n\n /**\n * Start (or restart) the TW child. Resolves once `/status` answers 200 or\n * the readiness deadline passes. Never throws on a crash — the exit handler\n * schedules a self-healing restart unless we are stopping.\n */\n async start(): Promise<WikiStatusView> {\n this.stopping = false\n this.restartDelay = 1_000\n await this.ensureWiki()\n if (this.child !== undefined) return this.status()\n this.health = 'starting'\n // Reuse an existing auto port across restarts (restart() → stop() → start())\n // so a fixed-baseUrl TiddlyWebClient stays valid and iframe src is stable.\n const port = this.options.port > 0 ? this.options.port : ((this.port ?? 0) > 0 ? this.port as number : await this.findFreePort())\n this.port = port\n const tw = resolveTwEntry()\n const args = [tw, this.wikiPath, '--listen', 'host=127.0.0.1', `port=${port}`]\n if (this.options.username) {\n // Locked-down mode for non-loopback exposure: Basic Auth + access lists.\n args.push(`username=${this.options.username}`)\n args.push(`password=${this.options.password ?? ''}`)\n args.push(`readers=${this.options.username}`)\n args.push(`writers=${this.options.username}`)\n }\n // Anonymous loopback mode carries NO auth args: TW's defaults open the\n // wiki to anonymous read/write on the bound (loopback) address. Passing\n // anon-username/readers/writers here was verified to 401 every request\n // ('undefined' is not authorized), so the anonymous branch stays bare.\n this.log(`spawn: ${process.execPath} ${args.join(' ')}`)\n const child = spawn(process.execPath, args, { cwd: this.wikiPath, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true })\n this.child = child\n child.stdout.on('data', (chunk: Buffer) => this.log(`[out] ${String(chunk).trimEnd()}`))\n child.stderr.on('data', (chunk: Buffer) => this.log(`[err] ${String(chunk).trimEnd()}`))\n child.once('exit', (code, signal) => {\n this.log(`exit code=${code} signal=${signal ?? ''} stopping=${this.stopping}`)\n this.child = undefined\n this.health = 'stopped'\n if (!this.stopping) this.scheduleRestart()\n })\n child.once('error', (err) => {\n this.log(`spawn error: ${err.message}`)\n this.error = err.message\n this.child = undefined\n this.health = 'failed'\n if (!this.stopping) this.scheduleRestart()\n })\n this.lastStartedAt = Date.now()\n await this.waitReady()\n return this.status()\n }\n\n /** Poll /status until 200 or the deadline; throws only on deadline/crash. */\n private async waitReady(): Promise<void> {\n const deadline = Date.now() + READY_TIMEOUT_MS\n for (;;) {\n if (this.child === undefined) throw new Error('wiki process exited before ready')\n try {\n const res = await fetch(`${this.url}/status`, { signal: AbortSignal.timeout(2_000) })\n if (res.ok) {\n this.health = 'running'\n this.log('ready: /status 200')\n return\n }\n } catch {\n /* not ready yet */\n }\n if (Date.now() > deadline) {\n this.health = 'failed'\n this.error = 'wiki server did not become ready in time'\n this.log(this.error)\n throw new Error(this.error)\n }\n await new Promise<void>((r) => setTimeout(r, READY_POLL_MS))\n }\n }\n\n private scheduleRestart(): void {\n if (this.stopping || this.restartTimer !== undefined) return\n const delay = this.restartDelay\n this.restartDelay = Math.min(this.restartDelay * 2, MAX_RESTART_BACKOFF_MS)\n this.log(`restart scheduled in ${delay}ms`)\n this.health = 'starting'\n this.restartTimer = setTimeout(() => {\n this.restartTimer = undefined\n void this.start().catch((err) => {\n this.health = 'failed'\n this.error = err instanceof Error ? err.message : String(err)\n this.log(`restart failed: ${this.error}`)\n })\n }, delay)\n }\n\n /** One-click restart (route /dsh-tiddlywiki/restart, panel retry button). */\n async restart(): Promise<WikiStatusView> {\n await this.stop()\n return this.start()\n }\n\n /** Deterministic teardown: cancel timers, SIGTERM, escalate to SIGKILL. */\n async stop(): Promise<void> {\n this.stopping = true\n if (this.restartTimer !== undefined) {\n clearTimeout(this.restartTimer)\n this.restartTimer = undefined\n }\n const child = this.child\n this.child = undefined\n if (child !== undefined && child.exitCode === null && child.signalCode === null) {\n try {\n child.kill('SIGTERM')\n } catch { /* already gone */ }\n await Promise.race([\n new Promise<void>((r) => child.once('exit', () => r())),\n new Promise<void>((r) => {\n setTimeout(() => {\n try { child.kill('SIGKILL') } catch { /* already gone */ }\n r()\n }, KILL_GRACE_MS).unref?.()\n }),\n ])\n }\n this.health = 'stopped'\n }\n\n /** Live status view (health, url, git-independent, recent logs). */\n status(): WikiStatusView {\n return {\n status: this.health,\n url: this.url,\n port: this.port,\n wikiPath: this.wikiPath,\n pid: this.child?.pid,\n lastStartedAt: this.lastStartedAt,\n ...(this.error !== undefined ? { error: this.error } : {}),\n logs: [...this.logs],\n }\n }\n}\n","/**\n * DSH webserver routes for dsh-tiddlywiki (design doc §10).\n *\n * | route | method | purpose |\n * |---------------------------|--------|------------------------------------------|\n * | /dsh-tiddlywiki/status | GET | panel health (service / url / git / tag) |\n * | /dsh-tiddlywiki/note | POST | quick-note → independent tiddler |\n * | /dsh-tiddlywiki/restart | POST | one-click retry/restart of the TW child |\n * | /dsh-tiddlywiki/api/* | any | passthrough to the TW service (JSON) |\n *\n * Matching is exact-over-prefix, so the exact routes win and the `/api`\n * prefix catches the rest. Client calls are same-origin (the DSH web server),\n * so no CORS is involved.\n *\n * @module dsh-tiddlywiki/host/routes\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport type { WikiServer } from './wiki.ts'\nimport type { GitFace } from './git.ts'\nimport { PATH_PREFIX } from './wiki.ts'\n\nexport const ROUTE_PREFIX = PATH_PREFIX\n\n/** Max JSON body for note/restart. */\nconst MAX_BODY_BYTES = 2 * 1024 * 1024\n\n/** Max passthrough body (tiddler content can be large). */\nconst MAX_PROXY_BODY_BYTES = 16 * 1024 * 1024\n\n/** Structural webserver face (a subset of dsh-host-webserver). */\nexport interface WebServerFace {\n register(route: { kind: 'exact' | 'prefix'; path: string; handler: (req: IncomingMessage, res: ServerResponse) => void }): () => void\n}\n\nexport interface RouteDeps {\n server: WikiServer\n getClient: () => TiddlyWebClient | undefined\n git: GitFace\n autoCommit: () => void\n noteDefaults: () => { tag: string }\n getWikiPath: () => string\n}\n\nfunction readBody(req: IncomingMessage, limit = MAX_BODY_BYTES): Promise<string> {\n return new Promise((resolveP, rejectP) => {\n let size = 0\n const chunks: Buffer[] = []\n req.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > limit) {\n rejectP(new Error('body too large'))\n req.destroy()\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => resolveP(Buffer.concat(chunks).toString('utf8')))\n req.on('error', rejectP)\n })\n}\n\nfunction json(res: ServerResponse, payload: unknown, status = 200): void {\n const body = JSON.stringify(payload)\n res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' })\n res.end(body)\n}\n\nfunction pad(n: number): string {\n return n < 10 ? `0${n}` : String(n)\n}\n\n/** Default note title: `YYYY-MM-DD HH:mm` (design doc D6). */\nfunction timestampTitle(date = new Date()): string {\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`\n}\n\n/**\n * Open a tiddler in TW's NATIVE editor: save the tiddler (when text is\n * non-empty), reuse or create a DRAFT tiddler carrying `draft.of`/`draft.title`\n * (TW's story view renders drafts with the EditTemplate — list.js:\n * `isDraft && editTemplate`), and return the draft title so the client can\n * navigate the panel iframe to `#<draftTitle>`.\n */\nexport async function openInTwEditor(\n client: TiddlyWebClient,\n title: string,\n text: string,\n tag: string,\n): Promise<{ title: string; draftTitle: string }> {\n if (text.trim().length > 0) {\n await client.put({ title, text, tags: [tag] })\n }\n // Draft content: the provided text, else the existing tiddler's content.\n let draftText = text\n if (draftText.trim().length === 0) {\n const existing = await client.get(title)\n draftText = existing?.text ?? ''\n }\n // Reuse an existing draft for this title (mirrors wiki.findDraft).\n let draftTitle: string | undefined\n try {\n const items = await client.list(undefined, true)\n for (const item of items) {\n if (item['draft.of'] === title && typeof item.title === 'string') {\n draftTitle = item.title\n break\n }\n }\n } catch {\n /* fall back to a fresh draft */\n }\n if (draftTitle === undefined) draftTitle = `Draft of \"${title}\" ${Date.now()}`\n await client.put({ title: draftTitle, text: draftText, 'draft.of': title, 'draft.title': title, type: 'text/vnd.tiddlywiki' })\n return { title, draftTitle }\n}\n\nexport function registerRoutes(ctx: { webServer: WebServerFace }, deps: RouteDeps): () => void {\n const handleStatus = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const view = deps.server.status()\n let gitSummary: GitStatusViewPublic | null = null\n try {\n gitSummary = await deps.git.status(deps.getWikiPath())\n } catch {\n gitSummary = null\n }\n json(res, { ok: true, ...view, git: gitSummary, note: { tag: deps.noteDefaults().tag } })\n }\n\n const handleNote = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n const body = JSON.parse(await readBody(req)) as { title?: unknown; tag?: unknown; text?: unknown }\n const text = typeof body.text === 'string' && body.text.trim().length > 0 ? body.text.trim() : null\n if (text === null) {\n json(res, { ok: false, error: 'text is required' }, 400)\n return\n }\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n const title = typeof body.title === 'string' && body.title.trim().length > 0 ? body.title.trim() : timestampTitle()\n const tag = typeof body.tag === 'string' && body.tag.trim().length > 0 ? body.tag.trim() : deps.noteDefaults().tag\n await client.put({ title, text, tags: [tag] })\n deps.autoCommit()\n json(res, { ok: true, title, tag, text })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n const handleEdit = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n const body = JSON.parse(await readBody(req)) as { title?: unknown; tag?: unknown; text?: unknown }\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n const title = typeof body.title === 'string' && body.title.trim().length > 0 ? body.title.trim() : timestampTitle()\n const tag = typeof body.tag === 'string' && body.tag.trim().length > 0 ? body.tag.trim() : deps.noteDefaults().tag\n const text = typeof body.text === 'string' ? body.text : ''\n const result = await openInTwEditor(client, title, text, tag)\n deps.autoCommit()\n json(res, { ok: true, ...result, twUrl: deps.server.url })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n const handleRestart = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n await deps.server.restart()\n json(res, { ok: true, status: deps.server.status().status })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n /** Passthrough /dsh-tiddlywiki/api/<rest> → TW root /<rest>. */\n const handleApiProxy = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n const url = new URL(req.url ?? '/', 'http://127.0.0.1')\n const rest = url.pathname.replace(/^\\/dsh-tiddlywiki\\/api/, '') || '/'\n try {\n const headers: Record<string, string> = {}\n const ct = req.headers['content-type']\n if (typeof ct === 'string') headers['content-type'] = ct\n const method = (req.method ?? 'GET').toUpperCase()\n // TW's CSRF gate requires X-Requested-With on writes; forward it through.\n if (method === 'PUT' || method === 'DELETE' || method === 'POST') headers['x-requested-with'] = 'TiddlyWiki'\n const init: RequestInit = { method, headers, signal: AbortSignal.timeout(15_000) }\n if (method === 'PUT' || method === 'POST') init.body = await readBody(req, MAX_PROXY_BODY_BYTES)\n const upstream = await fetch(`${deps.server.url}${rest}${url.search}`, init)\n const data = await upstream.text()\n res.writeHead(upstream.status, {\n 'content-type': upstream.headers.get('content-type') ?? 'application/json; charset=utf-8',\n 'cache-control': 'no-store',\n })\n res.end(data)\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 502)\n }\n }\n\n const disposers = [\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/status`, handler: (req, res) => { void handleStatus(req, res) } }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/note`, handler: (req, res) => { void handleNote(req, res) } }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/edit`, handler: (req, res) => { void handleEdit(req, res) } }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/restart`, handler: (req, res) => { void handleRestart(req, res) } }),\n ctx.webServer.register({ kind: 'prefix', path: `${ROUTE_PREFIX}/api`, handler: (req, res) => { void handleApiProxy(req, res) } }),\n ]\n return () => {\n for (const dispose of disposers) dispose()\n }\n}\n\n/** Public shape of the git status summary sent to the panel. */\nexport interface GitStatusViewPublic {\n exists: boolean\n branch: string\n dirty: boolean\n dirtyFiles: string[]\n remote: string\n lastCommit?: string\n ahead?: number\n behind?: number\n}\n","/**\n * Extensible plugin config (design doc §13, config panel).\n *\n * Two layers:\n * base — the cordis `config:` block (profile composition, defaults);\n * overrides — a user-editable config tiddler ($:/plugins/dsh-tiddlywiki/config,\n * a JSON string) written by the settings page.\n * The tiddler overlays the base (tiddler wins), so future config fields just\n * extend the shape — no schema, no @deepseek-ai dependency, and the config\n * travels with the wiki's git history.\n *\n * @module dsh-tiddlywiki/host/config\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\n\n/** Config tiddler (JSON string) where the settings page stores overrides. */\nexport const CONFIG_TIDDLER = '$:/plugins/dsh-tiddlywiki/config'\n\n/** Extensible, loose plugin config shape (future fields just appear here). */\nexport interface PluginConfigShape {\n note?: { tag?: string }\n git?: { autoCommit?: boolean; debounceMs?: number; remote?: string; branch?: string }\n uiLanguage?: string\n [key: string]: unknown\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** Deep-merge: `over` wins; nested plain objects merge recursively. */\nexport function deepMerge(base: Record<string, unknown>, over: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = { ...base }\n for (const [key, value] of Object.entries(over)) {\n if (value === undefined) continue\n if (isPlainObject(value) && isPlainObject(out[key])) {\n out[key] = deepMerge(out[key] as Record<string, unknown>, value)\n } else {\n out[key] = value\n }\n }\n return out\n}\n\n/**\n * Runtime config store: caches the override tiddler and exposes the effective\n * (merged) config. `load` runs at startup and after every write/restart.\n */\nexport class ConfigStore {\n private overrides: PluginConfigShape = {}\n\n constructor(private readonly base: PluginConfigShape) {}\n\n /** Effective config = cordis base overlaid with the user override tiddler. */\n get(): PluginConfigShape {\n return deepMerge(this.base, this.overrides) as PluginConfigShape\n }\n\n /** Reload the override tiddler (no-op when the wiki is unavailable). */\n async load(client: TiddlyWebClient | undefined): Promise<void> {\n this.overrides = {}\n if (client === undefined) return\n try {\n const tiddler = await client.get(CONFIG_TIDDLER)\n if (tiddler !== undefined && typeof tiddler.text === 'string') {\n const parsed = JSON.parse(tiddler.text) as unknown\n if (isPlainObject(parsed)) this.overrides = parsed as PluginConfigShape\n }\n } catch {\n // Wiki not ready or config tiddler unreadable → keep empty overrides.\n this.overrides = {}\n }\n }\n\n /** Merge a patch into the overrides and persist the tiddler. */\n async set(client: TiddlyWebClient, patch: PluginConfigShape): Promise<PluginConfigShape> {\n this.overrides = deepMerge(this.overrides, patch) as PluginConfigShape\n await client.put({\n title: CONFIG_TIDDLER,\n text: JSON.stringify(this.overrides, null, 2),\n type: 'application/json',\n tags: [],\n })\n return this.get()\n }\n}\n","/**\n * Admin surface for the plugin settings page (design doc §13, config panel).\n *\n * - dynamic plugin/theme management: enumerate the bundled catalog from the\n * installed tiddlywiki package, read/write the wiki's `tiddlywiki.info`\n * plugins/themes arrays, then restart the TW child so the change applies;\n * - extensible config: the settings page reads/writes a config tiddler\n * ($:/plugins/dsh-tiddlywiki/config, a JSON string) that overlays the\n * cordis `config:` block — future config fields just extend the shape.\n *\n * Routes (all under ROUTE_PREFIX/admin, JSON):\n * GET /admin/state current info + catalog + effective config + status\n * POST /admin/info { plugins?, themes? } → write info → restart TW\n * POST /admin/config { ...patch } → write config tiddler\n * POST /admin/restart restart the TW child\n *\n * @module dsh-tiddlywiki/host/admin\n */\nimport { createRequire } from 'node:module'\nimport { readFile, writeFile, readdir } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { TiddlyWebClient } from './tw-api.ts'\nimport type { WikiServer } from './wiki.ts'\nimport { ROUTE_PREFIX, type WebServerFace } from './routes.ts'\nimport { CONFIG_TIDDLER, type ConfigStore, type PluginConfigShape } from './config.ts'\n\n/** One bundled plugin/theme from the catalog. */\nexport interface CatalogEntry {\n /** Short name as used in tiddlywiki.info, e.g. \"tiddlywiki/katex\". */\n name: string\n /** Full tiddler title, e.g. \"$:/plugins/tiddlywiki/katex\". */\n title: string\n label: string\n description: string\n /** Dependent theme NAMES (converted from plugin.info `dependents`), e.g. heavier → [\"tiddlywiki/snowwhite\"]. */\n dependents?: string[]\n}\n\nexport interface Catalog {\n plugins: CatalogEntry[]\n themes: CatalogEntry[]\n /** Bundled language plugins (tiddlywiki package `languages/` dir). */\n languages: CatalogEntry[]\n}\n\n/** Shape of tiddlywiki.info (plugins/themes/languages + build/description). */\nexport interface WikiInfo {\n description?: string\n plugins: string[]\n themes: string[]\n languages?: string[]\n build?: Record<string, unknown>\n [key: string]: unknown\n}\n\n/** Resolve the installed tiddlywiki package root (for the catalog). */\nexport function resolveTwRoot(): string {\n const require = createRequire(import.meta.url)\n return dirname(require.resolve('tiddlywiki/package.json'))\n}\n\n/** Read the wiki's tiddlywiki.info. */\nexport async function readWikiInfo(wikiPath: string): Promise<WikiInfo> {\n let raw: string\n try {\n raw = await readFile(join(wikiPath, 'tiddlywiki.info'), 'utf8')\n } catch {\n return { plugins: [], themes: [], languages: [] }\n }\n const parsed = JSON.parse(raw) as Partial<WikiInfo>\n return {\n description: parsed.description,\n plugins: parsed.plugins ?? [],\n themes: parsed.themes ?? [],\n languages: parsed.languages ?? [],\n ...parsed,\n }\n}\n\n/** Write the wiki's tiddlywiki.info (pretty-printed, ordering preserved). */\nexport async function writeWikiInfo(wikiPath: string, info: WikiInfo): Promise<void> {\n await writeFile(join(wikiPath, 'tiddlywiki.info'), `${JSON.stringify(info, null, 4)}\\n`, 'utf8')\n}\n\n/** Enumerate bundled official plugins + themes + languages of tiddlywiki. */\nexport async function bundledCatalog(twRoot: string): Promise<Catalog> {\n // TW themes are SKINS layered on the vanilla base (which carries the full\n // 70KB base stylesheet). A theme whose stylesheet body is empty is a broken\n // stub (e.g. tight-heavier in some releases) — skip it so the settings list\n // never offers a no-op theme. vanilla itself is always kept.\n const themeHasCss = async (dir: string): Promise<boolean> => {\n for (const name of ['base.tid', 'styles.tid']) {\n try {\n const raw = await readFile(join(twRoot, 'themes', 'tiddlywiki', dir, name), 'utf8')\n const body = raw\n .replace(/^[\\s\\S]*?\\r?\\n\\r?\\n/, '')\n .split('\\n')\n .filter((line) => !/^\\\\rules\\b/.test(line.trim()))\n .join('\\n')\n .trim()\n if (body.length > 0) return true\n } catch {\n /* file absent */\n }\n }\n return false\n }\n const scan = async (sub: 'plugins' | 'themes'): Promise<CatalogEntry[]> => {\n const root = join(twRoot, sub, 'tiddlywiki')\n let dirs: string[]\n try {\n dirs = await readdir(root)\n } catch {\n return []\n }\n const out: CatalogEntry[] = []\n for (const dir of dirs) {\n let info: { name?: string; description?: string; dependents?: string[] } = {}\n try {\n info = JSON.parse(await readFile(join(root, dir, 'plugin.info'), 'utf8')) as typeof info\n } catch {\n info = {}\n }\n if (sub === 'themes' && dir !== 'vanilla' && !(await themeHasCss(dir))) continue\n out.push({\n name: `tiddlywiki/${dir}`,\n title: sub === 'plugins' ? `$:/plugins/tiddlywiki/${dir}` : `$:/themes/tiddlywiki/${dir}`,\n label: info.name ?? dir,\n description: info.description ?? '',\n // plugin.info `dependents` are full plugin titles → convert to names.\n dependents: Array.isArray(info.dependents)\n ? info.dependents.map((dep) => dep.replace(/^\\$:\\/themes\\/tiddlywiki\\//, 'tiddlywiki/'))\n : undefined,\n })\n }\n out.sort((a, b) => a.name.localeCompare(b.name))\n return out\n }\n // Language plugins live in the package ROOT `languages/` dir (not plugins/),\n // and are enabled via the tiddlywiki.info `languages` array (boot resolves\n // them through $tw.config.languagesPath). Fully offline — official builds.\n const scanLanguages = async (): Promise<CatalogEntry[]> => {\n const root = join(twRoot, 'languages')\n let dirs: string[]\n try {\n dirs = await readdir(root)\n } catch {\n return []\n }\n const out: CatalogEntry[] = []\n for (const dir of dirs) {\n let info: { name?: string; description?: string } = {}\n try {\n info = JSON.parse(await readFile(join(root, dir, 'plugin.info'), 'utf8')) as typeof info\n } catch {\n info = {}\n }\n out.push({\n name: dir,\n title: `$:/languages/${dir}`,\n label: info.name ?? dir,\n description: info.description ?? '',\n })\n }\n out.sort((a, b) => a.name.localeCompare(b.name))\n return out\n }\n const [plugins, themes, languages] = await Promise.all([scan('plugins'), scan('themes'), scanLanguages()])\n return { plugins, themes, languages }\n}\n\n/**\n * Normalize a theme selection into the tiddlywiki.info `themes` array.\n *\n * TW themes are SKINS with a dependency chain (plugin.info `dependents`):\n * vanilla ← snowwhite ← heavier / centralised / readonly / starlight\n * vanilla ← tight / seamless\n * The ACTIVE theme is `$:/theme`, and switching to it registers the theme PLUS\n * its transitive dependents (boot.js accumulatePlugin) — if a dependent isn't\n * loaded, the vanilla base stylesheet is lost and the UI breaks. So we always\n * emit the transitive closure, dependency-first (base first, active overlay\n * last), and force vanilla in as the base. Empty selection → vanilla.\n */\nexport function normalizeThemes(selected: string[], deps: Record<string, string[]> = {}): string[] {\n const sel = selected.filter((name) => typeof name === 'string' && name.length > 0)\n if (sel.length === 0) sel.push('tiddlywiki/vanilla')\n const out: string[] = []\n const seen = new Set<string>()\n const visit = (name: string): void => {\n if (seen.has(name)) return\n seen.add(name)\n for (const dep of deps[name] ?? []) {\n if (dep !== name) visit(dep)\n }\n out.push(name)\n }\n for (const name of sel) visit(name)\n if (!out.includes('tiddlywiki/vanilla')) out.unshift('tiddlywiki/vanilla')\n return out\n}\n\n/**\n * Ensure a language code (e.g. \"zh-Hans\") is in tiddlywiki.info `languages`.\n * Returns whether tiddlywiki.info changed (caller decides whether to restart).\n */\nexport async function ensureLanguage(wikiPath: string, twRoot: string, lang: string): Promise<boolean> {\n if (typeof lang !== 'string' || lang.trim().length === 0) return false\n const code = lang.trim()\n const catalog = await bundledCatalog(twRoot)\n if (!catalog.languages.some((l) => l.name === code)) {\n throw new Error(`unknown language plugin: ${code}`)\n }\n const info = await readWikiInfo(wikiPath)\n const current = info.languages ?? []\n if (current.includes(code)) return false\n info.languages = [...current, code]\n await writeWikiInfo(wikiPath, info)\n return true\n}\n\nfunction json(res: ServerResponse, payload: unknown, status = 200): void {\n res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' })\n res.end(JSON.stringify(payload))\n}\n\nasync function readBody(req: IncomingMessage, limit = 1024 * 1024): Promise<string> {\n return new Promise((resolveP, rejectP) => {\n let size = 0\n const chunks: Buffer[] = []\n req.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > limit) {\n rejectP(new Error('body too large'))\n req.destroy()\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => resolveP(Buffer.concat(chunks).toString('utf8')))\n req.on('error', rejectP)\n })\n}\n\nexport interface AdminDeps {\n server: WikiServer\n getClient: () => TiddlyWebClient | undefined\n getWikiPath: () => string\n twRoot: () => string\n config: ConfigStore\n}\n\nexport function registerAdminRoutes(ctx: { webServer: WebServerFace }, deps: AdminDeps): () => void {\n const handleState = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n const wikiPath = deps.getWikiPath()\n const [info, catalog] = await Promise.all([readWikiInfo(wikiPath), bundledCatalog(deps.twRoot())])\n let git: unknown = null\n try {\n const { GitFace } = await import('./git.ts')\n git = await new GitFace().status(wikiPath)\n } catch {\n git = null\n }\n json(res, {\n ok: true,\n server: deps.server.status(),\n info: { plugins: info.plugins, themes: info.themes, languages: info.languages ?? [] },\n catalog,\n config: deps.config.get(),\n git,\n })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n const handleInfo = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n const body = JSON.parse(await readBody(req)) as { plugins?: unknown; themes?: unknown; themeActive?: unknown; languages?: unknown }\n const wikiPath = deps.getWikiPath()\n const info = await readWikiInfo(wikiPath)\n const catalog = await bundledCatalog(deps.twRoot())\n const known = new Set([...catalog.plugins, ...catalog.themes].map((c) => c.name))\n const knownLangs = new Set(catalog.languages.map((c) => c.name))\n const applyList = (field: 'plugins' | 'themes', raw: unknown): string[] => {\n if (!Array.isArray(raw)) return info[field]\n const next: string[] = []\n for (const name of raw) {\n if (typeof name !== 'string') continue\n if (!known.has(name) && !info[field].includes(name)) {\n throw new Error(`unknown plugin/theme: ${name}`)\n }\n if (!next.includes(name)) next.push(name)\n }\n return next\n }\n const applyLanguages = (raw: unknown): string[] => {\n if (!Array.isArray(raw)) return info.languages ?? []\n const next: string[] = []\n for (const code of raw) {\n if (typeof code !== 'string') continue\n if (!knownLangs.has(code) && !(info.languages ?? []).includes(code)) {\n throw new Error(`unknown language plugin: ${code}`)\n }\n if (!next.includes(code)) next.push(code)\n }\n return next\n }\n info.plugins = applyList('plugins', body.plugins)\n // Activate the chosen theme: load its full dependency chain AND set\n // `$:/theme` so the browser's themeManager actually applies it (the\n // `themes` array alone only makes the plugin available).\n let activatedTheme: string | undefined\n if (Array.isArray(body.themes)) {\n const themeDeps: Record<string, string[]> = {}\n for (const theme of catalog.themes) {\n if (theme.dependents && theme.dependents.length > 0) themeDeps[theme.name] = theme.dependents\n }\n let selected = applyList('themes', body.themes)\n // Explicit active-theme pick (new two-layer UI): validate and auto-add\n // it to the loaded set so its dependency closure is loaded. Without an\n // explicit pick, activate the deepest loaded overlay (old single-radio).\n const explicitActive = typeof body.themeActive === 'string' && body.themeActive.length > 0\n if (explicitActive) {\n const activeName = body.themeActive as string\n if (known.has(activeName) || info.themes.includes(activeName)) {\n if (!selected.includes(activeName)) selected.push(activeName)\n activatedTheme = activeName\n }\n }\n info.themes = normalizeThemes(selected, themeDeps)\n if (activatedTheme === undefined && info.themes.length > 0) {\n activatedTheme = info.themes[info.themes.length - 1]\n }\n } else {\n info.themes = applyList('themes', body.themes)\n }\n if (Array.isArray(body.languages)) info.languages = applyLanguages(body.languages)\n await writeWikiInfo(wikiPath, info)\n await deps.server.restart()\n // Activate the chosen theme tiddler (mirrors TW's own Control Panel).\n if (activatedTheme !== undefined) {\n const client = deps.getClient()\n if (client !== undefined) {\n await client\n .put({ title: '$:/theme', text: `$:/themes/${activatedTheme}`, type: 'text/vnd.tiddlywiki', tags: [] })\n .catch(() => undefined)\n }\n }\n // After a languages change, pin the active language tiddler: first\n // enabled language, or en-GB when none is enabled. Only when the request\n // actually carried a languages array (plugins/themes restarts skip this).\n if (Array.isArray(body.languages)) {\n const client = deps.getClient()\n if (client !== undefined) {\n const langs = info.languages ?? []\n const active = langs.length > 0 ? `$:/languages/${langs[0]}` : '$:/languages/en-GB'\n await client.put({ title: '$:/language', text: active, type: 'text/plain', tags: [] }).catch(() => undefined)\n // Keep the startup auto-apply hint (config uiLanguage) consistent with\n // the active language, so a later dsh-web restart doesn't re-enable\n // a language the user just disabled here.\n const hint = langs.length > 0 ? langs[0] : ''\n if ((deps.config.get().uiLanguage ?? '') !== hint) {\n await deps.config.set(client, { uiLanguage: hint }).catch(() => undefined)\n }\n }\n }\n json(res, { ok: true, info: { plugins: info.plugins, themes: info.themes, languages: info.languages ?? [] } })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 400)\n }\n }\n\n const handleConfig = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n const body = JSON.parse(await readBody(req)) as PluginConfigShape\n const client = deps.getClient()\n if (client === undefined) {\n json(res, { ok: false, error: 'wiki service is not running' }, 503)\n return\n }\n await deps.config.set(client, body)\n json(res, { ok: true, config: deps.config.get() })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 400)\n }\n }\n\n const handleRestart = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {\n try {\n await deps.server.restart()\n json(res, { ok: true, status: deps.server.status().status })\n } catch (err) {\n json(res, { ok: false, error: err instanceof Error ? err.message : String(err) }, 500)\n }\n }\n\n const disposers = [\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/state`, handler: (req, res) => { void handleState(req, res) } }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/info`, handler: (req, res) => { void handleInfo(req, res) } }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/config`, handler: (req, res) => { void handleConfig(req, res) } }),\n ctx.webServer.register({ kind: 'exact', path: `${ROUTE_PREFIX}/admin/restart`, handler: (req, res) => { void handleRestart(req, res) } }),\n ]\n return () => {\n for (const dispose of disposers) dispose()\n }\n}\n","/**\n * Built-in doc note for the plugin (design doc §14): a short user-facing\n * \"how to use dsh-tiddlywiki\" note that is seeded into the wiki on first run.\n *\n * Idempotent seed (create-if-missing): on every plugin start we check whether\n * the note tiddler exists and only write it when it is absent — deleting it\n * and restarting dsh web recreates it, but editing it never gets overwritten.\n *\n * @module dsh-tiddlywiki/host/seed-notes\n */\nimport type { TiddlyWebClient } from './tw-api.ts'\n\n/** Note tiddler title (a normal, searchable note — not a system tiddler). */\nexport const DOC_NOTE_TITLE = 'dsh-tiddlywiki 插件说明'\n\n/** Tag that makes the note easy to find via `tiddlywiki_search tag=docs`. */\nexport const DOC_NOTE_TAG = 'docs'\n\n/** The note body, TiddlyWiki wiki-text. */\nexport const DOC_NOTE_TEXT = `! dsh-tiddlywiki 插件说明\n\n本插件把 **TiddlyWiki 5** 作为 DSH 的持久知识库(wiki 文件夹本身就是一个 git 仓库,随内容自动提交/同步)。\n\n!! 它能做什么\n\n* **5 个 agent 工具**:\\`tiddlywiki_search\\`(检索)/ \\`tiddlywiki_get\\`(读)/ \\`tiddlywiki_put\\`(写)/ \\`tiddlywiki_delete\\`(删)/ \\`tiddlywiki_git_sync\\`(git 同步)。\n* **TW 编辑器面板**:侧边栏「TiddlyWiki」按钮 → 在界面中央打开完整版 TW 编辑器。\n* **快速笔记**:右下角悬浮「📝 快速笔记」写随手记,\\`Ctrl+Enter\\` 保存;点「✏️ 在 TW 中编辑」会弹出独立小窗用 TW 原生编辑器编辑。\n* **git 同步**:写入自动防抖 commit(默认 60 秒);手动 \\`tiddlywiki_git_sync action=sync\\` 做 pull → commit → push。\n* **设置页**:DSH 设置 → 「TiddlyWiki 知识库」管理插件/主题/语言与运行配置。\n\n!! 知识库纪律(三条)\n\n1. 开工先 \\`tiddlywiki_git_sync action=pull\\`(rebase + autostash,真冲突会自动 abort 并报文件)。\n2. 收工 \\`tiddlywiki_git_sync action=sync\\`。\n3. 插件自动 commit 兜底,手动 sync 用于需要主动推送的场合。\n\n!! 主题与语言\n\n* **主题**分两层:每行一个「☑ 加载」(多选 = TW 里可用的主题,依赖链自动带上)和「◉ 活动」(单选 = 当前视觉主题)。应用后自动重启 TW。\n* **语言**:设置页勾选 \\`zh-Hans\\`(简体)并应用,TW 界面即切换为中文。\n\n!! 说明\n\n* 本笔记由插件在首次启动时自动写入 wiki(幂等:不存在才写)。删除后重启 dsh web 会重建;手动编辑过的内容不会被覆盖。\n* 更多细节见插件仓库 README。`\n\n/**\n * Seed the doc note when it is absent. Returns whether a note was written.\n * Never throws (missing wiki or note already present → no-op / false).\n */\nexport async function seedDocNote(client: TiddlyWebClient): Promise<boolean> {\n const existing = await client.get(DOC_NOTE_TITLE).catch(() => undefined)\n if (existing !== undefined) return false\n await client.put({\n title: DOC_NOTE_TITLE,\n text: DOC_NOTE_TEXT,\n type: 'text/vnd.tiddlywiki',\n tags: [DOC_NOTE_TAG],\n })\n return true\n}\n","/**\n * TiddlyWeb REST client (design doc §5) — the ONLY way every writer reaches\n * the wiki (quick notes, agent tools, editor saves all go through the TW\n * service, D1), so there is never a second write path.\n *\n * ROUTES ARE EMPIRICALLY VERIFIED against tiddlywiki 5.4.1's core-server\n * (`core-server/server/routes/`):\n * GET /recipes/default/tiddlers.json[?exclude=...] list (skinny)\n * GET /recipes/default/tiddlers/<title> read one (404 absent)\n * PUT /recipes/default/tiddlers/<title> write one (204)\n * DELETE /bags/default/tiddlers/<title> delete one (204)\n * Writes require the `X-Requested-With: TiddlyWiki` header (TW CSRF), which\n * this client always sends. Tags arrive as a whitespace-joined STRING and are\n * normalized to arrays here.\n *\n * SEARCH (R2): the server blocks arbitrary `filter=` queries with 403 unless\n * the exact filter is whitelisted in $:/config/Server/ExternalFilters. So\n * `search()` fetches the default listing WITH text (`?exclude=` a sentinel)\n * and matches locally — one request, no 403, no per-tiddler round-trips.\n *\n * @module dsh-tiddlywiki/host/tw-api\n */\n\n/** A tiddler's readable fields (loose on purpose). */\nexport interface Tiddler {\n title: string\n text?: string\n tags?: string[]\n type?: string\n created?: string\n modified?: string\n /** Extra custom fields returned by the server are folded under `fields`. */\n fields?: Record<string, unknown>\n [key: string]: unknown\n}\n\nconst REQUEST_TIMEOUT_MS = 10_000\n\n/** TW's CSRF gate: writes must carry this header (TW's own UI always does). */\nconst CSRF_HEADER = { 'x-requested-with': 'TiddlyWiki' }\n\n/** Sentinel `exclude` value: excludes nothing, so `text` stays in the list. */\nconst LIST_WITH_TEXT_EXCLUDE = '__dsh_tw_none__'\n\n/** Split TW's whitespace-joined tags string into an array. */\nfunction normalizeTags(tags: unknown): string[] | undefined {\n if (tags === undefined) return undefined\n if (Array.isArray(tags)) return tags.map(String)\n if (typeof tags === 'string') {\n const parts = tags.trim().split(/\\s+/).filter(Boolean)\n return parts.length > 0 ? parts : []\n }\n return []\n}\n\n/** Normalize a raw server tiddler (tags string → array, unknown fields nested). */\nfunction normalizeTiddler(raw: Record<string, unknown>): Tiddler {\n const out = { ...raw } as Tiddler\n const tags = normalizeTags(raw.tags)\n if (tags !== undefined) out.tags = tags\n return out\n}\n\nexport class TiddlyWebClient {\n constructor(private readonly baseUrl: string) {}\n\n private async request(path: string, init?: RequestInit): Promise<Response> {\n return fetch(`${this.baseUrl}${path}`, {\n ...init,\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n })\n }\n\n /** GET /status → { username, anonymous, space, tiddlywiki_version, ... }. */\n async status(): Promise<Record<string, unknown>> {\n const res = await this.request('/status')\n if (!res.ok) throw new Error(`TiddlyWeb /status HTTP ${res.status}`)\n return res.json() as Promise<Record<string, unknown>>\n }\n\n /** Read one tiddler; undefined when it does not exist (404). */\n async get(title: string): Promise<Tiddler | undefined> {\n const res = await this.request(`/recipes/default/tiddlers/${encodeURIComponent(title)}`)\n if (res.status === 404) return undefined\n if (!res.ok) throw new Error(`TiddlyWeb GET /recipes/default/tiddlers/${title} HTTP ${res.status}`)\n return normalizeTiddler((await res.json()) as Record<string, unknown>)\n }\n\n /** Write (create or overwrite) one tiddler via PUT (204 on success). */\n async put(tiddler: Tiddler): Promise<Tiddler> {\n const title = tiddler.title\n const res = await this.request(`/recipes/default/tiddlers/${encodeURIComponent(title)}`, {\n method: 'PUT',\n headers: { 'content-type': 'application/json', ...CSRF_HEADER },\n body: JSON.stringify(tiddler),\n })\n if (!res.ok) {\n const detail = await res.text().catch(() => '')\n throw new Error(`TiddlyWeb PUT /recipes/default/tiddlers/${title} HTTP ${res.status}: ${detail.slice(0, 300)}`)\n }\n return tiddler\n }\n\n /** Delete one tiddler via the bags route (204); a missing one is a no-op. */\n async delete(title: string): Promise<void> {\n const res = await this.request(`/bags/default/tiddlers/${encodeURIComponent(title)}`, {\n method: 'DELETE',\n headers: CSRF_HEADER,\n })\n if (res.status === 404) return\n if (!res.ok) throw new Error(`TiddlyWeb DELETE /bags/default/tiddlers/${title} HTTP ${res.status}`)\n }\n\n /**\n * List tiddlers via the default server filter. Arbitrary `filter=` queries\n * are blocked by the server (403) unless whitelisted, so callers needing a\n * subset should use search(); a supplied filter that is 403-blocked falls\n * back to the default listing.\n */\n async list(filter?: string, includeText = false): Promise<Tiddler[]> {\n const params = new URLSearchParams()\n if (includeText) params.set('exclude', LIST_WITH_TEXT_EXCLUDE)\n if (filter !== undefined && filter.length > 0) params.set('filter', filter)\n const query = params.toString()\n let res = await this.request(`/recipes/default/tiddlers.json${query.length > 0 ? `?${query}` : ''}`)\n if (!res.ok && res.status === 403 && filter !== undefined && filter.length > 0) {\n // Filter not whitelisted → refetch with the default filter.\n const retry = new URLSearchParams()\n if (includeText) retry.set('exclude', LIST_WITH_TEXT_EXCLUDE)\n const retryQuery = retry.toString()\n res = await this.request(`/recipes/default/tiddlers.json${retryQuery.length > 0 ? `?${retryQuery}` : ''}`)\n }\n if (!res.ok) throw new Error(`TiddlyWeb recipe list HTTP ${res.status}`)\n const data = (await res.json()) as Array<Record<string, unknown>> | { tiddlers?: Array<Record<string, unknown>> }\n const items = Array.isArray(data) ? data : (data.tiddlers ?? [])\n return items.map(normalizeTiddler)\n }\n\n /**\n * Search non-system tiddlers: one request (default listing with text) plus\n * local case-insensitive substring matching on title + text, optional exact\n * tag, capped at `limit`. Robust against the server's external-filter 403.\n */\n async search(query: string, tag?: string, limit = 30): Promise<Tiddler[]> {\n const items = await this.list(undefined, true)\n const needle = query.toLowerCase()\n const matched = items.filter((t) => {\n if (!t.title.toLowerCase().includes(needle) && !(t.text ?? '').toLowerCase().includes(needle)) return false\n if (tag !== undefined && tag.length > 0) {\n const tags = t.tags ?? []\n if (!tags.some((t2) => t2.toLowerCase() === tag.toLowerCase())) return false\n }\n return true\n })\n return matched.slice(0, limit)\n }\n}\n","/**\n * Self-contained replacements for the @deepseek-ai runtime imports the host\n * half must NEVER take from npm-mirror SDK packages (dsh-home-paths,\n * dsh-tools' defineTool).\n *\n * Why (design doc §4.4, taskboard lesson): a published copy must not resolve\n * `@deepseek-ai/dsh-tools` from the profile's node_modules — an npm-mirror\n * dsh-tools there shadows the CLI-internal build for the WHOLE base layer and\n * breaks the agent loop. Everything here is a pure, structure-compatible\n * reimplementation of the exact behavior the registry relies on:\n *\n * - `dshHomePath` mirrors `join(resolve(env.DSH_HOME ?? ~/.dsh), ...segments)`;\n * - `defineTool` compiles author-facing parameter specs into the same raw\n * JSON-Schema subset the registry expects and pre-validates model arguments.\n *\n * @module dsh-tiddlywiki/sdk\n */\nimport { homedir } from 'node:os'\nimport { join, resolve } from 'node:path'\n\n/** The DSH user home (DSH_HOME overrides). */\nexport function dshHomePath(...segments: string[]): string {\n const override = process.env.DSH_HOME\n const home = resolve(override !== undefined && override.length > 0 ? override : join(homedir(), '.dsh'))\n return join(home, ...segments)\n}\n\n/** Author-facing scalar spec. */\ninterface ScalarSpec {\n readonly type: 'string' | 'number' | 'integer' | 'boolean' | 'null'\n readonly description?: string\n readonly enum?: readonly unknown[]\n readonly const?: unknown\n}\n\n/** Author-facing object spec (additionalProperties is mandatory). */\ninterface ObjectSpec {\n readonly type: 'object'\n readonly additionalProperties: boolean\n readonly description?: string\n readonly properties?: Readonly<Record<string, ValueSpec>>\n}\n\n/** Author-facing value spec. */\ntype ValueSpec = ScalarSpec | ObjectSpec | { readonly type: 'json'; readonly description?: string } | { readonly type: 'array'; readonly items?: ValueSpec; readonly description?: string }\n\n/** Author-facing parameter entry (a value spec plus top-level required). */\ntype ParameterSpec = ValueSpec & { readonly required?: boolean }\n\n/** Raw JSON-Schema subset node. */\ntype RawSchema = Record<string, unknown>\n\n/** Compile one value spec to the raw subset (json → annotation-only). */\nfunction compileValue(spec: ValueSpec): RawSchema {\n const node: RawSchema = {}\n const description = (spec as { description?: string }).description\n if (typeof description === 'string' && description.length > 0) node.description = description\n const type = (spec as { type?: string }).type\n if (type === undefined || type === 'json') return node\n if (type === 'object') {\n const objectSpec = spec as ObjectSpec\n node.type = 'object'\n node.additionalProperties = objectSpec.additionalProperties\n if (objectSpec.properties !== undefined) node.properties = compilePropertyMap(objectSpec.properties).properties\n return node\n }\n if (type === 'array') {\n node.type = 'array'\n const items = (spec as { items?: ValueSpec }).items\n if (items !== undefined) node.items = compileValue(items)\n return node\n }\n node.type = type\n const enumValues = (spec as ScalarSpec).enum\n if (enumValues !== undefined) node.enum = [...enumValues]\n const constValue = (spec as ScalarSpec).const\n if (constValue !== undefined) node.const = constValue\n return node\n}\n\n/** Compile a property map: properties + collected required list. */\nfunction compilePropertyMap(spec: Readonly<Record<string, ParameterSpec>>): { properties: Record<string, RawSchema>; required?: string[] } {\n const properties: Record<string, RawSchema> = {}\n const required: string[] = []\n for (const [name, entry] of Object.entries(spec)) {\n const { required: isRequired, ...valueSpec } = entry as ParameterSpec & Record<string, unknown>\n properties[name] = compileValue(valueSpec as ValueSpec)\n if (isRequired === true) required.push(name)\n }\n return required.length > 0 ? { properties, required } : { properties }\n}\n\n/** Does a JS value match a raw-subset scalar type? */\nfunction matchesScalarType(value: unknown, type: string): boolean {\n switch (type) {\n case 'string': return typeof value === 'string'\n case 'number': return typeof value === 'number'\n case 'integer': return typeof value === 'number' && Number.isInteger(value)\n case 'boolean': return typeof value === 'boolean'\n case 'null': return value === null\n default: return true\n }\n}\n\n/** Validate a value against the compiled subset; returns path-qualified violations. */\nfunction validateValue(schema: RawSchema, value: unknown, path: string): string[] {\n if (typeof schema.type !== 'string' || schema.type.length === 0) return []\n if (schema.type === 'object') {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return [`${path} must be an object`]\n const violations: string[] = []\n const present = value as Record<string, unknown>\n for (const key of (schema.required as string[] | undefined) ?? []) {\n if (!(key in present)) violations.push(`${path}.${key} is required`)\n }\n if (schema.additionalProperties === false) {\n const known = new Set(Object.keys((schema.properties as Record<string, RawSchema> | undefined) ?? {}))\n for (const key of Object.keys(present)) {\n if (!known.has(key)) violations.push(`${path}.${key} is not a declared property`)\n }\n }\n for (const [key, child] of Object.entries((schema.properties as Record<string, RawSchema> | undefined) ?? {})) {\n if (key in present) violations.push(...validateValue(child, present[key], `${path}.${key}`))\n }\n return violations\n }\n if (schema.type === 'array') {\n if (!Array.isArray(value)) return [`${path} must be an array`]\n const violations: string[] = []\n const items = schema.items as RawSchema | undefined\n if (items !== undefined) {\n value.forEach((item, index) => { violations.push(...validateValue(items, item, `${path}[${index}]`)) })\n }\n return violations\n }\n if (!matchesScalarType(value, schema.type)) return [`${path} must be ${schema.type}`]\n const enumValues = schema.enum as unknown[] | undefined\n if (enumValues !== undefined && !enumValues.some(v => v === value)) {\n return [`${path} must be one of ${enumValues.map(String).join(', ')}`]\n }\n const constValue = (schema as { const?: unknown }).const\n if (constValue !== undefined && constValue !== value) {\n return [`${path} must be ${String(constValue)}`]\n }\n return []\n}\n\n/** Options shape we consume (a structural subset of the SDK's defineTool). */\nexport interface DefineToolOptions<A, V> {\n readonly name: string\n readonly description: string\n readonly parameters: Readonly<Record<string, ParameterSpec>>\n readonly output: {\n readonly schema: { readonly type: 'json' }\n render(args: A, value: V): Array<{ type: 'text'; text: string }>\n }\n execute(args: A, exec: unknown): Promise<V>\n}\n\n/** A registry-ready tool definition (structure-compatible with the SDK's). */\nexport interface ToolDefinition<A = unknown, V = unknown> {\n readonly name: string\n readonly description: string\n readonly parameters: RawSchema\n readonly output: {\n readonly schema: RawSchema\n render(args: A, value: V): Array<{ type: 'text'; text: string }>\n }\n execute(args: A, exec: unknown): Promise<V>\n}\n\n/**\n * Define a first-party tool: compile the parameter spec, pre-validate\n * arguments, and pass through the execution.\n */\nexport function defineTool<A extends Record<string, unknown>, V>(options: DefineToolOptions<A, V>): ToolDefinition<A, V> {\n const compiled = compilePropertyMap(options.parameters as Readonly<Record<string, ParameterSpec>>)\n const parameters: RawSchema = { type: 'object', properties: compiled.properties }\n if (compiled.required !== undefined) parameters.required = compiled.required\n const userExecute = options.execute\n return {\n name: options.name,\n description: options.description,\n parameters,\n output: {\n schema: {},\n render(args, value) {\n return options.output.render(args, value)\n },\n },\n async execute(args, exec) {\n const violations = validateValue(parameters, args, 'arguments')\n if (violations.length > 0) {\n throw new Error(`Error: invalid arguments: ${violations.join('; ')}`)\n }\n return userExecute(args, exec)\n },\n }\n}\n","/**\n * The five `tiddlywiki_*` agent tools (design doc §11, D8) plus the extension\n * point: `registerTiddlywikiTools(ctx, deps)` registers tools list-style, so a\n * new tool is just one more `defineTool` in the array — index.ts never changes.\n *\n * RENDER CONTRACT (design doc §4.3): the registry feeds `output.render(args,\n * value)` into the loop — the model sees ONLY the rendered text, never the raw\n * JSON `value`. Every render must carry the complete facts an agent needs to\n * act (titles, tags, snippets, git state); a terse UI summary starves it.\n *\n * @module dsh-tiddlywiki/host/tools\n */\nimport { defineTool } from '../sdk.ts'\nimport type { TiddlyWebClient, Tiddler } from './tw-api.ts'\nimport type { GitFace } from './git.ts'\n\n/** Structural tool-registry face (subset of the dsh tools service). */\nexport interface ToolsCtx {\n tools: { register(tool: unknown): () => void }\n}\n\nexport interface ToolsDeps {\n /** Lazy TW client — undefined while the service is not up. */\n wiki: () => TiddlyWebClient | undefined\n git: GitFace\n wikiPath: () => string\n noteTag: () => string\n /** Debounced auto-commit touch (fires after our writes). */\n autoCommit: () => void\n}\n\nfunction snippetOf(text: string, max = 160): string {\n const flat = text.replace(/\\s+/g, ' ').trim()\n return flat.length <= max ? flat : `${flat.slice(0, max)}…`\n}\n\n/** Strip dsh-tiddlywiki internal fields from a tiddler for the model. */\nfunction pickFields(t: Tiddler): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(t)) {\n if (k === 'title' || k === 'text' || k === 'tags') continue\n out[k] = v\n }\n return out\n}\n\nexport function registerTiddlywikiTools(ctx: ToolsCtx, deps: ToolsDeps): Array<() => void> {\n const disposers: Array<() => void> = []\n const register = (tool: unknown): void => { disposers.push(ctx.tools.register(tool)) }\n\n // ── tiddlywiki_search ────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_search',\n description: '检索 TiddlyWiki 持久知识库:按关键词(可选 tag 精确匹配)搜索非系统 tiddler,返回标题、标签与摘要片段。',\n parameters: {\n query: { type: 'string', description: '搜索关键词(大小写不敏感,子串匹配)', required: true },\n tag: { type: 'string', description: '可选:只返回带该 tag 的 tiddler' },\n },\n output: {\n schema: { type: 'json' },\n render: (_args, value: SearchResult) => {\n const lines = [`TiddlyWiki 搜索「${value.query}」${value.tag !== null ? ` (tag=${value.tag})` : ''}:命中 ${value.count} 条。`]\n if (value.results.length === 0) lines.push('没有匹配的 tiddler。')\n for (const r of value.results) {\n const tags = r.tags.length > 0 ? ` [${r.tags.join(', ')}]` : ''\n lines.push(`- ${r.title}${tags}`)\n if (r.snippet.length > 0) lines.push(` ${r.snippet}`)\n }\n if (value.count > value.results.length) lines.push(`(另有 ${value.count - value.results.length} 条未展开,可用 tiddlywiki_get 读取具体标题)`)\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { query: string; tag?: string }): Promise<SearchResult> => {\n const wiki = deps.wiki()\n if (wiki === undefined) throw new Error('TiddlyWiki 服务未运行(tiddlywiki_status 可查)')\n const results = await wiki.search(args.query, args.tag)\n return {\n query: args.query,\n tag: args.tag ?? null,\n count: results.length,\n results: results.map((t) => ({ title: t.title, tags: t.tags ?? [], snippet: snippetOf(t.text ?? '') })),\n }\n },\n }))\n\n // ── tiddlywiki_get ───────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_get',\n description: '读取一个 TiddlyWiki tiddler 的完整内容(标题、全文、标签、自定义字段)。',\n parameters: {\n title: { type: 'string', description: 'tiddler 标题(精确匹配)', required: true },\n },\n output: {\n schema: { type: 'json' },\n render: (_args, value: GetResult) => {\n if (value.notFound) return [{ type: 'text', text: `tiddler「${value.title}」不存在。可用 tiddlywiki_search 检索,或用 tiddlywiki_put 新建。` }]\n const lines = [`tiddler「${value.title}」`]\n if (value.tags.length > 0) lines.push(`标签: ${value.tags.join(', ')}`)\n const fields = Object.entries(value.fields)\n if (fields.length > 0) lines.push(`字段: ${fields.map(([k, v]) => `${k}=${String(v)}`).join(', ')}`)\n lines.push('--- 全文 ---')\n lines.push(value.text.length > 0 ? value.text : '(空)')\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { title: string }): Promise<GetResult> => {\n const wiki = deps.wiki()\n if (wiki === undefined) throw new Error('TiddlyWiki 服务未运行(tiddlywiki_status 可查)')\n const t = await wiki.get(args.title)\n if (t === undefined) return { notFound: true, title: args.title, text: '', tags: [], fields: {} }\n return { notFound: false, title: t.title, text: t.text ?? '', tags: t.tags ?? [], fields: pickFields(t) }\n },\n }))\n\n // ── tiddlywiki_put ───────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_put',\n description: '写入(新建或覆盖)一个 TiddlyWiki tiddler。同名覆盖;tags 为标签数组,fields 为附加自定义字段(json 对象,会写入 tiddler 字段)。写入后触发自动 commit。',\n parameters: {\n title: { type: 'string', description: 'tiddler 标题(精确匹配,覆盖同名)', required: true },\n text: { type: 'string', description: 'tiddler 全文(wiki 文本)', required: true },\n tags: { type: 'array', items: { type: 'string' }, description: '标签数组(可选)' },\n fields: { type: 'json', description: '附加自定义字段,如 {\"type\":\"meeting\",\"date\":\"2026-09-02\"}(可选)' },\n },\n output: {\n schema: { type: 'json' },\n render: (_args, value: PutResult) => {\n const lines = [`已写入 tiddler「${value.title}」`]\n if (value.tags.length > 0) lines.push(`标签: ${value.tags.join(', ')}`)\n if (value.fields !== null) {\n const entries = Object.entries(value.fields)\n if (entries.length > 0) lines.push(`字段: ${entries.map(([k, v]) => `${k}=${String(v)}`).join(', ')}`)\n }\n return [{ type: 'text', text: lines.join('\\n') }]\n },\n },\n execute: async (args: { title: string; text: string; tags?: string[]; fields?: Record<string, unknown> }): Promise<PutResult> => {\n const wiki = deps.wiki()\n if (wiki === undefined) throw new Error('TiddlyWiki 服务未运行(tiddlywiki_status 可查)')\n const tiddler: Tiddler = { title: args.title, text: args.text }\n if (Array.isArray(args.tags) && args.tags.length > 0) tiddler.tags = args.tags\n if (args.fields !== undefined && typeof args.fields === 'object' && args.fields !== null) Object.assign(tiddler, args.fields)\n await wiki.put(tiddler)\n deps.autoCommit()\n return { ok: true, title: args.title, tags: args.tags ?? [], fields: args.fields ?? null }\n },\n }))\n\n // ── tiddlywiki_delete ────────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_delete',\n description: '删除一个 TiddlyWiki tiddler(不存在时是幂等空操作)。删除后触发自动 commit。',\n parameters: {\n title: { type: 'string', description: 'tiddler 标题(精确匹配)', required: true },\n },\n output: {\n schema: { type: 'json' },\n render: (_args, value: DeleteResult) => [{ type: 'text', text: `已删除 tiddler「${value.title}」。` }],\n },\n execute: async (args: { title: string }): Promise<DeleteResult> => {\n const wiki = deps.wiki()\n if (wiki === undefined) throw new Error('TiddlyWiki 服务未运行(tiddlywiki_status 可查)')\n await wiki.delete(args.title)\n deps.autoCommit()\n return { ok: true, title: args.title }\n },\n }))\n\n // ── tiddlywiki_git_sync ──────────────────────────────────────────────────\n register(defineTool({\n name: 'tiddlywiki_git_sync',\n description: '对 TiddlyWiki 知识库的 git 仓库做同步:pull(拉取远端并 rebase 本地,冲突则 abort 并报文件)、push(推送本地提交到远端)、sync(pull → commit 本地改动 → push)。未配置 git.remote 时 push 会失败并提示。',\n parameters: {\n action: { type: 'string', enum: ['pull', 'push', 'sync'], description: '要执行的 git 操作', required: true },\n message: { type: 'string', description: 'commit 信息(可选,仅 sync 的本地 commit 使用)' },\n },\n output: {\n schema: { type: 'json' },\n render: (_args, value: SyncResult) => renderSync(value),\n },\n execute: async (args: { action: 'pull' | 'push' | 'sync'; message?: string }): Promise<SyncResult> => {\n const dir = deps.wikiPath()\n switch (args.action) {\n case 'pull': {\n const r = await deps.git.pull(dir)\n return { action: args.action, ok: r.ok, message: r.message, ...(r.conflictFiles !== undefined ? { conflictFiles: r.conflictFiles } : {}) }\n }\n case 'push': {\n const r = await deps.git.push(dir)\n return { action: args.action, ok: r.ok, message: r.message }\n }\n case 'sync': {\n const pulled = await deps.git.pull(dir)\n if (!pulled.ok) return { action: args.action, ok: false, message: pulled.message, ...(pulled.conflictFiles !== undefined ? { conflictFiles: pulled.conflictFiles } : {}) }\n const committed = await deps.git.commit(dir, args.message ?? `sync ${new Date().toISOString()}`)\n const pushed = await deps.git.push(dir)\n const status = await deps.git.status(dir)\n return {\n action: args.action,\n ok: pushed.ok,\n message: pushed.ok ? '同步完成' : pushed.message,\n pull: 'ok',\n commit: committed.message,\n push: pushed.message,\n status,\n }\n }\n }\n },\n }))\n\n return disposers\n}\n\n// ── tool result shapes + renders ───────────────────────────────────────────\n\ninterface SearchHit { title: string; tags: string[]; snippet: string }\ninterface SearchResult { query: string; tag: string | null; count: number; results: SearchHit[] }\ninterface GetResult { notFound: boolean; title: string; text: string; tags: string[]; fields: Record<string, unknown> }\ninterface PutResult { ok: boolean; title: string; tags: string[]; fields: Record<string, unknown> | null }\ninterface DeleteResult { ok: boolean; title: string }\ninterface SyncResult {\n action: string\n ok: boolean\n message: string\n conflictFiles?: string[]\n pull?: string\n commit?: string\n push?: string\n status?: { branch: string; dirty: boolean; dirtyFiles: string[]; remote: string; lastCommit?: string; ahead?: number; behind?: number }\n}\n\nfunction renderSync(value: SyncResult): Array<{ type: 'text'; text: string }> {\n const lines = [`git ${value.action}: ${value.ok ? '成功' : '失败'}`]\n lines.push(` ${value.message}`)\n if (value.conflictFiles !== undefined && value.conflictFiles.length > 0) {\n lines.push(`冲突文件(rebase 已 abort,勿自动覆盖):`)\n for (const f of value.conflictFiles) lines.push(` - ${f}`)\n lines.push('处理方式:git checkout --ours <file> 保留本地,或人工编辑后 git add + git rebase --continue;也可以直接让用户处理。')\n }\n if (value.commit !== undefined) lines.push(`本地 commit: ${value.commit}`)\n if (value.push !== undefined) lines.push(`远端 push: ${value.push}`)\n if (value.status !== undefined) {\n const s = value.status\n const bits = [`分支 ${s.branch}`]\n if (s.ahead !== undefined) bits.push(`领先 ${s.ahead}`)\n if (s.behind !== undefined) bits.push(`落后 ${s.behind}`)\n if (s.dirty) bits.push(`工作区有 ${s.dirtyFiles.length} 个未提交改动`)\n if (s.lastCommit !== undefined) bits.push(`最近提交 ${s.lastCommit}`)\n lines.push(`状态: ${bits.join(' · ')}`)\n if (s.dirty && s.dirtyFiles.length > 0) lines.push(` 未提交: ${s.dirtyFiles.join(', ')}`)\n }\n return [{ type: 'text', text: lines.join('\\n') }]\n}\n","/**\n * dsh-tiddlywiki — host half.\n *\n * TiddlyWiki 5 as the DSH persistent knowledge base. Wiring:\n * - WikiServer spawns/kills/self-heals the TW 5 child process (loopback, auto\n * port) and scaffolds the wiki folder on first run;\n * - the git face bootstraps the wiki folder as a repository and wires the\n * debounced auto-committer;\n * - `tiddlywiki_*` agent tools + a system-prompt section;\n * - /dsh-tiddlywiki routes when a webServer is present.\n *\n * Export shape follows dsh-taskboard: a function/namespace plugin —\n * `name` / `inject` / `apply`, NO default export. Config arrives as the\n * second apply() argument (Cordis `runtime.callback(ctx, config)`).\n *\n * Extra exports (WikiServer / TiddlyWebClient / GitFace / ...) exist for the\n * headless selftest and future reuse; the loader only reads name/inject/apply.\n *\n * @module dsh-tiddlywiki\n */\nimport { watch, type FSWatcher } from 'node:fs'\nimport { writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { AutoCommitter, GitFace } from './host/git.ts'\nimport { registerRoutes, type WebServerFace } from './host/routes.ts'\nimport { ConfigStore, deepMerge, type PluginConfigShape } from './host/config.ts'\nimport { registerAdminRoutes, ensureLanguage, resolveTwRoot, type AdminDeps } from './host/admin.ts'\nimport { seedDocNote, DOC_NOTE_TITLE } from './host/seed-notes.ts'\nimport { TiddlyWebClient } from './host/tw-api.ts'\nimport { registerTiddlywikiTools, type ToolsDeps } from './host/tools.ts'\nimport { PATH_PREFIX, WikiServer, type WikiServerOptions } from './host/wiki.ts'\nimport { dshHomePath, defineTool } from './sdk.ts'\n\n/** Cordis plugin name (also the client loader id / profile row id). */\nexport const name = 'dsh-tiddlywiki'\n\n/** Required host services (tool registry + prompt assembly). */\nexport const inject = ['tools', 'systemPrompt']\n\n/** Re-exports for the headless selftest and future consumers. */\nexport { AutoCommitter, GitFace, PATH_PREFIX, TiddlyWebClient, WikiServer, dshHomePath, defineTool }\nexport { ConfigStore, deepMerge } from './host/config.ts'\nexport { openInTwEditor } from './host/routes.ts'\nexport { registerAdminRoutes, resolveTwRoot, readWikiInfo, writeWikiInfo, bundledCatalog, ensureLanguage, normalizeThemes } from './host/admin.ts'\nexport { seedDocNote, DOC_NOTE_TITLE, DOC_NOTE_TAG, DOC_NOTE_TEXT } from './host/seed-notes.ts'\nexport type { PluginConfigShape } from './host/config.ts'\nexport type { GitStatusView } from './host/git.ts'\nexport type { Tiddler } from './host/tw-api.ts'\nexport type { WikiServerOptions, WikiStatusView } from './host/wiki.ts'\n\n/** Plugin config (design doc §13). Defaults are applied in apply(). */\nexport interface TiddlywikiConfig {\n wikiRoot?: string\n wiki?: string\n port?: number\n git?: { autoCommit?: boolean; debounceMs?: number; remote?: string; branch?: string }\n note?: { tag?: string }\n auth?: { username?: string; password?: string }\n}\n\n/** Structural host context (subset of the dsh host + cordis surfaces). */\nexport interface HostCtx {\n tools: { register(tool: unknown): () => void }\n systemPrompt: { section(opts: { name: string; order: number; text: string }): () => void }\n inject<T = unknown>(names: string | string[], callback: (ctx: HostCtx) => T, config?: unknown): unknown\n effect(fn: () => unknown, label?: string): void\n get(name: string): unknown\n [key: string]: unknown\n}\n\n/** Resolved plugin config (defaults merged with the `config:` block). */\ninterface ResolvedConfig {\n wikiRoot: string\n wiki: string\n port: number\n git: { autoCommit: boolean; debounceMs: number; remote: string; branch: string }\n note: { tag: string }\n auth: { username?: string; password?: string }\n}\n\nconst DEFAULTS: ResolvedConfig = {\n wikiRoot: '',\n wiki: 'main',\n port: 0,\n git: { autoCommit: true, debounceMs: 60_000, remote: '', branch: 'main' },\n note: { tag: 'inbox' },\n auth: { username: '', password: '' },\n}\n\n/** Expand $VAR / ${VAR} / %VAR% from process.env (config uses $DSH_HOME). */\nfunction expandEnvPath(input: string): string {\n return input\n .replace(/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g, (_, k: string) => process.env[k] ?? '')\n .replace(/\\$([A-Za-z_][A-Za-z0-9_]*)/g, (_, k: string) => process.env[k] ?? '')\n .replace(/%([A-Za-z_][A-Za-z0-9_]*%)/g, (_, k: string) => process.env[k.slice(0, -1)] ?? '')\n}\n\n/** Resolve wikiRoot: explicit config (env-expanded) else $DSH_HOME/tiddlywiki. */\nfunction resolveWikiRoot(config: TiddlywikiConfig): string {\n if (config.wikiRoot !== undefined && config.wikiRoot.trim().length > 0) {\n return expandEnvPath(config.wikiRoot.trim())\n }\n return dshHomePath('tiddlywiki')\n}\n\n/** Write the .gitignore for TW transient artifacts (idempotent). */\nasync function writeGitignore(wikiPath: string): Promise<void> {\n const lines = [\n '# TiddlyWiki transient artifacts (auto-managed by dsh-tiddlywiki)',\n 'tiddlers/$__temp_*',\n 'tiddlers/$__StoryList*',\n 'tiddlers/$__HistoryList*',\n '*.meta.tmp',\n '',\n ]\n await writeFile(join(wikiPath, '.gitignore'), lines.join('\\n'), 'utf8')\n}\n\n/** Watch the wiki folders and touch the auto-committer on changes. */\nfunction watchWiki(wikiPath: string, onChange: () => void): () => void {\n const watchers: FSWatcher[] = []\n for (const dir of [join(wikiPath, 'tiddlers'), wikiPath]) {\n try {\n const watcher = watch(dir, { persistent: false }, () => onChange())\n watchers.push(watcher)\n } catch {\n /* directory may not exist yet; the committer also fires on our writes */\n }\n }\n return () => {\n for (const watcher of watchers) {\n try { watcher.close() } catch { /* already closed */ }\n }\n }\n}\n\n/** System-prompt section text (design doc §11 D8). */\nconst PROMPT_SECTION_NAME = 'dsh-tiddlywiki'\nconst PROMPT_SECTION_ORDER = 100\nconst PROMPT_TEXT = `## TiddlyWiki 持久知识库\n\n本机有一个 TiddlyWiki 5 持久知识库(wiki 文件夹即 git 仓库)。你可以用工具读写 tiddler:\n\n- \\`tiddlywiki_search\\`(query, tag?)检索;\\`tiddlywiki_get\\`(title)读全文;\\`tiddlywiki_put\\`(title, text, tags?, fields?)写/覆盖;\\`tiddlywiki_delete\\`(title)删除。\n- \\`tiddlywiki_git_sync\\`(pull|push|sync)做 git 同步。\n\n知识库同步纪律(三条):\n1. 开工先 pull:\\`tiddlywiki_git_sync action=pull\\`(rebase + autostash;真冲突会自动 abort 并报冲突文件)。\n2. 收工 commit + push:\\`tiddlywiki_git_sync action=sync\\`(pull → commit → push)。\n3. 插件会自动防抖 commit(默认 60s),手动同步用上面的工具。\n\n把 wiki 当作长期记忆与知识沉淀的地方:会议纪要、决策记录、调研笔记、随手的想法都可存成独立 tiddler(tag 建议用 inbox/meeting/decision 等便于检索)。`\n\n/**\n * Mount the host half.\n * @param ctx - the plugin context (tools + systemPrompt injected).\n * @param rawConfig - the plugin row's `config:` block (Cordis second arg).\n */\nexport function apply(ctx: HostCtx, rawConfig: TiddlywikiConfig = {}): void {\n const config: ResolvedConfig = {\n wikiRoot: resolveWikiRoot(rawConfig),\n wiki: rawConfig.wiki ?? DEFAULTS.wiki,\n port: rawConfig.port ?? DEFAULTS.port,\n git: { ...DEFAULTS.git, ...(rawConfig.git ?? {}) },\n note: { ...DEFAULTS.note, ...(rawConfig.note ?? {}) },\n auth: { ...DEFAULTS.auth, ...(rawConfig.auth ?? {}) },\n }\n const wikiPath = join(config.wikiRoot, config.wiki)\n const git = new GitFace()\n\n // Runtime-editable config (settings page): the cordis `config:` block is the\n // BASE; a config tiddler ($:/plugins/dsh-tiddlywiki/config) written by the\n // settings page overlays it. Effective values come from configStore.get().\n const configStore = new ConfigStore({ note: config.note, git: config.git } satisfies PluginConfigShape)\n const eff = (): PluginConfigShape => configStore.get()\n const effectiveNoteTag = (): string => {\n const tag = eff().note?.tag\n return typeof tag === 'string' && tag.trim().length > 0 ? tag : config.note.tag\n }\n\n const disposers: Array<() => void> = []\n const disposeAll = (): void => {\n for (const dispose of disposers.splice(0)) dispose()\n }\n\n // System prompt section (independent of the wiki service).\n const disposeSection = ctx.systemPrompt.section({ name: PROMPT_SECTION_NAME, order: PROMPT_SECTION_ORDER, text: PROMPT_TEXT })\n ctx.effect(() => disposeSection, 'dsh-tiddlywiki: prompt section')\n\n // TW child server.\n const server = new WikiServer({\n wikiRoot: config.wikiRoot,\n wiki: config.wiki,\n port: config.port,\n username: config.auth.username,\n password: config.auth.password,\n })\n\n // Lazy TW client (rebuilt when the port is bound).\n let clientCache: TiddlyWebClient | undefined\n const client = (): TiddlyWebClient | undefined => {\n const port = server.currentPort\n if (port === undefined) return undefined\n clientCache ??= new TiddlyWebClient(`http://127.0.0.1:${port}`)\n return clientCache\n }\n\n // Auto-committer + filesystem watcher (created after the wiki dir exists).\n // Reads the EFFECTIVE config so a settings-page git change survives a restart.\n let committer: AutoCommitter | undefined\n let unwatch: (() => void) | undefined\n const setupCommitter = (): void => {\n const g = eff().git ?? {}\n committer = new AutoCommitter({\n git,\n dir: wikiPath,\n enabled: g.autoCommit ?? config.git.autoCommit,\n debounceMs: g.debounceMs ?? config.git.debounceMs,\n message: () => `wiki autocommit ${new Date().toISOString()}`,\n onError: (err) => console.warn('[dsh-tiddlywiki] autocommit:', err),\n })\n unwatch = watchWiki(wikiPath, () => committer?.touch())\n disposers.push(() => {\n committer?.dispose()\n unwatch?.()\n })\n }\n\n // Git bootstrap: repo init + initial commit + .gitignore (+ remote/first push).\n const bootstrapGit = async (): Promise<void> => {\n const g = eff().git ?? {}\n const branch = g.branch ?? config.git.branch\n const remote = g.remote ?? config.git.remote\n const isRepo = await git.isRepo(wikiPath)\n if (!isRepo) {\n await git.init(wikiPath, branch)\n await writeGitignore(wikiPath)\n await git.initialCommit(wikiPath)\n } else {\n await writeGitignore(wikiPath)\n }\n if (remote.trim().length > 0) {\n const ensured = await git.ensureRemote(wikiPath, remote.trim())\n if (ensured.ok) {\n const first = await git.firstPush(wikiPath)\n if (!first.ok) console.warn('[dsh-tiddlywiki] first push failed (retry with tiddlywiki_git_sync):', first.message)\n } else {\n console.warn('[dsh-tiddlywiki] git remote setup:', ensured.message)\n }\n }\n }\n\n // Tools (works even while the wiki is down; wiki() resolves lazily).\n const toolsDeps: ToolsDeps = {\n wiki: client,\n git,\n wikiPath: () => wikiPath,\n noteTag: effectiveNoteTag,\n autoCommit: () => committer?.touch(),\n }\n disposers.push(...registerTiddlywikiTools(ctx, toolsDeps))\n\n // Bring the wiki up, load the override config, then bootstrap git + committer.\n void (async () => {\n try {\n await server.start()\n await configStore.load(client())\n // Seed the built-in doc note (idempotent, create-if-missing) so a fresh\n // wiki gets the plugin guide by default.\n try {\n const seedClient = client()\n if (seedClient !== undefined) await seedDocNote(seedClient)\n } catch (err) {\n console.warn('[dsh-tiddlywiki] seeding doc note:', err)\n }\n // Apply the configured UI language (e.g. \"zh-Hans\"): enable the bundled\n // language plugin in tiddlywiki.info.languages + restart once so TW loads\n // it at boot (fully offline — official language packs ship in the pkg).\n const uiLang = eff().uiLanguage\n if (typeof uiLang === 'string' && uiLang.trim().length > 0) {\n try {\n const code = uiLang.trim()\n const changed = await ensureLanguage(wikiPath, resolveTwRoot(), code)\n if (changed) await server.restart()\n // Pin the active language tiddler so TW's UI actually switches.\n const langClient = client()\n if (langClient !== undefined) {\n await langClient.put({ title: '$:/language', text: `$:/languages/${code}`, type: 'text/plain', tags: [] }).catch(() => undefined)\n }\n } catch (err) {\n console.warn('[dsh-tiddlywiki] applying uiLanguage:', err)\n }\n }\n await bootstrapGit()\n setupCommitter()\n } catch (err) {\n console.warn('[dsh-tiddlywiki] startup issue (self-healing is armed):', err)\n }\n })()\n\n // Routes + settings-panel admin surface (lazy webServer).\n ctx.inject(['webServer'], (webCtx: HostCtx) => {\n const ws = (webCtx as unknown as { webServer: WebServerFace }).webServer\n const disposeRoutes = registerRoutes({ webServer: ws }, {\n server,\n getClient: client,\n git,\n autoCommit: () => committer?.touch(),\n noteDefaults: () => ({ tag: effectiveNoteTag() }),\n getWikiPath: () => wikiPath,\n })\n const adminDeps: AdminDeps = {\n server,\n getClient: client,\n getWikiPath: () => wikiPath,\n twRoot: resolveTwRoot,\n config: configStore,\n }\n const disposeAdmin = registerAdminRoutes({ webServer: ws }, adminDeps)\n return () => {\n disposeRoutes()\n disposeAdmin()\n }\n })\n\n // Teardown: everything reversible (R6 — hot reload must not leak).\n ctx.effect(() => () => {\n disposeAll()\n void server.stop()\n }, 'dsh-tiddlywiki: host teardown')\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-tiddlywiki",
|
|
3
|
+
"description": "TiddlyWiki 5 as the DSH persistent knowledge base: tiddlywiki_* agent tools (search/get/put/delete + git sync), a full TiddlyWiki editor embedded in the GUI center column, a floating quick-note widget in the chat area, and git-based sync with auto-commit. Mounts via the official dsh plugin system — no DSH source changes.",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./client": "./lib/client.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"dsh": {
|
|
13
|
+
"bundle": {
|
|
14
|
+
"patch": "./cordis.patch.yml"
|
|
15
|
+
},
|
|
16
|
+
"client": {
|
|
17
|
+
"inject": [
|
|
18
|
+
"slots"
|
|
19
|
+
],
|
|
20
|
+
"platform": "web"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"lib/**/*.js",
|
|
25
|
+
"lib/**/*.js.map",
|
|
26
|
+
"src",
|
|
27
|
+
"cordis.patch.yml",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"homepage": "https://github.com/bbqisbbq/dsh-tiddlywiki#readme",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/bbqisbbq/dsh-tiddlywiki.git"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/bbqisbbq/dsh-tiddlywiki/issues"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=22"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"tiddlywiki": "^5.4.1"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^22.20.1",
|
|
48
|
+
"@types/react": "^19.1.0",
|
|
49
|
+
"react": "^19.1.0",
|
|
50
|
+
"tsdown": "0.22.2",
|
|
51
|
+
"tsx": "^4.19.2",
|
|
52
|
+
"typescript": "~5.7.2",
|
|
53
|
+
"unrun": "*"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "node scripts/clean-lib.mjs && npm run build:host && npm run build:client",
|
|
57
|
+
"build:host": "tsdown -c tsdown.host.config.ts",
|
|
58
|
+
"build:client": "tsdown -c tsdown.client.config.ts && node scripts/wrap-client.mjs",
|
|
59
|
+
"watch": "tsdown -c tsdown.host.config.ts --watch",
|
|
60
|
+
"typecheck": "tsc --noEmit",
|
|
61
|
+
"selftest": "node scripts/selftest.mjs"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Floating popup iframe that loads TiddlyWiki's NATIVE editor for a draft
|
|
3
|
+
* (quick-note "✏️ 在 TW 中编辑"). A small draggable + resizable overlay with its
|
|
4
|
+
* own iframe pointed at `twUrl#<draftTitle>`: the fragment-only navigation
|
|
5
|
+
* triggers TW's hashchange, which opens the draft in the story, and because the
|
|
6
|
+
* draft tiddler carries `draft.of` the story renders the native EditTemplate.
|
|
7
|
+
*
|
|
8
|
+
* Independent of the center panel — a separate floating window so the user can
|
|
9
|
+
* edit a note without leaving the chat context.
|
|
10
|
+
*
|
|
11
|
+
* @module dsh-tiddlywiki/client/editor-popup
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
let root: HTMLDivElement | undefined
|
|
15
|
+
let frame: HTMLIFrameElement | undefined
|
|
16
|
+
let titleEl: HTMLSpanElement | undefined
|
|
17
|
+
|
|
18
|
+
/** Open (create on first use) the popup and load `url` (twUrl#draftTitle). */
|
|
19
|
+
export function openEditorPopup(url: string, label: string): void {
|
|
20
|
+
ensurePopup()
|
|
21
|
+
if (root === undefined || frame === undefined) return
|
|
22
|
+
if (titleEl !== undefined) titleEl.textContent = `TiddlyWiki 编辑器 · ${label}`
|
|
23
|
+
root.style.display = ''
|
|
24
|
+
// Same-base fragment navigation reloads nothing; a changed base (restart on a
|
|
25
|
+
// new port) reloads the app and TW still opens the draft from the hash.
|
|
26
|
+
frame.src = url
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Remove the popup DOM entirely (plugin dispose). */
|
|
30
|
+
export function disposeEditorPopup(): void {
|
|
31
|
+
root?.remove()
|
|
32
|
+
root = undefined
|
|
33
|
+
frame = undefined
|
|
34
|
+
titleEl = undefined
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function ensurePopup(): void {
|
|
38
|
+
if (root !== undefined && frame !== undefined) return
|
|
39
|
+
|
|
40
|
+
root = document.createElement('div')
|
|
41
|
+
root.className = 'dsh-tw-editor-popup'
|
|
42
|
+
root.style.display = 'none'
|
|
43
|
+
|
|
44
|
+
const bar = document.createElement('div')
|
|
45
|
+
bar.className = 'dsh-tw-editor-bar'
|
|
46
|
+
titleEl = document.createElement('span')
|
|
47
|
+
titleEl.className = 'dsh-tw-editor-title'
|
|
48
|
+
titleEl.textContent = 'TiddlyWiki 编辑器'
|
|
49
|
+
const close = document.createElement('button')
|
|
50
|
+
close.type = 'button'
|
|
51
|
+
close.className = 'dsh-tw-editor-close'
|
|
52
|
+
close.textContent = '✕'
|
|
53
|
+
close.title = '关闭'
|
|
54
|
+
bar.append(titleEl, close)
|
|
55
|
+
|
|
56
|
+
frame = document.createElement('iframe')
|
|
57
|
+
frame.className = 'dsh-tw-editor-frame'
|
|
58
|
+
frame.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups')
|
|
59
|
+
frame.title = 'TiddlyWiki 编辑器'
|
|
60
|
+
|
|
61
|
+
const resize = document.createElement('div')
|
|
62
|
+
resize.className = 'dsh-tw-editor-resize'
|
|
63
|
+
resize.title = '拖拽调整大小'
|
|
64
|
+
|
|
65
|
+
root.append(bar, frame, resize)
|
|
66
|
+
document.body.append(root)
|
|
67
|
+
|
|
68
|
+
close.addEventListener('click', () => {
|
|
69
|
+
if (root !== undefined) root.style.display = 'none'
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// Drag by the title bar (un-center by setting explicit left/top + margin 0).
|
|
73
|
+
bar.addEventListener('mousedown', (event) => {
|
|
74
|
+
if (event.button !== 0 || root === undefined) return
|
|
75
|
+
event.preventDefault()
|
|
76
|
+
const rect = root.getBoundingClientRect()
|
|
77
|
+
const startX = event.clientX
|
|
78
|
+
const startY = event.clientY
|
|
79
|
+
const baseLeft = rect.left
|
|
80
|
+
const baseTop = rect.top
|
|
81
|
+
const onMove = (ev: MouseEvent): void => {
|
|
82
|
+
if (root === undefined) return
|
|
83
|
+
root.style.left = `${baseLeft + ev.clientX - startX}px`
|
|
84
|
+
root.style.top = `${baseTop + ev.clientY - startY}px`
|
|
85
|
+
root.style.margin = '0'
|
|
86
|
+
root.style.right = 'auto'
|
|
87
|
+
root.style.bottom = 'auto'
|
|
88
|
+
}
|
|
89
|
+
const onUp = (): void => {
|
|
90
|
+
window.removeEventListener('mousemove', onMove)
|
|
91
|
+
window.removeEventListener('mouseup', onUp)
|
|
92
|
+
}
|
|
93
|
+
window.addEventListener('mousemove', onMove)
|
|
94
|
+
window.addEventListener('mouseup', onUp)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// Resize from the bottom-right corner.
|
|
98
|
+
resize.addEventListener('mousedown', (event) => {
|
|
99
|
+
if (event.button !== 0 || root === undefined) return
|
|
100
|
+
event.preventDefault()
|
|
101
|
+
event.stopPropagation()
|
|
102
|
+
const rect = root.getBoundingClientRect()
|
|
103
|
+
const startX = event.clientX
|
|
104
|
+
const startY = event.clientY
|
|
105
|
+
const baseW = rect.width
|
|
106
|
+
const baseH = rect.height
|
|
107
|
+
const onMove = (ev: MouseEvent): void => {
|
|
108
|
+
if (root === undefined) return
|
|
109
|
+
root.style.width = `${Math.max(360, baseW + ev.clientX - startX)}px`
|
|
110
|
+
root.style.height = `${Math.max(260, baseH + ev.clientY - startY)}px`
|
|
111
|
+
root.style.right = 'auto'
|
|
112
|
+
root.style.bottom = 'auto'
|
|
113
|
+
}
|
|
114
|
+
const onUp = (): void => {
|
|
115
|
+
window.removeEventListener('mousemove', onMove)
|
|
116
|
+
window.removeEventListener('mouseup', onUp)
|
|
117
|
+
}
|
|
118
|
+
window.addEventListener('mousemove', onMove)
|
|
119
|
+
window.addEventListener('mouseup', onUp)
|
|
120
|
+
})
|
|
121
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser half entry for dsh-tiddlywiki (design doc §12, D5): injects the
|
|
3
|
+
* stylesheet, mounts the sidebar entry, the center-column TiddlyWiki panel,
|
|
4
|
+
* the floating quick-note widget, and registers the plugin's settings page
|
|
5
|
+
* (config panel, §13) into the shell's Settings.
|
|
6
|
+
*
|
|
7
|
+
* Failure policy: DOM mounting problems are logged, never thrown — the web
|
|
8
|
+
* shell fails the whole boot when a plugin apply throws.
|
|
9
|
+
*
|
|
10
|
+
* Export shape: `name` / `inject` / `apply`, no default.
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-tiddlywiki/client
|
|
13
|
+
*/
|
|
14
|
+
import { injectStyles } from './styles.ts'
|
|
15
|
+
import { PanelState } from './state.ts'
|
|
16
|
+
import { mountSidebarEntry } from './sidebar-entry.ts'
|
|
17
|
+
import { mountPanel } from './panel.ts'
|
|
18
|
+
import { mountNoteWidget } from './note-widget.ts'
|
|
19
|
+
import { disposeEditorPopup } from './editor-popup.ts'
|
|
20
|
+
import { SettingsSection } from './settings-page.ts'
|
|
21
|
+
|
|
22
|
+
/** Client plugin name. */
|
|
23
|
+
export const name = 'dsh-tiddlywiki/client'
|
|
24
|
+
|
|
25
|
+
/** Required client services: the slots registry (settings.section seat). */
|
|
26
|
+
export const inject: string[] = ['slots']
|
|
27
|
+
|
|
28
|
+
/** Effect-hook face the runner provides on the client context. */
|
|
29
|
+
interface ClientContextFace {
|
|
30
|
+
slots?: {
|
|
31
|
+
inject(name: string, register: () => unknown): (() => void) | undefined
|
|
32
|
+
register(
|
|
33
|
+
opts: { name: string; id: string; order?: number; label?: string | (() => string) },
|
|
34
|
+
component: unknown,
|
|
35
|
+
): () => void
|
|
36
|
+
}
|
|
37
|
+
effect?(fn: () => unknown, label?: string): void
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Client entry: installs styles and mounts the DOM seats + settings page.
|
|
42
|
+
* @param ctx - the cordis client context.
|
|
43
|
+
*/
|
|
44
|
+
export function apply(ctx: ClientContextFace): void {
|
|
45
|
+
try {
|
|
46
|
+
injectStyles()
|
|
47
|
+
const state = new PanelState()
|
|
48
|
+
const disposers: Array<() => void> = []
|
|
49
|
+
try {
|
|
50
|
+
disposers.push(mountSidebarEntry(state))
|
|
51
|
+
disposers.push(mountPanel(state))
|
|
52
|
+
disposers.push(mountNoteWidget())
|
|
53
|
+
disposers.push(disposeEditorPopup)
|
|
54
|
+
} catch (error) {
|
|
55
|
+
// DOM failures degrade the plugin, never the GUI.
|
|
56
|
+
console.error('[dsh-tiddlywiki] mount failed:', error)
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
// Settings page → Settings → 「TiddlyWiki 知识库」(config panel §13).
|
|
60
|
+
const removeSettings = ctx.slots?.inject('settings.section', () =>
|
|
61
|
+
ctx.slots?.register(
|
|
62
|
+
{ name: 'settings.section', id: 'dsh-tiddlywiki', order: 50, label: 'TiddlyWiki 知识库' },
|
|
63
|
+
SettingsSection,
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
if (removeSettings !== undefined) disposers.push(removeSettings)
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error('[dsh-tiddlywiki] settings section failed:', error)
|
|
69
|
+
}
|
|
70
|
+
ctx.effect?.(() => () => {
|
|
71
|
+
for (const dispose of disposers.splice(0)) dispose()
|
|
72
|
+
}, 'dsh-tiddlywiki: client mount')
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error('[dsh-tiddlywiki] client half failed to start:', error)
|
|
75
|
+
}
|
|
76
|
+
}
|