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-ShpRedactionPolicy','Clear-ShpToolPolicy','Clear-ShpUsage','Compress-ShpChat','ConvertTo-ShpAnnotation','ConvertTo-ShpTokenCount','Get-ShpChat','Get-ShpContext','Get-ShpCosineSimilarity','Get-ShpCostEstimate','Get-ShpDefault','Get-ShpMcpServer','Get-ShpModel','Get-ShpModelName','Get-ShpRedactionPolicy','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-ShpRedactionPolicy','Set-ShpToolPolicy','Start-ShpChat','Test-ShpCiReadiness','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-preview0010] - 2026-08-26

### Added

- **`ConvertTo-ShpAnnotation` surfaces structured findings in CI.** Pipe a
  `ShellPilot.Result` from `Invoke-Shp -JsonSchema`, or any plain finding
  object, into the cmdlet to produce GitHub Actions annotations, Azure DevOps
  `task.logissue` commands, or readable text. `Level`, `Path`, `Line`,
  `Column`, `Title`, and `Message` are matched case-insensitively and can be
  redirected with `-PropertyMap`; an unknown or missing level is always a
  warning. Vendor-specific escaping keeps newlines and delimiters from
  corrupting a workflow command. Output stays on the success stream unless
  `-Emit` writes it to the host, and `-Summary` appends a Markdown table to
  `$env:GITHUB_STEP_SUMMARY` when available.
  See [specs/028-ci-annotations.md](specs/028-ci-annotations.md).

- **`Invoke-Shp -EventStream <path>` writes a headless JSONL event stream.** A
  CI log collector reads lines, not prose: everything the module said about a
  running turn was aimed at a person, so a nineteen-iteration turn that was
  refused twice by the tool policy, retried once on an expired session token
  and then stopped on `-MaxBudgetUSD` left one object saying
  `BudgetExceeded = $true` and nothing about the shape of the failure. The
  stream appends one JSON object per line - `turn.start`, `model.request`,
  `usage`, `reasoning` (one per streamed chunk under `-ShowThinking`),
  `tool.call`, `tool.result`, `todo`, `retry`, `error`, `final` - each carrying
  `schemaVersion`, a monotonic `sequence`, an ISO 8601 UTC `timestamp`, a
  `type` and a flat `data` object. Pass `-` to write the records to the
  Information stream instead of a file. Every line is appended whole, so a run
  killed mid-turn still leaves a file that parses up to its last complete line;
  a later call appending to that valid stream continues the sequence. Every
  string payload goes through the same redaction seam the request body does, so
  a secret a tool printed does not reach the stream verbatim; a `run_command`
  tool-call record names the tool and the policy decision but never the command
  line. The complete streamed reasoning trace is redacted before it is divided
  back into Event records, so an SSE boundary cannot split a secret around the
  redaction seam; partial reasoning is retained before a `retry` or `error`.
  Transient HTTP and network-outage retries from the shared request wrapper are
  recorded with attempt, delay and status data, and an invented `ask_user` call
  in a non-interactive turn records its denied Tool call and terminal error
  before the call stops. `-DisableProgressEvents` no longer switches this off -
  the two sinks are gated independently.
  See [specs/027-headless-event-stream.md](specs/027-headless-event-stream.md).

- **`Invoke-Shp -AsJob` and `Invoke-ShpBatch -AsJob` run a call in the
  background.** Both return a thread job whose `Receive-Job` resolves to the
  same `ShellPilot.Result` / `ShellPilot.BatchResult` objects the synchronous
  call returns - the same process, so nothing is serialised into a
  `Deserialized.*` copy. The job runspace inherits no module state, so the
  session context, session defaults, cached model limits, tool policy,
  redaction policy and registered tools are replayed into it and the module is
  imported by path. `Invoke-Shp -AsJob` is seeded from a snapshot of the
  session conversation and stays stateless from there, because a job that
  finishes at an arbitrary time must not race the caller''s next call. The CI
  entitlement gate is still evaluated at the call site, so a refused backend
  fails where you typed it rather than in the background of a green build. An
  event stream is honoured: `-AsJob` does not silently turn it off.
  See [specs/027-headless-event-stream.md](specs/027-headless-event-stream.md).

- **`Invoke-Shp` now redacts secrets before they leave the runner.** A CI job
  feeds the model diffs, build logs and attachments produced by untrusted
  pull-request content, and nothing scrubbed them before now - a leaked token
  in a log became a token sent to a third party. Immediately before each
  round-trip, the prompt, every inlined `-Attachment`, and every tool result
  (`run_command`, `read_file`, `fetch_url`, an MCP tool, a user-defined tool)
  is scanned for six built-in shapes - GitHub tokens, AWS access key ids, PEM
  private-key blocks, JWTs, basic-auth URL credentials, and connection-string
  password fields - and a match is replaced with a stable, named placeholder
  such as `[redacted:github-token]`, never simply deleted. The result reports
  `Redactions`: pattern name and count only, never the matched value.
  `Set-ShpRedactionPolicy` / `Get-ShpRedactionPolicy` / `Clear-ShpRedactionPolicy`
  add custom patterns on top of the built-ins, in the same `Name(Pattern)`
  shape `Set-ShpToolPolicy` already uses, and the custom policy travels to
  every `Invoke-ShpBatch` worker the same way the tool policy does. Redaction
  is on by default; pass `-DisableRedaction` to send a call verbatim. Only the
  model''s own reply is exempt - it was generated from input already redacted
  before it was sent, so it cannot reflect a secret it was never shown, and a
  `-JsonSchema` reply still parses onto `ContentObject` exactly as it would
  with redaction off.
  See [specs/026-egress-redaction.md](specs/026-egress-redaction.md).

