opencode-polkit 0.2.0 → 0.3.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,88 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.3.1] - 2026-11-04
9
+
10
+ ### Added
11
+
12
+ - `src/shims.ts`: `su` stand-in. `su -c COMMAND [USER]` (also `-cCOMMAND`,
13
+ `--command COMMAND`, `--command=COMMAND`) becomes
14
+ `pkexec --keep-cwd --user USER sh -c COMMAND`, because a non-login `su` keeps
15
+ the working directory and runs its command through a shell. Every other form
16
+ is refused with status `64` and one line naming the alternative: `su`,
17
+ `su -`, `su -l` (login shell → `pkexec bash`), `-m`/`-p`/`-w` (pkexec
18
+ sanitizes the environment), `-s` (name the shell in the command), `-g`/`-G`
19
+ (pkexec cannot switch groups), `--pty` (no terminal), `-f` and
20
+ `--session-command`.
21
+ - README scope statement: the plugin covers elevation → polkit only; ssh,
22
+ gpg/pinentry, keyrings, database clients, `kinit` and disk encryption are
23
+ explicitly out of scope and need an askpass helper or a PTY.
24
+
25
+ ### Changed
26
+
27
+ - README authorisation note: a desktop agent may reuse the password it already
28
+ holds, so a second command can run without a dialog; that cache belongs to
29
+ the agent and must not be relied on. `su -c … USER` authenticates an
30
+ administrator rather than `USER`, and runs the command with `sh -c`.
31
+
32
+ ## [0.3.0] - 2026-11-01
33
+
34
+ 0.3.0 replaces how the plugin decides anything. It used to parse the command
35
+ string to find `sudo`, then parse the output to guess whether authentication
36
+ failed; both are gone. The plugin now only prepends a directory of shims to
37
+ `PATH`, letting the shell's own name resolution pick them, and reads the
38
+ outcome from exit status alone. Everything that existed to support guessing —
39
+ the lexical scanner, the output matchers, the denial cache, the translations —
40
+ is deleted with it. The plugin no longer changes the command the agent wrote.
41
+
42
+ ### Added
43
+
44
+ - `src/shims.ts`: `sudo`, `doas`, `sudoedit` and `visudo` scripts written to a
45
+ private `0700` temporary directory at setup and removed when the plugin is
46
+ disposed. `sudo` and `doas` forward to `pkexec`; `sudoedit` and `visudo`
47
+ refuse, because polkit has no equivalent flow.
48
+ - Deterministic option contract in the shims: `-u USER`, `-uUSER`, `--user USER`
49
+ and `--user=USER` become `pkexec --user USER`, `--` ends option parsing, and
50
+ every other option is refused with exit status `64` and a message naming the
51
+ alternative (`-n` → polkit decides, `-i`/`-s` → `pkexec bash`, `-E`/`-H`/`-P`
52
+ → pkexec sanitizes the environment, `-b` → append `&`). `pkexec` would
53
+ otherwise reject them with `Cannot run program`, which says nothing about what
54
+ to do next.
55
+ - A one-line explanation on exit status `126`: it is the only pkexec failure
56
+ path that prints nothing of its own, so the shim says the dialog was
57
+ dismissed. Every `127` path already prints its reason.
58
+ - `SHIM_USAGE_EXIT` (64) documents that the refusal status is the shim's own and
59
+ cannot be confused with pkexec's `126`/`127`.
60
+
61
+ ### Changed
62
+
63
+ - **Breaking:** commands are no longer rewritten. The `shell` tool's
64
+ `execute.before`/`execute.after` hooks are replaced by a single
65
+ `shell.create.before` hook that only sets `event.env.PATH`, so the agent's
66
+ command reaches the shell exactly as written, and `sudo` inside quotes,
67
+ comments or heredocs needs no special case.
68
+ - **Breaking:** authentication failures are reported from exit status
69
+ (`126` dismissed, `127` not authorized, `64` refused invocation), never from
70
+ output. The locale-dependent matchers for `Not authorized`,
71
+ `Error executing command as another user` and
72
+ `Error creating textual authentication agent` are gone.
73
+ - **Breaking:** messages are English only. The `Messages` table, the ten
74
+ translations and `detectLocale` are removed; the consumer is the coding agent.
75
+ - **Breaking:** a refused command is no longer remembered. Retrying after a
76
+ dismissed dialog prompts again instead of being rejected by the plugin.
77
+ - PATH interception is idempotent: prepending happens once, so stacked hooks do
78
+ not grow `PATH`.
79
+
80
+ ### Removed
81
+
82
+ - `scanPrivileged` (the quote-, escape- and heredoc-aware lexical scanner) and
83
+ `findBadOption`.
84
+ - The `deniedCommands` and `originalCommands` maps, along with the
85
+ `execute.after` result rewriting they supported.
86
+ - The `translations` table for en, zh, ja, ko, de, fr, es, pt, ru, tr and uk.
87
+ - The i18n section of the README, which now documents the exit-status contract
88
+ and the PATH shim's limits instead.
package/README.md CHANGED
@@ -1,28 +1,31 @@
1
1
  # opencode-polkit
2
2
 
3
- Redirect `sudo`/`doas` to `pkexec` in [OpenCode](https://opencode.ai).
4
- Triggers the system's native polkit authentication dialog (KDE, GNOME, etc.)
5
- instead of requiring a terminal for password input.
3
+ Redirects `sudo`, `doas` and `su -c` to `pkexec` inside OpenCode, so
4
+ authorisation goes through the desktop's polkit agent (KDE, GNOME, …) instead of
5
+ a terminal the shell tool does not have. Applies to commands run by the shell
6
+ tool; `sudo` in other terminals is unchanged.
6
7
 
7
- Like CachyOS Hello, the plugin does not check for a polkit agent up front:
8
- `sudo` is always redirected to `pkexec` at execution time, and polkitd routes
9
- the request to whatever agent is currently registered. Without an agent,
10
- `pkexec` fails fast with `Error creating textual authentication agent`
11
- (no TTY); the plugin reports that as a clear denial. A hang (dialog never
12
- appears) is bounded by the shell tool's own timeout (default 2 min,
13
- configurable per call) and left untranslated — it cannot be told
14
- apart from a long-running command.
8
+ ## Scope
15
9
 
