ShellPilot.psd1
|
# # Module manifest for module 'ShellPilot' # # Generated by: raandree # # Generated on: 6/6/2026 # @{ # Script module or binary module file associated with this manifest. RootModule = 'ShellPilot.psm1' # Version number of this module. ModuleVersion = '0.4.0' # Supported PSEditions # CompatiblePSEditions = @() # ID used to uniquely identify this module GUID = 'd2a14b3e-8f6e-4a07-9c2d-1e5a6e3b9c01' # Author of this module Author = 'raandree' # Company or vendor of this module CompanyName = 'raandree' # Copyright statement for this module Copyright = '(c) raandree. All rights reserved.' # Description of the functionality provided by this module Description = 'GitHub Copilot in your PowerShell terminal: device-flow auth, model listing, chat and agentic tool-calling with usage and cost.' # Minimum version of the PowerShell engine required by this module PowerShellVersion = '7.0' # Name of the PowerShell host required by this module # PowerShellHostName = '' # Minimum version of the PowerShell host required by this module # PowerShellHostVersion = '' # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. # DotNetFrameworkVersion = '' # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. # ClrVersion = '' # Processor architecture (None, X86, Amd64) required by this module # ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module RequiredModules = @() # Assemblies that must be loaded prior to importing this module # RequiredAssemblies = @() # Script files (.ps1) that are run in the caller's environment prior to importing this module. # ScriptsToProcess = @() # Type files (.ps1xml) to be loaded when importing this module # TypesToProcess = @() # Format files (.ps1xml) to be loaded when importing this module FormatsToProcess = @('ShellPilot.Format.ps1xml') # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess # NestedModules = @() # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = @('Clear-ShpChat','Clear-ShpContext','Clear-ShpToolPolicy','Clear-ShpUsage','Compress-ShpChat','ConvertTo-ShpTokenCount','Get-ShpChat','Get-ShpContext','Get-ShpCosineSimilarity','Get-ShpCostEstimate','Get-ShpDefault','Get-ShpMcpServer','Get-ShpModel','Get-ShpModelName','Get-ShpTool','Get-ShpToolPolicy','Get-ShpUsage','Initialize-Shp','Invoke-Shp','Invoke-ShpBatch','Register-ShpMcpServer','Register-ShpTool','Request-ShpEmbedding','Resolve-ShpError','Select-ShpModel','Set-ShpContext','Set-ShpToolPolicy','Start-ShpChat','Unregister-ShpMcpServer','Unregister-ShpTool') # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = @() # Variables to export from this module VariablesToExport = @() # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. AliasesToExport = @() # DSC resources to export from this module DscResourcesToExport = @() # List of all modules packaged with this module # ModuleList = @() # List of all files packaged with this module # FileList = @() # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. PrivateData = @{ PSData = @{ # Tags applied to this module. These help with module discovery in online galleries. Tags = @('GitHubCopilot', 'Copilot', 'AI', 'LLM', 'Chat', 'Agent', 'PSEdition_Core') # A URL to the license for this module. LicenseUri = 'https://github.com/raandree/ShellPilot/blob/main/LICENSE' # A URL to the main website for this project. ProjectUri = 'https://github.com/raandree/ShellPilot' # A URL to an icon representing this module. IconUri = 'https://raw.githubusercontent.com/raandree/ShellPilot/main/assets/shellpilot-icon.png' # ReleaseNotes of this module ReleaseNotes = '## [0.4.0-preview0007] - 2026-08-12 ### Added - `Register-ShpMcpServer`, `Get-ShpMcpServer` and `Unregister-ShpMcpServer` attach MCP (Model Context Protocol) servers so their tools are offered to the model alongside the built-ins and any registered user tools. Opt out for one call with `Invoke-Shp -DisableMcp`; nothing is offered until you attach a server, so the default posture is unchanged. **Both protocol eras are supported over stdio.** Verified from the specification rather than from memory, which changed the design: the current revision **2026-07-28** removed the `initialize` handshake entirely - a modern request is stateless and carries its protocol version and client capabilities in `_meta`, with `server/discover` as a mandatory RPC - while nearly every server in the field still expects the handshake. The client therefore probes with `server/discover` and falls back to `initialize` on any other error or a timeout, never keyed to one error code. Both eras were negotiated live, and the real Azure MCP Server 2.0.5 turned out to be a **legacy** server - which settles whether supporting both was worth it. See [specs/021-mcp-server-support.md](specs/021-mcp-server-support.md). - Tool names are namespaced `mcp_<alias>_<tool>` using the alias **you** chose, not the server''s self-reported name, which the protocol says nothing verifies. The sanitiser is written to a measured constraint, not a guessed one: the Copilot endpoint enforces `^[a-zA-Z0-9_-]{1,128}$`, so a dot - which MCP explicitly permits in a tool name - becomes `_`. This matters because a rejected name identifies the offending tool only by its **index** in the request, and the resulting 400 is masked by the chat-to-responses fallback, surfacing as "model ... does not support Responses API" - a true statement about an entirely different problem. - `Get-ShpTool` now lists MCP tools next to user tools, with a new `Origin` column (`User` or `Mcp`) and the contributing `Server`. `Invoke-Shp` reports `McpEnabled`, `McpServersAvailable`, `McpToolsAvailable` and `McpToolsCalled`, and an MCP call emits the same `ToolCall` progress record as every other tool, so a host renders it identically. ### Security - **An MCP server is a third-party process running with your privileges, and there is no sandbox.** Its tool names and descriptions are untrusted input the model reads on every round-trip, and its results are untrusted content. Three controls follow, each stated with its limits: - **Nothing is discovered.** No scan of the working directory, `.vscode` or a user profile. `Register-ShpMcpServer -Path` reads a file you name. A configuration file is a command line, and a command line is arbitrary code. - **The tool list is frozen at registration.** The client opens no `subscriptions/listen` stream, so it receives no `notifications/tools/list_changed` and a server cannot add or alter tools after you approved them. Refreshing is an explicit `-Force`. This does not stop a server changing its *behaviour*. - **The child environment is built, not inherited.** Unlike `run_command`, which deliberately inherits the whole block for compatibility, an MCP child starts from a minimal base plus exactly the variables you name in `-Environment`, so an ambient `$env:` credential is not handed to somebody else''s process. - **`Set-ShpToolPolicy` cannot gate an MCP call, and this is stated rather than implied.** Its rules match resolved filesystem paths and leading command tokens; a tool call has neither. Demonstrated in one live Turn under `Read(<repo>/**)`: the built-in `read_file` was denied with a reason and the MCP tool call ran. A policy that scopes `read_file` to one directory does nothing about an attached filesystem server. Reduce reach at attachment instead, with `-ToolName`. - The injection path was measured, not asserted. With a hostile instruction in a tool *description* only, the model read a decoy credentials file and passed its contents to the third-party server as a tool argument - the server''s own log confirms receipt. The same run with `-DisableFileAccess` read nothing and leaked nothing. - A configuration entry asking for `sandboxEnabled` **warns and still starts**: a configuration written for a sandboxing host is exactly the one you want to reuse. The gap is surfaced twice rather than being fatal once - a warning that says the server is starting unsandboxed, plus `SandboxRequested` on the server record, because a warning scrolls away and a property does not. An entry carrying an unresolved `${...}` variable is refused, because starting the literal text would run a different command than the file describes. - `Get-ShpMcpServer` reports environment variable **names** only, never values. ### Known limits - stdio transport only. Streamable HTTP is deferred with the MCP Authorization framework; a half-authorised HTTP client would be worse than none. - `Invoke-ShpBatch` does not use attached servers and warns once. A worker runspace inherits nothing, so replaying an attachment would start one copy of every server per worker. - A server that exits is marked `Faulted` rather than restarted automatically. The specification says a client SHOULD restart one; automatic respawn of third-party code inside an unattended loop turns one crash into a crash loop nobody is watching. Re-attach with `Register-ShpMcpServer -Force`. - Resources, prompts, sampling, elicitation and roots are out of scope for this version. Client capabilities are declared empty, so a modern server needing one gets a protocol error naming it rather than hanging. - The cached OAuth token is now protected at rest, closing open decision #5. On Windows it is DPAPI-encrypted for your account; on every platform the file is restricted to the current user. Measured before the change, the real file was 40 bytes of clear text with the profile''s inherited ACL, readable by `BUILTIN\Administrators` as well as by you. The file is self-describing - `SHPv1:DPAPI:...` or `SHPv1:NONE:...` - and `Initialize-Shp` reports which protection it applied, because a scheme that silently degrades to clear text is worse than clear text. On Linux and macOS there is no DPAPI equivalent without a dependency, so the scheme is `NONE` and file permissions (mode 600) are the only control; that is stated in the file and on screen rather than implied. **No runtime dependency was added** - `ConvertTo-SecureString` is built in. SecretManagement was considered and rejected: SecretStore prompts to unlock, which would break every unattended run, and configuring it not to prompt reduces it to file permissions while costing this module its empty dependency list. This buys protection against another principal on the machine; it does **not** protect against code running as you, and no candidate scheme would. See [specs/020-encrypted-token-storage.md](specs/020-encrypted-token-storage.md). - An existing clear-text token file keeps working and is **upgraded in place by `Initialize-Shp` without re-authenticating**, so nobody needs a browser just to gain protection. A protected file that cannot be decrypted - typically one copied from another machine or account - throws with an actionable message naming `Initialize-Shp -Force`, rather than sending a ciphertext to the service as a bearer token. - `Set-ShpToolPolicy`, `Get-ShpToolPolicy` and `Clear-ShpToolPolicy` scope what the unsandboxed file and shell tools may reach, so an unattended run can be given the access it needs instead of the caller''s entire filesystem and shell. Rules are written `Read(./src/**)`, `Write(./out/**)`, `Shell(git status)`, with a leading `!` to deny; any matching deny beats every matching allow. **Nothing changes until you set a policy** - and once you do, the model is denied by default. Refusals are reported on the result as `ToolCallsDenied`, and the policy travels into every `Invoke-ShpBatch` worker, because a worker inherits no module state and would otherwise be the one unguarded path. This closes a real gap rather than a symmetry: `-Confirm` is interactive only so an unattended run never prompts, and `-DisableFileAccess` / `-DisableTerminal` are all-or-nothing, so the only safe unattended setting was "all tools off". See [specs/019-tool-access-policy.md](specs/019-tool-access-policy.md). - Paths are matched on the absolute, **link-resolved** path rather than the string the model supplied, so neither a `..` segment nor a directory junction walks out of an allowed root, and patterns are anchored so a rule for `out` cannot match `outsider`. `run_command` is matched on whole leading tokens and **refuses any shell metacharacter** whatever the rules say, because `git status; curl ...` would otherwise pass a rule that only ever meant `git status`. A `Shell` rule constrains which program runs, not what it does; the spec states that limit and the others plainly. - `Invoke-Shp -RetryDelaySec`, and `-TimeoutSec`, `-MaxRetryCount`, `-RetryDelaySec` and `-NetworkOutageToleranceSec` on `Get-ShpModel` and `Request-ShpEmbedding`. All four now resolve identically everywhere - explicit parameter, then `Set-ShpContext`, then the built-in default - and apply to **every** request the module makes, including the OAuth-to-session token exchange, `/models` and embeddings. Those three previously called the retry wrapper with the module''s own defaults, so `Set-ShpContext -TimeoutSec 10 -MaxRetryCount 0` silently did not apply to them; and `Invoke-Shp` resolved its options *after* the token exchange had already run, so an explicit `-TimeoutSec` never reached the one request that gates every other one. There is deliberately no exemption for the auth handshake: the exchange is cached, so `-MaxRetryCount 0` costs at most one un-retried att' # Prerelease string of this module Prerelease = 'preview0007' # Flag to indicate whether the module requires explicit user acceptance for install/update/save # RequireLicenseAcceptance = $false # External dependent modules of this module # ExternalModuleDependencies = @() } # End of PSData hashtable } # End of PrivateData hashtable # HelpInfo URI of this module # HelpInfoURI = '' # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. # DefaultCommandPrefix = '' } |