- **An unattended run is now a supported, deliberate profile rather than an
  accident.** `Invoke-Shp`, `Invoke-ShpBatch` and `Initialize-Shp` take
  `-NonInteractive`, on automatically when `$env:CI` is truthy and overridable
  with `-NonInteractive:$false`. It withdraws `ask_user`, refuses `-Confirm`
  instead of silently answering it yes, and refuses the device-code flow before
  the browser launch and the clipboard write - because a prompt on a runner does
  not fail, it burns the job''s whole timeout and then fails for the wrong
  reason. A model that calls `ask_user` anyway ends the turn with
  `ShpNonInteractivePrompt` rather than continuing on an answer nobody gave.
  See [specs/025-ci-profile.md](specs/025-ci-profile.md).

- **In CI, the default Copilot backend is refused unless you opt in.** That
  backend reaches the Copilot endpoints with the public VS Code client id, on
  the token owner''s personal entitlement - fine for a shell, a decision for a
  pipeline. Configure an OpenAI-compatible endpoint instead, or set
  `SHELLPILOT_ALLOW_COPILOT_BACKEND_IN_CI`. The error carries the id
  `ShpCopilotBackendInCi` and names both remedies, and it is raised **before**
  the token exchange so nothing is spent proving the point.
  A warning was the obvious alternative and is the wrong shape: nobody reads a
  warning in a green build, which is the whole finding behind `-FailOn`.

- **`$env:SHELLPILOT_API_BASE` and `$env:SHELLPILOT_API_KEY`** are read as
  backend defaults, below an explicit `-ApiBase` and the session context and
  above the built-in Copilot endpoint - so a pipeline points ShellPilot at its
  own endpoint with the variables it already injects.

- **`Test-ShpCiReadiness`** reports the whole resolved profile - token source,
  backend, interactive capability, `Ready`, and a list of named issues - without
  sending a chat request or exchanging a token. The three things an unattended
  run needs are decided by three different precedence chains with silent
  fallbacks, so a misconfigured job otherwise fails minutes later at the first
  `Invoke-Shp` with whichever chain gave out first. No secret is returned: the
  credential and the API key are reported by source only, and the endpoint has
  any URL credentials redacted.

### Changed

- **An alternative backend (`-ApiBase`) no longer carries the Copilot session
  token.** It previously fell back to that token whenever no `ApiKey` was
  configured. That was reachable before only through `Set-ShpContext`; reading
  `ApiBase` from the environment would have let anything able to set a variable
  on a runner redirect a live Copilot credential to a host of its choosing.
  With a key the request carries the key; without one it carries no
  `Authorization` header at all, which is what a local server expects anyway.
  A URL''s credentials are also redacted from the result''s `Endpoint` member.

- **A pipeline step can now fail when the call did not deliver.** A budget
  overrun was a `Write-Warning` plus a `BudgetExceeded` property, so an
  unattended run exited `0` on a truncated or abandoned answer and wrote the
  half-finished artifact anyway. `Invoke-Shp -FailOn` turns five named outcomes
  into terminating errors: `BudgetExceeded`, `Truncated`, `ToolIterationLimit`,
  `NoContent` and `SchemaMismatch`.
  Each carries a distinct, documented `FullyQualifiedErrorId`
  (`ShpBudgetExceeded,Invoke-Shp` and so on) so a wrapper branches on the
  condition instead of matching an English message - which is what
  `MaxToolIterations` forced, having used its own message text as the error id.
  **Omitting `-FailOn` changes nothing**, and the turn''s side effects are
  unchanged either way: the call is evaluated last, after the result is built,
  the usage row written and the session chat updated, so `-FailOn` decides only
  whether the call ends with a result or with an error. The whole
  `ShellPilot.Result` rides on `ErrorRecord.TargetObject`, so a `catch` block
  still knows what the abandoned turn cost.
  ShellPilot never sets `$LASTEXITCODE` and never calls `exit` - a module that
  terminates its host cannot be composed - so the exit code stays the caller''s
  job; the comment-based help carries the `try`/`catch` plus `exit 1` wrapper.
  See [specs/024-pipeline-failure-semantics.md](specs/024-pipeline-failure-semantics.md).

- **`Invoke-ShpBatch -FailOn` and `-FailBatchOnAnyItem`.** The same five
  conditions apply per item, and a tripped one never aborts the batch: the item
  reports `Success = $false` with the branchable `ErrorRecord` intact while every
  other item runs to comp'


        # Prerelease string of this module
        Prerelease =  'preview0010'

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

}