16
- Requires OpenCode >= 2.0 (the plugin uses the `@opencode/plugin` v2 API and
17
- is skipped by older versions).
10
+ This plugin covers one category: **elevation → polkit**. It exists because the
11
+ shell tool has no terminal, so any program that reads a password from `/dev/tty`
12
+ fails there. `sudo`, `doas` and `su -c` are that category, and they are routed
13
+ to `pkexec`, which asks the desktop's polkit agent instead.
18
14
 
19
- ## Install
15
+ Password prompts that are not polkit's business are out of scope: `ssh`,
16
+ `ssh-add`, `gpg`/pinentry, keyrings (`secret-tool`, KWallet), database clients
17
+ (`psql`, `mysql -p`, `redis-cli`), `kinit`, and disk encryption (`cryptsetup`,
18
+ `fscrypt`, `gocryptfs`). They read the process's own terminal, so they need an
19
+ askpass helper or a pseudo-terminal, not polkit; mixing them in would make this
20
+ plugin's boundary meaningless.
21
+
22
+ ## Installation
20
23
 
21
24
  ```sh
22
25
  opencode plugin add opencode-polkit
23
26
  ```
24
27
 
25
- Or add to `opencode.json` / `~/.config/opencode/opencode.jsonc`:
28
+ Or add it to `opencode.json` / `~/.config/opencode/opencode.jsonc`:
26
29
 
27
30
  ```json
28
31
  {
@@ -30,8 +33,7 @@ Or add to `opencode.json` / `~/.config/opencode/opencode.jsonc`:
30
33
  }
31
34
  ```
32
35
 
33
- For local development, build once (`npm run build` — the local entrypoint
34
- re-exports `dist/`) and point opencode at the project directory instead:
36
+ For a local checkout, run `npm run build` once, then reference the directory:
35
37
 
36
38
  ```json
37
39
  {
@@ -41,51 +43,68 @@ re-exports `dist/`) and point opencode at the project directory instead:
41
43
 
42
44
  ## Behavior
43
45
 
44
- | Command | result |
45
- |-----------------------|-------------------------------------------------------|
46
- | `sudo xxx` | redirects to `pkexec xxx` |
47
- | `doas xxx` | redirects to `pkexec xxx` |
48
- | `sudo -n xxx` | blocked: pkexec has no short options (unclear failure)|
49
- | `cat x \| sudo tee y` | redirects to `cat x \| pkexec tee y` (mid-command) |
50
- | `pkexec xxx` | passes through |
51
- | `sudoedit` / `visudo` | blocked |
52
-
53
- ### Minimal intervention
54
-
55
- The command is rewritten **only** by replacing `sudo`/`doas` with
56
- `pkexec` — its shape is otherwise untouched, so the agent always sees a
57
- command that behaves like the one it wrote. Privilege keywords are found
58
- by a lexical scanner (quote/escape/heredoc aware): `sudo` inside string
59
- literals, comments or heredocs is ignored; `sudo` in any executable
60
- position (leading, after `&&`/`||`/`|`, in `$(...)` or subshells, after
61
- an env assignment like `FOO=1 sudo x`) is rewritten, while the same word
62
- in argument position (`--name sudo bash`) is left alone. `sudo a && sudo b`
63
- rewrites both.
64
-
65
- ### Failures
66
-
67
- Unambiguous authentication failures are reported as clear errors and the
68
- command is remembered so a retry is rejected without prompting again
69
- (cleared when opencode restarts): `Not authorized`, `Error executing
70
- command as another user`, `Error creating textual authentication agent`
71
- (no polkit agent + no TTY). A hang (dialog never appears) is bounded by
72
- the shell tool's own timeout (default 2 min, configurable per call)
73
- and is left untranslated — it cannot be told apart from a long-running
74
- command.
75
-
76
- Options pkexec does not accept (all short options, plus any long option
77
- outside `--user`, `--keep-cwd`, `--disable-internal-agent`, `--help`,
78
- `--version`) are rejected before execution with a clear message, since
79
- the rewrite would otherwise surface pkexec's own confusing
80
- `Cannot run program -n` error. `sudo --` and options after the program
81
- name are left alone.
82
-
83
- ## i18n
84
-
85
- Messages adapt to `$LC_MESSAGES` / `$LANG`. Currently supports:
86
- en, zh, ja, ko, de, fr, es, pt, ru, tr, uk.
87
-
88
- PRs welcome for additional translations.
46
+ | Command | Result |
47
+ |------------------------|---------------------------------------------|
48
+ | `sudo apt install x` | polkit dialog, then runs as root |
49
+ | `sudo -u bob …` | as above, as `bob` |
50
+ | `cat x \| sudo tee y` | dialog for `tee` |
51
+ | `sudo -n …` | refused: pkexec has no non-interactive mode |
52
+ | `sudo -i`, `-s`, `-b` | refused, with the equivalent command |
53
+ | `sudoedit`, `visudo` | refused, with the alternative |
54
+ | `su -c 'id'` | dialog, then runs as root |
55
+ | `su -c 'id' bob` | as above, as `bob` (administrator password) |
56
+ | `su`, `su -`, `su -l` | refused: no interactive shell, no terminal |
57
+ | `su -m`, `-s`, `-g`, `-w`, `--pty` | refused, with the alternative |
58
+ | `pkexec …` | passed through |
59
+
60
+ Refused invocations exit with status 64 and one line naming the alternative.
61
+
62
+ ## How it works
63
+
64
+ `sudo`, `doas` and `su` stand-ins are placed first on `PATH`, so the shell
65
+ resolves those instead of the system binaries, and they forward the command to
66
+ `pkexec`. Command text is never rewritten, so quoting, pipes and subshells need
67
+ no special handling.
68
+
69
+ ## Authorisation
70
+
71
+ `polkitd` decides whether a password is required. These commands always require
72
+ one; other actions may not, according to policy. The plugin takes no part in
73
+ that decision.
74
+
75
+ `org.freedesktop.policykit.exec` asks for administrator authentication on every
76
+ call, but the desktop agent may keep the password it just received and answer
77
+ the next challenge itself (KDE's agent does). A second command in the same
78
+ session then runs without a dialog. That cache belongs to the agent: another
79
+ agent, a restarted agent or a dismissed dialog means the next command prompts
80
+ again, so do not rely on it.
81
+
82
+ ## Limitations
83
+
84
+ - Absolute paths (`/usr/bin/sudo`) and relative ones (`./sudo`) bypass `PATH`.
85
+ - Commands that reset `PATH` (`env -i`, `PATH=/usr/bin …`) bypass it as well.
86
+ - Programs that invoke `sudo` with a scrubbed environment bypass it.
87
+ - `su -c … USER` authenticates an administrator (polkit), not `USER`, so an
88
+ account that real `su` would allow can be refused here.
89
+ - `su -c` runs the command with `sh -c`, not with the target user's login shell.
90
+
91
+ These bypass the redirect, not polkit, which still decides whether the command
92
+ runs.
93
+
94
+ ## Requirements
95
+
96
+ `polkit` and a registered authentication agent. Desktop environments start one;
97
+ without one, `pkexec` fails instead of prompting.
98
+
99
+ ## Exit status
100
+
101
+ | Status | Meaning |
102
+ |--------|----------------------------------------------|
103
+ | 126 | the polkit dialog was dismissed |
104
+ | 127 | not authorised, or the program could not run |
105
+ | 64 | the plugin refused the invocation |
106
+
107
+ Messages are in English.
89
108
 
90
109
  ## License
91
110
 
package/dist/index.d.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  import type { Plugin } from "@opencode/plugin";
2
2
  /**
3
- * OpenCode 2.0 requires a default export shaped `{ id, setup }` (the loader
4
- * rejects anything else). Deliberately a plain object with type-only imports:
5
- * the built plugin has zero runtime dependencies.
3
+ * OpenCode 2.0 requires a default export shaped `{ id, setup }`. Type-only
4
+ * imports keep the built plugin free of runtime dependencies.
6
5
  */
7
6
  declare const _default: {
8
7
  id: string;
9
- setup: (ctx: Plugin.Context) => Promise<void>;
8
+ setup: (ctx: Plugin.Context) => Promise<Plugin.Cleanup>;
10
9
  };
11
10
  export default _default;
package/dist/index.js CHANGED
@@ -1,334 +1,36 @@
1
- /** Long options pkexec accepts (all of them; pkexec has no short options). */
2
- const PKEXEC_OPTIONS = new Set([
3
- "--version",
4
- "--help",
5
- "--disable-internal-agent",
6
- "--keep-cwd",
7
- "--user",
8
- ]);
1
+ import { installPolkitShims, removePolkitShims } from "./shims.js";
9
2
  /**
10
- * Lexical scan for privilege-escalation keywords OUTSIDE quotes.
11
- *
12
- * Shell commands may contain `sudo` anywhere a command starts (pipes,
13
- * `&&`/`||`, subshells, command substitution), and string literals /
14
- * comments may contain the same words spuriously. A plain regex cannot
15
- * tell those apart; this scanner tracks quote state (' " and backslash
16
- * escapes) and only reports keywords in executable position.
17
- *
18
- * - `node -e "…sudo…"` → no hit (inside quotes)
19
- * - `cat x | sudo tee y` → hit at index 8 (command boundary)
20
- * - `cd /tmp && sudo ./x` → hit (after `&&`)
21
- * - `--name sudo bash` → no hit (argument position)
22
- * - `echo x; # sudo y` → no hit (comment)
3
+ * PATH used when the shell tool supplies none, mirroring a normal Linux host.
4
+ * Only the prepend matters; the rest is a last-resort fallback.
23
5
  */
