diff --git a/.gitignore b/.gitignore index 714fed7..29ef508 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules/ main.js main.js.map versions.json +.scriptorium-vault-path diff --git a/README.md b/README.md index 1a57b13..2bcca33 100644 --- a/README.md +++ b/README.md @@ -3,122 +3,100 @@ An Obsidian plugin for creating, editing, and publishing Nostr document events directly from your vault. **Author**: Silberengel -**Homepage**: https://gitcitadel.com -**Funding**: gitcitadel@getalby.com +**Homepage of our development project**: https://gitcitadel.com +**Happy to receive Bitcoin-tips to our Lightning wallet**: gitcitadel@getalby.com ## Features -- **Multiple Event Kinds**: Support for Markdown-formatted kinds (1, 11, 30023, 30817) and Asdciidoc-formatted kinds (30040, 30041, 30818). -- **Bookstr Support**: Automatic parsing and splitting of e-books/publications into nested 30040/30041 structures -- **Metadata Management**: YAML metadata files with validation per event kind -- **Structure Preview**: Visual preview of publication structure before creating events -- **Two-Step Workflow**: Create and sign events separately from publishing -- **Relay Management**: Automatic fetching of relay lists (kind 10002) with AUTH support -- **d-tag Normalization**: Automatic NIP-54 compliant d-tag generation from titles +- Multiple event kinds: Markdown (1, 11, 30023, 30817) and AsciiDoc (30040, 30041, 30818) formats +- Automatic book/publication parsing into nested 30040/30041 structures +- YAML metadata management with validation +- Structure preview before creating events +- Two-step workflow: create/sign events separately from publishing +- Automatic relay list fetching (kind 10002) with AUTH support +- NIP-54 compliant d-tag normalization ## Installation -### Manual Installation +### Quick Start -1. Clone this repository -2. Run `npm install`.obsidian/plugins/scriptorium-obsidian/ -3. Run `npm run build` -4. Create the plugin directory in your Obsidian vault (if it doesn't exist): - - Navigate to your vault's root directory - - Create `.obsidian/plugins/scriptorium-obsidian/` directory -5. Copy the `main.js` and `manifest.json` files to `.obsidian/plugins/scriptorium-obsidian/` -6. Reload Obsidian and enable the plugin in Settings → Community Plugins +Use the startup script to build, install, and launch Obsidian: -**Note**: The `.obsidian` folder is hidden by default. You may need to show hidden files in your file manager to see it. +```bash +# First run (path required) +./start-obsidian.sh ~/Documents/MyVault -## Setup +# Subsequent runs (path is saved) +./start-obsidian.sh +``` -### Private Key Configuration +The script automatically: +- Installs npm dependencies +- Builds and installs the plugin +- Installs and enables obsidian-asciidoc (required for `.adoc` files) +- Starts Obsidian with console logging -You have three options to set your private key: +**Generate a new Nostr key:** +```bash +./start-obsidian.sh --generate-key +``` +Add the shown export command to your shell profile (`~/.bashrc`, `~/.zshrc`, etc.). -**Option 1: Manual Entry (Easiest)** -1. Open Obsidian settings → Scriptorium Nostr -2. Enter your private key in the password field -3. Click "Set Key" +### Manual Installation + +1. Clone repo → `npm install` → `npm run build` +2. Copy `main.js` and `manifest.json` to `.obsidian/plugins/scriptorium-obsidian/` +3. Enable in Obsidian Settings → Community Plugins +4. Install [obsidian-asciidoc](https://github.com/dzruyk/obsidian-asciidoc) plugin (required for `.adoc` files) -**Option 2: File in Vault (Most Reliable)** -1. Create a file named `.scriptorium_key` in your vault root -2. Put your private key on a single line (nsec1... or 64-char hex) -3. Open plugin settings and click "Refresh" +## Setup -**Option 3: Environment Variable** -1. Set `SCRIPTORIUM_OBSIDIAN_KEY` in your terminal: - ```bash - export SCRIPTORIUM_OBSIDIAN_KEY="nsec1..." - ``` -2. **Important:** Launch Obsidian from the same terminal: - ```bash - obsidian - ``` - (If Obsidian is already running, close it and restart from the terminal) -3. Open plugin settings → Scriptorium Nostr -4. Click "Refresh" to load your private key +### Private Key -**Note:** Environment variables are only available to processes launched from the terminal where they were set. If you launch Obsidian from a desktop shortcut or application menu, it won't have access to the environment variable. You must launch Obsidian from the terminal where you set the variable. +The plugin only loads keys from the `SCRIPTORIUM_OBSIDIAN_KEY` environment variable: + +```bash +# Set in terminal +export SCRIPTORIUM_OBSIDIAN_KEY="nsec1..." -See [ENV_SETUP.md](ENV_SETUP.md) for detailed instructions on setting environment variables. +# Make permanent (add to ~/.bashrc or ~/.zshrc) +echo 'export SCRIPTORIUM_OBSIDIAN_KEY="nsec1..."' >> ~/.bashrc +``` -**Key Format:** `nsec1...` (bech32) or 64-character hex string +**Important:** Launch Obsidian from the terminal where the variable is set, or use the startup script. Desktop shortcuts won't have access to the variable. -### Relay Configuration +### Relays -1. Open Obsidian settings → Scriptorium Nostr +1. Open Settings → Scriptorium Nostr 2. Click "Fetch" to get your relay list from Nostr relays -3. The plugin will automatically fetch from `wss://profiles.nostr1.com`, `wss://relay.damus.io`, and `wss://thecitadel.nostr1.com` +3. Relays are automatically fetched from default relays ## Usage ### Creating Events 1. Open a Markdown or AsciiDoc file -2. Run command: `Create Nostr Events` -3. If metadata doesn't exist, it will be created with defaults -4. For AsciiDoc documents with structure (`= Title`), a preview will be shown -5. Events are created, signed, and saved to `{filename}_events.jsonl` - -### Editing Metadata - -1. Open a file -2. Run command: `Edit Metadata` -3. Fill in the metadata form -4. Save +2. Run: `Create Nostr Events` +3. Events are created, signed, and saved to `{filename}_events.jsonl` -### Publishing Events +### Publishing -1. Ensure events have been created (check for `{filename}_events.jsonl`) -2. Run command: `Publish Events to Relays` -3. Events will be published to all configured write relays +1. Ensure events exist (`{filename}_events.jsonl`) +2. Run: `Publish Events to Relays` +3. Events publish to all configured write relays -### Previewing Structure +### Other Commands -1. Open an AsciiDoc file with structure -2. Run command: `Preview Document Structure` -3. Review the event hierarchy before creating +- `Edit Metadata` - Open metadata form for current file +- `Preview Document Structure` - Show event hierarchy (AsciiDoc only) ## File Formats - **Markdown** (`.md`): Kinds 1, 11, 30023, 30817 -- **AsciiDoc** (`.adoc`, `.asciidoc`): Kinds 30041, 30818 -- **AsciiDoc with Structure** (starts with `= Title`): Kind 30040 with nested 30041 events - -## Metadata Files - -Metadata is stored as `{filename}_metadata.yml` in the same directory as the document. +- **AsciiDoc** (`.adoc`): Kinds 30040, 30041, 30818 +- **Structured AsciiDoc** (starts with `= Title`): Kind 30040 with nested 30041 events -For 30040 events, the title is derived from the document header (`= Title`) but can be overridden in the metadata file. - -## Commands - -- `Create Nostr Events` - Create and sign events from current file -- `Preview Document Structure` - Show event hierarchy preview -- `Publish Events to Relays` - Publish from .jsonl file to relays -- `Edit Metadata` - Open metadata form for current file +Metadata is stored as `{filename}_metadata.yml` in the same directory. ## Development @@ -131,9 +109,3 @@ npm run build # Production build ## License MIT - -## Author - -**Silberengel** -- Homepage: https://gitcitadel.com -- Funding: gitcitadel@getalby.com \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index cd0c2d0..5226b94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,6 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "@codemirror/lang-yaml": "^6.1.2", - "codemirror-asciidoc": "^2.0.1", "js-yaml": "^4.1.0", "nostr-tools": "^2.20.0" }, @@ -26,68 +24,6 @@ "typescript": "5.3.3" } }, - "node_modules/@codemirror/autocomplete": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz", - "integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==", - "license": "MIT", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.17.0", - "@lezer/common": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-yaml": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz", - "integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==", - "license": "MIT", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.2.0", - "@lezer/lr": "^1.0.0", - "@lezer/yaml": "^1.0.0" - } - }, - "node_modules/@codemirror/language": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.1.tgz", - "integrity": "sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==", - "license": "MIT", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.23.0", - "@lezer/common": "^1.5.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0", - "style-mod": "^4.0.0" - } - }, - "node_modules/@codemirror/state": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.4.tgz", - "integrity": "sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==", - "license": "MIT", - "dependencies": { - "@marijn/find-cluster-break": "^1.0.0" - } - }, - "node_modules/@codemirror/view": { - "version": "6.39.11", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.11.tgz", - "integrity": "sha512-bWdeR8gWM87l4DB/kYSF9A+dVackzDb/V56Tq7QVrQ7rn86W0rgZFtlL3g3pem6AeGcb9NQNoy3ao4WpW4h5tQ==", - "license": "MIT", - "dependencies": { - "@codemirror/state": "^6.5.0", - "crelt": "^1.0.6", - "style-mod": "^4.1.0", - "w3c-keyname": "^2.2.4" - } - }, "node_modules/@esbuild/android-arm": { "version": "0.17.3", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.3.tgz", @@ -462,47 +398,6 @@ "node": ">=12" } }, - "node_modules/@lezer/common": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.0.tgz", - "integrity": "sha512-PNGcolp9hr4PJdXR4ix7XtixDrClScvtSCYW3rQG106oVMOOI+jFb+0+J3mbeL/53g1Zd6s0kJzaw6Ri68GmAA==", - "license": "MIT" - }, - "node_modules/@lezer/highlight": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", - "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", - "license": "MIT", - "dependencies": { - "@lezer/common": "^1.3.0" - } - }, - "node_modules/@lezer/lr": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.8.tgz", - "integrity": "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==", - "license": "MIT", - "dependencies": { - "@lezer/common": "^1.0.0" - } - }, - "node_modules/@lezer/yaml": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.3.tgz", - "integrity": "sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==", - "license": "MIT", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.4.0" - } - }, - "node_modules/@marijn/find-cluster-break": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", - "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", - "license": "MIT" - }, "node_modules/@noble/ciphers": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz", @@ -922,18 +817,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/codemirror-asciidoc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/codemirror-asciidoc/-/codemirror-asciidoc-2.0.1.tgz", - "integrity": "sha512-h6Xhj+ZsWh/DTNE3xMfRv9edufchsVVwPED7wSGMeEdoYk/UtCZmwRGH0ZZQkr43aNVF3tWGLZJGT+cAeYgUIg==", - "license": "BSD" - }, - "node_modules/crelt": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", - "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", - "license": "MIT" - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1428,12 +1311,6 @@ "node": ">=8" } }, - "node_modules/style-mod": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", - "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", - "license": "MIT" - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1497,12 +1374,6 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" - }, - "node_modules/w3c-keyname": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", - "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", - "license": "MIT" } } } diff --git a/package.json b/package.json index 36b28a7..fbc6e8a 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,6 @@ "typescript": "5.3.3" }, "dependencies": { - "@codemirror/lang-yaml": "^6.1.2", - "codemirror-asciidoc": "^2.0.1", "js-yaml": "^4.1.0", "nostr-tools": "^2.20.0" } diff --git a/src/main.ts b/src/main.ts index 0abe533..a06ccb9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,39 +12,26 @@ import { getWriteRelays } from "./relayManager"; import { parseAsciiDocStructure, isAsciiDocDocument } from "./asciidocParser"; import { normalizeSecretKey, getPubkeyFromPrivkey } from "./nostr/eventBuilder"; import { safeConsoleError, safeConsoleLog, verifyEventSecurity } from "./utils/security"; -// CodeMirror language packages for syntax highlighting -// These will be bundled with the plugin -import { yaml } from "@codemirror/lang-yaml"; -import { asciidoc } from "codemirror-asciidoc"; -import { StreamLanguage } from "@codemirror/language"; -import { Extension } from "@codemirror/state"; -import { EditorView } from "@codemirror/view"; export default class ScriptoriumPlugin extends Plugin { settings!: ScriptoriumSettings; async onload() { - await this.loadSettings(); - await this.loadPrivateKey(); - - // Register AsciiDoc file extensions so Obsidian can open them - // This tells Obsidian to treat .adoc and .asciidoc files as editable text files - // Using "text" view type so they open as plain text editors in Obsidian - this.registerExtensions(["adoc", "asciidoc"], "text"); - - // Register YAML file extensions so Obsidian can open metadata files - // This tells Obsidian to treat .yml and .yaml files as editable text files - this.registerExtensions(["yml", "yaml"], "text"); + // Log to terminal console that started Obsidian + console.error("[Scriptorium] Plugin loading..."); + process.stderr.write("[Scriptorium] Plugin loading...\n"); - // Register editor extensions for syntax highlighting - // Apply YAML and AsciiDoc language modes - // Note: Markdown files (.md) use Obsidian's default "markdown" view type - // which has built-in syntax highlighting, so they won't be affected by these extensions - // These extensions only apply to files registered with "text" view type (.yml, .yaml, .adoc, .asciidoc) - this.registerEditorExtension([ - yaml(), - StreamLanguage.define(asciidoc) - ]); + try { + await this.loadSettings(); + await this.loadPrivateKey(); + + // Note: We don't register file extensions for .yml, .yaml, .adoc, or .asciidoc files + // Users should install the obsidian-asciidoc plugin for .adoc file support + // YAML files can be edited with external editors or Obsidian may handle them natively + console.error("[Scriptorium] Plugin loaded - file extensions not registered"); + process.stderr.write("[Scriptorium] Plugin loaded - file extensions not registered\n"); + console.error("[Scriptorium] Install obsidian-asciidoc plugin for .adoc file editing support"); + process.stderr.write("[Scriptorium] Install obsidian-asciidoc plugin for .adoc file editing support\n"); // Add settings tab this.addSettingTab(new ScriptoriumSettingTab(this.app, this)); @@ -85,8 +72,22 @@ export default class ScriptoriumPlugin extends Plugin { this.handleNewDocument(); }); - // Status bar - this.addStatusBarItem().setText("Scriptorium"); + // Status bar + this.addStatusBarItem().setText("Scriptorium"); + + console.error("[Scriptorium] Plugin loaded successfully"); + process.stderr.write("[Scriptorium] Plugin loaded successfully\n"); + } catch (error: any) { + const errorMsg = error?.message || String(error); + const stackTrace = error?.stack || ""; + console.error(`[Scriptorium] Error loading plugin: ${errorMsg}`); + process.stderr.write(`[Scriptorium] Error loading plugin: ${errorMsg}\n`); + if (stackTrace) { + console.error(`[Scriptorium] Stack trace:`, stackTrace); + process.stderr.write(`[Scriptorium] Stack trace: ${stackTrace}\n`); + } + safeConsoleError("Error loading plugin:", error); + } } onunload() {} @@ -100,9 +101,7 @@ export default class ScriptoriumPlugin extends Plugin { } async loadPrivateKey(): Promise { - // Try multiple methods to load the private key - - // Method 1: Try environment variable (may not work in Obsidian's sandbox) + // Load private key from environment variable only try { // @ts-ignore - process.env may not be typed in Obsidian context if (typeof process !== "undefined" && process.env?.SCRIPTORIUM_OBSIDIAN_KEY) { @@ -117,38 +116,6 @@ export default class ScriptoriumPlugin extends Plugin { // Environment variable access not available } - // Method 2: Try reading from a file in the vault (.scriptorium_key) - try { - const keyFile = this.app.vault.getAbstractFileByPath(".scriptorium_key"); - if (keyFile && keyFile instanceof TFile) { - const keyContent = await this.app.vault.read(keyFile); - const key = keyContent.trim(); - if (key && (key.startsWith("nsec1") || /^[0-9a-f]{64}$/i.test(key))) { - this.settings.privateKey = key; - await this.saveSettings(); - return true; - } - } - } catch (error) { - // File doesn't exist or can't be read - } - - // Method 3: Try reading from .obsidian/scriptorium_key (hidden file) - try { - const hiddenKeyFile = this.app.vault.getAbstractFileByPath(".obsidian/scriptorium_key"); - if (hiddenKeyFile && hiddenKeyFile instanceof TFile) { - const keyContent = await this.app.vault.read(hiddenKeyFile); - const key = keyContent.trim(); - if (key && (key.startsWith("nsec1") || /^[0-9a-f]{64}$/i.test(key))) { - this.settings.privateKey = key; - await this.saveSettings(); - return true; - } - } - } catch (error) { - // File doesn't exist or can't be read - } - return false; } @@ -406,6 +373,9 @@ export default class ScriptoriumPlugin extends Plugin { private async handleNewDocument() { new NewDocumentModal(this.app, async (kind: EventKind, title: string) => { try { + console.error(`[Scriptorium] Creating new document: kind=${kind}, title=${title}`); + process.stderr.write(`[Scriptorium] Creating new document: kind=${kind}, title=${title}\n`); + // Ensure folder structure exists const folderPath = await this.ensureNostrNotesFolder(kind); @@ -453,17 +423,30 @@ export default class ScriptoriumPlugin extends Plugin { let file: TFile; try { + console.error(`[Scriptorium] Creating file: ${filePath}`); + process.stderr.write(`[Scriptorium] Creating file: ${filePath}\n`); file = await this.app.vault.create(filePath, content); + console.error(`[Scriptorium] File created successfully: ${file.path}`); + process.stderr.write(`[Scriptorium] File created successfully: ${file.path}\n`); // Verify file was actually created const verifyFile = this.app.vault.getAbstractFileByPath(filePath); if (!verifyFile || !(verifyFile instanceof TFile)) { - new Notice(`Error: File ${filename} was not created properly`); + const msg = `Error: File ${filename} was not created properly`; + console.error(`[Scriptorium] ${msg}`); + process.stderr.write(`[Scriptorium] ${msg}\n`); + new Notice(msg); safeConsoleError(`File creation verification failed for ${filePath}`); return; } } catch (error: any) { const safeMessage = error?.message ? String(error.message).replace(/nsec1[a-z0-9]{58,}/gi, "[REDACTED]").replace(/[0-9a-f]{64}/gi, "[REDACTED]") : "Unknown error"; + console.error(`[Scriptorium] Error creating file: ${safeMessage}`); + process.stderr.write(`[Scriptorium] Error creating file: ${safeMessage}\n`); + if (error?.stack) { + console.error(`[Scriptorium] Stack trace:`, error.stack); + process.stderr.write(`[Scriptorium] Stack trace: ${error.stack}\n`); + } new Notice(`Error creating file: ${safeMessage}`); safeConsoleError("Error creating file:", error); safeConsoleError("File path was:", filePath); @@ -485,19 +468,52 @@ export default class ScriptoriumPlugin extends Plugin { // Continue anyway - file was created } - // Open the new file in Obsidian workspace (use active leaf or create new) - try { - const leaf = this.app.workspace.getMostRecentLeaf(); - if (leaf) { - await leaf.openFile(file); - } else { - // Fallback: open in new leaf - const newLeaf = this.app.workspace.getLeaf("tab"); - await newLeaf.openFile(file); + // For .adoc files, ensure file is visible in explorer but don't auto-open + if (file.extension === "adoc" || file.extension === "asciidoc") { + console.error(`[Scriptorium] AsciiDoc file created: ${file.path}`); + process.stderr.write(`[Scriptorium] AsciiDoc file created: ${file.path}\n`); + + // File should be visible in Obsidian's file explorer automatically + // since we used vault.create(). The file explorer will refresh automatically. + // We don't auto-open to prevent crashes (obsidian-asciidoc plugin handles opening) + + new Notice(`Created ${filename} in ${folderPath}. Install obsidian-asciidoc plugin to edit in Obsidian.`); + } else { + // Open the new file in Obsidian workspace (use active leaf or create new) + // Use a small delay to ensure file is fully created before opening + console.error(`[Scriptorium] Waiting before opening file...`); + process.stderr.write(`[Scriptorium] Waiting before opening file...\n`); + await new Promise(resolve => setTimeout(resolve, 200)); + + try { + console.error(`[Scriptorium] Attempting to open file: ${file.path} (extension: ${file.extension})`); + process.stderr.write(`[Scriptorium] Attempting to open file: ${file.path} (extension: ${file.extension})\n`); + + const leaf = this.app.workspace.getMostRecentLeaf(); + if (leaf && leaf.view) { + await leaf.openFile(file, { active: true }); + console.error(`[Scriptorium] File opened successfully in existing leaf`); + process.stderr.write(`[Scriptorium] File opened successfully in existing leaf\n`); + } else { + // Fallback: open in new leaf + const newLeaf = this.app.workspace.getLeaf("tab"); + await newLeaf.openFile(file, { active: true }); + console.error(`[Scriptorium] File opened successfully in new leaf`); + process.stderr.write(`[Scriptorium] File opened successfully in new leaf\n`); + } + } catch (error: any) { + const errorMsg = error?.message || String(error); + const stackTrace = error?.stack || ""; + console.error(`[Scriptorium] Error opening file: ${errorMsg}`); + process.stderr.write(`[Scriptorium] Error opening file: ${errorMsg}\n`); + if (stackTrace) { + console.error(`[Scriptorium] Stack trace:`, stackTrace); + process.stderr.write(`[Scriptorium] Stack trace: ${stackTrace}\n`); + } + safeConsoleError("Error opening file:", error); + // File was created, just couldn't open it - show a notice + new Notice(`File created but couldn't open: ${file.name}`); } - } catch (error: any) { - safeConsoleError("Error opening file:", error); - // File was created, just couldn't open it } new Notice(`Created ${filename} in ${folderPath}`); diff --git a/src/ui/settingsTab.ts b/src/ui/settingsTab.ts index e3ba642..976ea74 100644 --- a/src/ui/settingsTab.ts +++ b/src/ui/settingsTab.ts @@ -71,40 +71,14 @@ export class ScriptoriumSettingTab extends PluginSettingTab { .setCta() .onClick(async () => { const loaded = await this.plugin.loadPrivateKey(); - if (!loaded && !this.plugin.settings.privateKey) { - new Notice("Could not load private key. Please enter it manually below."); + if (loaded) { + new Notice("Private key loaded from environment variable"); + } else { + new Notice("Could not load private key from SCRIPTORIUM_OBSIDIAN_KEY environment variable"); } await this.display(); }); }); - - // Allow manual update of private key - new Setting(containerEl) - .setName("Update Private Key") - .setDesc("Manually enter or update your private key (nsec1... or 64-char hex). Leave empty to keep current.") - .addText((text) => { - text.setPlaceholder("nsec1... or hex key") - .setValue("") - .inputEl.type = "password"; - }) - .addButton((button) => { - button.setButtonText("Update") - .onClick(async () => { - const input = containerEl.querySelector("input[type='password']") as HTMLInputElement; - if (input && input.value.trim()) { - const key = input.value.trim(); - if (key.startsWith("nsec1") || /^[0-9a-f]{64}$/i.test(key)) { - this.plugin.settings.privateKey = key; - await this.plugin.saveSettings(); - input.value = ""; - new Notice("Private key updated successfully"); - await this.display(); - } else { - new Notice("Invalid key format. Expected nsec1... or 64-char hex string."); - } - } - }); - }); } catch (error: any) { new Setting(containerEl) .setName("Private Key Status") @@ -121,39 +95,17 @@ export class ScriptoriumSettingTab extends PluginSettingTab { } else { new Setting(containerEl) .setName("Private Key") - .setDesc("Enter your private key manually, or set SCRIPTORIUM_OBSIDIAN_KEY environment variable, or create .scriptorium_key file in vault root.") - .addText((text) => { - text.setPlaceholder("nsec1... or 64-char hex key") - .inputEl.type = "password"; - }) - .addButton((button) => { - button.setButtonText("Set Key") - .setCta() - .onClick(async () => { - const input = containerEl.querySelector("input[type='password']") as HTMLInputElement; - if (input && input.value.trim()) { - const key = input.value.trim(); - if (key.startsWith("nsec1") || /^[0-9a-f]{64}$/i.test(key)) { - this.plugin.settings.privateKey = key; - await this.plugin.saveSettings(); - input.value = ""; - new Notice("Private key saved successfully"); - await this.display(); - } else { - new Notice("Invalid key format. Expected nsec1... or 64-char hex string."); - } - } - }); - }) + .setDesc("Set SCRIPTORIUM_OBSIDIAN_KEY environment variable to load your private key. Use the startup script with --generate-key to create a new key.") .addButton((button) => { button.setButtonText("Refresh") + .setCta() .onClick(async () => { const loaded = await this.plugin.loadPrivateKey(); if (loaded) { - new Notice("Private key loaded successfully"); + new Notice("Private key loaded from environment variable"); await this.display(); } else { - new Notice("Could not load private key. Please enter it manually above."); + new Notice("Could not load private key. Set SCRIPTORIUM_OBSIDIAN_KEY environment variable and restart Obsidian."); } }); }); diff --git a/start-obsidian.sh b/start-obsidian.sh new file mode 100755 index 0000000..a515dcf --- /dev/null +++ b/start-obsidian.sh @@ -0,0 +1,556 @@ +#!/bin/bash + +# Script to build, install, and start Obsidian with the Scriptorium plugin +# Usage: ./start-obsidian.sh [path-to-obsidian-vault] +# On first run, path is required. Subsequent runs will use the saved path. + +set -e # Exit on error + +# Function to show help +show_help() { + cat << EOF +Scriptorium Obsidian Plugin - Startup Script + +USAGE: + $0 [OPTIONS] [path-to-obsidian-vault] + +OPTIONS: + -h, --help Show this help message and exit + --generate-key Generate a new Nostr private key + The key will be shown for you to save as an environment variable + (most secure option - hides key from GUI) + +ARGUMENTS: + path-to-obsidian-vault + Path to your Obsidian vault (the folder containing .obsidian) + Required on first run. Optional on subsequent runs. + Note: The .obsidian folder may be hidden in your file manager. + +EXAMPLES: + # First run (path required): + $0 ~/Documents/MyVault + + # Subsequent runs (uses saved path): + $0 + + # Change to a different vault: + $0 ~/Documents/NewVault + + # Generate a new Nostr private key: + $0 --generate-key [vault-path] + + # Show help: + $0 --help + +DESCRIPTION: + This script will: + 1. Install npm dependencies (if needed) + 2. Build the Scriptorium plugin + 3. Install it to your Obsidian vault's plugin folder + 4. Install and enable the obsidian-asciidoc plugin (required for .adoc files) + 5. Start Obsidian with console logging enabled + + Note: The obsidian-asciidoc plugin is required for this plugin to work properly + with .adoc files. It will be installed and enabled automatically. + + On first run, you must provide the vault path. The path will be saved + to .scriptorium-vault-path for future use. + + If the saved path becomes invalid (vault moved/deleted), you'll be + prompted to provide a new path. + + The obsidian-asciidoc plugin is recommended for editing .adoc files + in Obsidian without crashes. It provides proper syntax highlighting + and editing support. + +NOTES: + - The vault path is the folder containing the .obsidian directory + - The .obsidian folder may be hidden in your file manager (enable "Show hidden files") + - Plugin logs will appear in this terminal + - Press Ctrl+C to stop Obsidian +EOF + exit 0 +} + +# Check for help flag +if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then + show_help +fi + +# Check for generate-key flag +GENERATE_KEY=false +if [ "$1" = "--generate-key" ]; then + GENERATE_KEY=true + # Remove the flag from arguments + shift +fi + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +VAULT_CONFIG_FILE="${SCRIPT_DIR}/.scriptorium-vault-path" + +# Function to check if a vault path is valid +is_valid_vault_path() { + local path="$1" + # Empty path is invalid + if [ -z "$path" ]; then + return 1 + fi + # Check if path exists as a directory, or if parent directory exists (so we can create it) + if [ -d "$path" ]; then + return 0 + elif [ -d "$(dirname "$path" 2>/dev/null)" ]; then + # Parent exists, so we can create the vault directory + return 0 + fi + return 1 +} + +# Function to load saved vault path +load_vault_path() { + if [ -f "$VAULT_CONFIG_FILE" ]; then + local saved_path=$(cat "$VAULT_CONFIG_FILE" | head -n 1 | tr -d '\n\r') + if [ -n "$saved_path" ] && is_valid_vault_path "$saved_path"; then + echo "$saved_path" + return 0 + fi + fi + return 1 +} + +# Function to save vault path +save_vault_path() { + local path="$1" + echo "$path" > "$VAULT_CONFIG_FILE" + echo "[Scriptorium] Saved vault path to $VAULT_CONFIG_FILE" +} + +# Function to generate a new Nostr private key +generate_nostr_key() { + echo "[Scriptorium] Generating new Nostr private key..." + + # Check if Node.js is available + if ! command -v node &> /dev/null; then + echo "Error: Node.js is required to generate a Nostr key" + echo "Please install Node.js and try again" + exit 1 + fi + + # Generate key using Node.js and nostr-tools + # We'll use a temporary script to generate the key + local temp_script=$(mktemp) + cat > "$temp_script" << 'NODE_SCRIPT' +const { generatePrivateKey, getPublicKey } = require('nostr-tools'); +const { nip19 } = require('nostr-tools'); + +try { + // Generate a new private key (32 bytes, hex encoded) + const privkey = generatePrivateKey(); + + // Get the public key to verify + const pubkey = getPublicKey(privkey); + + // Encode as nsec1 (bech32) + const nsec = nip19.nsecEncode(privkey); + const npub = nip19.npubEncode(pubkey); + + // Output both formats + console.log(JSON.stringify({ + nsec: nsec, + hex: privkey, + npub: npub, + pubkey: pubkey + })); +} catch (error) { + console.error(JSON.stringify({ error: error.message })); + process.exit(1); +} +NODE_SCRIPT + + # Run the script + local key_data=$(node "$temp_script" 2>/dev/null) + rm -f "$temp_script" + + if [ -z "$key_data" ] || echo "$key_data" | grep -q '"error"'; then + echo "Error: Failed to generate Nostr key" + echo "Make sure nostr-tools is installed: npm install" + exit 1 + fi + + # Parse the JSON output + local nsec=$(echo "$key_data" | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8')); console.log(d.nsec)") + local npub=$(echo "$key_data" | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8')); console.log(d.npub)") + + echo "" + echo "Generated new Nostr key:" + echo " Public key (npub): $npub" + echo "" + echo "To use this key, set it as an environment variable:" + echo "" + echo " export SCRIPTORIUM_OBSIDIAN_KEY=\"$nsec\"" + echo "" + echo "Add this line to your shell profile (~/.bashrc, ~/.zshrc, etc.) to make it permanent:" + echo " export SCRIPTORIUM_OBSIDIAN_KEY=\"$nsec\"" + echo "" + echo "Then launch Obsidian from that terminal (or restart your shell) to use the key, by typing 'obsidian' or './start-obsidian.sh'." + echo "" + echo "Note: The private key is hidden from the GUI for security. Only the public key (npub) is shown." + echo "To view the private key, you can use the following command:" + echo "" + echo " echo $nsec" + echo "" + echo "This will show the private key in clear text." + echo "Remember: The private key is only visible in the terminal and will not be available, unless you save it as an environment variable. Do not share it with anyone!" +} + +# If generating key, handle it separately (no vault path needed) +if [ "$GENERATE_KEY" = true ]; then + cd "$SCRIPT_DIR" + generate_nostr_key + exit 0 +fi + +# Determine vault path +VAULT_PATH="" + +# If path is provided as argument, use it +if [ -n "$1" ]; then + VAULT_PATH="$1" + # Expand ~ and resolve relative paths + VAULT_PATH=$(eval echo "$VAULT_PATH") + # Try to resolve to absolute path + if [ -d "$VAULT_PATH" ]; then + VAULT_PATH=$(cd "$VAULT_PATH" && pwd) + elif [ -d "$(dirname "$VAULT_PATH")" ]; then + # Parent exists, resolve parent and append basename + VAULT_PATH=$(cd "$(dirname "$VAULT_PATH")" && pwd)/$(basename "$VAULT_PATH") + else + # Can't resolve, use as-is + VAULT_PATH="$VAULT_PATH" + fi + + # Validate provided path + if ! is_valid_vault_path "$VAULT_PATH"; then + echo "Error: The provided path does not appear to be valid: $VAULT_PATH" + echo "Please provide a valid path to your Obsidian vault" + echo "Note: The .obsidian folder may be hidden in your file manager." + exit 1 + fi + + # Save the path for next time + save_vault_path "$VAULT_PATH" + echo "[Scriptorium] Using vault path: $VAULT_PATH" +else + # Try to load saved path + # Use || true to prevent set -e from exiting if load_vault_path fails + SAVED_PATH=$(load_vault_path || true) + if [ -n "$SAVED_PATH" ]; then + # Re-validate the saved path + if is_valid_vault_path "$SAVED_PATH"; then + VAULT_PATH="$SAVED_PATH" + echo "[Scriptorium] Using saved vault path: $VAULT_PATH" + else + echo "Error: Saved vault path is no longer valid: $SAVED_PATH" + echo "" + echo "The vault may have been moved or deleted. Please provide a new vault path:" + echo "" + echo "Usage: $0 " + echo "Example: $0 ~/Documents/MyVault" + echo "" + echo "Note: The .obsidian folder may be hidden in your file manager." + echo "For more information, run: $0 --help" + exit 1 + fi + else + echo "Error: No vault path provided and no saved path found" + echo "" + echo "This appears to be your first run. Please provide the path to your Obsidian vault:" + echo "" + echo "Usage: $0 " + echo "Example: $0 ~/Documents/MyVault" + echo "" + echo "The vault path is the folder containing the .obsidian directory." + echo "Note: The .obsidian folder may be hidden in your file manager (enable 'Show hidden files')." + echo "After the first run, the path will be saved and you won't need to provide it again." + echo "" + echo "For more information, run: $0 --help" + exit 1 + fi +fi + +# Final validation +if [ -z "$VAULT_PATH" ]; then + echo "Error: Could not determine vault path" + exit 1 +fi +OBSIDIAN_FOLDER="${VAULT_PATH}/.obsidian" +PLUGIN_FOLDER="${OBSIDIAN_FOLDER}/plugins/scriptorium-obsidian" + +# Change to script directory +cd "$SCRIPT_DIR" + +echo "[Scriptorium] Starting build and install process..." +echo "[Scriptorium] Vault path: $VAULT_PATH" +echo "[Scriptorium] Plugin folder: $PLUGIN_FOLDER" + +# Check if npm dependencies need to be installed +if [ ! -d "node_modules" ] || [ "package.json" -nt "node_modules" ]; then + echo "[Scriptorium] Installing npm dependencies..." + npm install + if [ $? -ne 0 ]; then + echo "Error: npm install failed" + exit 1 + fi + echo "[Scriptorium] Dependencies installed successfully" +else + echo "[Scriptorium] Dependencies are up to date" +fi + +# Create .obsidian folder if it doesn't exist +if [ ! -d "$OBSIDIAN_FOLDER" ]; then + echo "[Scriptorium] Creating .obsidian folder..." + mkdir -p "$OBSIDIAN_FOLDER" +fi + +# Create plugins folder if it doesn't exist +if [ ! -d "${OBSIDIAN_FOLDER}/plugins" ]; then + echo "[Scriptorium] Creating plugins folder..." + mkdir -p "${OBSIDIAN_FOLDER}/plugins" +fi + +# Create plugin directory if it doesn't exist +if [ ! -d "$PLUGIN_FOLDER" ]; then + echo "[Scriptorium] Creating plugin directory..." + mkdir -p "$PLUGIN_FOLDER" +fi + +# Build the plugin +echo "[Scriptorium] Building plugin..." +npm run build + +# Check if build was successful +if [ ! -f "main.js" ] || [ ! -f "manifest.json" ]; then + echo "Error: Build failed - main.js or manifest.json not found" + exit 1 +fi + +# Function to check if file needs to be copied +needs_copy() { + local source_file="$1" + local dest_file="$2" + + # If destination doesn't exist, we need to copy + if [ ! -f "$dest_file" ]; then + return 0 # true - needs copy + fi + + # If source is newer than destination, we need to copy + if [ "$source_file" -nt "$dest_file" ]; then + return 0 # true - needs copy + fi + + return 1 # false - no copy needed +} + +# Copy main.js if needed +if needs_copy "main.js" "${PLUGIN_FOLDER}/main.js"; then + echo "[Scriptorium] Copying main.js to plugin folder..." + cp "main.js" "${PLUGIN_FOLDER}/main.js" +else + echo "[Scriptorium] main.js is up to date, skipping copy" +fi + +# Copy manifest.json if needed +if needs_copy "manifest.json" "${PLUGIN_FOLDER}/manifest.json"; then + echo "[Scriptorium] Copying manifest.json to plugin folder..." + cp "manifest.json" "${PLUGIN_FOLDER}/manifest.json" +else + echo "[Scriptorium] manifest.json is up to date, skipping copy" +fi + +echo "[Scriptorium] Plugin installed successfully!" + +# Function to enable a plugin in Obsidian +enable_plugin() { + local plugin_id="$1" + local plugins_json="${OBSIDIAN_FOLDER}/community-plugins.json" + + # Create plugins JSON file if it doesn't exist + if [ ! -f "$plugins_json" ]; then + echo "[]" > "$plugins_json" + fi + + # Check if plugin is already enabled + if grep -q "\"$plugin_id\"" "$plugins_json" 2>/dev/null; then + echo "[Scriptorium] Plugin $plugin_id is already enabled" + return 0 + fi + + # Add plugin to enabled list using Node.js or Python + if command -v node &> /dev/null; then + # Use Node.js to update JSON + node << NODE_SCRIPT +const fs = require('fs'); +const pluginsJson = '${plugins_json}'; +let plugins = []; +try { + const content = fs.readFileSync(pluginsJson, 'utf8'); + plugins = JSON.parse(content); +} catch (e) { + plugins = []; +} +if (!Array.isArray(plugins)) { + plugins = []; +} +if (!plugins.includes('${plugin_id}')) { + plugins.push('${plugin_id}'); + fs.writeFileSync(pluginsJson, JSON.stringify(plugins, null, 2)); + console.log('Enabled plugin: ${plugin_id}'); +} +NODE_SCRIPT + if [ $? -eq 0 ]; then + echo "[Scriptorium] Enabled plugin: $plugin_id" + else + echo "[Scriptorium] Warning: Could not enable plugin $plugin_id automatically" + fi + elif command -v python3 &> /dev/null; then + # Fallback to Python + python3 << PYTHON_SCRIPT +import json +import os +plugins_json = '${plugins_json}' +plugin_id = '${plugin_id}' +try: + with open(plugins_json, 'r') as f: + plugins = json.load(f) +except: + plugins = [] +if not isinstance(plugins, list): + plugins = [] +if plugin_id not in plugins: + plugins.append(plugin_id) + with open(plugins_json, 'w') as f: + json.dump(plugins, f, indent=2) + print(f'Enabled plugin: {plugin_id}') +PYTHON_SCRIPT + if [ $? -eq 0 ]; then + echo "[Scriptorium] Enabled plugin: $plugin_id" + else + echo "[Scriptorium] Warning: Could not enable plugin $plugin_id automatically" + fi + else + echo "[Scriptorium] Warning: Node.js or Python3 required to auto-enable plugins" + echo "[Scriptorium] Please enable $plugin_id manually in Obsidian settings" + fi +} + +# Enable scriptorium-obsidian plugin +echo "[Scriptorium] Enabling scriptorium-obsidian plugin..." +enable_plugin "scriptorium-obsidian" + +# Install obsidian-asciidoc plugin (required for .adoc file support) +ASCIIDOC_PLUGIN_DIR="${OBSIDIAN_FOLDER}/plugins/obsidian-asciidoc" +ASCIIDOC_PLUGIN_REPO="https://github.com/dzruyk/obsidian-asciidoc.git" + +echo "[Scriptorium] Installing obsidian-asciidoc plugin (required for .adoc files)..." + +if [ -d "$ASCIIDOC_PLUGIN_DIR" ]; then + echo "[Scriptorium] obsidian-asciidoc plugin already exists, updating..." + cd "$ASCIIDOC_PLUGIN_DIR" + if [ -d ".git" ]; then + git pull || echo "[Scriptorium] Warning: Could not update plugin (git pull failed)" + # Rebuild after update + if [ -f "package.json" ]; then + echo "[Scriptorium] Rebuilding obsidian-asciidoc plugin..." + npm install && npm run build || { + echo "[Scriptorium] Warning: Could not rebuild obsidian-asciidoc plugin" + } + fi + else + echo "[Scriptorium] Warning: Plugin directory exists but is not a git repository" + fi + cd "$SCRIPT_DIR" + + # Ensure plugin is enabled + ASCIIDOC_PLUGIN_ID=$(node -e "const m=require('${ASCIIDOC_PLUGIN_DIR}/manifest.json'); console.log(m.id)" 2>/dev/null || echo "obsidian-asciidoc") + enable_plugin "$ASCIIDOC_PLUGIN_ID" +else + echo "[Scriptorium] Cloning obsidian-asciidoc plugin..." + if command -v git &> /dev/null; then + git clone "$ASCIIDOC_PLUGIN_REPO" "$ASCIIDOC_PLUGIN_DIR" || { + echo "[Scriptorium] Error: Could not clone obsidian-asciidoc plugin" + echo "[Scriptorium] Make sure git is installed and you have internet access" + exit 1 + } + + if [ -d "$ASCIIDOC_PLUGIN_DIR" ]; then + echo "[Scriptorium] Building obsidian-asciidoc plugin..." + cd "$ASCIIDOC_PLUGIN_DIR" + if [ -f "package.json" ]; then + npm install && npm run build || { + echo "[Scriptorium] Error: Could not build obsidian-asciidoc plugin" + echo "[Scriptorium] Check the error messages above" + exit 1 + } + else + echo "[Scriptorium] Warning: No package.json found in obsidian-asciidoc plugin" + fi + cd "$SCRIPT_DIR" + + # Verify installation + if [ -f "${ASCIIDOC_PLUGIN_DIR}/main.js" ] && [ -f "${ASCIIDOC_PLUGIN_DIR}/manifest.json" ]; then + echo "[Scriptorium] obsidian-asciidoc plugin installed successfully!" + + # Get plugin ID from manifest.json + ASCIIDOC_PLUGIN_ID=$(node -e "const m=require('${ASCIIDOC_PLUGIN_DIR}/manifest.json'); console.log(m.id)" 2>/dev/null || echo "obsidian-asciidoc") + + # Enable the plugin + echo "[Scriptorium] Enabling obsidian-asciidoc plugin..." + enable_plugin "$ASCIIDOC_PLUGIN_ID" + else + echo "[Scriptorium] Error: Plugin files not found after installation" + echo "[Scriptorium] main.js or manifest.json missing in ${ASCIIDOC_PLUGIN_DIR}" + exit 1 + fi + fi + else + echo "[Scriptorium] Error: git is not installed. Cannot install obsidian-asciidoc plugin" + echo "[Scriptorium] Please install git or install the plugin manually" + exit 1 + fi +fi + +# Start Obsidian +echo "[Scriptorium] Starting Obsidian..." +echo "[Scriptorium] Note: Plugin logs will appear in this terminal" + +# Try to find Obsidian command +if command -v obsidian &> /dev/null; then + # Start Obsidian with the vault path + obsidian "$VAULT_PATH" & + echo "[Scriptorium] Obsidian started in background (PID: $!)" + echo "[Scriptorium] You can close this terminal, but plugin logs will stop if you do" + echo "[Scriptorium] Press Ctrl+C to stop Obsidian (or close the window)" + echo "" + echo "[Scriptorium] To use a different vault next time, run: $0 " + + # Wait for user interrupt + wait +elif command -v flatpak &> /dev/null && flatpak list | grep -q "md.obsidian"; then + # Try Flatpak version + echo "[Scriptorium] Starting Obsidian via Flatpak..." + flatpak run md.obsidian.Obsidian "$VAULT_PATH" & + echo "[Scriptorium] Obsidian started in background (PID: $!)" + echo "[Scriptorium] You can close this terminal, but plugin logs will stop if you do" + echo "[Scriptorium] Press Ctrl+C to stop Obsidian (or close the window)" + echo "" + echo "[Scriptorium] To use a different vault next time, run: $0 " + + # Wait for user interrupt + wait +else + echo "Error: Could not find Obsidian command" + echo "Please install Obsidian or add it to your PATH" + echo "Or start Obsidian manually and open vault: $VAULT_PATH" + exit 1 +fi