From 5149bf24598cea9b53b532bd1a4db70ab3150e47 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 13:26:50 +0000 Subject: [PATCH 1/2] Initial plan From 44ad4903e00d819c8093c8427c95f0103e542952 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 13:33:39 +0000 Subject: [PATCH 2/2] OpenTerm v1.1.0: rename, modern PowerShell, settings, GitHub Action, cross-platform Co-authored-by: Daolyap <100187341+Daolyap@users.noreply.github.com> --- .github/workflows/build.yml | 39 +++++++ README.md | 22 +++- manifest.json | 9 +- package-lock.json | 8 +- package.json | 6 +- src/main.ts | 210 ++++++++++++++++++++++++++++++++---- versions.json | 4 + 7 files changed, 261 insertions(+), 37 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 versions.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a36862f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,39 @@ +name: Build + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: plugin + path: | + main.js + manifest.json + versions.json diff --git a/README.md b/README.md index 3ccc129..bbb9f59 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,27 @@ -# OpenCMD-PS +# OpenTerm Obsidian plugin that adds context menu options to open files and folders in a terminal. ## Context Menu Options -- **Open in default app** - Opens the directory in the system default terminal (available on all platforms). -- **Open in PowerShell** - Opens the directory in PowerShell (Windows only). +- **Open in default terminal** - Opens the directory in the configured terminal (available on all platforms). +- **Open in PowerShell** - Opens the directory in PowerShell (available on all platforms with `pwsh` installed; falls back to `powershell` on Windows). - **Open in CMD** - Opens the directory in Command Prompt (Windows only). -On Linux and macOS, only "Open in default app" is shown. +Each option can be toggled on or off from the plugin settings. + +## Settings + +The plugin provides a settings tab where you can configure: + +- Which context menu items to show +- The PowerShell executable (default: `pwsh` for PowerShell 7+) +- The CMD executable (default: `cmd.exe`) +- The default terminal executable / app per OS + +## Installation + +Copy `main.js`, `manifest.json`, and `versions.json` into your vault's `.obsidian/plugins/openterm/` directory. ## Development @@ -17,4 +30,3 @@ npm install npm run build ``` -Copy `main.js` and `manifest.json` into your vault's `.obsidian/plugins/opencmd-ps/` directory. diff --git a/manifest.json b/manifest.json index 2cd196e..53b635f 100644 --- a/manifest.json +++ b/manifest.json @@ -1,9 +1,10 @@ { - "id": "opencmd-ps", - "name": "OpenCMD-PS", - "version": "1.0.0", + "id": "openterm", + "name": "OpenTerm", + "version": "1.1.0", "minAppVersion": "0.15.0", - "description": "Adds context menu options to open files and folders in CMD, PowerShell, or the default terminal.", + "description": "Adds context menu options to open files and folders in a terminal, with configurable executables per OS.", "author": "Daolyap", + "authorUrl": "https://github.com/Daolyap", "isDesktopOnly": true } diff --git a/package-lock.json b/package-lock.json index 4b1b5eb..91cef44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "opencmd-ps", - "version": "1.0.0", + "name": "openterm", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "opencmd-ps", - "version": "1.0.0", + "name": "openterm", + "version": "1.1.0", "license": "MIT", "devDependencies": { "@types/node": "^16.11.6", diff --git a/package.json b/package.json index b5f5ddc..e085637 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "opencmd-ps", - "version": "1.0.0", - "description": "Obsidian plugin to open files and folders in CMD, PowerShell, or the default terminal.", + "name": "openterm", + "version": "1.1.0", + "description": "Obsidian plugin to open files and folders in a terminal, with configurable executables per OS.", "main": "main.js", "scripts": { "dev": "node esbuild.config.mjs", diff --git a/src/main.ts b/src/main.ts index 263ce9d..20965f6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,30 @@ -import { Plugin, TFolder, TAbstractFile, Platform } from "obsidian"; +import { Plugin, PluginSettingTab, Setting, TFolder, TAbstractFile, Platform, App } from "obsidian"; import { execFile } from "child_process"; import * as path from "path"; -const SECTION_ID = "opencmd-ps"; +const SECTION_ID = "openterm"; + +interface OpenTermSettings { + showDefaultTerminal: boolean; + showPowerShell: boolean; + showCmd: boolean; + windowsDefaultExe: string; + powershellExe: string; + cmdExe: string; + macTerminalApp: string; + linuxTerminalExe: string; +} + +const DEFAULT_SETTINGS: OpenTermSettings = { + showDefaultTerminal: true, + showPowerShell: true, + showCmd: true, + windowsDefaultExe: "cmd.exe", + powershellExe: "pwsh", + cmdExe: "cmd.exe", + macTerminalApp: "Terminal", + linuxTerminalExe: "x-terminal-emulator", +}; interface VaultAdapterWithBasePath { getBasePath(): string; @@ -19,13 +41,16 @@ function getDirectory(vaultBasePath: string, file: TAbstractFile): string { return path.join(vaultBasePath, file.parent?.path ?? ""); } -function openInDefaultTerminal(directory: string): void { +function openInDefaultTerminal(directory: string, settings: OpenTermSettings): void { if (Platform.isWin) { - execFile("cmd.exe", ["/c", "start", "cmd", "/k", `cd /d "${directory}"`]); + const exe = settings.windowsDefaultExe || "cmd.exe"; + execFile("cmd.exe", ["/c", "start", exe, "/k", `cd /d "${directory}"`]); } else if (Platform.isMacOS) { - execFile("open", ["-a", "Terminal", directory]); + const app = settings.macTerminalApp || "Terminal"; + execFile("open", ["-a", app, directory]); } else { - execFile("x-terminal-emulator", [`--working-directory=${directory}`], (err) => { + const exe = settings.linuxTerminalExe || "x-terminal-emulator"; + execFile(exe, [`--working-directory=${directory}`], (err) => { if (err) { execFile("xdg-open", [directory]); } @@ -33,17 +58,35 @@ function openInDefaultTerminal(directory: string): void { } } -function openInPowerShell(directory: string): void { - execFile("cmd.exe", ["/c", "start", "powershell", "-NoExit", "-Command", - `Set-Location -LiteralPath '${directory.replace(/'/g, "''")}'`]); +function openInPowerShell(directory: string, settings: OpenTermSettings): void { + const exe = settings.powershellExe || "pwsh"; + const psArgs = ["-NoExit", "-Command", + `Set-Location -LiteralPath '${directory.replace(/'/g, "''")}'`]; + + if (Platform.isWin) { + execFile("cmd.exe", ["/c", "start", exe, ...psArgs], (err) => { + const isNotFound = err && (err as NodeJS.ErrnoException).code === "ENOENT"; + if (isNotFound && exe !== "powershell") { + execFile("cmd.exe", ["/c", "start", "powershell", ...psArgs]); + } + }); + } else { + execFile(exe, psArgs); + } } -function openInCmd(directory: string): void { - execFile("cmd.exe", ["/c", "start", "cmd", "/k", `cd /d "${directory}"`]); +function openInCmd(directory: string, settings: OpenTermSettings): void { + const exe = settings.cmdExe || "cmd.exe"; + execFile("cmd.exe", ["/c", "start", exe, "/k", `cd /d "${directory}"`]); } -export default class OpenCmdPsPlugin extends Plugin { +export default class OpenTermPlugin extends Plugin { + settings: OpenTermSettings; + async onload(): Promise { + await this.loadSettings(); + this.addSettingTab(new OpenTermSettingTab(this.app, this)); + const adapter = this.app.vault.adapter; this.registerEvent( @@ -51,26 +94,30 @@ export default class OpenCmdPsPlugin extends Plugin { const basePath = hasBasePath(adapter) ? adapter.getBasePath() : ""; const directory = getDirectory(basePath, file); - menu.addItem((item) => { - item.setTitle("Open in default app") - .setIcon("terminal") - .setSection(SECTION_ID) - .onClick(() => openInDefaultTerminal(directory)); - }); + if (this.settings.showDefaultTerminal) { + menu.addItem((item) => { + item.setTitle("Open in terminal") + .setIcon("terminal") + .setSection(SECTION_ID) + .onClick(() => openInDefaultTerminal(directory, this.settings)); + }); + } - if (Platform.isWin) { + if (this.settings.showPowerShell) { menu.addItem((item) => { item.setTitle("Open in PowerShell") .setIcon("terminal") .setSection(SECTION_ID) - .onClick(() => openInPowerShell(directory)); + .onClick(() => openInPowerShell(directory, this.settings)); }); + } + if (Platform.isWin && this.settings.showCmd) { menu.addItem((item) => { item.setTitle("Open in CMD") .setIcon("terminal") .setSection(SECTION_ID) - .onClick(() => openInCmd(directory)); + .onClick(() => openInCmd(directory, this.settings)); }); } }) @@ -78,4 +125,125 @@ export default class OpenCmdPsPlugin extends Plugin { } onunload(): void {} + + async loadSettings(): Promise { + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + } + + async saveSettings(): Promise { + await this.saveData(this.settings); + } +} + +class OpenTermSettingTab extends PluginSettingTab { + plugin: OpenTermPlugin; + + constructor(app: App, plugin: OpenTermPlugin) { + super(app, plugin); + this.plugin = plugin; + } + + display(): void { + const { containerEl } = this; + containerEl.empty(); + + new Setting(containerEl).setName("Context menu items").setHeading(); + + new Setting(containerEl) + .setName("Show default terminal") + .setDesc("Show the 'Open in terminal' option in the context menu.") + .addToggle((toggle) => + toggle.setValue(this.plugin.settings.showDefaultTerminal).onChange(async (value) => { + this.plugin.settings.showDefaultTerminal = value; + await this.plugin.saveSettings(); + }) + ); + + new Setting(containerEl) + .setName("Show PowerShell") + .setDesc("Show the 'Open in PowerShell' option in the context menu.") + .addToggle((toggle) => + toggle.setValue(this.plugin.settings.showPowerShell).onChange(async (value) => { + this.plugin.settings.showPowerShell = value; + await this.plugin.saveSettings(); + }) + ); + + new Setting(containerEl) + .setName("Show CMD (Windows only)") + .setDesc("Show the 'Open in CMD' option in the context menu.") + .addToggle((toggle) => + toggle.setValue(this.plugin.settings.showCmd).onChange(async (value) => { + this.plugin.settings.showCmd = value; + await this.plugin.saveSettings(); + }) + ); + + new Setting(containerEl).setName("Executables").setHeading(); + + new Setting(containerEl) + .setName("PowerShell executable") + .setDesc("Executable for PowerShell. Uses 'pwsh' (PowerShell 7+) by default, falling back to 'powershell' on Windows if not found.") + .addText((text) => + text + .setPlaceholder("pwsh") + .setValue(this.plugin.settings.powershellExe) + .onChange(async (value) => { + this.plugin.settings.powershellExe = value.trim(); + await this.plugin.saveSettings(); + }) + ); + + new Setting(containerEl) + .setName("CMD executable (Windows)") + .setDesc("Executable for Command Prompt on Windows.") + .addText((text) => + text + .setPlaceholder("cmd.exe") + .setValue(this.plugin.settings.cmdExe) + .onChange(async (value) => { + this.plugin.settings.cmdExe = value.trim(); + await this.plugin.saveSettings(); + }) + ); + + new Setting(containerEl) + .setName("Default terminal executable (Windows)") + .setDesc("Executable used for the default terminal option on Windows.") + .addText((text) => + text + .setPlaceholder("cmd.exe") + .setValue(this.plugin.settings.windowsDefaultExe) + .onChange(async (value) => { + this.plugin.settings.windowsDefaultExe = value.trim(); + await this.plugin.saveSettings(); + }) + ); + + new Setting(containerEl) + .setName("Terminal app (macOS)") + .setDesc("Application used for the default terminal option on macOS.") + .addText((text) => + text + .setPlaceholder("Terminal") + .setValue(this.plugin.settings.macTerminalApp) + .onChange(async (value) => { + this.plugin.settings.macTerminalApp = value.trim(); + await this.plugin.saveSettings(); + }) + ); + + new Setting(containerEl) + .setName("Terminal executable (Linux)") + .setDesc("Executable used for the default terminal option on Linux.") + .addText((text) => + text + .setPlaceholder("x-terminal-emulator") + .setValue(this.plugin.settings.linuxTerminalExe) + .onChange(async (value) => { + this.plugin.settings.linuxTerminalExe = value.trim(); + await this.plugin.saveSettings(); + }) + ); + } } diff --git a/versions.json b/versions.json new file mode 100644 index 0000000..5934189 --- /dev/null +++ b/versions.json @@ -0,0 +1,4 @@ +{ + "1.0.0": "0.15.0", + "1.1.0": "0.15.0" +}