Commit log

84488fe chore: add prepublishOnly script

Click to expand commit body
Automatically build before npm publish

Amolith created

dc9c4e6 chore: prepare for npm publish

Click to expand commit body
- Remove 'private': true to allow publishing
- Add description, license (MIT), and files whitelist

Amolith created

9f87c66 fix: use published pi-agent-core package from NPM

Click to expand commit body
- Switch from local path dependency to ^0.52.8 from NPM
- Fix TypeScript strict mode errors with index signature access

Amolith created

423d319 feat: add version flag

Click to expand commit body
Add -v/--version flag support to CLI. Also fix dist/ entry point in
package.json.

Amolith created

30613bb chore: add dist to gitignore

Amolith created

5c91d1d Fix NaN cost display and show request count in usage summary

Click to expand commit body
When model cost config is zero (local/free models), the cost math
produced NaN. Now cost is only shown when it's a valid positive number.

Usage summary now includes the number of LLM requests (assistant
messages) for better observability: 'usage: N tokens across M requests'.

Co-authored-by: Shelley <shelley@exe.dev>

Amolith and Shelley created

feefce9 Upgrade to pi-agent-core 0.52.8 and adopt proper API key resolution

Click to expand commit body
Replace the old pi-agent (0.9.0, ProviderTransport-based) with
pi-agent-core (0.52.8 from pi-mono) which takes getApiKey directly on
AgentOptions — no transport abstraction.

Upgrade pi-ai from 0.6.x to 0.52.8 (latest npm), which fixes the
sanitizeSurrogates crash on null content from reasoning models.

API key resolution now follows pi-coding-agent conventions:
- Custom models use a new 'api_key' config field (not headers)
- resolveConfigValue() supports three formats: bare env var names,
  explicit $VAR/${VAR} references, and !shell-command execution
- resolveHeaders() applies the same resolution to custom HTTP headers
- Built-in providers fall back to pi-ai's getEnvApiKey()

All tool imports updated: AgentTool moved from pi-ai to pi-agent-core.
Runner error handling preserved: checks agent.state.error, stopReason,
and empty responses.

Config schema gains optional 'api_key' field on CustomModelSchema.
README documents value resolution formats.

Tests: 112 total (22 new) covering resolveConfigValue, expandEnvVars,
resolveHeaders, and buildGetApiKey with literal keys, env vars, $VAR
references, shell commands, and provider isolation.

Co-authored-by: Shelley <shelley@exe.dev>

Amolith and Shelley created

a4711fe Document git tool trust boundary for cloned repos

Click to expand commit body
Filesystem tools enforce workspace containment via ensureWorkspacePath().
Git tools pass refs and paths directly to simple-git, which is scoped to
the workspace directory. This is by design: the user explicitly chooses
which repository to clone, so its git objects are trusted content.

- Add trust boundary explanation to AGENTS.md § Workspace Sandboxing
- Add inline comments to all 6 git tool files referencing the docs

Co-authored-by: Shelley <shelley@exe.dev>

Amolith and Shelley created

6e5e8da Make partialObject recursively deep-partial

Click to expand commit body
partialObject() wrapped each property in Type.Optional() but did not
recurse into nested TObject properties. Adding a nested object to any
config section would require the full object in partial config files
rather than allowing a subset.

Fix: when a property has Kind === 'Object' with a properties field,
recursively apply partialObject before wrapping in Optional. Type.Record
(also type 'object' but uses patternProperties) is correctly excluded
via the Kind check.

Tests: 8 new cases using synthetic schemas to verify deep-partial
behavior — empty objects at every nesting level, partial inner fields,
type rejection inside nested objects, Type.Record passthrough, and
3-level deep nesting.

Co-authored-by: Shelley <shelley@exe.dev>

Amolith and Shelley created

f461815 Guard against symlink escape in workspace sandboxing

Click to expand commit body
ensureWorkspacePath() previously used path.resolve() which normalizes ..
segments but does not follow symlinks. A symlink inside the workspace
pointing outside (e.g. <workspace>/escape -> /etc) would pass the textual
prefix check and let tools operate beyond the sandbox boundary.

