@ordis_co_th/execute-powershell 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 OpenCode
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # Execute PowerShell Plugin
2
+
3
+ An OpenCode plugin that provides PowerShell script execution capabilities with built-in safety controls and permission management.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun install @ordis_co_th/execute-powershell
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Register in `opencode.json`
14
+
15
+ Add the plugin to your OpenCode configuration:
16
+
17
+ ```json
18
+ {
19
+ "plugins": [
20
+ "@ordis_co_th/execute-powershell"
21
+ ]
22
+ }
23
+ ```
24
+
25
+ Or with explicit import path:
26
+
27
+ ```json
28
+ {
29
+ "plugins": [
30
+ {
31
+ "import": "@ordis_co_th/execute-powershell",
32
+ "config": {}
33
+ }
34
+ ]
35
+ }
36
+ ```
37
+
38
+ ### Available Tools
39
+
40
+ Once registered, the plugin provides the `execute_powershell` tool with the following features:
41
+
42
+ - **Command Execution**: Execute PowerShell scripts with proper output capture
43
+ - **Permission Controls**: Built-in safety mechanisms for command validation
44
+ - **Working Directory**: Configurable working directory for script execution
45
+ - **Timeout Support**: Configurable execution timeouts
46
+ - **Output Streaming**: Real-time stdout/stderr capture
47
+
48
+ ### Example Tool Call
49
+
50
+ ```json
51
+ {
52
+ "tool": "execute_powershell",
53
+ "params": {
54
+ "command": "Get-Process",
55
+ "workdir": "/path/to/workdir",
56
+ "timeout_ms": 30000
57
+ }
58
+ }
59
+ ```
60
+
61
+ ## Development
62
+
63
+ ```bash
64
+ # Install dependencies
65
+ bun install
66
+
67
+ # Build the project
68
+ bun run build
69
+
70
+ # Run tests
71
+ bun test
72
+
73
+ # Check package contents
74
+ bun run package:check
75
+ ```
76
+
77
+ ## Package Structure
78
+
79
+ - **Entry Point**: `./dist/index.js`
80
+ - **Type Definitions**: `./dist/index.d.ts`
81
+ - **Export**: `ExecutePowerShellPlugin` - Main plugin function
82
+
83
+ ## License
84
+
85
+ MIT
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ export declare const ExecutePowerShellPlugin: Plugin;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAGlD,eAAO,MAAM,uBAAuB,EAAE,MAMrC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ import { execute_powershell } from "./tools/execute_powershell.js";
2
+ export const ExecutePowerShellPlugin = async () => {
3
+ return {
4
+ tool: {
5
+ execute_powershell,
6
+ },
7
+ };
8
+ };
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAEnE,MAAM,CAAC,MAAM,uBAAuB,GAAW,KAAK,IAAI,EAAE;IACxD,OAAO;QACL,IAAI,EAAE;YACJ,kBAAkB;SACnB;KACF,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { type ToolDefinition } from "@opencode-ai/plugin/tool";
2
+ import { z } from "zod";
3
+ declare const argsSchema: {
4
+ readonly command: z.ZodString;
5
+ readonly description: z.ZodString;
6
+ readonly timeout_ms: z.ZodDefault<z.ZodNumber>;
7
+ readonly workdir: z.ZodOptional<z.ZodString>;
8
+ };
9
+ export declare const execute_powershell: ToolDefinition;
10
+ export { argsSchema };
11
+ //# sourceMappingURL=execute_powershell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execute_powershell.d.ts","sourceRoot":"","sources":["../../src/tools/execute_powershell.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC;AACvF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,QAAA,MAAM,UAAU;;;;;CAKN,CAAC;AASX,eAAO,MAAM,kBAAkB,EAAE,cAiE/B,CAAC;AAEH,OAAO,EAAE,UAAU,EAAE,CAAC"}
@@ -0,0 +1,75 @@
1
+ import { tool } from "@opencode-ai/plugin/tool";
2
+ import { z } from "zod";
3
+ import { askExecutePowerShellPermission } from "./permissions.js";
4
+ import { askExternalDirectoryIfRequired, resolveWorkdir } from "./workdir.js";
5
+ import { formatMetadataFooter } from "./metadata.js";
6
+ import { resolvePowerShellExecutable } from "./powershell_exe.js";
7
+ import { spawnPowerShell, createTerminationSignal, terminateProcessTree } from "./process.js";
8
+ import { collectCombinedOutput } from "./output.js";
9
+ const argsSchema = {
10
+ command: z.string(),
11
+ description: z.string(),
12
+ timeout_ms: z.number().int().min(0).default(120000),
13
+ workdir: z.string().optional(),
14
+ };
15
+ export const execute_powershell = tool({
16
+ description: "Execute PowerShell commands on Windows",
17
+ args: argsSchema,
18
+ async execute(args, context) {
19
+ await askExecutePowerShellPermission(context, args.command);
20
+ const resolvedWorkdir = resolveWorkdir(context.directory, args.workdir);
21
+ await askExternalDirectoryIfRequired(context, resolvedWorkdir);
22
+ const timeoutMs = args.timeout_ms ?? 120000;
23
+ const { signal, getEndedBy } = createTerminationSignal(context.abort, timeoutMs);
24
+ const startTime = Date.now();
25
+ let exitCode = 0;
26
+ let shell = "unknown";
27
+ let proc;
28
+ try {
29
+ const exe = resolvePowerShellExecutable();
30
+ shell = exe.kind;
31
+ proc = spawnPowerShell({
32
+ exePath: exe.path,
33
+ command: args.command,
34
+ cwd: resolvedWorkdir,
35
+ signal,
36
+ });
37
+ const output = await collectCombinedOutput(proc);
38
+ exitCode = await proc.exited;
39
+ const durationMs = Date.now() - startTime;
40
+ const endedBy = getEndedBy();
41
+ const metadata = {
42
+ exitCode,
43
+ endedBy,
44
+ shell,
45
+ resolvedWorkdir,
46
+ timeoutMs,
47
+ durationMs,
48
+ };
49
+ return output + formatMetadataFooter(metadata);
50
+ }
51
+ catch (error) {
52
+ const durationMs = Date.now() - startTime;
53
+ const endedBy = getEndedBy();
54
+ exitCode = endedBy === "timeout" ? 1 : -1;
55
+ const metadata = {
56
+ exitCode,
57
+ endedBy,
58
+ shell,
59
+ resolvedWorkdir,
60
+ timeoutMs,
61
+ durationMs,
62
+ };
63
+ const errorOutput = error instanceof Error ? error.message : String(error);
64
+ return errorOutput + formatMetadataFooter(metadata);
65
+ }
66
+ finally {
67
+ const endedBy = getEndedBy();
68
+ if ((endedBy === "timeout" || endedBy === "abort") && proc?.pid) {
69
+ await terminateProcessTree(proc.pid);
70
+ }
71
+ }
72
+ },
73
+ });
74
+ export { argsSchema };
75
+ //# sourceMappingURL=execute_powershell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execute_powershell.js","sourceRoot":"","sources":["../../src/tools/execute_powershell.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Metadata footer for PowerShell execution outcomes.
3
+ *
4
+ * This module provides functions to format and parse metadata footers
5
+ * that are appended to PowerShell command output. The footer uses a
6
+ * tagged JSON format that can be easily extracted and parsed.
7
+ */
8
+ /**
9
+ * Metadata about a PowerShell execution outcome.
10
+ */
11
+ export interface PowerShellMetadata {
12
+ /** Exit code from the PowerShell process */
13
+ exitCode: number;
14
+ /** How the execution ended: "exit" (normal), "timeout", or "abort" */
15
+ endedBy: "exit" | "timeout" | "abort";
16
+ /** Shell identifier (e.g., "powershell", "pwsh") */
17
+ shell: string;
18
+ /** Resolved absolute path of the working directory */
19
+ resolvedWorkdir: string;
20
+ /** Timeout in milliseconds */
21
+ timeoutMs: number;
22
+ /** Duration of execution in milliseconds */
23
+ durationMs: number;
24
+ }
25
+ /**
26
+ * Format metadata as a tagged JSON footer.
27
+ *
28
+ * The footer format is: <powershell_metadata>{"exitCode": ..., ...}</powershell_metadata>
29
+ *
30
+ * @param meta - The metadata to format
31
+ * @returns The formatted metadata footer string
32
+ */
33
+ export declare function formatMetadataFooter(meta: PowerShellMetadata): string;
34
+ /**
35
+ * Parse a metadata footer from text.
36
+ *
37
+ * Extracts and parses the tagged JSON metadata from the end of text.
38
+ * Returns null if no valid metadata footer is found.
39
+ *
40
+ * @param text - The text to parse
41
+ * @returns The parsed metadata, or null if not found
42
+ */
43
+ export declare function parseMetadataFooter(text: string): PowerShellMetadata | null;
44
+ //# sourceMappingURL=metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../src/tools/metadata.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IACtC,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,eAAe,EAAE,MAAM,CAAC;IACxB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,kBAAkB,GAAG,MAAM,CAErE;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAwB3E"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Metadata footer for PowerShell execution outcomes.
3
+ *
4
+ * This module provides functions to format and parse metadata footers
5
+ * that are appended to PowerShell command output. The footer uses a
6
+ * tagged JSON format that can be easily extracted and parsed.
7
+ */
8
+ /**
9
+ * Format metadata as a tagged JSON footer.
10
+ *
11
+ * The footer format is: <powershell_metadata>{"exitCode": ..., ...}</powershell_metadata>
12
+ *
13
+ * @param meta - The metadata to format
14
+ * @returns The formatted metadata footer string
15
+ */
16
+ export function formatMetadataFooter(meta) {
17
+ return `<powershell_metadata>${JSON.stringify(meta)}</powershell_metadata>`;
18
+ }
19
+ /**
20
+ * Parse a metadata footer from text.
21
+ *
22
+ * Extracts and parses the tagged JSON metadata from the end of text.
23
+ * Returns null if no valid metadata footer is found.
24
+ *
25
+ * @param text - The text to parse
26
+ * @returns The parsed metadata, or null if not found
27
+ */
28
+ export function parseMetadataFooter(text) {
29
+ // Find ALL matches and take the LAST one (the actual footer, not any
30
+ // metadata-like text that might appear in command output)
31
+ const matches = [...text.matchAll(/<powershell_metadata>(.+?)<\/powershell_metadata>/gs)];
32
+ if (matches.length === 0)
33
+ return null;
34
+ const lastMatch = matches[matches.length - 1];
35
+ try {
36
+ const parsed = JSON.parse(lastMatch[1]);
37
+ // Validate all required fields
38
+ if (typeof parsed.exitCode !== "number" ||
39
+ !["exit", "timeout", "abort"].includes(parsed.endedBy) ||
40
+ typeof parsed.shell !== "string" ||
41
+ typeof parsed.resolvedWorkdir !== "string" ||
42
+ typeof parsed.timeoutMs !== "number" ||
43
+ typeof parsed.durationMs !== "number") {
44
+ return null;
45
+ }
46
+ return parsed;
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ }
52
+ //# sourceMappingURL=metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.js","sourceRoot":"","sources":["../../src/tools/metadata.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAoBH;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAwB;IAC3D,OAAO,wBAAwB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,wBAAwB,CAAC;AAC9E,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,qEAAqE;IACrE,0DAA0D;IAC1D,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,qDAAqD,CAAC,CAAC,CAAC;IAC1F,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEtC,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC9C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QACxC,+BAA+B;QAC/B,IACE,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ;YACnC,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC;YACtD,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;YAChC,OAAO,MAAM,CAAC,eAAe,KAAK,QAAQ;YAC1C,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ;YACpC,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,EACrC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,MAA4B,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Collect combined stdout and stderr output from a process.
3
+ *
4
+ * Reads both streams concurrently and appends chunks in observed arrival order,
5
+ * preserving the interleaving of output as it occurred. Uses separate TextDecoder
6
+ * instances per stream to prevent UTF-8 corruption when chunks interleave.
7
+ *
8
+ * @param proc - Object with stdout and stderr ReadableStreams
9
+ * @returns Promise resolving to combined output string
10
+ */
11
+ export declare function collectCombinedOutput(proc: {
12
+ stdout: ReadableStream<Uint8Array>;
13
+ stderr: ReadableStream<Uint8Array>;
14
+ }): Promise<string>;
15
+ //# sourceMappingURL=output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/tools/output.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE;IAAE,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,CAAA;CAAE,GAC/E,OAAO,CAAC,MAAM,CAAC,CAwCjB"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Collect combined stdout and stderr output from a process.
3
+ *
4
+ * Reads both streams concurrently and appends chunks in observed arrival order,
5
+ * preserving the interleaving of output as it occurred. Uses separate TextDecoder
6
+ * instances per stream to prevent UTF-8 corruption when chunks interleave.
7
+ *
8
+ * @param proc - Object with stdout and stderr ReadableStreams
9
+ * @returns Promise resolving to combined output string
10
+ */
11
+ export async function collectCombinedOutput(proc) {
12
+ const chunks = [];
13
+ let order = 0;
14
+ const readStream = async (stream, decoder) => {
15
+ const reader = stream.getReader();
16
+ try {
17
+ while (true) {
18
+ const { done, value } = await reader.read();
19
+ if (done)
20
+ break;
21
+ const text = decoder.decode(value, { stream: true });
22
+ chunks.push({ text, order: order++ });
23
+ }
24
+ }
25
+ finally {
26
+ reader.releaseLock();
27
+ }
28
+ };
29
+ // Use separate decoders for each stream to prevent UTF-8 corruption
30
+ const stdoutDecoder = new TextDecoder("utf-8");
31
+ const stderrDecoder = new TextDecoder("utf-8");
32
+ // Read both streams concurrently
33
+ await Promise.all([
34
+ readStream(proc.stdout, stdoutDecoder),
35
+ readStream(proc.stderr, stderrDecoder),
36
+ ]);
37
+ // Sort by arrival order
38
+ chunks.sort((a, b) => a.order - b.order);
39
+ // Join chunks and flush any remaining bytes from both decoders
40
+ return (chunks.map((c) => c.text).join("") +
41
+ stdoutDecoder.decode() +
42
+ stderrDecoder.decode());
43
+ }
44
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/tools/output.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,IAAgF;IAEhF,MAAM,MAAM,GAA2C,EAAE,CAAC;IAC1D,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,MAAM,UAAU,GAAG,KAAK,EACtB,MAAkC,EAClC,OAAoB,EACpB,EAAE;QACF,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;QAClC,IAAI,CAAC;YACH,OAAO,IAAI,EAAE,CAAC;gBACZ,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC5C,IAAI,IAAI;oBAAE,MAAM;gBAChB,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;gBACrD,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,WAAW,EAAE,CAAC;QACvB,CAAC;IACH,CAAC,CAAC;IAEF,oEAAoE;IACpE,MAAM,aAAa,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,aAAa,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC;IAE/C,iCAAiC;IACjC,MAAM,OAAO,CAAC,GAAG,CAAC;QAChB,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC;QACtC,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC;KACvC,CAAC,CAAC;IAEH,wBAAwB;IACxB,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAEzC,+DAA+D;IAC/D,OAAO,CACL,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;QAClC,aAAa,CAAC,MAAM,EAAE;QACtB,aAAa,CAAC,MAAM,EAAE,CACvB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { ToolContext } from "@opencode-ai/plugin/tool";
2
+ /**
3
+ * Asks for permission to execute a PowerShell command.
4
+ *
5
+ * @param context - The tool execution context
6
+ * @param command - The PowerShell command to execute
7
+ */
8
+ export declare function askExecutePowerShellPermission(context: ToolContext, command: string): Promise<void>;
9
+ /**
10
+ * Derives the always permission pattern from a PowerShell command.
11
+ *
12
+ * Rules:
13
+ * 1. Tokenize by whitespace
14
+ * 2. Skip leading tokens that equal '&' or '.'
15
+ * 3. Return '<prefix> *' from the first remaining token
16
+ */
17
+ export declare function deriveAlwaysPattern(command: string): string;
18
+ //# sourceMappingURL=permissions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/tools/permissions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE5D;;;;;GAKG;AACH,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAOf;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAiB3D"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Asks for permission to execute a PowerShell command.
3
+ *
4
+ * @param context - The tool execution context
5
+ * @param command - The PowerShell command to execute
6
+ */
7
+ export async function askExecutePowerShellPermission(context, command) {
8
+ await context.ask({
9
+ permission: "execute_powershell",
10
+ patterns: [command],
11
+ always: [deriveAlwaysPattern(command)],
12
+ metadata: {},
13
+ });
14
+ }
15
+ /**
16
+ * Derives the always permission pattern from a PowerShell command.
17
+ *
18
+ * Rules:
19
+ * 1. Tokenize by whitespace
20
+ * 2. Skip leading tokens that equal '&' or '.'
21
+ * 3. Return '<prefix> *' from the first remaining token
22
+ */
23
+ export function deriveAlwaysPattern(command) {
24
+ // Trim whitespace and split by whitespace
25
+ const tokens = command.trim().split(/\s+/).filter(token => token.length > 0);
26
+ // Find the first non-skippable token
27
+ for (const token of tokens) {
28
+ // Skip leading tokens that equal '&' or '.'
29
+ if (token === '&' || token === '.') {
30
+ continue;
31
+ }
32
+ // Return pattern with wildcard
33
+ return `${token} *`;
34
+ }
35
+ // Fallback: if no valid token found, throw error (fail-closed security)
36
+ // This prevents over-broad permission patterns from being generated
37
+ throw new Error("Cannot derive permission pattern: no valid command token found");
38
+ }
39
+ //# sourceMappingURL=permissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/tools/permissions.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,OAAoB,EACpB,OAAe;IAEf,MAAM,OAAO,CAAC,GAAG,CAAC;QAChB,UAAU,EAAE,oBAAoB;QAChC,QAAQ,EAAE,CAAC,OAAO,CAAC;QACnB,MAAM,EAAE,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC;QACtC,QAAQ,EAAE,EAAE;KACb,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe;IACjD,0CAA0C;IAC1C,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAE7E,qCAAqC;IACrC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,4CAA4C;QAC5C,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,GAAG,EAAE,CAAC;YACnC,SAAS;QACX,CAAC;QACD,+BAA+B;QAC/B,OAAO,GAAG,KAAK,IAAI,CAAC;IACtB,CAAC;IAED,wEAAwE;IACxE,oEAAoE;IACpE,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;AACpF,CAAC"}
@@ -0,0 +1,6 @@
1
+ export interface PowerShellExecutable {
2
+ kind: "pwsh" | "powershell";
3
+ path: string;
4
+ }
5
+ export declare function resolvePowerShellExecutable(): PowerShellExecutable;
6
+ //# sourceMappingURL=powershell_exe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"powershell_exe.d.ts","sourceRoot":"","sources":["../../src/tools/powershell_exe.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,GAAG,YAAY,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,2BAA2B,IAAI,oBAAoB,CAclE"}
@@ -0,0 +1,12 @@
1
+ export function resolvePowerShellExecutable() {
2
+ const pwshPath = Bun.which("pwsh");
3
+ if (pwshPath) {
4
+ return { kind: "pwsh", path: pwshPath };
5
+ }
6
+ const winPwshPath = Bun.which("powershell.exe");
7
+ if (winPwshPath) {
8
+ return { kind: "powershell", path: winPwshPath };
9
+ }
10
+ throw new Error("PowerShell not found. Please install PowerShell (pwsh) or Windows PowerShell (powershell.exe).");
11
+ }
12
+ //# sourceMappingURL=powershell_exe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"powershell_exe.js","sourceRoot":"","sources":["../../src/tools/powershell_exe.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,2BAA2B;IACzC,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACnC,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAC1C,CAAC;IAED,MAAM,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAChD,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IACnD,CAAC;IAED,MAAM,IAAI,KAAK,CACb,gGAAgG,CACjG,CAAC;AACJ,CAAC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Build PowerShell command argv array with security invariants.
3
+ *
4
+ * SECURITY: This function must NEVER accept or include user-supplied script text
5
+ * in the returned argv array. Script text must be passed via stdin to prevent
6
+ * command-line injection attacks.
7
+ *
8
+ * @param exePath - Path to the PowerShell executable
9
+ * @returns Array of command-line arguments for spawning PowerShell
10
+ */
11
+ export declare function buildPowerShellCommand(exePath: string): string[];
12
+ /**
13
+ * Termination signal interface for coordinating timeout and abort.
14
+ */
15
+ export interface TerminationSignal {
16
+ /** AbortSignal that is triggered on timeout or abort */
17
+ signal: AbortSignal;
18
+ /** Returns how the execution ended: "exit", "timeout", or "abort" */
19
+ getEndedBy: () => "exit" | "timeout" | "abort";
20
+ }
21
+ /**
22
+ * Creates a termination signal coordinator that merges context abort and timeout.
23
+ *
24
+ * This function creates an AbortController and wires up:
25
+ * - Context abort listener: triggers abort when the parent context is cancelled
26
+ * - Timeout logic: triggers abort after timeoutMs milliseconds (disabled if 0)
27
+ *
28
+ * The `getEndedBy()` function reports how the execution ended:
29
+ * - "exit": Process completed normally (signal not triggered by timeout/abort)
30
+ * - "timeout": Timeout duration was exceeded
31
+ * - "abort": Explicitly cancelled via context.abort
32
+ *
33
+ * @param contextAbort - Optional AbortSignal from parent context
34
+ * @param timeoutMs - Timeout in milliseconds (0 disables timeout)
35
+ * @returns TerminationSignal with signal and endedBy tracker
36
+ */
37
+ export declare function createTerminationSignal(contextAbort: AbortSignal | undefined, timeoutMs: number): TerminationSignal;
38
+ /**
39
+ * Options for spawning a PowerShell process with stdin transport.
40
+ */
41
+ export interface SpawnPowerShellOptions {
42
+ /** Path to the PowerShell executable */
43
+ exePath: string;
44
+ /** PowerShell script text (goes to stdin, NOT argv) */
45
+ command: string;
46
+ /** Working directory for the spawned process */
47
+ cwd: string;
48
+ /** Optional AbortSignal for cancellation */
49
+ signal?: AbortSignal;
50
+ }
51
+ /**
52
+ * Spawn a PowerShell process with script text passed via stdin.
53
+ *
54
+ * SECURITY: The command text is NEVER included in argv - it is always
55
+ * passed via stdin to prevent command-line injection attacks.
56
+ *
57
+ * @param options - Spawn options including exePath, command, cwd, and signal
58
+ * @returns Subprocess handle with piped stdout/stderr
59
+ */
60
+ export declare function spawnPowerShell(options: SpawnPowerShellOptions): Bun.Subprocess<NodeJS.NonSharedUint8Array, "pipe", "pipe">;
61
+ /**
62
+ * Terminate a process and its entire process tree.
63
+ *
64
+ * On Windows, uses `taskkill /T` to terminate the process tree.
65
+ * On Unix-like systems, uses `kill -9 -pid` (negative PID kills process group).
66
+ *
67
+ * @param pid - Process ID to terminate
68
+ * @returns Promise that resolves when the termination command completes
69
+ */
70
+ export declare function terminateProcessTree(pid: number): Promise<void>;
71
+ //# sourceMappingURL=process.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/tools/process.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAEhE;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,wDAAwD;IACxD,MAAM,EAAE,WAAW,CAAC;IACpB,qEAAqE;IACrE,UAAU,EAAE,MAAM,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;CAChD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,WAAW,GAAG,SAAS,EACrC,SAAS,EAAE,MAAM,GAChB,iBAAiB,CA4CnB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,8DAa9D;AAED;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBrE"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Build PowerShell command argv array with security invariants.
3
+ *
4
+ * SECURITY: This function must NEVER accept or include user-supplied script text
5
+ * in the returned argv array. Script text must be passed via stdin to prevent
6
+ * command-line injection attacks.
7
+ *
8
+ * @param exePath - Path to the PowerShell executable
9
+ * @returns Array of command-line arguments for spawning PowerShell
10
+ */
11
+ export function buildPowerShellCommand(exePath) {
12
+ return [exePath, "-NoProfile", "-NonInteractive", "-Command", "-"];
13
+ }
14
+ /**
15
+ * Creates a termination signal coordinator that merges context abort and timeout.
16
+ *
17
+ * This function creates an AbortController and wires up:
18
+ * - Context abort listener: triggers abort when the parent context is cancelled
19
+ * - Timeout logic: triggers abort after timeoutMs milliseconds (disabled if 0)
20
+ *
21
+ * The `getEndedBy()` function reports how the execution ended:
22
+ * - "exit": Process completed normally (signal not triggered by timeout/abort)
23
+ * - "timeout": Timeout duration was exceeded
24
+ * - "abort": Explicitly cancelled via context.abort
25
+ *
26
+ * @param contextAbort - Optional AbortSignal from parent context
27
+ * @param timeoutMs - Timeout in milliseconds (0 disables timeout)
28
+ * @returns TerminationSignal with signal and endedBy tracker
29
+ */
30
+ export function createTerminationSignal(contextAbort, timeoutMs) {
31
+ // Check if already aborted - if so, return immediately with abort status
32
+ if (contextAbort?.aborted) {
33
+ return {
34
+ signal: contextAbort,
35
+ getEndedBy: () => "abort",
36
+ };
37
+ }
38
+ const controller = new AbortController();
39
+ let endedBy = "exit";
40
+ // Handle context abort - triggered immediately when parent context cancels
41
+ if (contextAbort) {
42
+ contextAbort.addEventListener("abort", () => {
43
+ // Only set if not already decided (first-cause wins)
44
+ if (endedBy === "exit") {
45
+ endedBy = "abort";
46
+ controller.abort();
47
+ }
48
+ });
49
+ }
50
+ // Handle timeout - only set up if timeoutMs > 0
51
+ let timeoutId;
52
+ if (timeoutMs > 0) {
53
+ timeoutId = setTimeout(() => {
54
+ // Only set if not already decided (first-cause wins)
55
+ if (endedBy === "exit") {
56
+ endedBy = "timeout";
57
+ controller.abort();
58
+ }
59
+ }, timeoutMs);
60
+ }
61
+ // Clean up timeout when signal is aborted (prevents memory leaks)
62
+ controller.signal.addEventListener("abort", () => {
63
+ if (timeoutId)
64
+ clearTimeout(timeoutId);
65
+ });
66
+ return {
67
+ signal: controller.signal,
68
+ getEndedBy: () => endedBy,
69
+ };
70
+ }
71
+ /**
72
+ * Spawn a PowerShell process with script text passed via stdin.
73
+ *
74
+ * SECURITY: The command text is NEVER included in argv - it is always
75
+ * passed via stdin to prevent command-line injection attacks.
76
+ *
77
+ * @param options - Spawn options including exePath, command, cwd, and signal
78
+ * @returns Subprocess handle with piped stdout/stderr
79
+ */
80
+ export function spawnPowerShell(options) {
81
+ const cmd = buildPowerShellCommand(options.exePath);
82
+ const stdin = new TextEncoder().encode(options.command);
83
+ return Bun.spawn({
84
+ cmd,
85
+ stdin,
86
+ cwd: options.cwd,
87
+ stdout: "pipe",
88
+ stderr: "pipe",
89
+ signal: options.signal,
90
+ windowsHide: true,
91
+ });
92
+ }
93
+ /**
94
+ * Terminate a process and its entire process tree.
95
+ *
96
+ * On Windows, uses `taskkill /T` to terminate the process tree.
97
+ * On Unix-like systems, uses `kill -9 -pid` (negative PID kills process group).
98
+ *
99
+ * @param pid - Process ID to terminate
100
+ * @returns Promise that resolves when the termination command completes
101
+ */
102
+ export async function terminateProcessTree(pid) {
103
+ if (process.platform === "win32") {
104
+ // Windows: taskkill /T kills the process tree
105
+ const proc = Bun.spawn([
106
+ "taskkill",
107
+ "/PID",
108
+ String(pid),
109
+ "/T", // Terminate process tree
110
+ "/F", // Force
111
+ ]);
112
+ await proc.exited;
113
+ }
114
+ else {
115
+ // Unix: kill negative PID kills process group
116
+ try {
117
+ process.kill(-pid, "SIGKILL");
118
+ }
119
+ catch {
120
+ // Process may already be dead
121
+ }
122
+ }
123
+ }
124
+ //# sourceMappingURL=process.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process.js","sourceRoot":"","sources":["../../src/tools/process.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAe;IACpD,OAAO,CAAC,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;AACrE,CAAC;AAYD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,uBAAuB,CACrC,YAAqC,EACrC,SAAiB;IAEjB,yEAAyE;IACzE,IAAI,YAAY,EAAE,OAAO,EAAE,CAAC;QAC1B,OAAO;YACL,MAAM,EAAE,YAAY;YACpB,UAAU,EAAE,GAAG,EAAE,CAAC,OAAO;SAC1B,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,IAAI,OAAO,GAAiC,MAAM,CAAC;IAEnD,2EAA2E;IAC3E,IAAI,YAAY,EAAE,CAAC;QACjB,YAAY,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;YAC1C,qDAAqD;YACrD,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;gBACvB,OAAO,GAAG,OAAO,CAAC;gBAClB,UAAU,CAAC,KAAK,EAAE,CAAC;YACrB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,gDAAgD;IAChD,IAAI,SAA4B,CAAC;IACjC,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QAClB,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAC1B,qDAAqD;YACrD,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;gBACvB,OAAO,GAAG,SAAS,CAAC;gBACpB,UAAU,CAAC,KAAK,EAAE,CAAC;YACrB,CAAC;QACH,CAAC,EAAE,SAAS,CAAC,CAAC;IAChB,CAAC;IAED,kEAAkE;IAClE,UAAU,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;QAC/C,IAAI,SAAS;YAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,MAAM,EAAE,UAAU,CAAC,MAAM;QACzB,UAAU,EAAE,GAAG,EAAE,CAAC,OAAO;KAC1B,CAAC;AACJ,CAAC;AAgBD;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,OAA+B;IAC7D,MAAM,GAAG,GAAG,sBAAsB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACpD,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAExD,OAAO,GAAG,CAAC,KAAK,CAAC;QACf,GAAG;QACH,KAAK;QACL,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,MAAM;QACd,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,GAAW;IACpD,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,8CAA8C;QAC9C,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC;YACrB,UAAU;YACV,MAAM;YACN,MAAM,CAAC,GAAG,CAAC;YACX,IAAI,EAAG,yBAAyB;YAChC,IAAI,EAAG,QAAQ;SAChB,CAAC,CAAC;QACH,MAAM,IAAI,CAAC,MAAM,CAAC;IACpB,CAAC;SAAM,CAAC;QACN,8CAA8C;QAC9C,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,8BAA8B;QAChC,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,52 @@
1
+ import type { ToolContext } from "@opencode-ai/plugin/tool";
2
+ /**
3
+ * Asks for external_directory permission if the resolved workdir is outside the allowed boundary.
4
+ *
5
+ * Rules:
6
+ * 1. Build allowed roots from context
7
+ * 2. Check if resolvedWorkdir is within boundary
8
+ * 3. If outside: call context.ask() with permission "external_directory" and glob pattern
9
+ * 4. If inside: return without asking (no-op)
10
+ *
11
+ * @param context - The tool execution context
12
+ * @param resolvedWorkdir - The absolute, normalized workdir path
13
+ */
14
+ export declare function askExternalDirectoryIfRequired(context: ToolContext, resolvedWorkdir: string): Promise<void>;
15
+ /**
16
+ * Resolves a workdir argument to an absolute path.
17
+ *
18
+ * Rules:
19
+ * 1. If workdirArg is omitted, uses '.' semantics (resolves to contextDirectory)
20
+ * 2. If workdirArg is absolute, returns it normalized
21
+ * 3. If workdirArg is relative, resolves it against contextDirectory
22
+ *
23
+ * @param contextDirectory - The base directory from context
24
+ * @param workdirArg - Optional workdir argument (can be relative, absolute, or omitted)
25
+ * @returns The absolute, normalized path
26
+ */
27
+ export declare function resolveWorkdir(contextDirectory: string, workdirArg?: string): string;
28
+ /**
29
+ * Checks if a resolved path is within the allowed boundary roots.
30
+ *
31
+ * A path is considered "within boundary" if:
32
+ * - It exactly matches an allowed root
33
+ * - It starts with an allowed root path + path separator
34
+ * - The root is "/" (Unix root allows all paths)
35
+ *
36
+ * @param resolvedPath - The absolute, normalized path to check
37
+ * @param allowedRoots - Array of allowed root paths (should be absolute and normalized)
38
+ * @returns true if the path is within any of the allowed roots
39
+ */
40
+ export declare function isWithinBoundary(resolvedPath: string, allowedRoots: string[]): boolean;
41
+ /**
42
+ * Builds the list of allowed boundary roots from the tool context.
43
+ *
44
+ * Rules:
45
+ * 1. Always include context.directory
46
+ * 2. Include context.worktree only if it's not "/"
47
+ *
48
+ * @param context - The tool context containing directory and worktree information
49
+ * @returns Array of allowed root paths
50
+ */
51
+ export declare function buildAllowedRoots(context: ToolContext): string[];
52
+ //# sourceMappingURL=workdir.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workdir.d.ts","sourceRoot":"","sources":["../../src/tools/workdir.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE5D;;;;;;;;;;;GAWG;AACH,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,WAAW,EACpB,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,gBAAgB,EAAE,MAAM,EACxB,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,CAMR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EAAE,GACrB,OAAO,CAsBT;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,EAAE,CAMhE"}
@@ -0,0 +1,97 @@
1
+ import path from "path";
2
+ /**
3
+ * Asks for external_directory permission if the resolved workdir is outside the allowed boundary.
4
+ *
5
+ * Rules:
6
+ * 1. Build allowed roots from context
7
+ * 2. Check if resolvedWorkdir is within boundary
8
+ * 3. If outside: call context.ask() with permission "external_directory" and glob pattern
9
+ * 4. If inside: return without asking (no-op)
10
+ *
11
+ * @param context - The tool execution context
12
+ * @param resolvedWorkdir - The absolute, normalized workdir path
13
+ */
14
+ export async function askExternalDirectoryIfRequired(context, resolvedWorkdir) {
15
+ const allowedRoots = buildAllowedRoots(context);
16
+ if (isWithinBoundary(resolvedWorkdir, allowedRoots)) {
17
+ // Path is within boundary, no permission needed
18
+ return;
19
+ }
20
+ // Path is outside boundary, ask for external_directory permission
21
+ // Convert Windows backslashes to forward slashes for glob pattern
22
+ const globPattern = resolvedWorkdir.replace(/\\/g, "/") + "/**";
23
+ await context.ask({
24
+ permission: "external_directory",
25
+ patterns: [globPattern],
26
+ always: [globPattern],
27
+ metadata: {},
28
+ });
29
+ }
30
+ /**
31
+ * Resolves a workdir argument to an absolute path.
32
+ *
33
+ * Rules:
34
+ * 1. If workdirArg is omitted, uses '.' semantics (resolves to contextDirectory)
35
+ * 2. If workdirArg is absolute, returns it normalized
36
+ * 3. If workdirArg is relative, resolves it against contextDirectory
37
+ *
38
+ * @param contextDirectory - The base directory from context
39
+ * @param workdirArg - Optional workdir argument (can be relative, absolute, or omitted)
40
+ * @returns The absolute, normalized path
41
+ */
42
+ export function resolveWorkdir(contextDirectory, workdirArg) {
43
+ const rawWorkdir = workdirArg ?? ".";
44
+ if (path.isAbsolute(rawWorkdir)) {
45
+ return path.normalize(rawWorkdir);
46
+ }
47
+ return path.resolve(contextDirectory, rawWorkdir);
48
+ }
49
+ /**
50
+ * Checks if a resolved path is within the allowed boundary roots.
51
+ *
52
+ * A path is considered "within boundary" if:
53
+ * - It exactly matches an allowed root
54
+ * - It starts with an allowed root path + path separator
55
+ * - The root is "/" (Unix root allows all paths)
56
+ *
57
+ * @param resolvedPath - The absolute, normalized path to check
58
+ * @param allowedRoots - Array of allowed root paths (should be absolute and normalized)
59
+ * @returns true if the path is within any of the allowed roots
60
+ */
61
+ export function isWithinBoundary(resolvedPath, allowedRoots) {
62
+ const normalizedPath = path.normalize(resolvedPath);
63
+ return allowedRoots.some((root) => {
64
+ const normalizedRoot = path.normalize(root);
65
+ // Exact match
66
+ if (normalizedPath === normalizedRoot) {
67
+ return true;
68
+ }
69
+ // Root "/" allows everything on Unix
70
+ if (normalizedRoot === "/") {
71
+ return true;
72
+ }
73
+ // Ensure root has trailing separator for proper prefix check
74
+ const rootWithSep = normalizedRoot.endsWith(path.sep)
75
+ ? normalizedRoot
76
+ : normalizedRoot + path.sep;
77
+ return normalizedPath.startsWith(rootWithSep);
78
+ });
79
+ }
80
+ /**
81
+ * Builds the list of allowed boundary roots from the tool context.
82
+ *
83
+ * Rules:
84
+ * 1. Always include context.directory
85
+ * 2. Include context.worktree only if it's not "/"
86
+ *
87
+ * @param context - The tool context containing directory and worktree information
88
+ * @returns Array of allowed root paths
89
+ */
90
+ export function buildAllowedRoots(context) {
91
+ const roots = [context.directory];
92
+ if (context.worktree && context.worktree !== "/") {
93
+ roots.push(context.worktree);
94
+ }
95
+ return roots;
96
+ }
97
+ //# sourceMappingURL=workdir.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workdir.js","sourceRoot":"","sources":["../../src/tools/workdir.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAGxB;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,OAAoB,EACpB,eAAuB;IAEvB,MAAM,YAAY,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAEhD,IAAI,gBAAgB,CAAC,eAAe,EAAE,YAAY,CAAC,EAAE,CAAC;QACpD,gDAAgD;QAChD,OAAO;IACT,CAAC;IAED,kEAAkE;IAClE,kEAAkE;IAClE,MAAM,WAAW,GAAG,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;IAEhE,MAAM,OAAO,CAAC,GAAG,CAAC;QAChB,UAAU,EAAE,oBAAoB;QAChC,QAAQ,EAAE,CAAC,WAAW,CAAC;QACvB,MAAM,EAAE,CAAC,WAAW,CAAC;QACrB,QAAQ,EAAE,EAAE;KACb,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAC5B,gBAAwB,EACxB,UAAmB;IAEnB,MAAM,UAAU,GAAG,UAAU,IAAI,GAAG,CAAC;IACrC,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAChC,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;AACpD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAC9B,YAAoB,EACpB,YAAsB;IAEtB,MAAM,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IACpD,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;QAChC,MAAM,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAE5C,cAAc;QACd,IAAI,cAAc,KAAK,cAAc,EAAE,CAAC;YACtC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,qCAAqC;QACrC,IAAI,cAAc,KAAK,GAAG,EAAE,CAAC;YAC3B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,6DAA6D;QAC7D,MAAM,WAAW,GAAG,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC;YACnD,CAAC,CAAC,cAAc;YAChB,CAAC,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC;QAE9B,OAAO,cAAc,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAoB;IACpD,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAClC,IAAI,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,KAAK,GAAG,EAAE,CAAC;QACjD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@ordis_co_th/execute-powershell",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "packageManager": "bun@1.3.9",
6
+ "files": [
7
+ "dist/",
8
+ "README.md",
9
+ "LICENSE"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ }
16
+ },
17
+ "dependencies": {
18
+ "@opencode-ai/plugin": "1.2.10",
19
+ "zod": "4.3.6"
20
+ },
21
+ "overrides": {
22
+ "zod": "4.3.6"
23
+ },
24
+ "devDependencies": {
25
+ "@types/bun": "1.3.9",
26
+ "js-yaml": "^4.1.0",
27
+ "typescript": "5.9.3"
28
+ },
29
+ "scripts": {
30
+ "test": "bun test",
31
+ "build": "tsc",
32
+ "package:check": "bun run bin/package-check.ts"
33
+ }
34
+ }