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-ShpModel','Get-ShpModelName','Get-ShpTool','Get-ShpToolPolicy','Get-ShpUsage','Initialize-Shp','Invoke-Shp','Invoke-ShpBatch','Register-ShpTool','Request-ShpEmbedding','Resolve-ShpError','Select-ShpModel','Set-ShpContext','Set-ShpToolPolicy','Start-ShpChat','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-preview0006] - 2026-08-12 ### Added - 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 attempt per session, and outage tolerance is a separate option, so a dropped connection during auth is still ridden out. - `Compress-ShpChat` drops the oldest exchanges from the running session conversation so a session that has outgrown the model''s context window becomes usable again **without discarding it**. Until now the only ways out were `Clear-ShpChat` and a stateless `-History` call, and both throw the whole conversation away. `Invoke-Shp` writes the conversation back only when a call succeeds, so a refusal leaves it pinned and every later call is refused identically - measured at 0 successes in 108 retries. Measured against the live service, dropping the single oldest exchange was enough to restore a pinned session, so discarding all of it was never necessary. The first exchange (usually the task definition) and the newest exchange are kept, exchanges are dropped as whole user/assistant pairs, `-WhatIf` reports the plan without changing anything, and the returned report says exactly what went. Conversation turns are **never** elided automatically: a tool result is scaffolding the model produced for itself, but a user turn is something the user said, and a model answering from a silently truncated history can confidently contradict it. See [specs/018-conversation-history-overflow.md](specs/018-conversation-history-overflow.md). - `Invoke-Shp` now warns **before** sending once the context guard has elided every tool result it may and the conversation is still over budget, naming `Compress-ShpChat`. It is phrased as a fact about the guard rather than a prediction about the service, because `ConvertTo-ShpTokenCount` was measured at +30% / -12% against the service''s own token count and is not accurate enough to refuse a call on. The warning fires once per turn, not once per tool iteration. - The context-window guard now sizes itself from the model in use. Left unset, `-MaxContextWindowTokens` resolves in four steps - the parameter, then `Set-ShpContext -MaxContextWindowTokens`, then the model''s own advertised limits, then the built-in 900000 - and the resolved figure and the step that produced it are reported on the result as `ContextBudget` and `ContextBudgetSource`. The third step is not simply the advertised context window: that figure covers prompt *plus* completion, so the model''s output allowance is reserved first and a 10% margin taken from what remains. Measured against the live service, `claude-haiku-4.5` advertises a 200000 window with a 64000 output cap and refuses a prompt at 136000 - exactly 200000 - 64000 - so a margin on the advertised window alone would still have missed it. No advertised pair on offer resolves above 900000, so this can only ever tighten an existing caller''s guard, never loosen it. See [specs/017-context-window-budget-from-model.md](specs/017-context-window-budget-from-model.md). - `Get-ShpModel` records each model''s advertised context window and output cap in a session cache as a side effect. That cache is what lets the guard resolve a real window with **no** request of its own: a turn is a loop, so consulting `/models` per turn would add a round-trip to calls that are otherwise local. Until something fills it - `Get-ShpModel`, or `Get-ShpModelName`, which calls it - the guard uses the fallback, which is no model''s real window and is too permissive for 22 of the 36 models that advertise one. Run `Get-ShpModel` once per session to fix that. `Invoke-ShpBatch` copies the cache to every worker, and `Initialize-Shp` discards it on re-auth. - A model that is absent from a model list that *was* fetched now warns once per model per session that the guard is running on the fallback, in the same spirit as `Priced` / `PriceTableKey` making an unpriced call observable. A cache that has simply never been populated - the default state of every session - stays quiet. - `Get-ShpUsage` now records calls that **failed**, not only the ones that succeeded. A failed call carries `Success` `$false` and the failure message on `Error`, plus whatever spend its completed round-trips had already incurred. Both halves of that matter: a success rate computed from the old log was 100% by construction, because only successes were in it; and a turn is a loop of billable round-trips, so a turn refused on its third round-trip really was charged for the first two and reported nothing. Only a call that reached the API is recorded - a parameter combination rejected before any request was never a call. `Invoke-ShpBatch` inherits this, so a failed batch item now shows up in `Get-ShpUsage` too. See [specs/016-failed-call-usage-accounting.md](specs/016-failed-call-usage-accounting.md). - `Get-ShpUsage -Summary` gained `Succeeded`, `Failed`, `TotalDurationMs`, `MeanDurationMs`, `FirstCall`, `LastCall` and `ElapsedMs`, and the `ByModel` breakdown gained `Succeeded`, `Failed` and `DurationMs`. `ElapsedMs` is wall-clock between the first and last call and is deliberately *not* the sum of `DurationMs`: under `Invoke-ShpBatch` the calls overlap, so the sum can far exceed the elapsed time and the ratio between them is the speed-up the batch bought. - `Get-ShpUsage -Since` and `-Before` filter by time window, so one phase of a run can be summarised without clearing the log between phases. Both apply to the records and to `-Summary`. There is deliberately no `-GroupBy`: `Get-ShpUsage` returns the records, so `Group-Object` already groups by any field, and `ByModel` is pre-aggregated only because that split is the common case. - `Invoke-ShpBatch` runs ' # Prerelease string of this module Prerelease = 'preview0006' # 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 = '' } |