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 = ''

}