ShellPilot

0.4.0-preview0006

GitHub Copilot in your PowerShell terminal: device-flow auth, model listing, chat and agentic tool-calling with usage and cost.

Minimum PowerShell version

7.0

This is a prerelease version of ShellPilot.
There is a newer prerelease version of this module available.
See the version list below for details.

Installation Options

Copy and Paste the following command to install this package using PowerShellGet More Info

Install-Module -Name ShellPilot -RequiredVersion 0.4.0-preview0006 -AllowPrerelease

Copy and Paste the following command to install this package using Microsoft.PowerShell.PSResourceGet More Info

Install-PSResource -Name ShellPilot -Version 0.4.0-preview0006 -Prerelease

You can deploy this package directly to Azure Automation. Note that deploying packages with dependencies will deploy all the dependencies to Azure Automation. Learn More

Manually download the .nupkg file to your system's default download location. Note that the file won't be unpacked, and won't include any dependencies. Learn More

Owners

Copyright

(c) raandree. All rights reserved.

Package Details

Author(s)

  • raandree

Tags

GitHubCopilot Copilot AI LLM Chat Agent

Functions

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

PSEditions

Core

Dependencies

This module has no dependencies.

Release Notes

## [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

FileList

Version History

Version Downloads Last updated
0.4.0-previe... 3 8/19/2026
0.4.0-previe... 5 8/12/2026
0.4.0-previe... (current version) 7 8/12/2026
0.4.0-previe... 5 8/11/2026
0.4.0-previe... 3 8/11/2026
0.4.0-previe... 10 8/6/2026
0.4.0-previe... 10 7/28/2026
0.4.0-previe... 3 7/28/2026
0.3.1 54 7/23/2026
0.3.1-previe... 3 7/23/2026
0.3.0-previe... 8 7/12/2026
0.3.0-previe... 7 7/9/2026
0.3.0-previe... 4 7/9/2026
0.3.0-previe... 7 7/9/2026
0.2.1-previe... 8 7/8/2026
0.2.0 37 7/8/2026
0.2.0-previe... 6 7/8/2026
0.2.0-previe... 4 7/8/2026
0.2.0-previe... 31 6/12/2026
Show more