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
See the version list below for details.
Installation Options
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
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
- ShellPilot.nuspec
- ShellPilot.psm1
- ShellPilot.psd1
- en-US\about_ShellPilot.help.txt
- data\PriceTable.psd1
- ShellPilot.Format.ps1xml
- en-US\ShellPilot-help.xml
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 |