24
- function scanPrivileged(command) {
25
- const hits = [];
26
- let inSingle = false;
27
- let inDouble = false;
28
- let escaped = false;
29
- // True right after a command boundary (`;`, `&&`, `||`, `|`, `(`, start).
30
- let atCommandStart = true;
31
- // Heredoc delimiters waiting to close (stack; multiple << are legal).
32
- const heredocs = [];
33
- for (let i = 0; i < command.length; i++) {
34
- const ch = command[i];
35
- // Inside a heredoc body: only a line consisting of the delimiter ends
36
- // it. Content there is a string literal, never a command. Pop the
37
- // matching delimiter (nested heredocs close in any order).
38
- if (heredocs.length > 0) {
39
- const lineEnd = command.indexOf("\n", i);
40
- const line = lineEnd === -1 ? command.slice(i) : command.slice(i, lineEnd);
41
- const idx = heredocs.lastIndexOf(line);
42
- if (idx !== -1) {
43
- heredocs.splice(idx);
44
- // The line after the delimiter starts a new command position.
45
- atCommandStart = true;
46
- }
47
- i = lineEnd === -1 ? command.length : lineEnd;
48
- continue;
49
- }
50
- if (escaped) {
51
- escaped = false;
52
- continue;
53
- }
54
- if (ch === "\\" && !inSingle) {
55
- escaped = true;
56
- continue;
57
- }
58
- if (ch === "'" && !inDouble) {
59
- inSingle = !inSingle;
60
- continue;
61
- }
62
- if (ch === '"' && !inDouble) {
63
- inDouble = !inDouble;
64
- continue;
65
- }
66
- if (inSingle || inDouble)
67
- continue;
68
- // Heredoc start: `<<[-]DELIM` (delim may be quoted or unquoted).
69
- if (ch === "<" && command[i + 1] === "<") {
70
- let j = i + 2;
71
- if (command[j] === "-")
72
- j++;
73
- while (j < command.length && /\s/.test(command[j]))
74
- j++;
75
- if (j < command.length && (command[j] === "'" || command[j] === '"')) {
76
- const q = command[j];
77
- const end = command.indexOf(q, j + 1);
78
- if (end !== -1) {
79
- heredocs.push(command.slice(j + 1, end));
80
- i = end;
81
- continue;
82
- }
83
- }
84
- else {
85
- const m = /^[^\s|;&<>()]+/.exec(command.slice(j));
86
- if (m) {
87
- heredocs.push(m[0]);
88
- i = j + m[0].length - 1;
89
- continue;
90
- }
91
- }
92
- }
93
- // Command boundaries open a new executable position.
94
- if (ch === ";" || ch === "|" || ch === "&" || ch === "(" || ch === "\n") {
95
- atCommandStart = true;
96
- continue;
97
- }
98
- // `#` at a word start begins a comment that runs to end of line.
99
- if (ch === "#") {
100
- const prev = command[i - 1] ?? "";
101
- if (i === 0 || /\s/.test(prev) || /[;|&(]/.test(prev)) {
102
- const nl = command.indexOf("\n", i);
103
- i = nl === -1 ? command.length : nl;
104
- continue;
105
- }
106
- }
107
- if (/\s/.test(ch))
108
- continue;
109
- // An env assignment (`FOO=1 sudo x`) still starts a command; any other
110
- // word in argument position (`--name sudo`) does not.
111
- let isCommandPos = atCommandStart;
112
- if (!isCommandPos) {
113
- let j = i - 1;
114
- while (j >= 0 && /\s/.test(command[j]))
115
- j--;
116
- while (j >= 0 && !/[\s;|&()]/.test(command[j]))
117
- j--;
118
- const token = command.slice(j + 1, i);
119
- if (token.includes("="))
120
- isCommandPos = true;
121
- }
122
- atCommandStart = false;
123
- if (!isCommandPos)
124
- continue;
125
- for (const kw of ["sudoedit", "visudo", "sudo", "doas", "pkexec"]) {
126
- if (command.startsWith(kw, i)) {
127
- const next = command[i + kw.length] ?? "";
128
- if (!/[-\/.\w]/.test(next)) {
129
- hits.push({ index: i, kw });
130
- i += kw.length - 1;
131
- break;
132
- }
133
- }
134
- }
135
- }
136
- return hits;
137
- }
6
+ const FALLBACK_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
138
7
  /**
139
- * First option token after a privilege keyword that pkexec does not
140
- * accept, or null when the command's options are pkexec-compatible.
141
- * Scanning stops at the program name (`--` also terminates options, as
142
- * in sudo); tokens after it belong to the program, not to pkexec.
8
+ * Redirects sudo/doas/su -c to pkexec without touching the command text.
9
+ *
10
+ * The shell tool spawns `$SHELL -c 'command'` with the environment built here,
11
+ * so putting the shims first on PATH lets the shell's own name resolution pick
12
+ * them up — `sudo`, `$(sudo …)`, `xargs sudo`, `FOO=1 sudo`, nested scripts.
13
+ * Nothing parses the command string, and nothing parses pkexec's output: the
14
+ * shims hand pkexec's exit status (126 dismissed, 127 not authorized) back to
15
+ * the caller unchanged.
143
16
  *
144
- * - `sudo -n true` → `-n` (pkexec has no short options)
145
- * - `sudo --non-interactive x` → `--non-interactive`
146
- * - `sudo --user root x` → null (compatible)
147
- * - `sudo -- x -n` → null (`--` ends sudo options)
17
+ * The shell is non-interactive and non-login, so no profile rewrites PATH and
18
+ * no alias can shadow the shims.
148
19
  */
149
- function findBadOption(command, hit) {
150
- let i = hit.index + hit.kw.length;
151
- while (i < command.length && /\s/.test(command[i]))
152
- i++;
153
- while (i < command.length && command[i] === "-") {
154
- let end = i + 1;
155
- while (end < command.length && !/\s/.test(command[end]) && !/[;|&()]/.test(command[end]))
156
- end++;
157
- const token = command.slice(i, end);
158
- if (token === "--")
159
- break;
160
- if (!token.startsWith("--"))
161
- return token;
162
- if (!PKEXEC_OPTIONS.has(token.split("=")[0]))
163
- return token;
164
- i = end;
165
- while (i < command.length && /\s/.test(command[i]))
166
- i++;
167
- }
168
- return null;
169
- }
170
- const translations = {
171
- en: {
172
- blocked: "Privilege escalation command blocked by plugin",
173
- polkitDenied: "Polkit authentication denied by user",
174
- badOption: 'Option "{opt}" is not supported by pkexec. Remove it or use a pkexec-compatible one (--user, --keep-cwd, --disable-internal-agent).',
175
- },
176
- zh: {
177
- blocked: "权限提升命令已被插件拦截",
178
- polkitDenied: "Polkit 认证已被用户拒绝",
179
- badOption: '选项 "{opt}" 不被 pkexec 支持,请移除或改用 pkexec 兼容选项(--user、--keep-cwd、--disable-internal-agent)。',
180
- },
181
- ja: {
182
- blocked: "特権昇格コマンドはプラグインによりブロックされました",
183
- polkitDenied: "Polkit 認証がユーザーによって拒否されました",
184
- badOption: 'オプション "{opt}" は pkexec ではサポートされていません。削除するか、pkexec 互換オプション(--user、--keep-cwd、--disable-internal-agent)を使用してください。',
185
- },
186
- ko: {
187
- blocked: "권한 상승 명령이 플러그인에 의해 차단되었습니다",
188
- polkitDenied: "Polkit 인증이 사용자에 의해 거부되었습니다",
189
- badOption: '옵션 "{opt}"은(는) pkexec에서 지원되지 않습니다. 제거하거나 pkexec 호환 옵션(--user, --keep-cwd, --disable-internal-agent)을 사용하세요.',
190
- },
191
- de: {
192
- blocked: "Berechtigungseskalation vom Plugin blockiert",
193
- polkitDenied: "Polkit-Authentifizierung vom Benutzer abgelehnt",
194
- badOption: 'Option "{opt}" wird von pkexec nicht unterstützt. Entfernen Sie sie oder verwenden Sie eine pkexec-kompatible Option (--user, --keep-cwd, --disable-internal-agent).',
195
- },
196
- fr: {
197
- blocked: "Commande d'élévation de privilèges bloquée par le plugin",
198
- polkitDenied: "Authentification polkit refusée par l'utilisateur",
199
- badOption: 'Option "{opt}" non prise en charge par pkexec. Supprimez-la ou utilisez une option compatible pkexec (--user, --keep-cwd, --disable-internal-agent).',
200
- },
201
- es: {
202
- blocked: "Comando de elevación de privilegios bloqueado por el plugin",
203
- polkitDenied: "Autenticación polkit denegada por el usuario",
204
- badOption: 'Opción "{opt}" no admitida por pkexec. Elimínela o use una opción compatible con pkexec (--user, --keep-cwd, --disable-internal-agent).',
205
- },
206
- pt: {
207
- blocked: "Comando de elevação de privilégios bloqueado pelo plugin",
208
- polkitDenied: "Autenticação polkit negada pelo usuário",
209
- badOption: 'Opção "{opt}" não suportada pelo pkexec. Remova-a ou use uma opção compatível com pkexec (--user, --keep-cwd, --disable-internal-agent).',
210
- },
211
- ru: {
212
- blocked: "Команда повышения привилегий заблокирована плагином",
213
- polkitDenied: "Аутентификация polkit отклонена пользователем",
214
- badOption: 'Параметр "{opt}" не поддерживается pkexec. Удалите его или используйте совместимый параметр (--user, --keep-cwd, --disable-internal-agent).',
215
- },
216
- tr: {
217
- blocked: "Yetki yükseltme komutu eklenti tarafından engellendi",
218
- polkitDenied: "Polkit kimlik doğrulaması kullanıcı tarafından reddedildi",
219
- badOption: '"{opt}" seçeneği pkexec tarafından desteklenmiyor. Kaldırın veya pkexec uyumlu bir seçenek kullanın (--user, --keep-cwd, --disable-internal-agent).',
220
- },
221
- uk: {
222
- blocked: "Команду підвищення привілеїв заблоковано плагіном",
223
- polkitDenied: "Автентифікацію polkit відхилено користувачем",
224
- badOption: 'Параметр "{opt}" не підтримується pkexec. Видаліть його або використайте сумісний параметр (--user, --keep-cwd, --disable-internal-agent).',
225
- },
226
- };
227
- function detectLocale() {
228
- const raw = process.env.LC_MESSAGES || process.env.LANG || "en";
229
- const tag = raw.split(".")[0].replace(/_/g, "-").toLowerCase();
230
- for (const prefix of ["zh", "ja", "ko", "de", "fr", "es", "pt", "ru", "tr", "uk"]) {
231
- if (tag.startsWith(prefix))
232
- return prefix;
233
- }
234
- return "en";
235
- }
236
- const MSG = translations[detectLocale()] ?? translations.en;
237
- /** The command string of a shell tool call, or "" for anything else. */
238
- function shellCommand(input) {
239
- if (typeof input !== "object" || input === null)
240
- return "";
241
- const command = input.command;
242
- return typeof command === "string" ? command : "";
243
- }
244
- /** Plain text of a tool result, whether content is a string or Content[]. */
245
- function resultText(result) {
246
- const content = result.content;
247
- if (typeof content === "string")
248
- return content;
249
- if (!Array.isArray(content))
250
- return "";
251
- return content
252
- .map((part) => typeof part === "object" && part !== null && "type" in part && part.type === "text" && "text" in part
253
- ? String(part.text)
254
- : "")
255
- .join("\n");
256
- }
257
20
  const setup = async (ctx) => {
258
- const deniedCommands = new Set();
259
- await ctx.tool.hook("execute.before", (event) => {
260
- if (event.tool !== "shell")
261
- return;
262
- // Mutating the input object in place rewrites the executed command;
263
- // core uses `event.input` after every hook has run.
264
- const args = event.input;
265
- const command = shellCommand(args);
266
- const hits = scanPrivileged(command);
267
- if (hits.length === 0)
268
- return;
269
- if (deniedCommands.has(command)) {
270
- throw new Error(MSG.polkitDenied);
271
- }
272
- if (hits.some((h) => h.kw === "sudoedit" || h.kw === "visudo")) {
273
- throw new Error(MSG.blocked);
274
- }
275
- // Rewrite would produce `pkexec <bad option>`: fail with a clear
276
- // message instead of a confusing `Cannot run program` error.
277
- for (const h of hits) {
278
- if (h.kw === "sudo" || h.kw === "doas" || h.kw === "pkexec") {
279
- const bad = findBadOption(command, h);
280
- if (bad)
281
- throw new Error(MSG.badOption.replace("{opt}", bad));
282
- }
283
- }
284
- // Already pkexec (leading or mid-command): pass through untouched.
285
- if (hits.every((h) => h.kw === "pkexec"))
286
- return;
287
- // Rewrite every in-command sudo/doas to pkexec (back to front so
288
- // indices stay valid): `cat x | sudo tee y` -> `cat x | pkexec tee y`.
289
- // No other rewriting: the command keeps its exact shape so the agent
290
- // sees only the minimal change (sudo -> pkexec).
291
- let rewritten = command;
292
- for (const h of [...hits].reverse()) {
293
- if (h.kw === "sudo" || h.kw === "doas") {
294
- rewritten = rewritten.slice(0, h.index) + "pkexec" + rewritten.slice(h.index + h.kw.length);
295
- }
296
- }
297
- ;
298
- args.command = rewritten;
299
- });
300
- await ctx.tool.hook("execute.after", (event) => {
301
- if (event.tool !== "shell")
302
- return;
303
- // `event.input` is the (possibly rewritten) command the shell ran:
304
- // map pkexec back to sudo to reconstruct the original for the deny list.
305
- const command = shellCommand(event.input);
306
- const hits = scanPrivileged(command);
307
- if (hits.length === 0)
308
- return;
309
- let original = command;
310
- for (const h of [...hits].reverse()) {
311
- if (h.kw === "pkexec") {
312
- original = original.slice(0, h.index) + "sudo" + original.slice(h.index + 6);
313
- }
314
- }
315
- const text = event.status === "completed" ? resultText(event.result) : event.status === "error" ? event.error.message : "";
316
- // Only unambiguous authentication failures are reported and deny
317
- // listed. A hang (dialog never appears) is bounded by the shell tool's
318
- // own timeout (default 2 min) and is NOT translated here: it cannot
319
- // be told apart from a long-running command.
320
- if (/\bNot authorized\b/.test(text) ||
321
- /\bError executing command as another user\b/.test(text) ||
322
- /\bError creating textual authentication agent\b/.test(text)) {
323
- deniedCommands.add(original);
324
- throw new Error(MSG.polkitDenied);
325
- }
21
+ const shimDir = installPolkitShims();
22
+ const prefix = `${shimDir}:`;
23
+ // Hooks stack across plugin loads; prepending once keeps PATH duplicate-free.
24
+ await ctx.shell.hook("create.before", (event) => {
25
+ const path = event.env.PATH || FALLBACK_PATH;
26
+ if (!path.startsWith(prefix))
27
+ event.env.PATH = prefix + path;
326
28
  });
29
+ return () => removePolkitShims(shimDir);
327
30
  };
328
31
  /**
329
- * OpenCode 2.0 requires a default export shaped `{ id, setup }` (the loader
330
- * rejects anything else). Deliberately a plain object with type-only imports:
331
- * the built plugin has zero runtime dependencies.
32
+ * OpenCode 2.0 requires a default export shaped `{ id, setup }`. Type-only
33
+ * imports keep the built plugin free of runtime dependencies.
332
34
  */
333
35
  export default { id: "opencode-polkit", setup };
334
36
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAElE;;;GAGG;AACH,MAAM,aAAa,GAAG,8DAA8D,CAAA;AAEpF;;;;;;;;;;;;GAYG;AACH,MAAM,KAAK,GAAG,KAAK,EAAE,GAAmB,EAA2B,EAAE;IACnE,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAA;IACpC,MAAM,MAAM,GAAG,GAAG,OAAO,GAAG,CAAA;IAE5B,8EAA8E;IAC9E,MAAM,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC,KAAK,EAAE,EAAE;QAC9C,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI,aAAa,CAAA;QAC5C,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;IAC9D,CAAC,CAAC,CAAA;IAEF,OAAO,GAAG,EAAE,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAA;AACzC,CAAC,CAAA;AAED;;;GAGG;AACH,eAAe,EAAE,EAAE,EAAE,iBAAiB,EAAE,KAAK,EAA0B,CAAA"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Exit status every shim uses for an invocation pkexec cannot express
3
+ * (EX_USAGE). Distinct from pkexec's own 126 (dismissed) and 127 (not
4
+ * authorized or failed) so callers can tell the two apart without reading
5
+ * any output.
6
+ */
7
+ export declare const SHIM_USAGE_EXIT = 64;
8
+ /**
9
+ * Writes the shim scripts into a fresh private directory and returns it.
10
+ * The caller prepends this directory to PATH; the shell then resolves
11
+ * `sudo`/`doas`/`su` to these files instead of the system ones. The directory
12
+ * is 0700 so no other local user can rewrite the scripts.
13
+ */
14
+ export declare function installPolkitShims(): string;
15
+ /** Deletes a directory created by installPolkitShims. */
16
+ export declare function removePolkitShims(directory: string): void;
package/dist/shims.js ADDED
@@ -0,0 +1,325 @@
1
+ import { chmodSync, existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { isAbsolute, join } from "node:path";
4
+ /**
5
+ * Exit status every shim uses for an invocation pkexec cannot express
6
+ * (EX_USAGE). Distinct from pkexec's own 126 (dismissed) and 127 (not
7
+ * authorized or failed) so callers can tell the two apart without reading
8
+ * any output.
9
+ */
10
+ export const SHIM_USAGE_EXIT = 64;
11
+ /**
12
+ * The sudo shim. Lives on PATH ahead of the real sudo, so the shell — not
13
+ * this plugin — decides that `sudo` means this file. It forwards to pkexec
14
+ * and never parses what pkexec prints: the exit status is the whole result.
15
+ *
16
+ * Only options with an exact pkexec equivalent are translated. Everything
17
+ * else is refused with an actionable message rather than forwarded, because
18
+ * pkexec would reject it with a message that does not say what to do instead.
19
+ */
20
+ const SUDO_SHIM = `#!/bin/sh
21
+ # opencode-polkit: sudo shim. Routes sudo to pkexec so polkit can authorize it.
22
+ # Generated at plugin startup; do not edit.
23
+
24
+ fail() {
25
+ printf 'opencode-polkit: %s\\n' "$1" >&2
26
+ exit ${SHIM_USAGE_EXIT}
27
+ }
28
+
29
+ command -v pkexec >/dev/null 2>&1 || fail 'pkexec is not installed; install polkit'
30
+
31
+ user=
32
+ while [ $# -gt 0 ]; do
33
+ case $1 in
34
+ --)
35
+ shift
36
+ break
37
+ ;;
38
+ -u)
39
+ [ $# -ge 2 ] || fail 'sudo -u needs a username: -u USER PROGRAM'
40
+ user=$2
41
+ shift 2
42
+ ;;
43
+ --user)
44
+ [ $# -ge 2 ] || fail 'sudo --user needs a username: --user USER PROGRAM'
45
+ user=$2
46
+ shift 2
47
+ ;;
48
+ -u?*)
49
+ user=\${1#-u}
50
+ [ -n "$user" ] || fail 'sudo -u needs a username: -u USER PROGRAM'
51
+ shift
52
+ ;;
53
+ --user=*)
54
+ user=\${1#--user=}
55
+ [ -n "$user" ] || fail 'sudo --user needs a username: --user USER PROGRAM'
56
+ shift
57
+ ;;
58
+ -n | --non-interactive)
59
+ fail 'sudo -n is not supported: pkexec has no non-interactive mode, polkit decides whether authentication is needed. Drop -n.'
60
+ ;;
61
+ -v | --validate | -k | --reset-timestamp | -K | --remove-timestamp)
62
+ fail 'sudo credential caching does not exist under polkit; drop -v/-k.'
63
+ ;;
64
+ -l | --list)
65
+ fail 'sudo -l is not available under polkit; inspect policy with pkaction --verbose.'
66
+ ;;
67
+ -e | --edit)
68
+ fail 'sudoedit is not supported; write a temp file and run pkexec install or pkexec cp on it.'
69
+ ;;
70
+ -i | --login | -s | --shell)
71
+ fail 'sudo -i/-s opens a login shell; run pkexec bash instead.'
72
+ ;;
73
+ -S | --stdin)
74
+ fail 'sudo -S reads the password from stdin; pkexec authenticates through the polkit agent. Drop -S.'
75
+ ;;
76
+ -A | --askpass)
77
+ fail 'SUDO_ASKPASS does not apply to pkexec; drop -A.'
78
+ ;;
79
+ -b | --background)
80
+ fail 'sudo -b backgrounds the command; append & instead.'
81
+ ;;
82
+ -E | --preserve-env | --preserve-env=* | -H | --set-home | -P | --preserve-groups | -N | --no-update)
83
+ fail 'pkexec deliberately sanitizes the environment; drop -E/-H/-P.'
84
+ ;;
85
+ -?*)
86
+ fail "option has no pkexec equivalent: $1 (pkexec accepts --user, --keep-cwd, --disable-internal-agent)"
87
+ ;;
88
+ *)
89
+ break
90
+ ;;
91
+ esac
92
+ done
93
+
94
+ # pkexec is silent when the dialog is dismissed (status 126), so say what the
95
+ # status means. Every other failure path already prints its own reason.
96
+ run_pkexec() {
97
+ pkexec "$@"
98
+ status=$?
99
+ if [ $status -eq 126 ]; then
100
+ printf 'opencode-polkit: %s\n' 'the polkit authentication dialog was dismissed' >&2
101
+ fi
102
+ exit $status
103
+ }
104
+
105
+ if [ -n "$user" ]; then
106
+ run_pkexec --user "$user" "$@"
107
+ fi
108
+ run_pkexec "$@"
109
+ `;
110
+ /** The doas shim: same contract, doas option grammar. */
111
+ const DOAS_SHIM = `#!/bin/sh
112
+ # opencode-polkit: doas shim. Routes doas to pkexec so polkit can authorize it.
113
+ # Generated at plugin startup; do not edit.
114
+
115
+ fail() {
116
+ printf 'opencode-polkit: %s\\n' "$1" >&2
117
+ exit ${SHIM_USAGE_EXIT}
118
+ }
119
+
120
+ command -v pkexec >/dev/null 2>&1 || fail 'pkexec is not installed; install polkit'
121
+
122
+ user=
123
+ while [ $# -gt 0 ]; do
124
+ case $1 in
125
+ --)
126
+ shift
127
+ break
128
+ ;;
129
+ -u)
130
+ [ $# -ge 2 ] || fail 'doas -u needs a username: -u USER PROGRAM'
131
+ user=$2
132
+ shift 2
133
+ ;;
134
+ -u?*)
135
+ user=\${1#-u}
136
+ [ -n "$user" ] || fail 'doas -u needs a username: -u USER PROGRAM'
137
+ shift
138
+ ;;
139
+ -n)
140
+ fail 'doas -n is not supported: pkexec has no non-interactive mode. Drop -n.'
141
+ ;;
142
+ -s)
143
+ fail 'doas -s opens a shell; run pkexec bash instead.'
144
+ ;;
145
+ -C | -a | -L)
146
+ fail "option has no pkexec equivalent: $1"
147
+ ;;
148
+ -?*)
149
+ fail "unknown doas option: $1"
150
+ ;;
151
+ *)
152
+ break
153
+ ;;
154
+ esac
155
+ done
156
+
157
+ # pkexec is silent when the dialog is dismissed (status 126), so say what the
158
+ # status means. Every other failure path already prints its own reason.
159
+ run_pkexec() {
160
+ pkexec "$@"
161
+ status=$?
162
+ if [ $status -eq 126 ]; then
163
+ printf 'opencode-polkit: %s\n' 'the polkit authentication dialog was dismissed' >&2
164
+ fi
165
+ exit $status
166
+ }
167
+
168
+ if [ -n "$user" ]; then
169
+ run_pkexec --user "$user" "$@"
170
+ fi
171
+ run_pkexec "$@"
172
+ `;
173
+ /**
174
+ * The su shim. `su -c` is the only form pkexec can express exactly: a command
175
+ * run as the target user through a shell, in the current directory. Every
176
+ * other form asks for a login shell, an environment, a shell binary, a group
177
+ * or a terminal, none of which pkexec can honour, so it is refused instead.
178
+ */
179
+ const SU_SHIM = `#!/bin/sh
180
+ # opencode-polkit: su shim. Routes su -c to pkexec so polkit can authorize it.
181
+ # Generated at plugin startup; do not edit.
182
+
183
+ fail() {
184
+ printf 'opencode-polkit: %s\\n' "$1" >&2
185
+ exit ${SHIM_USAGE_EXIT}
186
+ }
187
+
188
+ command -v pkexec >/dev/null 2>&1 || fail 'pkexec is not installed; install polkit'
189
+
190
+ user=
191
+ cmd=
192
+ has_cmd=
193
+ while [ $# -gt 0 ]; do
194
+ case $1 in
195
+ --)
196
+ shift
197
+ break
198
+ ;;
199
+ -c)
200
+ [ $# -ge 2 ] || fail 'su -c needs a command: su -c COMMAND [USER]'
201
+ cmd=$2
202
+ has_cmd=1
203
+ shift 2
204
+ ;;
205
+ -c?*)
206
+ cmd=\${1#-c}
207
+ has_cmd=1
208
+ shift
209
+ ;;
210
+ --command)
211
+ [ $# -ge 2 ] || fail 'su --command needs a command: su --command COMMAND [USER]'
212
+ cmd=$2
213
+ has_cmd=1
214
+ shift 2
215
+ ;;
216
+ --command=*)
217
+ cmd=\${1#--command=}
218
+ has_cmd=1
219
+ shift
220
+ ;;
221
+ - | -l | --login)
222
+ fail 'su starts a login shell; run pkexec bash instead.'
223
+ ;;
224
+ -s | --shell | -s?* | --shell=*)
225
+ fail 'su -s picks the shell; name the shell in the command: pkexec sh -c ...'
226
+ ;;
227
+ -m | -p | --preserve-environment)
228
+ fail 'pkexec deliberately sanitizes the environment; drop -m/-p.'
229
+ ;;
230
+ -g | -G | --group | --supp-group | -g?* | -G?* | --group=* | --supp-group=*)
231
+ fail 'su -g/-G switches groups; pkexec can only switch users.'
232
+ ;;
233
+ -w | --whitelist-environment | -w?* | --whitelist-environment=*)
234
+ fail 'su -w whitelists environment variables; pkexec sanitizes the environment.'
235
+ ;;
236
+ -P | --pty)
237
+ fail 'su --pty asks for a terminal; the shell tool has none.'
238
+ ;;
239
+ -f | --fast)
240
+ fail 'su -f is a shell flag; pass it to the shell in the command instead.'
241
+ ;;
242
+ --session-command | --session-command=*)
243
+ fail 'su --session-command keeps the session; pkexec always starts a new process.'
244
+ ;;
245
+ -h | --help | -V | --version)
246
+ fail "option has no pkexec equivalent: $1"
247
+ ;;
248
+ -?*)
249
+ fail "unknown su option: $1"
250
+ ;;
251
+ *)
252
+ [ -z "$user" ] || fail 'su passes arguments after the user name to the shell; use su -c COMMAND USER.'
253
+ user=$1
254
+ shift
255
+ ;;
256
+ esac
257
+ done
258
+
259
+ # Operands after -- are the same user-then-nothing sequence.
260
+ while [ $# -gt 0 ]; do
261
+ [ -z "$user" ] || fail 'su passes arguments after the user name to the shell; use su -c COMMAND USER.'
262
+ user=$1
263
+ shift
264
+ done
265
+
266
+ [ -n "$user" ] || user=root
267
+ [ -n "$has_cmd" ] || fail 'su without -c opens an interactive shell; run pkexec bash instead.'
268
+
269
+ # su keeps the working directory unless it is a login shell, and --keep-cwd is
270
+ # how pkexec does the same. pkexec is silent when the dialog is dismissed
271
+ # (status 126), so say what the status means.
272
+ pkexec --keep-cwd --user "$user" sh -c "$cmd"
273
+ status=$?
274
+ if [ $status -eq 126 ]; then
275
+ printf 'opencode-polkit: %s\\n' 'the polkit authentication dialog was dismissed' >&2
276
+ fi
277
+ exit $status
278
+ `;
279
+ /** sudoedit edits through sudo's own temporary-file flow; polkit has none. */
280
+ const SUDOEDIT_SHIM = `#!/bin/sh
281
+ printf 'opencode-polkit: sudoedit is not supported; write a temp file and run pkexec install or pkexec cp on it.\\n' >&2
282
+ exit ${SHIM_USAGE_EXIT}
283
+ `;
284
+ /** visudo needs an interactive editor on a terminal the shell tool does not have. */
285
+ const VISUDO_SHIM = `#!/bin/sh
286
+ printf 'opencode-polkit: visudo needs an interactive terminal; validate a candidate file with pkexec visudo -c -f FILE instead.\\n' >&2
287
+ exit ${SHIM_USAGE_EXIT}
288
+ `;
289
+ /** File name → script body. Names must match the commands being redirected. */
290
+ const POLKIT_SHIM_SCRIPTS = {
291
+ sudo: SUDO_SHIM,
292
+ doas: DOAS_SHIM,
293
+ su: SU_SHIM,
294
+ sudoedit: SUDOEDIT_SHIM,
295
+ visudo: VISUDO_SHIM,
296
+ };
297
+ /**
298
+ * Writes the shim scripts into a fresh private directory and returns it.
299
+ * The caller prepends this directory to PATH; the shell then resolves
300
+ * `sudo`/`doas`/`su` to these files instead of the system ones. The directory
301
+ * is 0700 so no other local user can rewrite the scripts.
302
+ */
303
+ export function installPolkitShims() {
304
+ const directory = mkdtempSync(join(shimBaseDirectory(), "opencode-polkit-"));
305
+ for (const [name, script] of Object.entries(POLKIT_SHIM_SCRIPTS)) {
306
+ const file = join(directory, name);
307
+ writeFileSync(file, script);
308
+ // writeFileSync's mode is filtered by umask, so set it explicitly.
309
+ chmodSync(file, 0o755);
310
+ }
311
+ return directory;
312
+ }
313
+ /**
314
+ * Where shim directories live. Prefers the login session's runtime directory,
315
+ * which the system clears on logout, over /tmp which keeps leftovers.
316
+ */
317
+ function shimBaseDirectory() {
318
+ const runtime = process.env.XDG_RUNTIME_DIR;
319
+ return runtime && isAbsolute(runtime) && existsSync(runtime) ? runtime : tmpdir();
320
+ }
321
+ /** Deletes a directory created by installPolkitShims. */
322
+ export function removePolkitShims(directory) {
323
+ rmSync(directory, { recursive: true, force: true });
324
+ }
325
+ //# sourceMappingURL=shims.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shims.js","sourceRoot":"","sources":["../src/shims.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACnF,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAE5C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAA;AAEjC;;;;;;;;GAQG;AACH,MAAM,SAAS,GAAG;;;;;;QAMV,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmFtB,CAAA;AAED,yDAAyD;AACzD,MAAM,SAAS,GAAG;;;;;;QAMV,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuDtB,CAAA;AAED;;;;;GAKG;AACH,MAAM,OAAO,GAAG;;;;;;QAMR,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6FtB,CAAA;AAED,8EAA8E;AAC9E,MAAM,aAAa,GAAG;;OAEf,eAAe;CACrB,CAAA;AAED,qFAAqF;AACrF,MAAM,WAAW,GAAG;;OAEb,eAAe;CACrB,CAAA;AAED,+EAA+E;AAC/E,MAAM,mBAAmB,GAA2B;IAClD,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,SAAS;IACf,EAAE,EAAE,OAAO;IACX,QAAQ,EAAE,aAAa;IACvB,MAAM,EAAE,WAAW;CACpB,CAAA;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAA;IAC5E,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAAE,CAAC;QACjE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;QAClC,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QAC3B,mEAAmE;QACnE,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IACxB,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;GAGG;AACH,SAAS,iBAAiB;IACxB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;IAC3C,OAAO,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,CAAA;AACnF,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,MAAM,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AACrD,CAAC"}
package/index.js CHANGED
@@ -1,6 +1,6 @@
1
- // Local-directory entrypoint for OpenCode 2.0: the loader resolves a plugin
2
- // directory by looking for `server`/`index` at its root (package.json "main"
3
- // and "exports" are only consulted for npm package names). npm installs reach
4
- // the real build through package.json "exports"; this shim covers
5
- // `"plugins": ["/path/to/opencode-polkit"]`.
1
+ // Local-directory entrypoint for OpenCode: OpenCode 2.2 also resolves a
2
+ // directory through a named package.json and its "exports", but older 2.0
3
+ // releases only look for `server`/`index` at the root. npm installs reach the
4
+ // real build through package.json "exports"; this shim keeps
5
+ // `"plugins": ["/path/to/opencode-polkit"]` working on both.
6
6
  export { default } from "./dist/index.js"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opencode-polkit",
3
- "version": "0.2.0",
4
- "description": "Redirect sudo/doas to pkexec in OpenCode — uses system polkit agent for native auth dialogs",
3
+ "version": "0.3.1",
4
+ "description": "Redirect sudo, doas and su to pkexec in OpenCode via PATH shims, so the system polkit agent handles authentication",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -15,6 +15,7 @@
15
15
  "dist",
16
16
  "index.js",
17
17
  "README.md",
18
+ "CHANGELOG.md",
18
19
  "LICENSE"
19
20
  ],
20
21
  "scripts": {
@@ -22,10 +23,10 @@
22
23
  "check": "tsc -p tsconfig.json --noEmit"
23
24
  },
24
25
  "engines": {
25
- "opencode": ">=2.0.0"
26
+ "opencode": ">=2.0.29"
26
27
  },
27
28
  "peerDependencies": {
28
- "@opencode/plugin": ">=2.0.0"
29
+ "@opencode/plugin": ">=2.0.29"
29
30
  },
30
31
  "peerDependenciesMeta": {
31
32
  "@opencode/plugin": {
@@ -33,11 +34,11 @@
33
34
  }
34
35
  },
35
36
  "devDependencies": {
36
- "@opencode/plugin": "^2.0.12",
37
+ "@opencode/plugin": "^2.2.5",
37
38
  "@types/node": "^24.0.0",
38
39
  "typescript": "^5.5.0"
39
40
  },
40
- "keywords": ["opencode-plugin", "sudo", "pkexec", "polkit", "security"],
41
+ "keywords": ["opencode-plugin", "sudo", "doas", "su", "pkexec", "polkit", "security"],
41
42
  "license": "MIT",
42
43
  "repository": {
43
44
  "type": "git",