feat: initial commit
This commit is contained in:
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# dependencies (bun install)
|
||||
node_modules
|
||||
|
||||
# output
|
||||
out
|
||||
dist
|
||||
*.tgz
|
||||
|
||||
# code coverage
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# logs
|
||||
logs
|
||||
_.log
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# caches
|
||||
.eslintcache
|
||||
.cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
82
AGENTS.md
Normal file
82
AGENTS.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Overview
|
||||
|
||||
`wow-pi` is a Bun + TypeScript pi extension package that ports selected `wow-omp` features to original pi. It provides two runtime modules: Markdown context injection and environment variable injection.
|
||||
|
||||
## Architecture & Data Flow
|
||||
|
||||
- Root `package.json` declares a private workspace and pi extension entry at `packages/wow-pi/src/index.ts`.
|
||||
- `packages/wow-pi/src/index.ts` imports `wow-contexts` and `wow-inject` for side-effect module registration, then calls each registered module with pi's `ExtensionAPI`.
|
||||
- `packages/wow-core` owns shared infrastructure: config loading, module registry, logger, UI rendering, path utilities, and `${protocol:value}` reference resolution.
|
||||
- `packages/wow-contexts` registers `/wow:contexts:list`, `/wow:contexts:reload`, `/wow:init`, then injects configured Markdown files into `before_agent_start` system prompts.
|
||||
- `packages/wow-inject` loads `.env` files and `inject.env` values during setup and `session_start`, writing resolved values into `process.env`.
|
||||
|
||||
## Key Directories
|
||||
|
||||
- `packages/wow-pi/`: pi extension entry package and bundled `dist/` output.
|
||||
- `packages/wow-core/`: shared config, registry, logger, UI, utilities, and ref resolver.
|
||||
- `packages/wow-contexts/`: context scanning/building, command handlers, and init prompt.
|
||||
- `packages/wow-inject/`: `.env` parser and environment injection module.
|
||||
- `scripts/`: build and install/uninstall orchestration scripts.
|
||||
- `docs/contexts/`: subsystem reference cards loaded by wow contexts.
|
||||
|
||||
## Development Commands
|
||||
|
||||
- `bun install`: install workspace dependencies.
|
||||
- `bun run check`: run TypeScript type checking with `tsc --noEmit`.
|
||||
- `bun run build`: debug bundle for `packages/wow-pi` using Bun with `--target node`.
|
||||
- `bun run build:release`: minified release bundle.
|
||||
- `bun run script:install`: debug build and copy `packages/wow-pi/dist` to `~/.pi/agent/extensions/wow-pi`.
|
||||
- `bun run script:install:release`: release build and install with `WOW_RELEASE=1`.
|
||||
- `bun run script:uninstall`: remove installed `~/.pi/agent/extensions/wow-pi`.
|
||||
- `pi -e /path/to/wow-pi`: load this extension once from source for manual testing.
|
||||
|
||||
## Code Conventions & Common Patterns
|
||||
|
||||
- Use TypeScript ESM with strict type checking and Bun runtime types.
|
||||
- Keep feature modules self-registering: import side effects call `registerModule({ name, register })`.
|
||||
- Keep shared behavior in `wow-core`; feature packages should depend on `wow-core`, not each other.
|
||||
- Use `getWowSettingSync(key, cwd)` for module config and call `resetWowConfigCache()` when config should be re-read.
|
||||
- Use `registerWowCommand(pi, name, options)` so commands are namespaced as `/wow:<name>`.
|
||||
- Use `showInfo`, `showWarn`, or `show` for command UI notifications.
|
||||
- Avoid relying on source changes after install; installed pi code comes from copied `packages/wow-pi/dist`.
|
||||
|
||||
## Important Files
|
||||
|
||||
- `package.json`: workspace scripts, pi manifest, peer dependencies.
|
||||
- `packages/wow-pi/src/index.ts`: extension bootstrap and module registration loop.
|
||||
- `packages/wow-core/src/config.ts`: JSON/YAML config loading and merge rules.
|
||||
- `packages/wow-core/src/registry.ts`: module registration contract.
|
||||
- `packages/wow-contexts/src/index.ts`: `before_agent_start` context injection.
|
||||
- `packages/wow-contexts/src/commands.ts`: `/wow:*` command handlers.
|
||||
- `packages/wow-contexts/src/init-prompt.ts`: `/wow:init` prompt template.
|
||||
- `packages/wow-inject/src/env.ts`: environment injection flow.
|
||||
- `scripts/build.ts`: debug/release build orchestrator.
|
||||
- `scripts/install.ts`: hard-copy install/uninstall logic.
|
||||
- `wow.example.yaml`: example global/project wow configuration.
|
||||
|
||||
## Runtime/Tooling Preferences
|
||||
|
||||
- Use Bun for package management, scripts, and bundling.
|
||||
- Build bundles with `--target node`; pi loads extensions through Node/Jiti, and `--target bun` can emit `import.meta.require`, which fails under pi.
|
||||
- Externalize pi host packages in the bundle: `@earendil-works/pi-coding-agent`, `@earendil-works/pi-ai`, `@earendil-works/pi-tui`, and `typebox`.
|
||||
- Config files are `~/.pi/agent/wow.{json,yaml,yml}` and `<project>/.pi/wow.{json,yaml,yml}`; project config extends global config.
|
||||
- Logs write to `~/.pi/agent/wow/logs/wow.YYYY-MM-DD.log` with best-effort rotation.
|
||||
- `pi-subagents` may be installed in the user environment; `/wow:init` explicitly asks for read-only scout/context-builder fanout when available.
|
||||
|
||||
## Testing & QA
|
||||
|
||||
- There are no unit tests or test framework dependencies currently.
|
||||
- `bun run check` is the primary validation command and only runs TypeScript.
|
||||
- Build validation should include `bun run build` and `bun run build:release` when touching bundling or dependencies.
|
||||
- Install validation should include `bun run script:install` and a pi smoke command such as `PI_CODING_AGENT_DIR="$HOME/.pi/agent" pi --help`.
|
||||
- For extension behavior, manually verify `/wow:contexts:list`, `/wow:contexts:reload`, and `/wow:init` in pi.
|
||||
|
||||
## Module Contexts (`docs/contexts/`)
|
||||
|
||||
- `docs/contexts/wow-pi.md`: extension entry and module orchestration.
|
||||
- `docs/contexts/wow-core.md`: shared config, registry, logger, UI, and utilities.
|
||||
- `docs/contexts/wow-contexts.md`: Markdown context scanning, injection, and commands.
|
||||
- `docs/contexts/wow-inject.md`: `.env` loading and ref-based environment injection.
|
||||
- `docs/contexts/build-and-install.md`: bundle, install, release, and validation flow.
|
||||
36
README.md
Normal file
36
README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# wow-pi
|
||||
|
||||
A small extension pack for the [pi](https://github.com/earendil-works/pi) coding agent.
|
||||
|
||||
## Install for local development
|
||||
|
||||
```bash
|
||||
bun install
|
||||
pi install <wow-pi directory>
|
||||
```
|
||||
|
||||
Or test once:
|
||||
|
||||
```bash
|
||||
pi -e <wow-pi directory>
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
wow-pi reads JSON or YAML config from both global and project locations, in order:
|
||||
|
||||
1. `~/.pi/agent/wow.json`, `~/.pi/agent/wow.yaml`, `~/.pi/agent/wow.yml`
|
||||
2. `<project>/.pi/wow.json`, `<project>/.pi/wow.yaml`, `<project>/.pi/wow.yml`
|
||||
|
||||
Project config extends global config. Array fields are merged and de-duplicated; object fields are shallow-merged. See `wow.example.yaml` for a complete example.
|
||||
|
||||
## Packages
|
||||
|
||||
This repo is organized as a workspace monorepo:
|
||||
|
||||
- `packages/wow-core`: shared config, module registry, logger, UI helpers, and `${protocol:value}` resolvers.
|
||||
- `packages/wow-contexts`: self-registering Markdown context injector with `/wow:contexts:list`, `/wow:contexts:reload`, and `/wow:init`.
|
||||
- `packages/wow-inject`: self-registering env loader that reads `.env` files and resolves `${file:...}` / `${env:...}` into `process.env`.
|
||||
- `packages/wow-pi`: pi extension entry point; imports feature packages for side-effect registration, then runs registered modules.
|
||||
|
||||
Logs are written to `~/.pi/agent/wow/logs/`.
|
||||
12
biome.json
Normal file
12
biome.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2
|
||||
},
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true
|
||||
}
|
||||
}
|
||||
417
bun.lock
Normal file
417
bun.lock
Normal file
@@ -0,0 +1,417 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "wow-pi",
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.3.8",
|
||||
"@types/bun": "latest",
|
||||
"arg": "^5.0.2",
|
||||
"typescript": "^5.9.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@earendil-works/pi-ai": "*",
|
||||
"@earendil-works/pi-coding-agent": "*",
|
||||
"@earendil-works/pi-tui": "*",
|
||||
"typebox": "*",
|
||||
},
|
||||
},
|
||||
"packages/wow-contexts": {
|
||||
"name": "wow-contexts",
|
||||
"dependencies": {
|
||||
"wow-core": "workspace:*",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@earendil-works/pi-coding-agent": "*",
|
||||
},
|
||||
},
|
||||
"packages/wow-core": {
|
||||
"name": "wow-core",
|
||||
"dependencies": {
|
||||
"boxen": "^8.0.1",
|
||||
"yaml": "^2.8.2",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@earendil-works/pi-coding-agent": "*",
|
||||
},
|
||||
},
|
||||
"packages/wow-inject": {
|
||||
"name": "wow-inject",
|
||||
"dependencies": {
|
||||
"wow-core": "workspace:*",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@earendil-works/pi-coding-agent": "*",
|
||||
},
|
||||
},
|
||||
"packages/wow-pi": {
|
||||
"name": "wow-pi",
|
||||
"dependencies": {
|
||||
"wow-contexts": "workspace:*",
|
||||
"wow-core": "workspace:*",
|
||||
"wow-inject": "workspace:*",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@earendil-works/pi-coding-agent": "*",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.91.1", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw=="],
|
||||
|
||||
"@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="],
|
||||
|
||||
"@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="],
|
||||
|
||||
"@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="],
|
||||
|
||||
"@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="],
|
||||
|
||||
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
|
||||
|
||||
"@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/eventstream-handler-node": "^3.972.16", "@aws-sdk/middleware-eventstream": "^3.972.12", "@aws-sdk/middleware-websocket": "^3.972.19", "@aws-sdk/token-providers": "3.1048.0", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ=="],
|
||||
|
||||
"@aws-sdk/core": ["@aws-sdk/core@3.974.18", "", { "dependencies": { "@aws-sdk/types": "^3.973.11", "@aws-sdk/xml-builder": "^3.972.28", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.6", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-JDYCPI0j7zGrzXTDFsLB346cxss7J/AxH7+O0MzWlqppJBEyB9Qe6TQXRL6iwLUo/xZkNv9KFmBL2hqElmwW0g=="],
|
||||
|
||||
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.44", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-3hKJVrZ7bqXzDAXCQp+OaQ1ASN+vWstaNuEH418wQVl//cRZhqhfR9Bjk1qIWmgUGe8/D3gdO73PgidRj378EQ=="],
|
||||
|
||||
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.46", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-VhwC9pGAZHhiQ2xSViyOPDFqvr9aRxGCAXZtADsUhU3R65nad7y//CwynE6mQnWNR+suRlqE79W36IVayL+m1g=="],
|
||||
|
||||
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.50", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/credential-provider-env": "^3.972.44", "@aws-sdk/credential-provider-http": "^3.972.46", "@aws-sdk/credential-provider-login": "^3.972.49", "@aws-sdk/credential-provider-process": "^3.972.44", "@aws-sdk/credential-provider-sso": "^3.972.49", "@aws-sdk/credential-provider-web-identity": "^3.972.49", "@aws-sdk/nested-clients": "^3.997.17", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-09Xi6ovxiK42+De/qBGF71sT5F2bWgYM+1fFyDwSOpy1xpsQ5R/naIu7MVDpH6Dic36QNc8dAv4KADtMGK2JYg=="],
|
||||
|
||||
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.49", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/nested-clients": "^3.997.17", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-EfJF/1Fh9mI4pZyoheU2RY9xUhTcugIZNkD63+orXMkYj/QXacJNbKVDUK90Yv5hE+aX+rt9J/EZ9Qr3vKOa7g=="],
|
||||
|
||||
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.52", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.44", "@aws-sdk/credential-provider-http": "^3.972.46", "@aws-sdk/credential-provider-ini": "^3.972.50", "@aws-sdk/credential-provider-process": "^3.972.44", "@aws-sdk/credential-provider-sso": "^3.972.49", "@aws-sdk/credential-provider-web-identity": "^3.972.49", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-7QX+PbyiWBEOVipJq8Nke/TqXT6lAPLE7fvTaopa39/IVWuLfS+Fzdy71sZJONf/mLGgmtj6aU17+REw3+aRrw=="],
|
||||
|
||||
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.44", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-V+UUhZpRP7QDRhi+qgBDisM9tUBnYmMje8Bk77A6MZsfeGeGdMsQXmaHP1CDYFcept0o/Rz5g2Y0TMeVlG9dzg=="],
|
||||
|
||||
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.49", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/nested-clients": "^3.997.17", "@aws-sdk/token-providers": "3.1063.0", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-9QqOYGuh5tZ76OzaT68kwI78AH+5lS/uZGGvkfxb3fc8FzRrIz2jOufNTliEBEeSAwmgK2rWLNsK+IB3zbtNPA=="],
|
||||
|
||||
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.49", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/nested-clients": "^3.997.17", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-IYx1lN38MnnPXv+NBLpuATu0cZakbZ321TAfjW+aVkw7HIJF38YnEwdeEO55MSl3pl7hIX1IvvnD6EmnAzmAJw=="],
|
||||
|
||||
"@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.20", "", { "dependencies": { "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-qr/S1iFCDIXlZwlZPaCqjKcHbJFr9scIFUhbh2+SrwPXZvRhyOUWjVDJpp8xoU4qrrMR0PqK1Yw5C2sSj7xAyw=="],
|
||||
|
||||
"@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.16", "", { "dependencies": { "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-KR2Gdui/QLbkdG9FxW3vk/vIa8KiDP5vQBNERo7MmlPHjn23GXJ53Cq5P/ok7/ALbTUiYZ78DiBHoDcvzPWvgQ=="],
|
||||
|
||||
"@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.26", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-foM3KvxGBHY9lRIm6C9JJJ5haodtXfJPPgJQcv5/c4A2pN4I7tlnOjh1o2d8Il1Y/j6GWOw3YeIYc2/VYjtGVQ=="],
|
||||
|
||||
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.17", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.18", "@aws-sdk/signature-v4-multi-region": "^3.996.32", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-lDRgraoTfKRawUyc176Ow93mrNrOho/x+EoK4C+lKU+vKkHWhNhzvSMVAx0WEJUJoeQxxDN5ZdKMfiGEyNejig=="],
|
||||
|
||||
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.32", "", { "dependencies": { "@aws-sdk/types": "^3.973.11", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-llvApLcsWtmRFhG2wT3WIp1CmDeRaIYutqty1ZZXoMzK7TiJ6MOLOimk9eXUS8PwgG4ew4pa4QAbt0lfhn++1w=="],
|
||||
|
||||
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1048.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/nested-clients": "^3.997.9", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA=="],
|
||||
|
||||
"@aws-sdk/types": ["@aws-sdk/types@3.973.11", "", { "dependencies": { "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-YjS0qFuECClRh4qhEyW8XagW0fwEPBeZ1cfsW/gU73Kh/ExFILxbzxOfPCmzF/2DwEvhvsHYt0b0qnvStwKYrg=="],
|
||||
|
||||
"@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.6", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ZfHjfwSzeXj+Lg9AK5ZNmeDkXev6V+w2tn1t4kgDdRtUaRCthepTQiFwbD06EF9oNGH4LaLg+Mb6U16Ypv5bSw=="],
|
||||
|
||||
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.28", "", { "dependencies": { "@smithy/types": "^4.14.3", "fast-xml-parser": "5.7.3", "tslib": "^2.6.2" } }, "sha512-lI/l3c/vPvsxmspzV63NfS3x9q4CkMmdhJy4QiM+NThAufVkDvi/PZZQ6xETnICL0UD7jI808pY83gllf86RFg=="],
|
||||
|
||||
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="],
|
||||
|
||||
"@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
|
||||
|
||||
"@biomejs/biome": ["@biomejs/biome@2.4.16", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.16", "@biomejs/cli-darwin-x64": "2.4.16", "@biomejs/cli-linux-arm64": "2.4.16", "@biomejs/cli-linux-arm64-musl": "2.4.16", "@biomejs/cli-linux-x64": "2.4.16", "@biomejs/cli-linux-x64-musl": "2.4.16", "@biomejs/cli-win32-arm64": "2.4.16", "@biomejs/cli-win32-x64": "2.4.16" }, "bin": { "biome": "bin/biome" } }, "sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA=="],
|
||||
|
||||
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A=="],
|
||||
|
||||
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw=="],
|
||||
|
||||
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ=="],
|
||||
|
||||
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg=="],
|
||||
|
||||
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.16", "", { "os": "linux", "cpu": "x64" }, "sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ=="],
|
||||
|
||||
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.16", "", { "os": "linux", "cpu": "x64" }, "sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg=="],
|
||||
|
||||
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A=="],
|
||||
|
||||
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.16", "", { "os": "win32", "cpu": "x64" }, "sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw=="],
|
||||
|
||||
"@earendil-works/pi-agent-core": ["@earendil-works/pi-agent-core@0.78.1", "", { "dependencies": { "@earendil-works/pi-ai": "^0.78.1", "ignore": "7.0.5", "typebox": "1.1.38", "yaml": "2.9.0" } }, "sha512-oPwVRkkAvyKPWyM7E4k+EaTNmynbYn7ZLG/LBh9BUnMNb2gvpMp+VQ420R6JCJ20uogSqrHnWTyosSa/rU8lVw=="],
|
||||
|
||||
"@earendil-works/pi-ai": ["@earendil-works/pi-ai@0.78.1", "", { "dependencies": { "@anthropic-ai/sdk": "0.91.1", "@aws-sdk/client-bedrock-runtime": "3.1048.0", "@google/genai": "1.52.0", "@mistralai/mistralai": "2.2.1", "@smithy/node-http-handler": "4.7.3", "http-proxy-agent": "7.0.2", "https-proxy-agent": "7.0.6", "openai": "6.26.0", "partial-json": "0.1.7", "typebox": "1.1.38" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-CM2pkTs1iupG/maw381lC9Q/Y/aQaMGK7GILc28ttImD0ci3LDwKroDsGkWbly5JIy3iqxdRxB9JlG7vvzCzTg=="],
|
||||
|
||||
"@earendil-works/pi-coding-agent": ["@earendil-works/pi-coding-agent@0.78.1", "", { "dependencies": { "@earendil-works/pi-agent-core": "^0.78.1", "@earendil-works/pi-ai": "^0.78.1", "@earendil-works/pi-tui": "^0.78.1", "@silvia-odwyer/photon-node": "0.3.4", "chalk": "5.6.2", "cross-spawn": "7.0.6", "diff": "8.0.4", "glob": "13.0.6", "highlight.js": "10.7.3", "hosted-git-info": "9.0.3", "ignore": "7.0.5", "jiti": "2.7.0", "minimatch": "10.2.5", "proper-lockfile": "4.1.2", "typebox": "1.1.38", "undici": "8.3.0", "yaml": "2.9.0" }, "optionalDependencies": { "@mariozechner/clipboard": "0.3.9" }, "bin": { "pi": "dist/cli.js" } }, "sha512-Syjf6Ib8UoY5t9ZdKjp0BRrQZuFkFBc8j2KEU9zG/ZnmYPcAxYeioofdv2Q3MEXnHEX2U8sKQptkSnJIdMsd0g=="],
|
||||
|
||||
"@earendil-works/pi-tui": ["@earendil-works/pi-tui@0.78.1", "", { "dependencies": { "get-east-asian-width": "1.6.0", "marked": "15.0.12" } }, "sha512-07GVQo/38a0yvIPlWDr3RJn1B8gk3ZuIX9h2oIQ+Biyu3JN0KppWmgWHfaWRydQgse5JtC++KDw5MWaIRnV0mw=="],
|
||||
|
||||
"@google/genai": ["@google/genai@1.52.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q=="],
|
||||
|
||||
"@mariozechner/clipboard": ["@mariozechner/clipboard@0.3.9", "", { "optionalDependencies": { "@mariozechner/clipboard-darwin-arm64": "0.3.9", "@mariozechner/clipboard-darwin-universal": "0.3.9", "@mariozechner/clipboard-darwin-x64": "0.3.9", "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9", "@mariozechner/clipboard-linux-arm64-musl": "0.3.9", "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9", "@mariozechner/clipboard-linux-x64-gnu": "0.3.9", "@mariozechner/clipboard-linux-x64-musl": "0.3.9", "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9", "@mariozechner/clipboard-win32-x64-msvc": "0.3.9" } }, "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA=="],
|
||||
|
||||
"@mariozechner/clipboard-darwin-arm64": ["@mariozechner/clipboard-darwin-arm64@0.3.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ=="],
|
||||
|
||||
"@mariozechner/clipboard-darwin-universal": ["@mariozechner/clipboard-darwin-universal@0.3.9", "", { "os": "darwin" }, "sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ=="],
|
||||
|
||||
"@mariozechner/clipboard-darwin-x64": ["@mariozechner/clipboard-darwin-x64@0.3.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg=="],
|
||||
|
||||
"@mariozechner/clipboard-linux-arm64-gnu": ["@mariozechner/clipboard-linux-arm64-gnu@0.3.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw=="],
|
||||
|
||||
"@mariozechner/clipboard-linux-arm64-musl": ["@mariozechner/clipboard-linux-arm64-musl@0.3.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ=="],
|
||||
|
||||
"@mariozechner/clipboard-linux-riscv64-gnu": ["@mariozechner/clipboard-linux-riscv64-gnu@0.3.9", "", { "os": "linux", "cpu": "none" }, "sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw=="],
|
||||
|
||||
"@mariozechner/clipboard-linux-x64-gnu": ["@mariozechner/clipboard-linux-x64-gnu@0.3.9", "", { "os": "linux", "cpu": "x64" }, "sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw=="],
|
||||
|
||||
"@mariozechner/clipboard-linux-x64-musl": ["@mariozechner/clipboard-linux-x64-musl@0.3.9", "", { "os": "linux", "cpu": "x64" }, "sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ=="],
|
||||
|
||||
"@mariozechner/clipboard-win32-arm64-msvc": ["@mariozechner/clipboard-win32-arm64-msvc@0.3.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ=="],
|
||||
|
||||
"@mariozechner/clipboard-win32-x64-msvc": ["@mariozechner/clipboard-win32-x64-msvc@0.3.9", "", { "os": "win32", "cpu": "x64" }, "sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA=="],
|
||||
|
||||
"@mistralai/mistralai": ["@mistralai/mistralai@2.2.1", "", { "dependencies": { "ws": "^8.18.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.25.0" } }, "sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ=="],
|
||||
|
||||
"@nodable/entities": ["@nodable/entities@2.1.1", "", {}, "sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg=="],
|
||||
|
||||
"@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
|
||||
|
||||
"@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="],
|
||||
|
||||
"@protobufjs/codegen": ["@protobufjs/codegen@2.0.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="],
|
||||
|
||||
"@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.1", "", {}, "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg=="],
|
||||
|
||||
"@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="],
|
||||
|
||||
"@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="],
|
||||
|
||||
"@protobufjs/inquire": ["@protobufjs/inquire@1.1.2", "", {}, "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw=="],
|
||||
|
||||
"@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="],
|
||||
|
||||
"@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="],
|
||||
|
||||
"@protobufjs/utf8": ["@protobufjs/utf8@1.1.1", "", {}, "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg=="],
|
||||
|
||||
"@silvia-odwyer/photon-node": ["@silvia-odwyer/photon-node@0.3.4", "", {}, "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA=="],
|
||||
|
||||
"@smithy/core": ["@smithy/core@3.24.6", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-wBXDRup6UU97VKyaiRo8AssnfStPtG0oAAfpq/bC0a1YYau8pM86YB4kM6ccoVi1mS8l/UHbn9oDM+7uozr/ug=="],
|
||||
|
||||
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.3.8", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-5cAM+KZC02sTqDt6NaLXyu50M/GNMd1eTzDVR8Lb0BBsVtu7RWHo47VPPEEv1vt3Yub6uzr+M5FHC+GtoT0USg=="],
|
||||
|
||||
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.4.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-FEwEYJ1jlBKdhe9TPzfghEi1bP55ZeEImlDkEa62bBBYzUcnB6RUCyuiS2mqKt6ZVjUbBgcNhzfIctH+Hevx9g=="],
|
||||
|
||||
"@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
||||
|
||||
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA=="],
|
||||
|
||||
"@smithy/signature-v4": ["@smithy/signature-v4@5.4.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-Ojg4B6oIDlIr1R86xCDJt1zJWnYa0VINmqdjfe9qxWjdRivHalZ3iSlQgVqYbW0MdpFOC5XfHEWsnbmdnpIILQ=="],
|
||||
|
||||
"@smithy/types": ["@smithy/types@4.14.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ=="],
|
||||
|
||||
"@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||
|
||||
"@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
|
||||
|
||||
"@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="],
|
||||
|
||||
"@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
|
||||
|
||||
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
||||
|
||||
"ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
|
||||
|
||||
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
|
||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||
|
||||
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
|
||||
|
||||
"bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="],
|
||||
|
||||
"boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
|
||||
|
||||
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
||||
|
||||
"camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="],
|
||||
|
||||
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
"cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="],
|
||||
|
||||
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
|
||||
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
||||
|
||||
"fast-xml-builder": ["fast-xml-builder@1.2.0", "", { "dependencies": { "path-expression-matcher": "^1.5.0", "xml-naming": "^0.1.0" } }, "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q=="],
|
||||
|
||||
"fast-xml-parser": ["fast-xml-parser@5.7.3", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.7", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg=="],
|
||||
|
||||
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
|
||||
|
||||
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
|
||||
|
||||
"gaxios": ["gaxios@7.1.5", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-5FZy72Rh8LhtjmvDrKkI+lVhrsQrVKVsItxMoDm5mNQE+xR0WVIIs+jzPSJgBvKVsLi24fZhXJIsNI0bihDzFg=="],
|
||||
|
||||
"gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
|
||||
|
||||
"get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="],
|
||||
|
||||
"glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
|
||||
|
||||
"google-auth-library": ["google-auth-library@10.7.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-QpTAbNJ36TliZLx3TTtahR8HG0hN9RllL1e3FymOvQSIKK8JmgV58H924ub2wa2DsS3ANjjP1Aw1N+Ramc8hqQ=="],
|
||||
|
||||
"google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="],
|
||||
|
||||
"hosted-git-info": ["hosted-git-info@9.0.3", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg=="],
|
||||
|
||||
"http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
|
||||
|
||||
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
|
||||
|
||||
"ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
|
||||
|
||||
"json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
|
||||
|
||||
"json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
|
||||
|
||||
"jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
|
||||
|
||||
"jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
|
||||
|
||||
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
|
||||
|
||||
"lru-cache": ["lru-cache@11.5.1", "", {}, "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A=="],
|
||||
|
||||
"marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="],
|
||||
|
||||
"minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
|
||||
|
||||
"node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
|
||||
|
||||
"openai": ["openai@6.26.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA=="],
|
||||
|
||||
"p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="],
|
||||
|
||||
"partial-json": ["partial-json@0.1.7", "", {}, "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA=="],
|
||||
|
||||
"path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
|
||||
|
||||
"proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="],
|
||||
|
||||
"protobufjs": ["protobufjs@7.6.2", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-N9EiLovGEQOJSPF26Ij7qUGvahfEnq0eeYZ02aigIedkmz1qZSwjnP9SBITHJuF/6MYbIW4HDN8zdYjsjqJKXQ=="],
|
||||
|
||||
"retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
|
||||
|
||||
"strnum": ["strnum@2.3.0", "", {}, "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q=="],
|
||||
|
||||
"ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
|
||||
|
||||
"typebox": ["typebox@1.2.2", "", {}, "sha512-0nqIJFL+baWoAEtwa0l/vfbfXg0+3gEhiWGnHuoIiivXjlk/TpxDddG0WER34CojKNpHi4ZXku8XGEz9H55b5Q=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"undici": ["undici@8.3.0", "", {}, "sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q=="],
|
||||
|
||||
"undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
||||
|
||||
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="],
|
||||
|
||||
"wow-contexts": ["wow-contexts@workspace:packages/wow-contexts"],
|
||||
|
||||
"wow-core": ["wow-core@workspace:packages/wow-core"],
|
||||
|
||||
"wow-inject": ["wow-inject@workspace:packages/wow-inject"],
|
||||
|
||||
"wow-pi": ["wow-pi@workspace:packages/wow-pi"],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
|
||||
|
||||
"ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="],
|
||||
|
||||
"xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="],
|
||||
|
||||
"yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
|
||||
|
||||
"zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
||||
|
||||
"zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
|
||||
|
||||
"@aws-sdk/credential-provider-http/@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.7", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-ZAFvHXrEk6K180EVhmZVg8GU5pUH5BSFqRs27JW3j1qEFx9YyYwWFx17x/MHcjALYimGAji7qEOlF1++be+G5A=="],
|
||||
|
||||
"@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1063.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.18", "@aws-sdk/nested-clients": "^3.997.17", "@aws-sdk/types": "^3.973.11", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-nYDaWWdzjKiDP5xj8k4oUgcYd4WPgzfAOgdU5vJsaqH/07Dfvm7ffisHCFJ+NEl7kUC9JEIUxh0kznvenbo3NQ=="],
|
||||
|
||||
"@aws-sdk/nested-clients/@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.7", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-ZAFvHXrEk6K180EVhmZVg8GU5pUH5BSFqRs27JW3j1qEFx9YyYwWFx17x/MHcjALYimGAji7qEOlF1++be+G5A=="],
|
||||
|
||||
"@earendil-works/pi-agent-core/typebox": ["typebox@1.1.38", "", {}, "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA=="],
|
||||
|
||||
"@earendil-works/pi-ai/typebox": ["typebox@1.1.38", "", {}, "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA=="],
|
||||
|
||||
"@earendil-works/pi-coding-agent/typebox": ["typebox@1.1.38", "", {}, "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA=="],
|
||||
|
||||
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
|
||||
|
||||
"ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
}
|
||||
}
|
||||
40
package.json
Normal file
40
package.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "wow-pi-workspace",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"keywords": [
|
||||
"pi-package"
|
||||
],
|
||||
"pi": {
|
||||
"extensions": [
|
||||
"./packages/wow-pi/src/index.ts"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"check": "tsc -p tsconfig.json --noEmit",
|
||||
"build": "bun scripts/build.ts",
|
||||
"build:release": "bun scripts/build.ts --target release",
|
||||
"format": "biome check --write .",
|
||||
"lint": "biome check .",
|
||||
|
||||
"script:install": "bun scripts/install.ts",
|
||||
"script:install:release": "WOW_RELEASE=1 bun scripts/install.ts",
|
||||
"script:uninstall": "bun scripts/install.ts --uninstall"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.3.8",
|
||||
"@types/bun": "latest",
|
||||
"arg": "^5.0.2",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@earendil-works/pi-ai": "*",
|
||||
"@earendil-works/pi-coding-agent": "*",
|
||||
"@earendil-works/pi-tui": "*",
|
||||
"typebox": "*"
|
||||
}
|
||||
}
|
||||
16
packages/wow-contexts/package.json
Normal file
16
packages/wow-contexts/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "wow-contexts",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"wow-core": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@earendil-works/pi-coding-agent": "*"
|
||||
}
|
||||
}
|
||||
61
packages/wow-contexts/src/builder.ts
Normal file
61
packages/wow-contexts/src/builder.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
type ContextFileEntry,
|
||||
resolveContextPath,
|
||||
scanDir,
|
||||
tryRead,
|
||||
} from "./files";
|
||||
import { resolvePaths } from "./resolver";
|
||||
|
||||
export interface BuildResult {
|
||||
content: string | null;
|
||||
loadedPaths: Map<string, string>;
|
||||
}
|
||||
|
||||
export async function build(cwd: string): Promise<BuildResult> {
|
||||
const paths = resolvePaths(cwd);
|
||||
const files: ContextFileEntry[] = [];
|
||||
const loadedPaths = new Map<string, string>();
|
||||
|
||||
for (const entry of paths) {
|
||||
if (entry.endsWith("/*.md")) {
|
||||
const dir = resolveContextPath(entry.slice(0, -4), cwd);
|
||||
const before = files.length;
|
||||
await scanDir(dir, files);
|
||||
for (let index = before; index < files.length; index++) {
|
||||
const file = files[index];
|
||||
if (file) loadedPaths.set(file.rel, fileHash(file.content));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const abs = resolveContextPath(entry, cwd);
|
||||
const before = files.length;
|
||||
await tryRead(abs, entry, files);
|
||||
if (files.length > before) {
|
||||
const file = files[files.length - 1];
|
||||
if (file) loadedPaths.set(abs, fileHash(file.content));
|
||||
}
|
||||
}
|
||||
|
||||
const nonEmpty = files.filter((file) => file.content.length > 0);
|
||||
|
||||
return {
|
||||
content:
|
||||
nonEmpty.length > 0
|
||||
? nonEmpty
|
||||
.map(
|
||||
(file) => `<file path="${file.rel}">\n${file.content}\n</file>`,
|
||||
)
|
||||
.join("\n\n")
|
||||
: null,
|
||||
loadedPaths,
|
||||
};
|
||||
}
|
||||
|
||||
export function fileHash(content: string): string {
|
||||
let hash = 0;
|
||||
for (let index = 0; index < content.length; index++) {
|
||||
hash = ((hash << 5) - hash + content.charCodeAt(index)) | 0;
|
||||
}
|
||||
return hash.toString(16);
|
||||
}
|
||||
187
packages/wow-contexts/src/commands.ts
Normal file
187
packages/wow-contexts/src/commands.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import type {
|
||||
ExtensionAPI,
|
||||
ExtensionCommandContext,
|
||||
} from "@earendil-works/pi-coding-agent";
|
||||
import { registerWowCommand, show, showInfo, showWarn } from "wow-core";
|
||||
import { fileHash } from "./builder";
|
||||
import { resolveContextPath } from "./files";
|
||||
import { buildInitPrompt } from "./init-prompt";
|
||||
import { resolvePaths } from "./resolver";
|
||||
import { contextState } from "./state";
|
||||
|
||||
interface FileEntry {
|
||||
abs: string;
|
||||
display: string;
|
||||
}
|
||||
|
||||
export function registerCommands(pi: ExtensionAPI): void {
|
||||
registerWowCommand(pi, "contexts:list", {
|
||||
description: "List all wow context files with load status",
|
||||
handler: handleList,
|
||||
});
|
||||
registerWowCommand(pi, "contexts:reload", {
|
||||
description: "Reload wow context files on the next message",
|
||||
handler: handleReload,
|
||||
});
|
||||
registerWowCommand(pi, "init", {
|
||||
description:
|
||||
"Generate AGENTS.md and docs/contexts with a pi-optimized prompt",
|
||||
handler: (args, ctx) => handleInit(args, ctx, pi),
|
||||
});
|
||||
}
|
||||
|
||||
async function handleList(
|
||||
_args: string,
|
||||
ctx: ExtensionCommandContext,
|
||||
): Promise<void> {
|
||||
if (!contextState.cwd) {
|
||||
showWarn(ctx, "No context files scanned yet. Send a message first.");
|
||||
return;
|
||||
}
|
||||
|
||||
const found = await findCandidateFiles(contextState.cwd);
|
||||
if (found.length === 0) {
|
||||
showInfo(ctx, "No context files found in configured paths.");
|
||||
return;
|
||||
}
|
||||
|
||||
const loaded: FileEntry[] = [];
|
||||
const modified: FileEntry[] = [];
|
||||
const unloaded: FileEntry[] = [];
|
||||
|
||||
for (const file of found) {
|
||||
const storedHash = contextState.wowPaths.get(file.abs);
|
||||
if (storedHash === undefined) {
|
||||
unloaded.push(file);
|
||||
continue;
|
||||
}
|
||||
|
||||
const currentHash = await computeHash(file.abs);
|
||||
if (currentHash !== null && currentHash === storedHash) {
|
||||
loaded.push(file);
|
||||
} else {
|
||||
modified.push(file);
|
||||
}
|
||||
}
|
||||
|
||||
const hasChanges = modified.length > 0 || unloaded.length > 0;
|
||||
|
||||
show(
|
||||
ctx,
|
||||
"info",
|
||||
"Context Files",
|
||||
{
|
||||
type: "list",
|
||||
sections: [
|
||||
{
|
||||
summary: `● ${loaded.length} loaded, ○ ${modified.length} modified, ○ ${unloaded.length} unloaded`,
|
||||
items: [
|
||||
...loaded.map((file) => `● ${file.display}`),
|
||||
...modified.map((file) => `○ ${file.display} (modified)`),
|
||||
...unloaded.map((file) => `○ ${file.display} (new)`),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
...(hasChanges
|
||||
? [
|
||||
{
|
||||
type: "text" as const,
|
||||
content: "Use /wow:contexts:reload to pick up changes",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
);
|
||||
}
|
||||
|
||||
async function handleReload(
|
||||
_args: string,
|
||||
ctx: ExtensionCommandContext,
|
||||
): Promise<void> {
|
||||
if (!contextState.cwd) {
|
||||
showWarn(ctx, "No context files scanned yet. Send a message first.");
|
||||
return;
|
||||
}
|
||||
|
||||
const hadFiles = contextState.wowPaths.size > 0;
|
||||
contextState.rebuild = true;
|
||||
|
||||
showInfo(
|
||||
ctx,
|
||||
`Context reload queued${hadFiles ? "" : " — no files were loaded previously"}`,
|
||||
["New context files will be picked up on your next message."],
|
||||
);
|
||||
}
|
||||
|
||||
async function handleInit(
|
||||
args: string,
|
||||
ctx: ExtensionCommandContext,
|
||||
pi: ExtensionAPI,
|
||||
): Promise<void> {
|
||||
const prompt = buildInitPrompt(args);
|
||||
if (ctx.isIdle()) {
|
||||
pi.sendUserMessage(prompt);
|
||||
} else {
|
||||
pi.sendUserMessage(prompt, { deliverAs: "followUp" });
|
||||
}
|
||||
showInfo(ctx, "Repository init prompt sent", [
|
||||
"The agent will generate AGENTS.md and docs/contexts using pi-native guidance.",
|
||||
"If pi-subagents is available, it is instructed to use read-only scout/context-builder fanout before synthesis.",
|
||||
]);
|
||||
}
|
||||
|
||||
async function findCandidateFiles(cwd: string): Promise<FileEntry[]> {
|
||||
const found: FileEntry[] = [];
|
||||
|
||||
for (const entry of resolvePaths(cwd)) {
|
||||
if (entry.endsWith("/*.md")) {
|
||||
const dir = resolveContextPath(entry.slice(0, -4), cwd);
|
||||
const names = await readdirSafe(dir);
|
||||
for (const name of names.sort()) {
|
||||
if (!name.endsWith(".md")) continue;
|
||||
const abs = path.resolve(dir, name);
|
||||
found.push({ abs, display: path.relative(cwd, abs) });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const abs = resolveContextPath(entry, cwd);
|
||||
if (await existsSafe(abs)) found.push({ abs, display: entry });
|
||||
}
|
||||
|
||||
const deduped: FileEntry[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const file of found) {
|
||||
if (seen.has(file.abs)) continue;
|
||||
seen.add(file.abs);
|
||||
deduped.push(file);
|
||||
}
|
||||
return deduped;
|
||||
}
|
||||
|
||||
async function readdirSafe(dir: string): Promise<string[]> {
|
||||
try {
|
||||
return await fs.readdir(dir);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function existsSafe(filePath: string): Promise<boolean> {
|
||||
try {
|
||||
return (await fs.stat(filePath)).isFile();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function computeHash(absPath: string): Promise<string | null> {
|
||||
try {
|
||||
const content = await fs.readFile(absPath, "utf-8");
|
||||
return fileHash(content.trim());
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
44
packages/wow-contexts/src/files.ts
Normal file
44
packages/wow-contexts/src/files.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import { resolvePath } from "wow-core";
|
||||
|
||||
export interface ContextFileEntry {
|
||||
rel: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export function resolveContextPath(input: string, cwd: string): string {
|
||||
return resolvePath(input, cwd);
|
||||
}
|
||||
|
||||
export async function scanDir(
|
||||
dir: string,
|
||||
out: ContextFileEntry[],
|
||||
): Promise<void> {
|
||||
try {
|
||||
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
||||
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
||||
const filePath = path.join(dir, entry.name);
|
||||
const content = await fs.readFile(filePath, "utf-8");
|
||||
out.push({ rel: filePath, content: content.trim() });
|
||||
}
|
||||
} catch {
|
||||
// Missing/unreadable context directories are ignored.
|
||||
}
|
||||
}
|
||||
|
||||
export async function tryRead(
|
||||
absPath: string,
|
||||
relHint: string,
|
||||
out: ContextFileEntry[],
|
||||
): Promise<void> {
|
||||
try {
|
||||
const stat = await fs.stat(absPath);
|
||||
if (!stat.isFile()) return;
|
||||
const content = await fs.readFile(absPath, "utf-8");
|
||||
out.push({ rel: relHint, content: content.trim() });
|
||||
} catch {
|
||||
// Missing/unreadable context files are ignored.
|
||||
}
|
||||
}
|
||||
4
packages/wow-contexts/src/global.ts
Normal file
4
packages/wow-contexts/src/global.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { createLogger } from "wow-core";
|
||||
|
||||
export const TAG = "contexts";
|
||||
export const log = createLogger(TAG);
|
||||
60
packages/wow-contexts/src/index.ts
Normal file
60
packages/wow-contexts/src/index.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import type { BeforeAgentStartEventResult } from "@earendil-works/pi-coding-agent";
|
||||
import type { ModuleSetup } from "wow-core";
|
||||
import { registerModule, resetWowConfigCache } from "wow-core";
|
||||
import { build } from "./builder";
|
||||
import { registerCommands } from "./commands";
|
||||
import { log, TAG } from "./global";
|
||||
import { contextState } from "./state";
|
||||
|
||||
export type { ContextFileEntry } from "./files";
|
||||
|
||||
const setup: ModuleSetup = (pi) => {
|
||||
let builtContent: string | null = null;
|
||||
|
||||
registerCommands(pi);
|
||||
|
||||
contextState.cwd = process.cwd();
|
||||
void rebuild(contextState.cwd).then((content) => {
|
||||
builtContent = content;
|
||||
});
|
||||
|
||||
pi.on(
|
||||
"before_agent_start",
|
||||
async (event, ctx): Promise<BeforeAgentStartEventResult | undefined> => {
|
||||
const forceRebuild = contextState.rebuild;
|
||||
const cwdChanged = ctx.cwd !== contextState.cwd;
|
||||
|
||||
if (forceRebuild || cwdChanged || builtContent === null) {
|
||||
resetWowConfigCache();
|
||||
contextState.rebuild = false;
|
||||
contextState.cwd = ctx.cwd;
|
||||
builtContent = await rebuild(ctx.cwd);
|
||||
}
|
||||
|
||||
if (!builtContent) return undefined;
|
||||
|
||||
return {
|
||||
systemPrompt: `${event.systemPrompt}\n\n${builtContent}`,
|
||||
};
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
registerModule({ name: TAG, register: setup });
|
||||
|
||||
async function rebuild(cwd: string): Promise<string> {
|
||||
try {
|
||||
const result = await build(cwd);
|
||||
contextState.wowPaths = result.loadedPaths;
|
||||
if (result.content) {
|
||||
log.debug("context files rebuilt", {
|
||||
paths: [...contextState.wowPaths.keys()],
|
||||
});
|
||||
return result.content;
|
||||
}
|
||||
} catch (error) {
|
||||
contextState.wowPaths = new Map();
|
||||
log.error("context build failed", { error: String(error) });
|
||||
}
|
||||
return "";
|
||||
}
|
||||
99
packages/wow-contexts/src/init-prompt.ts
Normal file
99
packages/wow-contexts/src/init-prompt.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
const INIT_PROMPT_LINES = [
|
||||
"# Generate AGENTS.md by launching multiple explore agents in parallel when available, first identifying the project ecosystem, then scanning evidence-based areas and synthesizing findings into a single file.",
|
||||
"",
|
||||
"This prompt must work for repositories written in any programming language or technology stack, including application code, libraries, services, mobile apps, infrastructure/IaC, docs-only repos, monorepos, polyglot workspaces, and mixed tooling.",
|
||||
"",
|
||||
"Start with an ecosystem discovery pass before assigning deeper scans. Identify languages, runtimes, package/build systems, frameworks, test tools, entry points, deployment shape, and whether the repo is a monorepo or single project. Use concrete files as evidence, not assumptions.",
|
||||
"",
|
||||
"If pi-subagents is installed and available, use it for parallel read-only exploration after the ecosystem pass. Prefer `scout` or `context-builder` agents with distinct evidence-based scopes. If pi-subagents is not available, perform the same exploration sequentially with normal read/search tools and continue without asking for permission just because parallelism is unavailable.",
|
||||
"",
|
||||
"Keep the parent agent responsible for final synthesis and all repository writes. Parallel agents should gather context and produce findings only.",
|
||||
"",
|
||||
"<structure>",
|
||||
"- **Project Overview**: Brief description of project purpose",
|
||||
"- **Architecture & Data Flow**: High-level structure, key modules, data flow",
|
||||
"- **Key Directories**: Main source directories, purposes",
|
||||
"- **Development Commands**: Build, test, lint, run commands",
|
||||
"- **Code Conventions & Common Patterns**: Formatting, naming, error handling, async patterns, dependency injection, state management",
|
||||
"- **Important Files**: Entry points, config files, key modules",
|
||||
"- **Runtime/Tooling Preferences**: Required runtime(s), package/build tools, environment setup, tooling constraints, pi-specific notes when relevant",
|
||||
"- **Testing & QA**: Test frameworks, running tests, coverage expectations",
|
||||
"- **Module Contexts** (`docs/contexts/`): One `.md` per subsystem",
|
||||
"</structure>",
|
||||
"",
|
||||
"<directives>",
|
||||
"- You MUST title the document `Repository Guidelines`",
|
||||
"- You MUST use Markdown headings for structure",
|
||||
"- You MUST be concise and practical",
|
||||
"- You MUST focus on what an AI assistant needs to help with the codebase",
|
||||
"- You SHOULD include examples where helpful (commands, paths, naming patterns)",
|
||||
"- You SHOULD include file paths where relevant",
|
||||
"- You MUST call out architecture and code patterns explicitly",
|
||||
"- You SHOULD omit information obvious from code structure",
|
||||
"- You MUST NOT paste concrete implementation code (function bodies, struct fields, line-by-line logic); signatures or interface shapes are the maximum level of detail",
|
||||
"- You MUST keep every file concise; shorter is better",
|
||||
"- When uncertain, prefer less detail over speculative detail",
|
||||
"- You MUST preserve existing useful guidance from `AGENTS.md`, `CLAUDE.md`, `.pi/`, README files, and docs instead of overwriting it blindly",
|
||||
"- You SHOULD note installed pi helper capabilities when they materially affect future agents, especially `pi-subagents` orchestration patterns if available",
|
||||
"</directives>",
|
||||
"",
|
||||
"<exploration-workflow>",
|
||||
"1. Ecosystem discovery (parent or one scout): identify repo shape and evidence files before deeper work. Look for language/tool markers such as `package.json`, `bun.lock`, `pnpm-lock.yaml`, `pyproject.toml`, `requirements.txt`, `go.mod`, `Cargo.toml`, `pom.xml`, `build.gradle`, `settings.gradle`, `*.sln`, `*.csproj`, `Package.swift`, `Gemfile`, `composer.json`, `mix.exs`, `Makefile`, `CMakeLists.txt`, `Dockerfile`, `docker-compose.yml`, Terraform/OpenTofu files, Kubernetes manifests, CI workflows, and docs.",
|
||||
"2. Parallel or sequential evidence scans: choose scopes based on the discovered ecosystem. Do not force JavaScript-style categories on non-JS repos.",
|
||||
"3. Parent synthesis: verify high-impact claims, resolve contradictions, decide module context files, then write final outputs.",
|
||||
"</exploration-workflow>",
|
||||
"",
|
||||
"<scan-matrix>",
|
||||
"Use these language-agnostic scan targets, adapting names and examples to the actual repo:",
|
||||
"- **Entry Points & Lifecycle**: executable entry points, library exports, service startup, CLI commands, web/mobile app bootstrap, extension hooks, background jobs, migrations, deployment entrypoints.",
|
||||
"- **Architecture & Module Boundaries**: major packages/modules/services, dependency direction, public APIs, data/control flow, storage/network boundaries, generated code boundaries.",
|
||||
"- **Configuration & Environment**: config files, env vars, secrets conventions, profiles/flavors, feature flags, local dev setup, runtime prerequisites.",
|
||||
"- **Build, Install & Release**: build graph, package manager, scripts/tasks, generated artifacts, install/deploy scripts, containers, CI/CD, versioning/release notes.",
|
||||
"- **Testing & Validation**: unit/integration/e2e tests, fixtures, linters, formatters, type checks, static analysis, manual validation flows, known gaps.",
|
||||
"- **Docs & Existing Guidance**: README, AGENTS/CLAUDE files, docs, examples, ADRs/design notes, changelogs, existing `docs/contexts` content to preserve or update.",
|
||||
"- **Operational Risks & Gotchas**: platform assumptions, concurrency/state, migrations, external services, security/privacy concerns, performance-sensitive paths, flaky tests, generated files not to edit.",
|
||||
"</scan-matrix>",
|
||||
"",
|
||||
"<parallel-exploration>",
|
||||
"When pi-subagents is available, launch a read-only parallel context pass after ecosystem discovery. Use fresh context unless inherited conversation decisions are required. Pick 2-4 agents based on repo size and shape. Recommended generic scopes:",
|
||||
"",
|
||||
"1. `scout` or `context-builder`: Entry points, architecture, module boundaries, and data/control flow.",
|
||||
"2. `scout` or `context-builder`: Configuration, environment, build/install/release tooling, generated artifacts, and deployment shape.",
|
||||
"3. `scout` or `context-builder`: Tests, QA, validation commands, fixtures, static analysis, and reliability gaps.",
|
||||
"4. `scout` or `context-builder`: Existing docs/guidance, examples, module context candidates, stale or conflicting instructions.",
|
||||
"",
|
||||
"Each child must return:",
|
||||
"- `Files Retrieved`: exact files and line ranges inspected.",
|
||||
"- `Facts With Evidence`: concrete findings tied to file paths.",
|
||||
"- `Commands`: real commands discovered from the repo, not generic guesses.",
|
||||
"- `Module Candidates`: recommended `docs/contexts/<name>.md` files from this scan area.",
|
||||
"- `Gotchas`: risks or caveats backed by evidence.",
|
||||
"- `Unknowns`: unresolved facts; do not speculate.",
|
||||
"",
|
||||
"Children must not edit project files. The parent must synthesize child outputs, verify important claims by reading high-value files when needed, then write final files.",
|
||||
"</parallel-exploration>",
|
||||
"",
|
||||
"<module-format>",
|
||||
"- No paragraphs, tables, code blocks, or ASCII diagrams",
|
||||
"- Write each file as a reference card: purpose, API surface, key types, file paths only. No explanations or walkthroughs.",
|
||||
"- Include commands/tests/gotchas only when they are specific to that subsystem",
|
||||
"- Keep module context files short enough to be loaded as context",
|
||||
"</module-format>",
|
||||
"",
|
||||
"<output>",
|
||||
"After analysis, you MUST write `AGENTS.md` to the project root.",
|
||||
"You MUST also write all module-related files (`<module-name>.md`) to the `docs/contexts` directory.",
|
||||
"Create `docs/contexts` if it does not exist.",
|
||||
"If existing module context files already exist, update them instead of duplicating them.",
|
||||
"",
|
||||
"$@",
|
||||
"</output>",
|
||||
];
|
||||
|
||||
export const INIT_PROMPT = INIT_PROMPT_LINES.join("\n");
|
||||
|
||||
export function buildInitPrompt(args: string): string {
|
||||
const trimmed = args.trim();
|
||||
if (!trimmed) return INIT_PROMPT;
|
||||
return INIT_PROMPT.replace("$@", trimmed);
|
||||
}
|
||||
16
packages/wow-contexts/src/resolver.ts
Normal file
16
packages/wow-contexts/src/resolver.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { getWowSettingSync } from "wow-core";
|
||||
import { TAG } from "./global";
|
||||
|
||||
const DEFAULT_PATHS: string[] = [
|
||||
"~/.pi/agent/contexts/*.md",
|
||||
".pi/contexts/*.md",
|
||||
"docs/contexts/*.md",
|
||||
];
|
||||
|
||||
export function resolvePaths(cwd = process.cwd()): string[] {
|
||||
const configured = getWowSettingSync<string[]>(TAG, cwd);
|
||||
if (Array.isArray(configured) && configured.length > 0) {
|
||||
return configured.map(String);
|
||||
}
|
||||
return DEFAULT_PATHS;
|
||||
}
|
||||
11
packages/wow-contexts/src/state.ts
Normal file
11
packages/wow-contexts/src/state.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export interface ContextState {
|
||||
cwd: string;
|
||||
wowPaths: Map<string, string>;
|
||||
rebuild: boolean;
|
||||
}
|
||||
|
||||
export const contextState: ContextState = {
|
||||
cwd: "",
|
||||
wowPaths: new Map<string, string>(),
|
||||
rebuild: false,
|
||||
};
|
||||
13
packages/wow-contexts/tsconfig.json
Normal file
13
packages/wow-contexts/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"types": ["bun"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
17
packages/wow-core/package.json
Normal file
17
packages/wow-core/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "wow-core",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"boxen": "^8.0.1",
|
||||
"yaml": "^2.8.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@earendil-works/pi-coding-agent": "*"
|
||||
}
|
||||
}
|
||||
23
packages/wow-core/src/commands.ts
Normal file
23
packages/wow-core/src/commands.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type {
|
||||
ExtensionAPI,
|
||||
ExtensionCommandContext,
|
||||
} from "@earendil-works/pi-coding-agent";
|
||||
|
||||
const WOW_NS = "wow";
|
||||
|
||||
export function registerWowCommand(
|
||||
pi: Pick<ExtensionAPI, "registerCommand">,
|
||||
module: string,
|
||||
options: {
|
||||
description?: string;
|
||||
getArgumentCompletions?: (
|
||||
prefix: string,
|
||||
) => unknown[] | null | Promise<unknown[] | null>;
|
||||
handler: (args: string, ctx: ExtensionCommandContext) => Promise<void>;
|
||||
},
|
||||
): void {
|
||||
pi.registerCommand(
|
||||
`${WOW_NS}:${module}`,
|
||||
options as Parameters<ExtensionAPI["registerCommand"]>[1],
|
||||
);
|
||||
}
|
||||
184
packages/wow-core/src/config.ts
Normal file
184
packages/wow-core/src/config.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import * as path from "node:path";
|
||||
import YAML from "yaml";
|
||||
|
||||
export interface WowConfig {
|
||||
contexts?: string[];
|
||||
logger?: { level?: string };
|
||||
inject?: {
|
||||
enabled?: boolean;
|
||||
envFiles?: string[];
|
||||
env?: Record<string, string> | Array<Record<string, string>>;
|
||||
overrideExisting?: boolean;
|
||||
};
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
const AGENT_DIR_VAR = "PI_CODING_AGENT_DIR";
|
||||
const CONFIG_FILES = ["wow.json", "wow.yaml", "wow.yml"];
|
||||
|
||||
let cachedConfig: WowConfig | undefined;
|
||||
let cachedCwd = "";
|
||||
|
||||
export function getAgentDir(): string {
|
||||
return process.env[AGENT_DIR_VAR] || path.join(homedir(), ".pi", "agent");
|
||||
}
|
||||
|
||||
export function resolveConfigPaths(cwd = process.cwd()): string[] {
|
||||
const globalConfigs = CONFIG_FILES.map((file) =>
|
||||
path.join(getAgentDir(), file),
|
||||
);
|
||||
const projectConfigs = CONFIG_FILES.map((file) =>
|
||||
path.join(cwd, ".pi", file),
|
||||
);
|
||||
return [...globalConfigs, ...projectConfigs].filter(
|
||||
(filePath, index, all) => all.indexOf(filePath) === index,
|
||||
);
|
||||
}
|
||||
|
||||
export function resetWowConfigCache(): void {
|
||||
cachedConfig = undefined;
|
||||
cachedCwd = "";
|
||||
}
|
||||
|
||||
export function getWowSettings(cwd = process.cwd()): WowConfig | undefined {
|
||||
if (cachedConfig !== undefined && cachedCwd === cwd) return cachedConfig;
|
||||
cachedCwd = cwd;
|
||||
cachedConfig = loadWowConfig(cwd);
|
||||
return cachedConfig;
|
||||
}
|
||||
|
||||
export function getWowSettingSync<T = unknown>(
|
||||
key: string,
|
||||
cwd = process.cwd(),
|
||||
): T | undefined {
|
||||
const config = getWowSettings(cwd);
|
||||
return config?.[key] as T | undefined;
|
||||
}
|
||||
|
||||
export async function readWowConfig<T = unknown>(
|
||||
key: string,
|
||||
cwd = process.cwd(),
|
||||
): Promise<T | undefined> {
|
||||
return getWowSettingSync<T>(key, cwd);
|
||||
}
|
||||
|
||||
function loadWowConfig(cwd: string): WowConfig | undefined {
|
||||
let merged: WowConfig | undefined;
|
||||
|
||||
for (const filePath of resolveConfigPaths(cwd)) {
|
||||
const config = readConfigFile(filePath);
|
||||
if (!config) continue;
|
||||
merged = mergeWowConfig(merged ?? {}, config);
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
function readConfigFile(filePath: string): WowConfig | undefined {
|
||||
try {
|
||||
if (!existsSync(filePath)) return undefined;
|
||||
const content = readFileSync(filePath, "utf-8");
|
||||
const parsed = parseConfigContent(filePath, content);
|
||||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
||||
return undefined;
|
||||
return parsed as WowConfig;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function parseConfigContent(filePath: string, content: string): unknown {
|
||||
if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) {
|
||||
return YAML.parse(content);
|
||||
}
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
function mergeWowConfig(base: WowConfig, override: WowConfig): WowConfig {
|
||||
const merged: WowConfig = { ...base, ...override };
|
||||
|
||||
if (base.logger || override.logger) {
|
||||
merged.logger = { ...(base.logger ?? {}), ...(override.logger ?? {}) };
|
||||
}
|
||||
|
||||
if (base.inject || override.inject) {
|
||||
merged.inject = mergeInjectConfig(base.inject, override.inject);
|
||||
}
|
||||
|
||||
if (Array.isArray(base.contexts) || Array.isArray(override.contexts)) {
|
||||
merged.contexts = mergeStringArrays(base.contexts, override.contexts);
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
function mergeInjectConfig(
|
||||
base: WowConfig["inject"],
|
||||
override: WowConfig["inject"],
|
||||
): WowConfig["inject"] {
|
||||
const merged = { ...(base ?? {}), ...(override ?? {}) };
|
||||
merged.envFiles = mergeStringArrays(base?.envFiles, override?.envFiles);
|
||||
merged.env = mergeEnvConfig(base?.env, override?.env);
|
||||
return merged;
|
||||
}
|
||||
|
||||
type EnvConfig = NonNullable<WowConfig["inject"]>["env"];
|
||||
|
||||
function mergeEnvConfig(
|
||||
base: EnvConfig | undefined,
|
||||
override: EnvConfig | undefined,
|
||||
): Record<string, string> | undefined {
|
||||
const merged: Record<string, string> = {};
|
||||
|
||||
for (const source of [base, override]) {
|
||||
for (const [key, value] of Object.entries(normaliseEnvMap(source))) {
|
||||
merged[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys(merged).length > 0 ? merged : undefined;
|
||||
}
|
||||
|
||||
function normaliseEnvMap(input: unknown): Record<string, string> {
|
||||
if (!input) return {};
|
||||
const entries = Array.isArray(input)
|
||||
? Object.assign(
|
||||
{},
|
||||
...input.filter(
|
||||
(item) => item && typeof item === "object" && !Array.isArray(item),
|
||||
),
|
||||
)
|
||||
: input;
|
||||
|
||||
if (!entries || typeof entries !== "object" || Array.isArray(entries))
|
||||
return {};
|
||||
|
||||
const result: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(entries)) {
|
||||
if (typeof value === "string" || value instanceof String) {
|
||||
result[key] = String(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function mergeStringArrays(
|
||||
base?: string[],
|
||||
override?: string[],
|
||||
): string[] | undefined {
|
||||
const merged: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
|
||||
for (const list of [base, override]) {
|
||||
if (!Array.isArray(list)) continue;
|
||||
for (const item of list) {
|
||||
if (typeof item !== "string" || seen.has(item)) continue;
|
||||
seen.add(item);
|
||||
merged.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return merged.length > 0 ? merged : undefined;
|
||||
}
|
||||
7
packages/wow-core/src/index.ts
Normal file
7
packages/wow-core/src/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export * from "./commands";
|
||||
export * from "./config";
|
||||
export * from "./logger";
|
||||
export * from "./registry";
|
||||
export * from "./ui";
|
||||
export * from "./utils";
|
||||
export * from "./utils/ref-resolver";
|
||||
45
packages/wow-core/src/logger/index.ts
Normal file
45
packages/wow-core/src/logger/index.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { WowConfig } from "../config";
|
||||
import { getWowSettingSync } from "../config";
|
||||
import { parseLevel } from "./levels";
|
||||
import { WowLogger } from "./logger";
|
||||
|
||||
export type { LogLevel } from "./levels";
|
||||
export { WowLogger } from "./logger";
|
||||
|
||||
export type TaggedLogger = {
|
||||
debug: (message: string, data?: Record<string, unknown>) => void;
|
||||
info: (message: string, data?: Record<string, unknown>) => void;
|
||||
warn: (message: string, data?: Record<string, unknown>) => void;
|
||||
error: (message: string, data?: Record<string, unknown>) => void;
|
||||
};
|
||||
|
||||
let instance: WowLogger | null = null;
|
||||
|
||||
export function createLogger(module: string): TaggedLogger {
|
||||
const logger = getLogger();
|
||||
return {
|
||||
debug: (message, data) => logger.debug(module, message, data),
|
||||
info: (message, data) => logger.info(module, message, data),
|
||||
warn: (message, data) => logger.warn(module, message, data),
|
||||
error: (message, data) => logger.error(module, message, data),
|
||||
};
|
||||
}
|
||||
|
||||
export function initLogger(cwd = process.cwd()): void {
|
||||
const base = resolveDefaultLevel();
|
||||
let finalLevel = base;
|
||||
const cfg = getWowSettingSync<WowConfig["logger"]>("logger", cwd);
|
||||
if (typeof cfg?.level === "string") {
|
||||
finalLevel = parseLevel(cfg.level) ?? finalLevel;
|
||||
}
|
||||
getLogger().setMinLevel(finalLevel);
|
||||
}
|
||||
|
||||
function getLogger(): WowLogger {
|
||||
if (!instance) instance = new WowLogger(resolveDefaultLevel());
|
||||
return instance;
|
||||
}
|
||||
|
||||
function resolveDefaultLevel(): "debug" | "error" | "info" | "warn" {
|
||||
return parseLevel(process.env.WOW_LOG_LEVEL) ?? "info";
|
||||
}
|
||||
28
packages/wow-core/src/logger/levels.ts
Normal file
28
packages/wow-core/src/logger/levels.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Log level definitions for the wow logger.
|
||||
*/
|
||||
|
||||
export const LEVELS = {
|
||||
debug: 0,
|
||||
info: 1,
|
||||
warn: 2,
|
||||
error: 3,
|
||||
} as const;
|
||||
|
||||
export type LogLevel = keyof typeof LEVELS;
|
||||
|
||||
/**
|
||||
* Parse a string to a `LogLevel`. Returns `undefined` for unknown values.
|
||||
*/
|
||||
export function parseLevel(s: string | undefined): LogLevel | undefined {
|
||||
if (!s) return undefined;
|
||||
const lower = s.toLowerCase() as string;
|
||||
for (const level of Object.keys(LEVELS)) {
|
||||
if (lower === level) return level as LogLevel;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function levelToNumber(level: LogLevel): number {
|
||||
return LEVELS[level];
|
||||
}
|
||||
118
packages/wow-core/src/logger/logger.ts
Normal file
118
packages/wow-core/src/logger/logger.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import { getAgentDir } from "../config";
|
||||
import type { LogLevel } from "./levels";
|
||||
import { levelToNumber } from "./levels";
|
||||
|
||||
const LOG_DIR = path.join(getAgentDir(), "wow", "logs");
|
||||
const MAX_BYTES = 10 * 1024 * 1024;
|
||||
const MAX_FILES = 5;
|
||||
|
||||
export class WowLogger {
|
||||
#minLevel: number;
|
||||
#dirEnsured = false;
|
||||
|
||||
constructor(level: LogLevel) {
|
||||
this.#minLevel = levelToNumber(level);
|
||||
}
|
||||
|
||||
setMinLevel(level: LogLevel): void {
|
||||
this.#minLevel = levelToNumber(level);
|
||||
}
|
||||
|
||||
debug(module: string, message: string, data?: Record<string, unknown>): void {
|
||||
void this.#write("debug", module, message, data);
|
||||
}
|
||||
|
||||
info(module: string, message: string, data?: Record<string, unknown>): void {
|
||||
void this.#write("info", module, message, data);
|
||||
}
|
||||
|
||||
warn(module: string, message: string, data?: Record<string, unknown>): void {
|
||||
void this.#write("warn", module, message, data);
|
||||
}
|
||||
|
||||
error(module: string, message: string, data?: Record<string, unknown>): void {
|
||||
void this.#write("error", module, message, data);
|
||||
}
|
||||
|
||||
async #write(
|
||||
level: LogLevel,
|
||||
module: string,
|
||||
message: string,
|
||||
data?: Record<string, unknown>,
|
||||
): Promise<void> {
|
||||
if (levelToNumber(level) < this.#minLevel) return;
|
||||
|
||||
try {
|
||||
await this.#ensureDir();
|
||||
const date = dateStr();
|
||||
const filePath = logPath(date);
|
||||
await this.#rotateIfNeeded(filePath, date);
|
||||
await fs.appendFile(
|
||||
filePath,
|
||||
`${JSON.stringify({
|
||||
timestamp: new Date().toISOString(),
|
||||
level,
|
||||
pid: process.pid,
|
||||
module,
|
||||
message,
|
||||
...data,
|
||||
})}\n`,
|
||||
);
|
||||
} catch {
|
||||
// Logging must never break the extension.
|
||||
}
|
||||
}
|
||||
|
||||
async #ensureDir(): Promise<void> {
|
||||
if (this.#dirEnsured) return;
|
||||
await fs.mkdir(LOG_DIR, { recursive: true });
|
||||
this.#dirEnsured = true;
|
||||
}
|
||||
|
||||
async #rotateIfNeeded(filePath: string, date: string): Promise<void> {
|
||||
try {
|
||||
const stat = await fs.stat(filePath);
|
||||
if (stat.size < MAX_BYTES) return;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.unlink(rotatedPath(date, MAX_FILES));
|
||||
} catch {
|
||||
// Rotated file may not exist.
|
||||
}
|
||||
|
||||
for (let i = MAX_FILES - 1; i >= 1; i--) {
|
||||
try {
|
||||
await fs.rename(rotatedPath(date, i), rotatedPath(date, i + 1));
|
||||
} catch {
|
||||
// Older rotation slot may not exist.
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.rename(filePath, rotatedPath(date, 1));
|
||||
} catch {
|
||||
// File may have been moved by another process.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function logPath(date: string): string {
|
||||
return path.join(LOG_DIR, `wow.${date}.log`);
|
||||
}
|
||||
|
||||
function rotatedPath(date: string, index: number): string {
|
||||
return path.join(LOG_DIR, `wow.${date}.${index}.log`);
|
||||
}
|
||||
|
||||
function dateStr(): string {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(now.getDate()).padStart(2, "0");
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
18
packages/wow-core/src/registry.ts
Normal file
18
packages/wow-core/src/registry.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
export type ModuleSetup = (pi: ExtensionAPI) => void | Promise<void>;
|
||||
|
||||
export interface WowModule {
|
||||
name: string;
|
||||
register: ModuleSetup;
|
||||
}
|
||||
|
||||
const modules: WowModule[] = [];
|
||||
|
||||
export function registerModule(mod: WowModule): void {
|
||||
modules.push(mod);
|
||||
}
|
||||
|
||||
export function getModules(): readonly WowModule[] {
|
||||
return [...modules].sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
50
packages/wow-core/src/ui/component.ts
Normal file
50
packages/wow-core/src/ui/component.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Component types and rendering for the wow-pi display system.
|
||||
*
|
||||
* Each component type knows how to append its visual lines to a buffer.
|
||||
*/
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface ListSection {
|
||||
summary: string;
|
||||
items?: string[];
|
||||
emptyText?: string;
|
||||
}
|
||||
|
||||
export interface ShowText {
|
||||
type: "text";
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface ShowList {
|
||||
type: "list";
|
||||
sections: ListSection[];
|
||||
}
|
||||
|
||||
export type ShowComponent = ShowText | ShowList;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Render helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Append lines for a single component into `buf`. */
|
||||
export function renderComponent(comp: ShowComponent, buf: string[]): void {
|
||||
if (comp.type === "text") {
|
||||
buf.push(comp.content);
|
||||
} else if (comp.type === "list") {
|
||||
for (let si = 0; si < comp.sections.length; si++) {
|
||||
const section = comp.sections[si]!;
|
||||
if (si > 0) buf.push("");
|
||||
buf.push(section.summary);
|
||||
const items = section.items;
|
||||
if (items && items.length > 0) {
|
||||
for (const item of items) buf.push(` ${item}`);
|
||||
} else {
|
||||
buf.push(` ${section.emptyText ?? "(empty)"}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
packages/wow-core/src/ui/index.ts
Normal file
2
packages/wow-core/src/ui/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./component";
|
||||
export * from "./render";
|
||||
71
packages/wow-core/src/ui/render.ts
Normal file
71
packages/wow-core/src/ui/render.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
||||
import boxen from "boxen";
|
||||
import type { ShowComponent } from "./component";
|
||||
import { renderComponent } from "./component";
|
||||
|
||||
type Level = "info" | "warning" | "error";
|
||||
|
||||
export function show(
|
||||
ctx: { ui: ExtensionCommandContext["ui"] },
|
||||
level: Level,
|
||||
title: string,
|
||||
...components: ShowComponent[]
|
||||
): void {
|
||||
if (components.length === 0) {
|
||||
ctx.ui.notify(title, level);
|
||||
return;
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
for (const [index, component] of components.entries()) {
|
||||
if (index > 0) lines.push("");
|
||||
renderComponent(component, lines);
|
||||
}
|
||||
|
||||
ctx.ui.notify(
|
||||
boxen(lines.join("\n"), {
|
||||
borderStyle: "single",
|
||||
padding: 1,
|
||||
title,
|
||||
titleAlignment: "left",
|
||||
}),
|
||||
level,
|
||||
);
|
||||
}
|
||||
|
||||
export function showInfo(
|
||||
ctx: { ui: ExtensionCommandContext["ui"] },
|
||||
title: string,
|
||||
body?: string[],
|
||||
): void {
|
||||
showText(ctx, "info", title, body);
|
||||
}
|
||||
|
||||
export function showWarn(
|
||||
ctx: { ui: ExtensionCommandContext["ui"] },
|
||||
title: string,
|
||||
body?: string[],
|
||||
): void {
|
||||
showText(ctx, "warning", title, body);
|
||||
}
|
||||
|
||||
export function showError(
|
||||
ctx: { ui: ExtensionCommandContext["ui"] },
|
||||
title: string,
|
||||
body?: string[],
|
||||
): void {
|
||||
showText(ctx, "error", title, body);
|
||||
}
|
||||
|
||||
function showText(
|
||||
ctx: { ui: ExtensionCommandContext["ui"] },
|
||||
level: Level,
|
||||
title: string,
|
||||
body?: string[],
|
||||
): void {
|
||||
if (!body || body.length === 0) {
|
||||
show(ctx, level, title);
|
||||
return;
|
||||
}
|
||||
show(ctx, level, title, { type: "text", content: body.join("\n") });
|
||||
}
|
||||
12
packages/wow-core/src/utils/index.ts
Normal file
12
packages/wow-core/src/utils/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { homedir } from "node:os";
|
||||
import * as path from "node:path";
|
||||
|
||||
export function expandHome(input: string): string {
|
||||
if (input === "~") return homedir();
|
||||
if (input.startsWith("~/")) return path.join(homedir(), input.slice(2));
|
||||
return input;
|
||||
}
|
||||
|
||||
export function resolvePath(input: string, cwd = process.cwd()): string {
|
||||
return path.resolve(cwd, expandHome(input));
|
||||
}
|
||||
60
packages/wow-core/src/utils/ref-resolver.ts
Normal file
60
packages/wow-core/src/utils/ref-resolver.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import * as fs from "node:fs";
|
||||
import type { TaggedLogger } from "../logger";
|
||||
import { expandHome } from ".";
|
||||
|
||||
export interface KeyResolver {
|
||||
protocol: string;
|
||||
resolve(value: string): string | undefined;
|
||||
}
|
||||
|
||||
export const resolvers: KeyResolver[] = [];
|
||||
|
||||
export function registerKeyResolver(resolver: KeyResolver): void {
|
||||
resolvers.push(resolver);
|
||||
}
|
||||
|
||||
registerKeyResolver({
|
||||
protocol: "file",
|
||||
resolve(value: string): string | undefined {
|
||||
const content = fs.readFileSync(expandHome(value), "utf-8").trim();
|
||||
return content.length > 0 ? content : undefined;
|
||||
},
|
||||
});
|
||||
|
||||
registerKeyResolver({
|
||||
protocol: "env",
|
||||
resolve(value: string): string | undefined {
|
||||
return process.env[value];
|
||||
},
|
||||
});
|
||||
|
||||
const REF_RE = /^\$\{([a-zA-Z_][a-zA-Z0-9_]*):/;
|
||||
|
||||
export function resolveRef(ref: string, log: TaggedLogger): string | undefined {
|
||||
const match = REF_RE.exec(ref);
|
||||
if (!match) return ref;
|
||||
if (!ref.endsWith("}")) return undefined;
|
||||
|
||||
const protocol = match[1];
|
||||
const value = ref.slice(match[0].length, -1);
|
||||
if (!protocol || !value) return undefined;
|
||||
|
||||
for (const resolver of resolvers) {
|
||||
if (resolver.protocol !== protocol) continue;
|
||||
try {
|
||||
const resolved = resolver.resolve(value);
|
||||
if (resolved !== undefined) {
|
||||
log.debug(`ref "${protocol}" resolved`, {
|
||||
length: resolved.length,
|
||||
value,
|
||||
});
|
||||
return resolved;
|
||||
}
|
||||
} catch (error) {
|
||||
log.warn(`ref "${protocol}" error`, { value, error: String(error) });
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
13
packages/wow-core/tsconfig.json
Normal file
13
packages/wow-core/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"types": ["bun"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
16
packages/wow-inject/package.json
Normal file
16
packages/wow-inject/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "wow-inject",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"wow-core": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@earendil-works/pi-coding-agent": "*"
|
||||
}
|
||||
}
|
||||
68
packages/wow-inject/src/env-file.ts
Normal file
68
packages/wow-inject/src/env-file.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { resolvePath } from "wow-core";
|
||||
|
||||
export interface EnvLoadResult {
|
||||
path: string;
|
||||
loaded: string[];
|
||||
skipped: string[];
|
||||
missing: boolean;
|
||||
}
|
||||
|
||||
export function loadEnvFile(
|
||||
rawPath: string,
|
||||
options: { cwd: string; overrideExisting: boolean },
|
||||
): EnvLoadResult {
|
||||
const filePath = resolvePath(rawPath, options.cwd);
|
||||
const result: EnvLoadResult = {
|
||||
path: filePath,
|
||||
loaded: [],
|
||||
skipped: [],
|
||||
missing: false,
|
||||
};
|
||||
|
||||
if (!existsSync(filePath)) {
|
||||
result.missing = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
const content = readFileSync(filePath, "utf-8");
|
||||
for (const line of content.split(/\r?\n/)) {
|
||||
const entry = parseEnvLine(line);
|
||||
if (!entry) continue;
|
||||
|
||||
if (!options.overrideExisting && entry.key in process.env) {
|
||||
result.skipped.push(entry.key);
|
||||
continue;
|
||||
}
|
||||
|
||||
process.env[entry.key] = entry.value;
|
||||
result.loaded.push(entry.key);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseEnvLine(line: string): { key: string; value: string } | null {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith("#")) return null;
|
||||
|
||||
const cleaned = trimmed.startsWith("export ")
|
||||
? trimmed.slice(7).trimStart()
|
||||
: trimmed;
|
||||
const eqIndex = cleaned.indexOf("=");
|
||||
if (eqIndex === -1) return null;
|
||||
|
||||
const key = cleaned.slice(0, eqIndex).trim();
|
||||
if (!key) return null;
|
||||
|
||||
return { key, value: unwrapValue(cleaned.slice(eqIndex + 1).trim()) };
|
||||
}
|
||||
|
||||
function unwrapValue(value: string): string {
|
||||
if (value.length < 2) return value;
|
||||
const quote = value[0];
|
||||
if ((quote === '"' || quote === "'") && value.endsWith(quote)) {
|
||||
return value.slice(1, -1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
88
packages/wow-inject/src/env.ts
Normal file
88
packages/wow-inject/src/env.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { WowConfig } from "wow-core";
|
||||
import { getWowSettingSync, resolveRef } from "wow-core";
|
||||
import { loadEnvFile } from "./env-file";
|
||||
import { log, resolvedEnv } from "./global";
|
||||
|
||||
interface InjectStats {
|
||||
envFilesLoaded: number;
|
||||
envVarsLoaded: number;
|
||||
skippedExisting: number;
|
||||
missingFiles: number;
|
||||
}
|
||||
|
||||
export function injectProcessEnv(cwd = process.cwd()): InjectStats {
|
||||
const cfg = getWowSettingSync<WowConfig["inject"]>("inject", cwd);
|
||||
const stats: InjectStats = {
|
||||
envFilesLoaded: 0,
|
||||
envVarsLoaded: 0,
|
||||
skippedExisting: 0,
|
||||
missingFiles: 0,
|
||||
};
|
||||
|
||||
if (!cfg || cfg.enabled === false) return stats;
|
||||
|
||||
const overrideExisting = cfg.overrideExisting ?? false;
|
||||
|
||||
for (const rawPath of cfg.envFiles ?? []) {
|
||||
const result = loadEnvFile(rawPath, { cwd, overrideExisting });
|
||||
if (result.missing) {
|
||||
stats.missingFiles++;
|
||||
log.debug("env file missing", { path: result.path });
|
||||
continue;
|
||||
}
|
||||
stats.envFilesLoaded++;
|
||||
stats.envVarsLoaded += result.loaded.length;
|
||||
stats.skippedExisting += result.skipped.length;
|
||||
log.debug("env file loaded", {
|
||||
path: result.path,
|
||||
loaded: result.loaded,
|
||||
skipped: result.skipped,
|
||||
});
|
||||
}
|
||||
|
||||
for (const [name, rawValue] of Object.entries(normaliseEnvMap(cfg.env))) {
|
||||
if (!overrideExisting && name in process.env) {
|
||||
stats.skippedExisting++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const resolved = resolveRef(rawValue, log);
|
||||
if (resolved === undefined) {
|
||||
log.warn("env ref unresolved", { name });
|
||||
continue;
|
||||
}
|
||||
|
||||
process.env[name] = resolved;
|
||||
resolvedEnv.set(name, resolved);
|
||||
stats.envVarsLoaded++;
|
||||
}
|
||||
|
||||
if (stats.envFilesLoaded > 0 || stats.envVarsLoaded > 0) {
|
||||
log.info("env injected", { ...stats });
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
function normaliseEnvMap(input: unknown): Record<string, string> {
|
||||
if (!input) return {};
|
||||
const entries = Array.isArray(input)
|
||||
? Object.assign(
|
||||
{},
|
||||
...input.filter(
|
||||
(item) => item && typeof item === "object" && !Array.isArray(item),
|
||||
),
|
||||
)
|
||||
: input;
|
||||
|
||||
if (!entries || typeof entries !== "object" || Array.isArray(entries))
|
||||
return {};
|
||||
|
||||
const result: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(entries)) {
|
||||
if (typeof value === "string" || value instanceof String) {
|
||||
result[key] = String(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
5
packages/wow-inject/src/global.ts
Normal file
5
packages/wow-inject/src/global.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { createLogger } from "wow-core";
|
||||
|
||||
export const TAG = "inject";
|
||||
export const log = createLogger(TAG);
|
||||
export const resolvedEnv = new Map<string, string>();
|
||||
28
packages/wow-inject/src/index.ts
Normal file
28
packages/wow-inject/src/index.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { ModuleSetup, WowConfig } from "wow-core";
|
||||
import {
|
||||
getWowSettingSync,
|
||||
registerModule,
|
||||
resetWowConfigCache,
|
||||
} from "wow-core";
|
||||
import { injectProcessEnv } from "./env";
|
||||
import { log, TAG } from "./global";
|
||||
|
||||
const setup: ModuleSetup = (pi) => {
|
||||
const load = (cwd = process.cwd()) => {
|
||||
resetWowConfigCache();
|
||||
const cfg = getWowSettingSync<WowConfig["inject"]>(TAG, cwd);
|
||||
if (cfg?.enabled === false) {
|
||||
log.debug("inject disabled — skipping module");
|
||||
return;
|
||||
}
|
||||
injectProcessEnv(cwd);
|
||||
};
|
||||
|
||||
load();
|
||||
|
||||
pi.on("session_start", (_event, ctx) => {
|
||||
load(ctx.cwd);
|
||||
});
|
||||
};
|
||||
|
||||
registerModule({ name: TAG, register: setup });
|
||||
13
packages/wow-inject/tsconfig.json
Normal file
13
packages/wow-inject/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"types": ["bun"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
23
packages/wow-pi/package.json
Normal file
23
packages/wow-pi/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "wow-pi",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf ./dist",
|
||||
"build:debug": "bun run clean && bun build ./src/index.ts --outdir ./dist --target node --external @earendil-works/pi-coding-agent --external @earendil-works/pi-ai --external @earendil-works/pi-tui --external typebox --define 'process.env.WOW_LOG_LEVEL=\"debug\"'",
|
||||
"build:release": "bun run clean && bun build ./src/index.ts --outdir ./dist --target node --external @earendil-works/pi-coding-agent --external @earendil-works/pi-ai --external @earendil-works/pi-tui --external typebox --define 'process.env.WOW_LOG_LEVEL=\"info\"' --minify"
|
||||
},
|
||||
"dependencies": {
|
||||
"wow-contexts": "workspace:*",
|
||||
"wow-core": "workspace:*",
|
||||
"wow-inject": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@earendil-works/pi-coding-agent": "*"
|
||||
}
|
||||
}
|
||||
23
packages/wow-pi/src/index.ts
Normal file
23
packages/wow-pi/src/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
import "wow-contexts";
|
||||
import "wow-inject";
|
||||
import {
|
||||
createLogger,
|
||||
getModules,
|
||||
initLogger,
|
||||
resetWowConfigCache,
|
||||
} from "wow-core";
|
||||
|
||||
export default async function wowPi(pi: ExtensionAPI): Promise<void> {
|
||||
resetWowConfigCache();
|
||||
initLogger();
|
||||
const log = createLogger("main");
|
||||
|
||||
for (const mod of getModules()) {
|
||||
await mod.register(pi);
|
||||
log.debug("module registered", { module: mod.name });
|
||||
}
|
||||
|
||||
log.info("wow-pi loaded", { modules: getModules().map((mod) => mod.name) });
|
||||
}
|
||||
13
packages/wow-pi/tsconfig.json
Normal file
13
packages/wow-pi/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"types": ["bun"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
35
scripts/build.ts
Normal file
35
scripts/build.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* build — orchestrates the wow-pi bundle build.
|
||||
*
|
||||
* Usage:
|
||||
* bun scripts/build.ts # default: debug
|
||||
* bun scripts/build.ts --target release
|
||||
*/
|
||||
|
||||
import arg from "arg";
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const args = arg({ "--target": String }, { argv: process.argv.slice(2) });
|
||||
|
||||
const target = args["--target"] || "debug";
|
||||
if (target !== "debug" && target !== "release") {
|
||||
console.error(`[build] unknown target: ${target} (use debug or release)`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const buildScript = `build:${target}`;
|
||||
console.log(`[build] building extension bundle (${target})...`);
|
||||
const build =
|
||||
await Bun.$`bun run --cwd packages/wow-pi ${buildScript}`.quiet();
|
||||
if (build.exitCode !== 0) {
|
||||
console.error(build.stderr?.toString() || "build failed");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("[build] done");
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error("[build] failed:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
93
scripts/install.ts
Normal file
93
scripts/install.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* wow-pi install/uninstall script.
|
||||
*
|
||||
* Builds the extension and copies the output into pi's extensions directory.
|
||||
* The installed plugin is hard-copied so pi can load it without depending on
|
||||
* workspace symlinks or source paths.
|
||||
*
|
||||
* Usage:
|
||||
* bun scripts/install.ts # build + install
|
||||
* bun scripts/install.ts --uninstall # remove installed plugin
|
||||
*/
|
||||
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import arg from "arg";
|
||||
|
||||
const DIST_REL = "packages/wow-pi/dist";
|
||||
const TARGET_REL = ".pi/agent/extensions/wow-pi";
|
||||
|
||||
const PROJECT_ROOT = path.resolve(import.meta.dir, "..");
|
||||
const HOME = process.env.HOME;
|
||||
|
||||
if (!HOME) {
|
||||
console.error("[install] HOME is not set");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const TARGET = path.join(HOME, TARGET_REL);
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const parsed = arg(
|
||||
{ "--uninstall": Boolean },
|
||||
{ argv: process.argv.slice(2) },
|
||||
);
|
||||
|
||||
if (parsed["--uninstall"]) {
|
||||
await uninstall();
|
||||
return;
|
||||
}
|
||||
|
||||
await install();
|
||||
}
|
||||
|
||||
async function install(): Promise<void> {
|
||||
const buildTarget = process.env.WOW_RELEASE === "1" ? "release" : "debug";
|
||||
const build = await Bun.$`bun scripts/build.ts --target ${buildTarget}`.cwd(
|
||||
PROJECT_ROOT,
|
||||
);
|
||||
if (build.exitCode !== 0) {
|
||||
console.error(`[install] build failed (target: ${buildTarget})`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const distPath = path.join(PROJECT_ROOT, DIST_REL);
|
||||
|
||||
await fs.mkdir(path.dirname(TARGET), { recursive: true });
|
||||
|
||||
try {
|
||||
await fs.rm(TARGET, { recursive: true, force: true });
|
||||
} catch {
|
||||
// Existing target may not exist.
|
||||
}
|
||||
|
||||
await fs.cp(distPath, TARGET, { recursive: true });
|
||||
console.log(`[install] ${distPath} -> ${TARGET}`);
|
||||
}
|
||||
|
||||
async function uninstall(): Promise<void> {
|
||||
try {
|
||||
await fs.rm(TARGET, { recursive: true, force: true });
|
||||
const exists = await fs
|
||||
.stat(TARGET)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
if (exists) {
|
||||
console.error(`[install] failed to remove ${TARGET}`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`[install] removed ${TARGET}`);
|
||||
} catch (err: unknown) {
|
||||
const code = (err as { code?: string }).code;
|
||||
if (code === "ENOENT") {
|
||||
console.log(`[install] nothing to remove (${TARGET} does not exist)`);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error("[install] failed:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
13
tsconfig.json
Normal file
13
tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"types": ["bun"]
|
||||
},
|
||||
"include": ["packages/*/src"]
|
||||
}
|
||||
22
wow.example.yaml
Normal file
22
wow.example.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
# Example wow-pi configuration.
|
||||
# Copy to ~/.pi/agent/wow.yaml for global defaults or .pi/wow.yaml for a project.
|
||||
|
||||
contexts:
|
||||
- ~/.pi/agent/contexts/*.md
|
||||
- .pi/contexts/*.md
|
||||
- docs/contexts/*.md
|
||||
- AGENTS.md
|
||||
- CLAUDE.md
|
||||
|
||||
inject:
|
||||
enabled: true
|
||||
overrideExisting: false
|
||||
envFiles:
|
||||
- ~/.pi/agent/.env
|
||||
- .env
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${file:~/.secrets/anthropic-key}
|
||||
OPENAI_API_KEY: ${env:OPENAI_API_KEY_BACKUP}
|
||||
|
||||
logger:
|
||||
level: info
|
||||
Reference in New Issue
Block a user