Fix:
- Add safeRealpath() that calls fs.realpathSync() and walks up to the
  nearest existing ancestor on ENOENT (needed for write targets that
  don't exist yet)
- ensureWorkspacePath() now resolves both the workspace and target via
  realpath before checking containment
- The cheap textual check runs first as a fast path; the realpath check
  catches symlink-based escapes

Tests: 9 new cases covering symlink dirs/files pointing outside,
symlinks within workspace (allowed), nested escapes, non-existent write
targets through escaped parents, and writeWorkspaceFile integration.

Co-authored-by: Shelley <shelley@exe.dev>

Amolith and Shelley created

cdfe1f4 chore: ignore node_modules

Amolith created

9cb8e33 fix: review round 2 — credential error types, test gaps, git_refs truncation

Click to expand commit body
- Use ConfigError (not ToolInputError) for missing Kagi/Tabstack
  credentials in web command — these are config/env errors
- Fix web_search test that silently passed when no error was thrown
- Truncate git_refs output consistent with other git tools
- Add expandHomePath unit tests
- Add "test" script to package.json
- Update AGENTS.md to reflect test suite existence
- Minor import formatting cleanup in cli/index.ts

Co-authored-by: Shelley <shelley@exe.dev>

Amolith and Shelley created

5695021 fix: review cleanup — dedup containment check, remove dead imports, fix -u edge case

Click to expand commit body
- Deduplicate ensureContained() in content.ts: import ensureWorkspacePath
  from path-utils.ts instead of maintaining an identical private copy
- Remove unused DEFAULT_MAX_BYTES/DEFAULT_MAX_LINES imports from
  git blame, diff, and show tools (truncateHead uses them internally)
- Fix -u without value silently setting options["u"] = true: now leaves
  uri unset so command handlers can validate properly
- Extract expandHomePath() utility for system_prompt_path tilde
  expansion, replacing fragile inline regex that missed bare ~ and
  empty HOME
- Remove unnecessary parseArgs re-export from cli/index.ts (tests
  import directly from parse-args.ts)
- Add test for -u at end of args

Co-authored-by: Shelley <shelley@exe.dev>

Amolith and Shelley created

2a407a8 fix: split-on-first-delimiter bugs, -u flag guard, web_search errors, git_log validation

Click to expand commit body
- #5: modelString.split(':') now splits on first colon only, preserving
  segments after second colon (e.g. 'openrouter:google/gemini:free')
- #6: --key=value parsing splits on first '=' only, preserving values
  containing '=' (e.g. '--header=Authorization=Bearer token')
- #7: -u short flag guards against swallowing next arg when it starts
  with '-' (e.g. '-u --verbose' no longer sets uri='--verbose')
- #10: loadConfig already rethrows ConfigError directly (verified with
  test); TOML parse errors wrapped as ConfigError with original message
- #11: web_search errors from kagi API now wrapped as FetchError with
  query context, consistent with web_fetch error handling
- #12: git_log author/since/until validation rewritten: check
  '!== undefined' then validate, so empty strings are now caught
  (previously slipped through due to falsy short-circuit)

Also: extract parseArgs to src/cli/parse-args.ts for testability,
remove unused ToolInputError import from runner.ts.

Co-authored-by: Shelley <shelley@exe.dev>

Amolith and Shelley created

d570fc4 fix: truncate git tool output and enforce default log limit (#8, #9)

Click to expand commit body
- git_show, git_diff, git_blame: apply truncateHead() consistent with
  filesystem tools (DEFAULT_MAX_LINES=2000, DEFAULT_MAX_BYTES=50KB),
  appending a [truncated] notice when output is clipped.
- git_log: apply default limit of 20 when n is omitted, matching the
  schema description.
- Add test/git-tools.test.ts covering both truncation and default-limit
  behavior.

Co-authored-by: Shelley <shelley@exe.dev>

Amolith and Shelley created

514753c fix(config): validate parsed TOML against ConfigSchema at runtime

Click to expand commit body
Export ConfigSchema and PartialConfigSchema from schema.ts.
Use TypeBox Value.Check/Value.Errors in loader.ts to validate
parsed TOML before merging with defaults, and validate the
merged result against the full schema. Invalid config now throws
ConfigError with path, expected type, and actual value for each
violation.

Closes review issue #3.

Co-authored-by: Shelley <shelley@exe.dev>

Amolith and Shelley created

4819fd7 fix: wrap all post-workspace code in try/finally to prevent leak on early throws

Click to expand commit body
Move the try/finally cleanup block to start immediately after
createWorkspace() in both web.ts and repo.ts so that early failures
(credential validation, custom prompt read, checkout) trigger cleanup.

Remove redundant manual workspace.cleanup() calls in catch blocks
(clone error in repo.ts, fetch error in web.ts) since the outer
finally now handles all paths, preventing double-cleanup.

Fixes #2

Co-authored-by: Shelley <shelley@exe.dev>

Amolith and Shelley created

589bce1 fix(tools): enforce workspace path containment

Click to expand commit body
Address review issues #1 and #4:

Issue #1 - Path traversal in tools:
- Add ensureWorkspacePath() call after path resolution in read, grep,
  ls,
and find tools. Previously, paths were resolved but never checked
against workspace boundaries, allowing traversal via ../ or absolute
paths.
- Remove tilde (~) expansion from expandPath(). In a workspace-sandboxed
context, expanding ~ to the user's home directory bypasses containment.
Tildes are now treated as literal path characters.
- Move ensureWorkspacePath() from index.ts to path-utils.ts to avoid
circular imports, re-export from index.ts for backward compatibility.

Issue #4 - writeWorkspaceFile lacks traversal protection:
- Add ensureContained() validation in writeWorkspaceFile() that checks
  the
resolved file path stays within the workspace boundary before writing.

Also:
- Fix tsconfig.json rootDir from 'src' to '.' so test/ files are
  included
in type checking (the include array already listed 'test').
- Add comprehensive test suite (28 tests) covering workspace containment
for all affected modules.

Amolith created

1aa893a chore: initial commit

Amolith created