Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3a82d4a92 | ||
|
|
790824af20 | ||
|
|
4137a29507 | ||
|
|
5b9362918e | ||
|
|
bfa538fa00 | ||
|
|
96234425ba | ||
|
|
3148f2e62b | ||
|
|
554350cc0e | ||
|
|
d9812cf4f2 | ||
|
|
aed607ce62 | ||
|
|
ab8a284c74 | ||
|
|
62d63be1d8 |
35
.github/workflows/publish.yml
vendored
35
.github/workflows/publish.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ steps.version.outputs.version }}
|
version: ${{ steps.version.outputs.version }}
|
||||||
should_publish: ${{ steps.version.outputs.should_publish }}
|
should_release: ${{ steps.version.outputs.should_release }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
- uses: actions/setup-node@v5
|
- uses: actions/setup-node@v5
|
||||||
@@ -21,38 +21,20 @@ jobs:
|
|||||||
node-version: 24.14.0
|
node-version: 24.14.0
|
||||||
- id: version
|
- id: version
|
||||||
shell: bash
|
shell: bash
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
CURRENT=$(npm view @companion-ai/feynman version 2>/dev/null || echo "0.0.0")
|
|
||||||
LOCAL=$(node -p "require('./package.json').version")
|
LOCAL=$(node -p "require('./package.json').version")
|
||||||
echo "version=$LOCAL" >> "$GITHUB_OUTPUT"
|
echo "version=$LOCAL" >> "$GITHUB_OUTPUT"
|
||||||
if [ "$CURRENT" != "$LOCAL" ]; then
|
if gh release view "v$LOCAL" >/dev/null 2>&1; then
|
||||||
echo "should_publish=true" >> "$GITHUB_OUTPUT"
|
echo "should_release=false" >> "$GITHUB_OUTPUT"
|
||||||
else
|
else
|
||||||
echo "should_publish=false" >> "$GITHUB_OUTPUT"
|
echo "should_release=true" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
publish-npm:
|
|
||||||
needs: version-check
|
|
||||||
if: needs.version-check.outputs.should_publish == 'true'
|
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- uses: actions/setup-node@v5
|
|
||||||
with:
|
|
||||||
node-version: 24.14.0
|
|
||||||
registry-url: https://registry.npmjs.org
|
|
||||||
- run: npm ci --ignore-scripts
|
|
||||||
- run: npm run build
|
|
||||||
- run: npm test
|
|
||||||
- run: npm publish --access public
|
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
build-native-bundles:
|
build-native-bundles:
|
||||||
needs: version-check
|
needs: version-check
|
||||||
if: needs.version-check.outputs.should_publish == 'true'
|
if: needs.version-check.outputs.should_release == 'true'
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -101,9 +83,8 @@ jobs:
|
|||||||
release-github:
|
release-github:
|
||||||
needs:
|
needs:
|
||||||
- version-check
|
- version-check
|
||||||
- publish-npm
|
|
||||||
- build-native-bundles
|
- build-native-bundles
|
||||||
if: needs.version-check.outputs.should_publish == 'true' && needs.build-native-bundles.result == 'success' && needs.publish-npm.result == 'success'
|
if: needs.version-check.outputs.should_release == 'true' && needs.build-native-bundles.result == 'success'
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|||||||
90
CHANGELOG.md
90
CHANGELOG.md
@@ -77,3 +77,93 @@ Use this file to track chronology, not release notes. Keep entries short, factua
|
|||||||
- Failed / learned: The open subagent issue is fixed on `main` but still user-visible on tagged installs until a fresh release is cut.
|
- Failed / learned: The open subagent issue is fixed on `main` but still user-visible on tagged installs until a fresh release is cut.
|
||||||
- Blockers: Need the GitHub publish workflow to finish successfully before the issue can be honestly closed as released.
|
- Blockers: Need the GitHub publish workflow to finish successfully before the issue can be honestly closed as released.
|
||||||
- Next: Push `0.2.15`, monitor the publish workflow, then update and close the relevant GitHub issue/PR once the release is live.
|
- Next: Push `0.2.15`, monitor the publish workflow, then update and close the relevant GitHub issue/PR once the release is live.
|
||||||
|
|
||||||
|
### 2026-03-28 15:15 PDT — pi-subagents-agent-dir-compat
|
||||||
|
|
||||||
|
- Objective: Debug why tagged installs can still fail subagent/auth flows after `0.2.15` when users are not on Anthropic.
|
||||||
|
- Changed: Added `scripts/lib/pi-subagents-patch.mjs` plus type declarations and wired `scripts/patch-embedded-pi.mjs` to rewrite vendored `pi-subagents` runtime files so they resolve user-scoped paths from `PI_CODING_AGENT_DIR` instead of hardcoded `~/.pi/agent`; added `tests/pi-subagents-patch.test.ts`.
|
||||||
|
- Verified: Materialized `.feynman/npm`, inspected the shipped `pi-subagents@0.11.11` sources, confirmed the hardcoded `~/.pi/agent` paths in `index.ts`, `agents.ts`, `artifacts.ts`, `run-history.ts`, `skills.ts`, and `chain-clarify.ts`; ran `node scripts/patch-embedded-pi.mjs`; ran `npm test`, `npm run typecheck`, and `npm run build`.
|
||||||
|
- Failed / learned: The earlier `0.2.15` fix only proved that Feynman exported `PI_CODING_AGENT_DIR` to the top-level Pi child; it did not cover vendored extension code that still hardcoded `.pi` paths internally.
|
||||||
|
- Blockers: Users still need a release containing this patch before tagged installs benefit from it.
|
||||||
|
- Next: Cut the next release and verify a tagged install exercises subagents without reading from `~/.pi/agent`.
|
||||||
|
|
||||||
|
### 2026-03-28 21:46 PDT — release-0.2.16
|
||||||
|
|
||||||
|
- Objective: Ship the vendored `pi-subagents` agent-dir compatibility fix to tagged installs.
|
||||||
|
- Changed: Bumped the package version from `0.2.15` to `0.2.16` in `package.json` and `package-lock.json`; updated pinned installer examples in `README.md` and `website/src/content/docs/getting-started/installation.md`.
|
||||||
|
- Verified: Re-ran `npm test`, `npm run typecheck`, and `npm run build`; ran `cd website && npm run build`; ran `npm pack` and confirmed the `0.2.16` tarball includes the new `scripts/lib/pi-subagents-patch.*` files.
|
||||||
|
- Failed / learned: An initial local `build:native-bundle` check failed because `npm pack` and `build:native-bundle` were run in parallel, and `prepack` intentionally removes `dist/release`; rerunning `npm run build:native-bundle` sequentially succeeded.
|
||||||
|
- Blockers: None in the repo; publishing still depends on the GitHub workflow running on the bumped version.
|
||||||
|
- Next: Push the `0.2.16` release bump and monitor npm/GitHub release publication.
|
||||||
|
|
||||||
|
### 2026-03-31 10:45 PDT — pi-maintenance-issues-prs
|
||||||
|
|
||||||
|
- Objective: Triage open Pi-related issues/PRs, fix the concrete package update regression, and refresh Pi dependencies against current upstream releases.
|
||||||
|
- Changed: Pinned direct package-manager operations (`feynman update`, `feynman packages install`) to Feynman's npm prefix by exporting `FEYNMAN_NPM_PREFIX`, `NPM_CONFIG_PREFIX`, and `npm_config_prefix` before invoking Pi's `DefaultPackageManager`; bumped `@mariozechner/pi-ai` and `@mariozechner/pi-coding-agent` from `0.62.0` to `0.64.0`; adapted `src/model/registry.ts` to the new `ModelRegistry.create(...)` factory; integrated PR #15's `/feynman-model` command on top of current `main`.
|
||||||
|
- Verified: Ran `npm test`, `npm run typecheck`, and `npm run build` successfully after the dependency bump and PR integration; confirmed upstream `pi-coding-agent@0.64.0` still uses `npm install -g` for user-scope package updates, so the Feynman-side prefix fix is still required.
|
||||||
|
- Failed / learned: PR #14 is a stale branch with no clean merge path against current `main`; the only user-facing delta is the ValiChord prompt/skill addition, and the branch also carries unrelated release churn plus demo-style material, so it was not merged in this pass.
|
||||||
|
- Blockers: None in the local repo state; remote merge/push still depends on repository credentials and branch policy.
|
||||||
|
- Next: If remote write access is available, commit and push the validated maintenance changes, then close issue #22 and resolve PR #15 as merged while leaving PR #14 unmerged pending a cleaned-up, non-promotional resubmission.
|
||||||
|
|
||||||
|
### 2026-03-31 12:05 PDT — pi-backlog-cleanup-round-2
|
||||||
|
|
||||||
|
- Objective: Finish the remaining high-confidence open tracker items after the Pi 0.64.0 upgrade instead of leaving the issue list half-reconciled.
|
||||||
|
- Changed: Added a Windows extension-loader patch helper so Feynman rewrites Pi extension imports to `file://` URLs on Windows before interactive startup; added `/commands`, `/tools`, and `/capabilities` discovery commands and surfaced `/hotkeys` plus `/service-tier` in help metadata; added explicit service-tier support via `feynman model tier`, `--service-tier`, status/doctor output, and a provider-payload hook that passes `service_tier` only to supported OpenAI/OpenAI Codex/Anthropic models; added Exa provider recognition to Feynman's web-search status layer and vendored `pi-web-access`.
|
||||||
|
- Verified: Ran `npm test`, `npm run typecheck`, and `npm run build`; smoke-imported the modified vendored `pi-web-access` modules with `node --import tsx`.
|
||||||
|
- Failed / learned: The remaining ValiChord PR is still stale and mixes a real prompt/skill update with unrelated branch churn; it is a review/triage item, not a clean merge candidate.
|
||||||
|
- Blockers: No local build blockers remain; issue/PR closure still depends on the final push landing on `main`.
|
||||||
|
- Next: Push the verified cleanup commit, then close issues fixed by the dependency bump plus the new discoverability/service-tier/Windows patches, and close the stale ValiChord PR explicitly instead of leaving it open indefinitely.
|
||||||
|
|
||||||
|
### 2026-04-09 09:37 PDT — windows-startup-import-specifiers
|
||||||
|
|
||||||
|
- Objective: Fix Windows startup failures where `feynman` exits before the Pi child process initializes.
|
||||||
|
- Changed: Converted the Node preload module paths passed via `node --import` in `src/pi/launch.ts` to `file://` specifiers using a new `toNodeImportSpecifier(...)` helper in `src/pi/runtime.ts`; expanded `scripts/patch-embedded-pi.mjs` so it also patches the bundled workspace copy of Pi's extension loader when present.
|
||||||
|
- Verified: Added a regression test in `tests/pi-runtime.test.ts` covering absolute-path to `file://` conversion for preload imports; ran `npm test`, `npm run typecheck`, and `npm run build`.
|
||||||
|
- Failed / learned: The raw Windows `ERR_UNSUPPORTED_ESM_URL_SCHEME` stack is more consistent with Node rejecting the child-process `--import C:\\...` preload before Pi starts than with a normal in-app extension load failure.
|
||||||
|
- Blockers: Windows runtime execution was not available locally, so the fix is verified by code path inspection and automated tests rather than an actual Windows shell run.
|
||||||
|
- Next: Ask the affected user to reinstall or update to the next published package once released, and confirm the Windows REPL now starts from a normal PowerShell session.
|
||||||
|
|
||||||
|
### 2026-04-09 11:02 PDT — tracker-hardening-pass
|
||||||
|
|
||||||
|
- Objective: Triage the open repo backlog, land the highest-signal fixes locally, and add guardrails against stale promotional workflow content.
|
||||||
|
- Changed: Hardened Windows launch paths in `bin/feynman.js`, `scripts/build-native-bundle.mjs`, and `scripts/install/install.ps1`; set npm prefix overrides earlier in `scripts/patch-embedded-pi.mjs`; added a `pi-web-access` runtime patch helper plus `FEYNMAN_WEB_SEARCH_CONFIG` env wiring so bundled web search reads the same `~/.feynman/web-search.json` that doctor/status report; taught `src/pi/web-access.ts` to honor the legacy `route` key; fixed bundled skill references and expanded the skills-only installers/docs to ship the prompt and guidance files those skills reference; added regression tests for config paths, catalog snapshot edges, skill-path packaging, `pi-web-access` patching, and blocked promotional content.
|
||||||
|
- Verified: Ran `npm test`, `npm run typecheck`, and `npm run build` successfully after the full maintenance pass.
|
||||||
|
- Failed / learned: The skills-only install issue was not just docs drift; the shipped `SKILL.md` files referenced prompt paths that only made sense after installation, so the repo needed both path normalization and packaging changes.
|
||||||
|
- Blockers: Remote issue/PR closure and merge actions still depend on the final reviewed branch state being pushed.
|
||||||
|
- Next: Push the validated fixes, close the duplicate Windows/reporting issues they supersede, reject the promotional ValiChord PR explicitly, and then review whether the remaining docs-only or feature PRs should be merged separately.
|
||||||
|
|
||||||
|
### 2026-04-09 10:28 PDT — verification-and-security-pass
|
||||||
|
|
||||||
|
- Objective: Run a deeper install/security verification pass against the post-cleanup `0.2.17` tree instead of assuming the earlier targeted fixes covered the shipped artifacts.
|
||||||
|
- Changed: Reworked `extensions/research-tools/header.ts` to use `@mariozechner/pi-tui` width-aware helpers for truncation/wrapping so wide Unicode text does not overflow custom header rows; changed `src/pi/launch.ts` to stop mirroring child crash signals back onto the parent process and instead emit a conventional exit code; added `FEYNMAN_INSTALL_SKILLS_ARCHIVE_URL` overrides to the skills installers for pre-release smoke testing; aligned root and website dependency trees with patched transitive versions using npm `overrides`; fixed `src/pi/web-access.ts` so `search status` respects `FEYNMAN_HOME` semantics instead of hardcoding the current shell home directory; added `tests/pi-launch.test.ts`.
|
||||||
|
- Verified: Ran `npm test`, `npm run typecheck`, `npm run build`, `cd website && npm run build`, `npm run build:native-bundle`; smoke-tested `scripts/install/install.sh` against a locally served `dist/release/feynman-0.2.17-darwin-arm64.tar.gz`; smoke-tested `scripts/install/install-skills.sh` against a local source archive; confirmed installed `feynman --version`, `feynman --help`, `feynman doctor`, and packaged `feynman search status` work from the installed bundle; `npm audit --omit=dev` is clean in the root app and website after overrides.
|
||||||
|
- Failed / learned: The first packaged `search status` smoke test still showed the user home path because the native bundle had been built before the `FEYNMAN_HOME` path fix; rebuilding the native bundle resolved that mismatch.
|
||||||
|
- Blockers: PowerShell runtime was unavailable locally, so Windows installer execution remained code-path validated rather than actually executed.
|
||||||
|
- Next: Push the second-pass hardening commit, then keep issue `#46` and issue `#47` open until users on the affected Linux/CJK environments confirm whether the launcher/header fixes fully resolve them.
|
||||||
|
|
||||||
|
### 2026-04-09 10:36 PDT — remaining-tracker-triage-pass
|
||||||
|
|
||||||
|
- Objective: Reduce the remaining open tracker items by landing the lowest-risk missing docs/catalog updates and a targeted Cloud Code Assist compatibility patch instead of only hand-triaging them.
|
||||||
|
- Changed: Added MiniMax M2.7 recommendation preferences in `src/model/catalog.ts`; documented model switching, authenticated-provider visibility, and `/feynman-model` subagent overrides in `website/src/content/docs/getting-started/configuration.md` and `website/src/content/docs/reference/slash-commands.md`; added a runtime patch helper in `scripts/lib/pi-google-legacy-schema-patch.mjs` and wired `scripts/patch-embedded-pi.mjs` to normalize JSON Schema `const` into `enum` for the legacy `parameters` field used by Cloud Code Assist Claude models.
|
||||||
|
- Verified: Ran `npm test`, `npm run typecheck`, `npm run build`, and `cd website && npm run build` after the patch/helper/docs changes.
|
||||||
|
- Failed / learned: The MiniMax provider catalog in Pi already uses canonical IDs like `MiniMax-M2.7`, so the only failure during validation was a test assertion using the wrong casing rather than a runtime bug.
|
||||||
|
- Blockers: The Cloud Code Assist fix is validated by targeted patch tests and code-path review rather than an end-to-end Google account repro in this environment.
|
||||||
|
- Next: Push the tracker-triage commit, close the docs/MiniMax PRs as superseded by main, close the support-style model issues against the new docs, and decide whether the remaining feature requests should be left open or closed as not planned/upstream-dependent.
|
||||||
|
|
||||||
|
### 2026-04-10 10:22 PDT — web-access-stale-override-fix
|
||||||
|
|
||||||
|
- Objective: Fix the new `ctx.modelRegistry.getApiKeyAndHeaders is not a function` / stale `search-filter.js` report without reintroducing broad vendor drift.
|
||||||
|
- Changed: Removed the stale `.feynman/vendor-overrides/pi-web-access/*` files and removed `syncVendorOverride` from `scripts/patch-embedded-pi.mjs`; kept the targeted `pi-web-access` runtime config-path patch; added `feynman search set <provider> [api-key]` and `feynman search clear` commands with a shared save path in `src/pi/web-access.ts`.
|
||||||
|
- Verified: Ran `npm test`, `npm run typecheck`, `npm run build`; ran `node scripts/patch-embedded-pi.mjs`, confirmed the installed `pi-web-access/index.ts` has no `search-filter` / condense helper references, and smoke-imported `./.feynman/npm/node_modules/pi-web-access/index.ts`; ran `npm pack --dry-run` and confirmed stale `vendor-overrides` files are no longer in the package tarball.
|
||||||
|
- Failed / learned: The public Linux installer Docker test was attempted but Docker Desktop became unresponsive even for simple `docker run node:22-bookworm node -v` commands; the earlier Linux npm-artifact container smoke remains valid, but this specific public-installer run is blocked by the local Docker daemon.
|
||||||
|
- Blockers: Issue `#54` is too underspecified to fix directly without logs; public Linux installer behavior still needs a stable Docker daemon or a real Linux shell to reproduce the user's exact npm errors.
|
||||||
|
- Next: Push the stale-override fix, close PR `#52` and PR `#53` as superseded/merged-by-main once pushed, and ask for logs on issue `#54` instead of guessing.
|
||||||
|
|
||||||
|
### 2026-04-10 10:49 PDT — rpc-and-website-verification-pass
|
||||||
|
|
||||||
|
- Objective: Exercise the Feynman wrapper's RPC mode and the website quality gates that were not fully covered by the prior passes.
|
||||||
|
- Changed: Added `--mode <text|json|rpc>` pass-through support in the Feynman wrapper and skipped terminal clearing in RPC mode; added `@astrojs/check` to the website dev dependencies, fixed React Refresh lint violations in the generated UI components by exporting only components, and added safe website dependency overrides for dev-audit findings.
|
||||||
|
- Verified: Ran a JSONL RPC smoke test through `node bin/feynman.js --mode rpc` with `get_state`; ran `npm test`, `npm run typecheck`, `npm run build`, `cd website && npm run lint`, `cd website && npm run typecheck`, `cd website && npm run build`, full root `npm audit`, full website `npm audit`, and `npm run build:native-bundle`.
|
||||||
|
- Failed / learned: Website typecheck was previously a no-op prompt because `@astrojs/check` was missing; installing it exposed dev-audit findings that needed explicit overrides before the full website audit was clean.
|
||||||
|
- Blockers: Docker Desktop remained unreliable after restart attempts, so this pass still does not include a second successful public-installer Linux Docker run.
|
||||||
|
- Next: Push the RPC/website verification commit and keep future Docker/public-installer validation separate from repo correctness unless Docker is stable.
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ npm run build
|
|||||||
- Avoid refactor-only PRs unless they are necessary to unblock a real fix or requested by a maintainer.
|
- Avoid refactor-only PRs unless they are necessary to unblock a real fix or requested by a maintainer.
|
||||||
- Do not silently change release behavior, installer behavior, or runtime defaults without documenting the reason in the PR.
|
- Do not silently change release behavior, installer behavior, or runtime defaults without documenting the reason in the PR.
|
||||||
- Use American English in docs, comments, prompts, UI copy, and examples.
|
- Use American English in docs, comments, prompts, UI copy, and examples.
|
||||||
|
- Do not add bundled prompts, skills, or docs whose primary purpose is to market, endorse, or funnel users toward a third-party product or service. Product integrations must be justified by user-facing utility and written in neutral language.
|
||||||
|
|
||||||
## Repo-Specific Checks
|
## Repo-Specific Checks
|
||||||
|
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -25,9 +25,11 @@ curl -fsSL https://feynman.is/install | bash
|
|||||||
irm https://feynman.is/install.ps1 | iex
|
irm https://feynman.is/install.ps1 | iex
|
||||||
```
|
```
|
||||||
|
|
||||||
The one-line installer fetches the latest tagged release. To pin a version, pass it explicitly, for example `curl -fsSL https://feynman.is/install | bash -s -- 0.2.15`.
|
The one-line installer fetches the latest tagged release. To pin a version, pass it explicitly, for example `curl -fsSL https://feynman.is/install | bash -s -- 0.2.17`.
|
||||||
|
|
||||||
If you install via `pnpm` or `bun` instead of the standalone bundle, Feynman requires Node.js `20.19.0` or newer.
|
The installer downloads a standalone native bundle with its own Node.js runtime.
|
||||||
|
|
||||||
|
Local models are supported through the custom-provider flow. For Ollama, run `feynman setup`, choose `Custom provider (baseUrl + API key)`, use `openai-completions`, and point it at `http://localhost:11434/v1`.
|
||||||
|
|
||||||
### Skills Only
|
### Skills Only
|
||||||
|
|
||||||
@@ -63,6 +65,8 @@ curl -fsSL https://feynman.is/install-skills | bash -s -- --repo
|
|||||||
|
|
||||||
That installs into `.agents/skills/feynman` under the current repository.
|
That installs into `.agents/skills/feynman` under the current repository.
|
||||||
|
|
||||||
|
These installers download the bundled `skills/` and `prompts/` trees plus the repo guidance files referenced by those skills. They do not install the Feynman terminal, bundled Node runtime, auth storage, or Pi packages.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### What you type → what happens
|
### What you type → what happens
|
||||||
@@ -82,9 +86,6 @@ $ feynman audit 2401.12345
|
|||||||
|
|
||||||
$ feynman replicate "chain-of-thought improves math"
|
$ feynman replicate "chain-of-thought improves math"
|
||||||
→ Replicates experiments on local or cloud GPUs
|
→ Replicates experiments on local or cloud GPUs
|
||||||
|
|
||||||
$ feynman valichord "study-id-or-topic"
|
|
||||||
→ Runs the ValiChord reproducibility workflow or checks existing Harmony Records
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -100,7 +101,6 @@ Ask naturally or use slash commands as shortcuts.
|
|||||||
| `/review <artifact>` | Simulated peer review with severity and revision plan |
|
| `/review <artifact>` | Simulated peer review with severity and revision plan |
|
||||||
| `/audit <item>` | Paper vs. codebase mismatch audit |
|
| `/audit <item>` | Paper vs. codebase mismatch audit |
|
||||||
| `/replicate <paper>` | Replicate experiments on local or cloud GPUs |
|
| `/replicate <paper>` | Replicate experiments on local or cloud GPUs |
|
||||||
| `/valichord <study-or-topic>` | Reproducibility attestation workflow and Harmony Record lookup |
|
|
||||||
| `/compare <topic>` | Source comparison matrix |
|
| `/compare <topic>` | Source comparison matrix |
|
||||||
| `/draft <topic>` | Paper-style draft from research findings |
|
| `/draft <topic>` | Paper-style draft from research findings |
|
||||||
| `/autoresearch <idea>` | Autonomous experiment loop |
|
| `/autoresearch <idea>` | Autonomous experiment loop |
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
import { pathToFileURL } from "node:url";
|
||||||
|
|
||||||
const MIN_NODE_VERSION = "20.19.0";
|
const MIN_NODE_VERSION = "20.19.0";
|
||||||
|
|
||||||
function parseNodeVersion(version) {
|
function parseNodeVersion(version) {
|
||||||
@@ -27,5 +30,7 @@ if (compareNodeVersions(parseNodeVersion(process.versions.node), parseNodeVersio
|
|||||||
: "curl -fsSL https://feynman.is/install | bash");
|
: "curl -fsSL https://feynman.is/install | bash");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
await import(new URL("../scripts/patch-embedded-pi.mjs", import.meta.url).href);
|
const here = import.meta.dirname;
|
||||||
await import(new URL("../dist/index.js", import.meta.url).href);
|
|
||||||
|
await import(pathToFileURL(resolve(here, "..", "scripts", "patch-embedded-pi.mjs")).href);
|
||||||
|
await import(pathToFileURL(resolve(here, "..", "dist", "index.js")).href);
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||||
|
|
||||||
import { registerAlphaTools } from "./research-tools/alpha.js";
|
import { registerAlphaTools } from "./research-tools/alpha.js";
|
||||||
|
import { registerDiscoveryCommands } from "./research-tools/discovery.js";
|
||||||
|
import { registerFeynmanModelCommand } from "./research-tools/feynman-model.js";
|
||||||
import { installFeynmanHeader } from "./research-tools/header.js";
|
import { installFeynmanHeader } from "./research-tools/header.js";
|
||||||
import { registerHelpCommand } from "./research-tools/help.js";
|
import { registerHelpCommand } from "./research-tools/help.js";
|
||||||
import { registerInitCommand, registerOutputsCommand } from "./research-tools/project.js";
|
import { registerInitCommand, registerOutputsCommand } from "./research-tools/project.js";
|
||||||
|
import { registerServiceTierControls } from "./research-tools/service-tier.js";
|
||||||
|
|
||||||
export default function researchTools(pi: ExtensionAPI): void {
|
export default function researchTools(pi: ExtensionAPI): void {
|
||||||
const cache: { agentSummaryPromise?: Promise<{ agents: string[]; chains: string[] }> } = {};
|
const cache: { agentSummaryPromise?: Promise<{ agents: string[]; chains: string[] }> } = {};
|
||||||
@@ -17,7 +20,10 @@ export default function researchTools(pi: ExtensionAPI): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
registerAlphaTools(pi);
|
registerAlphaTools(pi);
|
||||||
|
registerDiscoveryCommands(pi);
|
||||||
|
registerFeynmanModelCommand(pi);
|
||||||
registerHelpCommand(pi);
|
registerHelpCommand(pi);
|
||||||
registerInitCommand(pi);
|
registerInitCommand(pi);
|
||||||
registerOutputsCommand(pi);
|
registerOutputsCommand(pi);
|
||||||
|
registerServiceTierControls(pi);
|
||||||
}
|
}
|
||||||
|
|||||||
130
extensions/research-tools/discovery.ts
Normal file
130
extensions/research-tools/discovery.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { existsSync, readFileSync } from "node:fs";
|
||||||
|
import { homedir } from "node:os";
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
|
||||||
|
import type { ExtensionAPI, SlashCommandInfo, ToolInfo } from "@mariozechner/pi-coding-agent";
|
||||||
|
|
||||||
|
function resolveFeynmanSettingsPath(): string {
|
||||||
|
const configured = process.env.PI_CODING_AGENT_DIR?.trim();
|
||||||
|
const agentDir = configured
|
||||||
|
? configured.startsWith("~/")
|
||||||
|
? resolve(homedir(), configured.slice(2))
|
||||||
|
: resolve(configured)
|
||||||
|
: resolve(homedir(), ".feynman", "agent");
|
||||||
|
return resolve(agentDir, "settings.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
function readConfiguredPackages(): string[] {
|
||||||
|
const settingsPath = resolveFeynmanSettingsPath();
|
||||||
|
if (!existsSync(settingsPath)) return [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(readFileSync(settingsPath, "utf8")) as { packages?: unknown[] };
|
||||||
|
return Array.isArray(parsed.packages)
|
||||||
|
? parsed.packages
|
||||||
|
.map((entry) => {
|
||||||
|
if (typeof entry === "string") return entry;
|
||||||
|
if (!entry || typeof entry !== "object") return undefined;
|
||||||
|
const record = entry as { source?: unknown };
|
||||||
|
return typeof record.source === "string" ? record.source : undefined;
|
||||||
|
})
|
||||||
|
.filter((entry): entry is string => Boolean(entry))
|
||||||
|
: [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatSourceLabel(sourceInfo: { source: string; path: string }): string {
|
||||||
|
if (sourceInfo.source === "local") {
|
||||||
|
if (sourceInfo.path.includes("/prompts/")) return "workflow";
|
||||||
|
if (sourceInfo.path.includes("/extensions/")) return "extension";
|
||||||
|
return "local";
|
||||||
|
}
|
||||||
|
return sourceInfo.source.replace(/^npm:/, "").replace(/^git:/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCommandLine(command: SlashCommandInfo): string {
|
||||||
|
const source = formatSourceLabel(command.sourceInfo);
|
||||||
|
return `/${command.name} — ${command.description ?? ""} [${source}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function summarizeToolParameters(tool: ToolInfo): string {
|
||||||
|
const properties =
|
||||||
|
tool.parameters &&
|
||||||
|
typeof tool.parameters === "object" &&
|
||||||
|
"properties" in tool.parameters &&
|
||||||
|
tool.parameters.properties &&
|
||||||
|
typeof tool.parameters.properties === "object"
|
||||||
|
? Object.keys(tool.parameters.properties as Record<string, unknown>)
|
||||||
|
: [];
|
||||||
|
return properties.length > 0 ? properties.join(", ") : "no parameters";
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatToolLine(tool: ToolInfo): string {
|
||||||
|
const source = formatSourceLabel(tool.sourceInfo);
|
||||||
|
return `${tool.name} — ${tool.description ?? ""} [${source}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerDiscoveryCommands(pi: ExtensionAPI): void {
|
||||||
|
pi.registerCommand("commands", {
|
||||||
|
description: "Browse all available slash commands, including package and built-in commands.",
|
||||||
|
handler: async (_args, ctx) => {
|
||||||
|
const commands = pi
|
||||||
|
.getCommands()
|
||||||
|
.slice()
|
||||||
|
.sort((left, right) => left.name.localeCompare(right.name));
|
||||||
|
const items = commands.map((command) => formatCommandLine(command));
|
||||||
|
const selected = await ctx.ui.select("Slash Commands", items);
|
||||||
|
if (!selected) return;
|
||||||
|
ctx.ui.setEditorText(selected.split(" — ")[0] ?? "");
|
||||||
|
ctx.ui.notify(`Prefilled ${selected.split(" — ")[0]}`, "info");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
pi.registerCommand("tools", {
|
||||||
|
description: "Browse all callable tools with their source and parameter summary.",
|
||||||
|
handler: async (_args, ctx) => {
|
||||||
|
const tools = pi
|
||||||
|
.getAllTools()
|
||||||
|
.slice()
|
||||||
|
.sort((left, right) => left.name.localeCompare(right.name));
|
||||||
|
const selected = await ctx.ui.select("Tools", tools.map((tool) => formatToolLine(tool)));
|
||||||
|
if (!selected) return;
|
||||||
|
|
||||||
|
const toolName = selected.split(" — ")[0] ?? selected;
|
||||||
|
const tool = tools.find((entry) => entry.name === toolName);
|
||||||
|
if (!tool) return;
|
||||||
|
ctx.ui.notify(`${tool.name}: ${summarizeToolParameters(tool)}`, "info");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
pi.registerCommand("capabilities", {
|
||||||
|
description: "Show installed packages, discovery entrypoints, and high-level runtime capability counts.",
|
||||||
|
handler: async (_args, ctx) => {
|
||||||
|
const commands = pi.getCommands();
|
||||||
|
const tools = pi.getAllTools();
|
||||||
|
const workflows = commands.filter((command) => formatSourceLabel(command.sourceInfo) === "workflow");
|
||||||
|
const packages = readConfiguredPackages();
|
||||||
|
const items = [
|
||||||
|
`Commands: ${commands.length}`,
|
||||||
|
`Workflows: ${workflows.length}`,
|
||||||
|
`Tools: ${tools.length}`,
|
||||||
|
`Packages: ${packages.length}`,
|
||||||
|
"--- Discovery ---",
|
||||||
|
"/commands — browse slash commands",
|
||||||
|
"/tools — inspect callable tools",
|
||||||
|
"/hotkeys — view keyboard shortcuts",
|
||||||
|
"/service-tier — set request tier for supported providers",
|
||||||
|
"--- Installed Packages ---",
|
||||||
|
...packages.map((pkg) => pkg),
|
||||||
|
];
|
||||||
|
const selected = await ctx.ui.select("Capabilities", items);
|
||||||
|
if (!selected || selected.startsWith("---")) return;
|
||||||
|
if (selected.startsWith("/")) {
|
||||||
|
ctx.ui.setEditorText(selected.split(" — ")[0] ?? selected);
|
||||||
|
ctx.ui.notify(`Prefilled ${selected.split(" — ")[0]}`, "info");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
309
extensions/research-tools/feynman-model.ts
Normal file
309
extensions/research-tools/feynman-model.ts
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
import { type Dirent, existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||||
|
import { homedir } from "node:os";
|
||||||
|
import { basename, join, resolve } from "node:path";
|
||||||
|
|
||||||
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||||
|
|
||||||
|
const FRONTMATTER_PATTERN = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/;
|
||||||
|
const INHERIT_MAIN = "__inherit_main__";
|
||||||
|
|
||||||
|
type FrontmatterDocument = {
|
||||||
|
lines: string[];
|
||||||
|
body: string;
|
||||||
|
eol: string;
|
||||||
|
trailingNewline: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SubagentModelConfig = {
|
||||||
|
agent: string;
|
||||||
|
model?: string;
|
||||||
|
filePath: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SelectOption<T> = {
|
||||||
|
label: string;
|
||||||
|
value: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CommandContext = Parameters<Parameters<ExtensionAPI["registerCommand"]>[1]["handler"]>[1];
|
||||||
|
|
||||||
|
type TargetChoice =
|
||||||
|
| { type: "main" }
|
||||||
|
| { type: "subagent"; agent: string; model?: string };
|
||||||
|
|
||||||
|
function expandHomePath(value: string): string {
|
||||||
|
if (value === "~") return homedir();
|
||||||
|
if (value.startsWith("~/")) return resolve(homedir(), value.slice(2));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveFeynmanAgentDir(): string {
|
||||||
|
const configured = process.env.PI_CODING_AGENT_DIR ?? process.env.FEYNMAN_CODING_AGENT_DIR;
|
||||||
|
if (configured?.trim()) {
|
||||||
|
return resolve(expandHomePath(configured.trim()));
|
||||||
|
}
|
||||||
|
return resolve(homedir(), ".feynman", "agent");
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatModelSpec(model: { provider: string; id: string }): string {
|
||||||
|
return `${model.provider}/${model.id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectEol(text: string): string {
|
||||||
|
return text.includes("\r\n") ? "\r\n" : "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLineEndings(text: string): string {
|
||||||
|
return text.replace(/\r\n/g, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFrontmatterDocument(text: string): FrontmatterDocument | null {
|
||||||
|
const normalized = normalizeLineEndings(text);
|
||||||
|
const match = normalized.match(FRONTMATTER_PATTERN);
|
||||||
|
if (!match) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
lines: match[1].split("\n"),
|
||||||
|
body: match[2] ?? "",
|
||||||
|
eol: detectEol(text),
|
||||||
|
trailingNewline: normalized.endsWith("\n"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeFrontmatterDocument(document: FrontmatterDocument): string {
|
||||||
|
const normalized = `---\n${document.lines.join("\n")}\n---\n${document.body}`;
|
||||||
|
const withTrailingNewline =
|
||||||
|
document.trailingNewline && !normalized.endsWith("\n") ? `${normalized}\n` : normalized;
|
||||||
|
return document.eol === "\n" ? withTrailingNewline : withTrailingNewline.replace(/\n/g, "\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFrontmatterKey(line: string): string | undefined {
|
||||||
|
const match = line.match(/^\s*([A-Za-z0-9_-]+)\s*:/);
|
||||||
|
return match?.[1]?.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFrontmatterValue(lines: string[], key: string): string | undefined {
|
||||||
|
const normalizedKey = key.toLowerCase();
|
||||||
|
for (const line of lines) {
|
||||||
|
const parsedKey = parseFrontmatterKey(line);
|
||||||
|
if (parsedKey !== normalizedKey) continue;
|
||||||
|
const separatorIndex = line.indexOf(":");
|
||||||
|
if (separatorIndex === -1) return undefined;
|
||||||
|
const value = line.slice(separatorIndex + 1).trim();
|
||||||
|
return value.length > 0 ? value : undefined;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function upsertFrontmatterValue(lines: string[], key: string, value: string): string[] {
|
||||||
|
const normalizedKey = key.toLowerCase();
|
||||||
|
const nextLines = [...lines];
|
||||||
|
const existingIndex = nextLines.findIndex((line) => parseFrontmatterKey(line) === normalizedKey);
|
||||||
|
const serialized = `${key}: ${value}`;
|
||||||
|
|
||||||
|
if (existingIndex !== -1) {
|
||||||
|
nextLines[existingIndex] = serialized;
|
||||||
|
return nextLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
const descriptionIndex = nextLines.findIndex((line) => parseFrontmatterKey(line) === "description");
|
||||||
|
const nameIndex = nextLines.findIndex((line) => parseFrontmatterKey(line) === "name");
|
||||||
|
const insertIndex = descriptionIndex !== -1 ? descriptionIndex + 1 : nameIndex !== -1 ? nameIndex + 1 : nextLines.length;
|
||||||
|
nextLines.splice(insertIndex, 0, serialized);
|
||||||
|
return nextLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFrontmatterKey(lines: string[], key: string): string[] {
|
||||||
|
const normalizedKey = key.toLowerCase();
|
||||||
|
return lines.filter((line) => parseFrontmatterKey(line) !== normalizedKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAgentName(name: string): string {
|
||||||
|
return name.trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAgentsDir(agentDir: string): string {
|
||||||
|
return join(agentDir, "agents");
|
||||||
|
}
|
||||||
|
|
||||||
|
function listAgentFiles(agentsDir: string): string[] {
|
||||||
|
if (!existsSync(agentsDir)) return [];
|
||||||
|
|
||||||
|
return readdirSync(agentsDir, { withFileTypes: true })
|
||||||
|
.filter((entry: Dirent) => (entry.isFile() || entry.isSymbolicLink()) && entry.name.endsWith(".md"))
|
||||||
|
.filter((entry) => !entry.name.endsWith(".chain.md"))
|
||||||
|
.map((entry) => join(agentsDir, entry.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
function readAgentConfig(filePath: string): SubagentModelConfig {
|
||||||
|
const content = readFileSync(filePath, "utf8");
|
||||||
|
const parsed = parseFrontmatterDocument(content);
|
||||||
|
const fallbackName = basename(filePath, ".md");
|
||||||
|
if (!parsed) return { agent: fallbackName, filePath };
|
||||||
|
|
||||||
|
return {
|
||||||
|
agent: getFrontmatterValue(parsed.lines, "name") ?? fallbackName,
|
||||||
|
model: getFrontmatterValue(parsed.lines, "model"),
|
||||||
|
filePath,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function listSubagentModelConfigs(agentDir: string): SubagentModelConfig[] {
|
||||||
|
return listAgentFiles(getAgentsDir(agentDir))
|
||||||
|
.map((filePath) => readAgentConfig(filePath))
|
||||||
|
.sort((left, right) => left.agent.localeCompare(right.agent));
|
||||||
|
}
|
||||||
|
|
||||||
|
function findAgentConfig(configs: SubagentModelConfig[], agentName: string): SubagentModelConfig | undefined {
|
||||||
|
const normalized = normalizeAgentName(agentName);
|
||||||
|
return (
|
||||||
|
configs.find((config) => normalizeAgentName(config.agent) === normalized) ??
|
||||||
|
configs.find((config) => normalizeAgentName(basename(config.filePath, ".md")) === normalized)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAgentConfigOrThrow(agentDir: string, agentName: string): SubagentModelConfig {
|
||||||
|
const configs = listSubagentModelConfigs(agentDir);
|
||||||
|
const target = findAgentConfig(configs, agentName);
|
||||||
|
if (target) return target;
|
||||||
|
|
||||||
|
if (configs.length === 0) {
|
||||||
|
throw new Error(`No subagent definitions found in ${getAgentsDir(agentDir)}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const availableAgents = configs.map((config) => config.agent).join(", ");
|
||||||
|
throw new Error(`Unknown subagent: ${agentName}. Available agents: ${availableAgents}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSubagentModel(agentDir: string, agentName: string, modelSpec: string): void {
|
||||||
|
const normalizedModelSpec = modelSpec.trim();
|
||||||
|
if (!normalizedModelSpec) throw new Error("Model spec cannot be empty.");
|
||||||
|
|
||||||
|
const target = getAgentConfigOrThrow(agentDir, agentName);
|
||||||
|
const content = readFileSync(target.filePath, "utf8");
|
||||||
|
const parsed = parseFrontmatterDocument(content);
|
||||||
|
|
||||||
|
if (!parsed) {
|
||||||
|
const eol = detectEol(content);
|
||||||
|
const injected = `---${eol}name: ${target.agent}${eol}model: ${normalizedModelSpec}${eol}---${eol}${content}`;
|
||||||
|
writeFileSync(target.filePath, injected, "utf8");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextLines = upsertFrontmatterValue(parsed.lines, "model", normalizedModelSpec);
|
||||||
|
if (nextLines.join("\n") !== parsed.lines.join("\n")) {
|
||||||
|
writeFileSync(target.filePath, serializeFrontmatterDocument({ ...parsed, lines: nextLines }), "utf8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unsetSubagentModel(agentDir: string, agentName: string): void {
|
||||||
|
const target = getAgentConfigOrThrow(agentDir, agentName);
|
||||||
|
const content = readFileSync(target.filePath, "utf8");
|
||||||
|
const parsed = parseFrontmatterDocument(content);
|
||||||
|
if (!parsed) return;
|
||||||
|
|
||||||
|
const nextLines = removeFrontmatterKey(parsed.lines, "model");
|
||||||
|
if (nextLines.join("\n") !== parsed.lines.join("\n")) {
|
||||||
|
writeFileSync(target.filePath, serializeFrontmatterDocument({ ...parsed, lines: nextLines }), "utf8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function selectOption<T>(
|
||||||
|
ctx: CommandContext,
|
||||||
|
title: string,
|
||||||
|
options: SelectOption<T>[],
|
||||||
|
): Promise<T | undefined> {
|
||||||
|
const selected = await ctx.ui.select(
|
||||||
|
title,
|
||||||
|
options.map((option) => option.label),
|
||||||
|
);
|
||||||
|
if (!selected) return undefined;
|
||||||
|
return options.find((option) => option.label === selected)?.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerFeynmanModelCommand(pi: ExtensionAPI): void {
|
||||||
|
pi.registerCommand("feynman-model", {
|
||||||
|
description: "Open Feynman model menu (main + per-subagent overrides).",
|
||||||
|
handler: async (_args, ctx) => {
|
||||||
|
if (!ctx.hasUI) {
|
||||||
|
ctx.ui.notify("feynman-model requires interactive mode.", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ctx.modelRegistry.refresh();
|
||||||
|
const availableModels = [...ctx.modelRegistry.getAvailable()].sort((left, right) =>
|
||||||
|
formatModelSpec(left).localeCompare(formatModelSpec(right)),
|
||||||
|
);
|
||||||
|
if (availableModels.length === 0) {
|
||||||
|
ctx.ui.notify("No models available.", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const agentDir = resolveFeynmanAgentDir();
|
||||||
|
const subagentConfigs = listSubagentModelConfigs(agentDir);
|
||||||
|
const currentMain = ctx.model ? formatModelSpec(ctx.model) : "(none)";
|
||||||
|
|
||||||
|
const targetOptions: SelectOption<TargetChoice>[] = [
|
||||||
|
{ label: `main (default): ${currentMain}`, value: { type: "main" } },
|
||||||
|
...subagentConfigs.map((config) => ({
|
||||||
|
label: `${config.agent}: ${config.model ?? "default"}`,
|
||||||
|
value: { type: "subagent" as const, agent: config.agent, model: config.model },
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
|
||||||
|
const target = await selectOption(ctx, "Choose target", targetOptions);
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
if (target.type === "main") {
|
||||||
|
const selectedModel = await selectOption(
|
||||||
|
ctx,
|
||||||
|
"Select main model",
|
||||||
|
availableModels.map((model) => {
|
||||||
|
const spec = formatModelSpec(model);
|
||||||
|
const suffix = spec === currentMain ? " (current)" : "";
|
||||||
|
return { label: `${spec}${suffix}`, value: model };
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if (!selectedModel) return;
|
||||||
|
|
||||||
|
const success = await pi.setModel(selectedModel);
|
||||||
|
if (!success) {
|
||||||
|
ctx.ui.notify(`No API key found for ${selectedModel.provider}.`, "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.ui.notify(`Main model set to ${formatModelSpec(selectedModel)}.`, "info");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedSubagentModel = await selectOption(
|
||||||
|
ctx,
|
||||||
|
`Select model for ${target.agent}`,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: target.model ? "(inherit main default)" : "(inherit main default) (current)",
|
||||||
|
value: INHERIT_MAIN,
|
||||||
|
},
|
||||||
|
...availableModels.map((model) => {
|
||||||
|
const spec = formatModelSpec(model);
|
||||||
|
const suffix = spec === target.model ? " (current)" : "";
|
||||||
|
return { label: `${spec}${suffix}`, value: spec };
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
if (!selectedSubagentModel) return;
|
||||||
|
|
||||||
|
if (selectedSubagentModel === INHERIT_MAIN) {
|
||||||
|
unsetSubagentModel(agentDir, target.agent);
|
||||||
|
ctx.ui.notify(`${target.agent} now inherits the main model.`, "info");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSubagentModel(agentDir, target.agent, selectedSubagentModel);
|
||||||
|
ctx.ui.notify(`${target.agent} model set to ${selectedSubagentModel}.`, "info");
|
||||||
|
} catch (error) {
|
||||||
|
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { execSync } from "node:child_process";
|
|||||||
import { resolve as resolvePath } from "node:path";
|
import { resolve as resolvePath } from "node:path";
|
||||||
|
|
||||||
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
||||||
|
import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
APP_ROOT,
|
APP_ROOT,
|
||||||
@@ -11,10 +12,8 @@ import {
|
|||||||
FEYNMAN_VERSION,
|
FEYNMAN_VERSION,
|
||||||
} from "./shared.js";
|
} from "./shared.js";
|
||||||
|
|
||||||
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
||||||
|
|
||||||
function visibleLength(text: string): number {
|
function visibleLength(text: string): number {
|
||||||
return text.replace(ANSI_RE, "").length;
|
return visibleWidth(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatHeaderPath(path: string): string {
|
function formatHeaderPath(path: string): string {
|
||||||
@@ -23,10 +22,8 @@ function formatHeaderPath(path: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function truncateVisible(text: string, maxVisible: number): string {
|
function truncateVisible(text: string, maxVisible: number): string {
|
||||||
const raw = text.replace(ANSI_RE, "");
|
if (visibleWidth(text) <= maxVisible) return text;
|
||||||
if (raw.length <= maxVisible) return text;
|
return truncateToWidth(text, maxVisible, maxVisible <= 3 ? "" : "...");
|
||||||
if (maxVisible <= 3) return ".".repeat(maxVisible);
|
|
||||||
return `${raw.slice(0, maxVisible - 3)}...`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrapWords(text: string, maxW: number): string[] {
|
function wrapWords(text: string, maxW: number): string[] {
|
||||||
@@ -34,12 +31,12 @@ function wrapWords(text: string, maxW: number): string[] {
|
|||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
let cur = "";
|
let cur = "";
|
||||||
for (let word of words) {
|
for (let word of words) {
|
||||||
if (word.length > maxW) {
|
if (visibleWidth(word) > maxW) {
|
||||||
if (cur) { lines.push(cur); cur = ""; }
|
if (cur) { lines.push(cur); cur = ""; }
|
||||||
word = maxW > 3 ? `${word.slice(0, maxW - 1)}…` : word.slice(0, maxW);
|
word = truncateToWidth(word, maxW, maxW > 3 ? "…" : "");
|
||||||
}
|
}
|
||||||
const test = cur ? `${cur} ${word}` : word;
|
const test = cur ? `${cur} ${word}` : word;
|
||||||
if (cur && test.length > maxW) {
|
if (cur && visibleWidth(test) > maxW) {
|
||||||
lines.push(cur);
|
lines.push(cur);
|
||||||
cur = word;
|
cur = word;
|
||||||
} else {
|
} else {
|
||||||
@@ -56,9 +53,10 @@ function padRight(text: string, width: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function centerText(text: string, width: number): string {
|
function centerText(text: string, width: number): string {
|
||||||
if (text.length >= width) return text.slice(0, width);
|
const textWidth = visibleWidth(text);
|
||||||
const left = Math.floor((width - text.length) / 2);
|
if (textWidth >= width) return truncateToWidth(text, width, "");
|
||||||
const right = width - text.length - left;
|
const left = Math.floor((width - textWidth) / 2);
|
||||||
|
const right = width - textWidth - left;
|
||||||
return `${" ".repeat(left)}${text}${" ".repeat(right)}`;
|
return `${" ".repeat(left)}${text}${" ".repeat(right)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,8 +285,8 @@ export function installFeynmanHeader(
|
|||||||
|
|
||||||
if (activity) {
|
if (activity) {
|
||||||
const maxActivityLen = leftW * 2;
|
const maxActivityLen = leftW * 2;
|
||||||
const trimmed = activity.length > maxActivityLen
|
const trimmed = visibleWidth(activity) > maxActivityLen
|
||||||
? `${activity.slice(0, maxActivityLen - 1)}…`
|
? truncateToWidth(activity, maxActivityLen, "…")
|
||||||
: activity;
|
: activity;
|
||||||
leftLines.push("");
|
leftLines.push("");
|
||||||
leftLines.push(theme.fg("accent", theme.bold("Last Activity")));
|
leftLines.push(theme.fg("accent", theme.bold("Last Activity")));
|
||||||
|
|||||||
174
extensions/research-tools/service-tier.ts
Normal file
174
extensions/research-tools/service-tier.ts
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import { homedir } from "node:os";
|
||||||
|
import { readFileSync, writeFileSync } from "node:fs";
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
|
||||||
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||||
|
|
||||||
|
const FEYNMAN_SERVICE_TIERS = [
|
||||||
|
"auto",
|
||||||
|
"default",
|
||||||
|
"flex",
|
||||||
|
"priority",
|
||||||
|
"standard_only",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
type FeynmanServiceTier = (typeof FEYNMAN_SERVICE_TIERS)[number];
|
||||||
|
|
||||||
|
const SERVICE_TIER_SET = new Set<string>(FEYNMAN_SERVICE_TIERS);
|
||||||
|
const OPENAI_SERVICE_TIERS = new Set<FeynmanServiceTier>(["auto", "default", "flex", "priority"]);
|
||||||
|
const ANTHROPIC_SERVICE_TIERS = new Set<FeynmanServiceTier>(["auto", "standard_only"]);
|
||||||
|
|
||||||
|
type CommandContext = Parameters<Parameters<ExtensionAPI["registerCommand"]>[1]["handler"]>[1];
|
||||||
|
|
||||||
|
type SelectOption<T> = {
|
||||||
|
label: string;
|
||||||
|
value: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
function resolveFeynmanSettingsPath(): string {
|
||||||
|
const configured = process.env.PI_CODING_AGENT_DIR?.trim();
|
||||||
|
const agentDir = configured
|
||||||
|
? configured.startsWith("~/")
|
||||||
|
? resolve(homedir(), configured.slice(2))
|
||||||
|
: resolve(configured)
|
||||||
|
: resolve(homedir(), ".feynman", "agent");
|
||||||
|
return resolve(agentDir, "settings.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeServiceTier(value: string | undefined): FeynmanServiceTier | undefined {
|
||||||
|
if (!value) return undefined;
|
||||||
|
const normalized = value.trim().toLowerCase();
|
||||||
|
return SERVICE_TIER_SET.has(normalized) ? (normalized as FeynmanServiceTier) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfiguredServiceTier(settingsPath: string): FeynmanServiceTier | undefined {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(readFileSync(settingsPath, "utf8")) as { serviceTier?: string };
|
||||||
|
return normalizeServiceTier(parsed.serviceTier);
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setConfiguredServiceTier(settingsPath: string, tier: FeynmanServiceTier | undefined): void {
|
||||||
|
let settings: Record<string, unknown> = {};
|
||||||
|
try {
|
||||||
|
settings = JSON.parse(readFileSync(settingsPath, "utf8")) as Record<string, unknown>;
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
if (tier) {
|
||||||
|
settings.serviceTier = tier;
|
||||||
|
} else {
|
||||||
|
delete settings.serviceTier;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveActiveServiceTier(settingsPath: string): FeynmanServiceTier | undefined {
|
||||||
|
return normalizeServiceTier(process.env.FEYNMAN_SERVICE_TIER) ?? getConfiguredServiceTier(settingsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveProviderServiceTier(
|
||||||
|
provider: string | undefined,
|
||||||
|
tier: FeynmanServiceTier | undefined,
|
||||||
|
): FeynmanServiceTier | undefined {
|
||||||
|
if (!provider || !tier) return undefined;
|
||||||
|
if ((provider === "openai" || provider === "openai-codex") && OPENAI_SERVICE_TIERS.has(tier)) {
|
||||||
|
return tier;
|
||||||
|
}
|
||||||
|
if (provider === "anthropic" && ANTHROPIC_SERVICE_TIERS.has(tier)) {
|
||||||
|
return tier;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function selectOption<T>(
|
||||||
|
ctx: CommandContext,
|
||||||
|
title: string,
|
||||||
|
options: SelectOption<T>[],
|
||||||
|
): Promise<T | undefined> {
|
||||||
|
const selected = await ctx.ui.select(
|
||||||
|
title,
|
||||||
|
options.map((option) => option.label),
|
||||||
|
);
|
||||||
|
if (!selected) return undefined;
|
||||||
|
return options.find((option) => option.label === selected)?.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseRequestedTier(rawArgs: string): FeynmanServiceTier | null | undefined {
|
||||||
|
const trimmed = rawArgs.trim();
|
||||||
|
if (!trimmed) return undefined;
|
||||||
|
if (trimmed === "unset" || trimmed === "clear" || trimmed === "off") return null;
|
||||||
|
return normalizeServiceTier(trimmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerServiceTierControls(pi: ExtensionAPI): void {
|
||||||
|
pi.on("before_provider_request", (event, ctx) => {
|
||||||
|
if (!ctx.model || !event.payload || typeof event.payload !== "object") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeTier = resolveActiveServiceTier(resolveFeynmanSettingsPath());
|
||||||
|
const providerTier = resolveProviderServiceTier(ctx.model.provider, activeTier);
|
||||||
|
if (!providerTier) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...(event.payload as Record<string, unknown>),
|
||||||
|
service_tier: providerTier,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
pi.registerCommand("service-tier", {
|
||||||
|
description: "View or set the provider service tier override used for supported models.",
|
||||||
|
handler: async (args, ctx) => {
|
||||||
|
const settingsPath = resolveFeynmanSettingsPath();
|
||||||
|
const requested = parseRequestedTier(args);
|
||||||
|
|
||||||
|
if (requested === undefined && !args.trim()) {
|
||||||
|
if (!ctx.hasUI) {
|
||||||
|
ctx.ui.notify(getConfiguredServiceTier(settingsPath) ?? "not set", "info");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const current = getConfiguredServiceTier(settingsPath);
|
||||||
|
const selected = await selectOption(
|
||||||
|
ctx,
|
||||||
|
"Select service tier",
|
||||||
|
[
|
||||||
|
{ label: current ? `unset (current: ${current})` : "unset (current)", value: null },
|
||||||
|
...FEYNMAN_SERVICE_TIERS.map((tier) => ({
|
||||||
|
label: tier === current ? `${tier} (current)` : tier,
|
||||||
|
value: tier,
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
if (selected === undefined) return;
|
||||||
|
if (selected === null) {
|
||||||
|
setConfiguredServiceTier(settingsPath, undefined);
|
||||||
|
ctx.ui.notify("Cleared service tier override.", "info");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setConfiguredServiceTier(settingsPath, selected);
|
||||||
|
ctx.ui.notify(`Service tier set to ${selected}.`, "info");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requested === null) {
|
||||||
|
setConfiguredServiceTier(settingsPath, undefined);
|
||||||
|
ctx.ui.notify("Cleared service tier override.", "info");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!requested) {
|
||||||
|
ctx.ui.notify("Use auto, default, flex, priority, standard_only, or unset.", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfiguredServiceTier(settingsPath, requested);
|
||||||
|
ctx.ui.notify(`Service tier set to ${requested}.`, "info");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -35,9 +35,14 @@ export function readPromptSpecs(appRoot) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const extensionCommandSpecs = [
|
export const extensionCommandSpecs = [
|
||||||
|
{ name: "capabilities", args: "", section: "Project & Session", description: "Show installed packages, discovery entrypoints, and runtime capability counts.", publicDocs: true },
|
||||||
|
{ name: "commands", args: "", section: "Project & Session", description: "Browse all available slash commands, including built-in and package commands.", publicDocs: true },
|
||||||
{ name: "help", args: "", section: "Project & Session", description: "Show grouped Feynman commands and prefill the editor with a selected command.", publicDocs: true },
|
{ name: "help", args: "", section: "Project & Session", description: "Show grouped Feynman commands and prefill the editor with a selected command.", publicDocs: true },
|
||||||
|
{ name: "feynman-model", args: "", section: "Project & Session", description: "Open Feynman model menu (main + per-subagent overrides).", publicDocs: true },
|
||||||
{ name: "init", args: "", section: "Project & Session", description: "Bootstrap AGENTS.md and session-log folders for a research project.", publicDocs: true },
|
{ name: "init", args: "", section: "Project & Session", description: "Bootstrap AGENTS.md and session-log folders for a research project.", publicDocs: true },
|
||||||
{ name: "outputs", args: "", section: "Project & Session", description: "Browse all research artifacts (papers, outputs, experiments, notes).", publicDocs: true },
|
{ name: "outputs", args: "", section: "Project & Session", description: "Browse all research artifacts (papers, outputs, experiments, notes).", publicDocs: true },
|
||||||
|
{ name: "service-tier", args: "", section: "Project & Session", description: "View or set the provider service tier override for supported models.", publicDocs: true },
|
||||||
|
{ name: "tools", args: "", section: "Project & Session", description: "Browse all callable tools with their source and parameter summary.", publicDocs: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const livePackageCommandGroups = [
|
export const livePackageCommandGroups = [
|
||||||
@@ -57,6 +62,7 @@ export const livePackageCommandGroups = [
|
|||||||
{ name: "schedule-prompt", usage: "/schedule-prompt" },
|
{ name: "schedule-prompt", usage: "/schedule-prompt" },
|
||||||
{ name: "search", usage: "/search" },
|
{ name: "search", usage: "/search" },
|
||||||
{ name: "preview", usage: "/preview" },
|
{ name: "preview", usage: "/preview" },
|
||||||
|
{ name: "hotkeys", usage: "/hotkeys" },
|
||||||
{ name: "new", usage: "/new" },
|
{ name: "new", usage: "/new" },
|
||||||
{ name: "quit", usage: "/quit" },
|
{ name: "quit", usage: "/quit" },
|
||||||
{ name: "exit", usage: "/exit" },
|
{ name: "exit", usage: "/exit" },
|
||||||
@@ -83,6 +89,7 @@ export const cliCommandSections = [
|
|||||||
{ usage: "feynman model login [id]", description: "Login to a Pi OAuth model provider." },
|
{ usage: "feynman model login [id]", description: "Login to a Pi OAuth model provider." },
|
||||||
{ usage: "feynman model logout [id]", description: "Logout from a Pi OAuth model provider." },
|
{ usage: "feynman model logout [id]", description: "Logout from a Pi OAuth model provider." },
|
||||||
{ usage: "feynman model set <provider/model>", description: "Set the default model." },
|
{ usage: "feynman model set <provider/model>", description: "Set the default model." },
|
||||||
|
{ usage: "feynman model tier [value]", description: "View or set the request service tier override." },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -99,6 +106,8 @@ export const cliCommandSections = [
|
|||||||
{ usage: "feynman packages list", description: "Show core and optional Pi package presets." },
|
{ usage: "feynman packages list", description: "Show core and optional Pi package presets." },
|
||||||
{ usage: "feynman packages install <preset>", description: "Install optional package presets on demand." },
|
{ usage: "feynman packages install <preset>", description: "Install optional package presets on demand." },
|
||||||
{ usage: "feynman search status", description: "Show Pi web-access status and config path." },
|
{ usage: "feynman search status", description: "Show Pi web-access status and config path." },
|
||||||
|
{ usage: "feynman search set <provider> [api-key]", description: "Set the web search provider and optionally save its API key." },
|
||||||
|
{ usage: "feynman search clear", description: "Reset web search provider to auto while preserving API keys." },
|
||||||
{ usage: "feynman update [package]", description: "Update installed packages, or a specific package." },
|
{ usage: "feynman update [package]", description: "Update installed packages, or a specific package." },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -110,6 +119,7 @@ export const legacyFlags = [
|
|||||||
{ usage: "--alpha-logout", description: "Clear alphaXiv auth and exit." },
|
{ usage: "--alpha-logout", description: "Clear alphaXiv auth and exit." },
|
||||||
{ usage: "--alpha-status", description: "Show alphaXiv auth status and exit." },
|
{ usage: "--alpha-status", description: "Show alphaXiv auth status and exit." },
|
||||||
{ usage: "--model <provider:model>", description: "Force a specific model." },
|
{ usage: "--model <provider:model>", description: "Force a specific model." },
|
||||||
|
{ usage: "--service-tier <tier>", description: "Override request service tier for this run." },
|
||||||
{ usage: "--thinking <level>", description: "Set thinking level: off | minimal | low | medium | high | xhigh." },
|
{ usage: "--thinking <level>", description: "Set thinking level: off | minimal | low | medium | high | xhigh." },
|
||||||
{ usage: "--cwd <path>", description: "Set the working directory for tools." },
|
{ usage: "--cwd <path>", description: "Set the working directory for tools." },
|
||||||
{ usage: "--session-dir <path>", description: "Set the session storage directory." },
|
{ usage: "--session-dir <path>", description: "Set the session storage directory." },
|
||||||
|
|||||||
72
package-lock.json
generated
72
package-lock.json
generated
@@ -1,17 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "@companion-ai/feynman",
|
"name": "@companion-ai/feynman",
|
||||||
"version": "0.2.15",
|
"version": "0.2.17",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@companion-ai/feynman",
|
"name": "@companion-ai/feynman",
|
||||||
"version": "0.2.15",
|
"version": "0.2.17",
|
||||||
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@companion-ai/alpha-hub": "^0.1.2",
|
"@companion-ai/alpha-hub": "^0.1.2",
|
||||||
"@mariozechner/pi-ai": "^0.62.0",
|
"@mariozechner/pi-ai": "^0.64.0",
|
||||||
"@mariozechner/pi-coding-agent": "^0.62.0",
|
"@mariozechner/pi-coding-agent": "^0.64.0",
|
||||||
"@sinclair/typebox": "^0.34.48",
|
"@sinclair/typebox": "^0.34.48",
|
||||||
"dotenv": "^17.3.1"
|
"dotenv": "^17.3.1"
|
||||||
},
|
},
|
||||||
@@ -1264,9 +1265,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@hono/node-server": {
|
"node_modules/@hono/node-server": {
|
||||||
"version": "1.19.11",
|
"version": "1.19.13",
|
||||||
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz",
|
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.13.tgz",
|
||||||
"integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==",
|
"integrity": "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.14.1"
|
"node": ">=18.14.1"
|
||||||
@@ -1468,21 +1469,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mariozechner/pi-agent-core": {
|
"node_modules/@mariozechner/pi-agent-core": {
|
||||||
"version": "0.62.0",
|
"version": "0.64.0",
|
||||||
"resolved": "https://registry.npmjs.org/@mariozechner/pi-agent-core/-/pi-agent-core-0.62.0.tgz",
|
"resolved": "https://registry.npmjs.org/@mariozechner/pi-agent-core/-/pi-agent-core-0.64.0.tgz",
|
||||||
"integrity": "sha512-SBjqgDrgKOaC+IGzFGB3jXQErv9H1QMYnWFvUg6ra6dG0ZgWFBUZb6unidngWLsmaxSDWes6KeKiVFMsr2VSEQ==",
|
"integrity": "sha512-IN/sIxWOD0v1OFVXHB605SGiZhO5XdEWG5dO8EAV08n3jz/p12o4OuYGvhGXmHhU28WXa/FGWC+FO5xiIih8Uw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mariozechner/pi-ai": "^0.62.0"
|
"@mariozechner/pi-ai": "^0.64.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mariozechner/pi-ai": {
|
"node_modules/@mariozechner/pi-ai": {
|
||||||
"version": "0.62.0",
|
"version": "0.64.0",
|
||||||
"resolved": "https://registry.npmjs.org/@mariozechner/pi-ai/-/pi-ai-0.62.0.tgz",
|
"resolved": "https://registry.npmjs.org/@mariozechner/pi-ai/-/pi-ai-0.64.0.tgz",
|
||||||
"integrity": "sha512-mJgryZ5RgBQG++tiETMtCQQJoH2MAhKetCfqI98NMvGydu7L9x2qC2JekQlRaAgIlTgv4MRH1UXHMEs4UweE/Q==",
|
"integrity": "sha512-Z/Jnf+JSVDPLRcxJsa8XhYTJKIqKekNueaCpBLGQHgizL1F9RQ1Rur3rIfZpfXkt2cLu/AIPtOs223ueuoWaWg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.73.0",
|
"@anthropic-ai/sdk": "^0.73.0",
|
||||||
@@ -1507,16 +1508,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mariozechner/pi-coding-agent": {
|
"node_modules/@mariozechner/pi-coding-agent": {
|
||||||
"version": "0.62.0",
|
"version": "0.64.0",
|
||||||
"resolved": "https://registry.npmjs.org/@mariozechner/pi-coding-agent/-/pi-coding-agent-0.62.0.tgz",
|
"resolved": "https://registry.npmjs.org/@mariozechner/pi-coding-agent/-/pi-coding-agent-0.64.0.tgz",
|
||||||
"integrity": "sha512-f1NnExqsHuA6w8UVlBtPsvTBhdkMc0h1JD9SzGCdWTLou5GHJr2JIP6DlwV9IKWAnM+sAelaoFez+14wLP2zOQ==",
|
"integrity": "sha512-Q4tcqSqFGQtOgCtRyIp1D80Nv2if13Q2pfbnrOlaT/mix90mLcZGML9jKVnT1jGSy5GMYudU1HsS7cx53kxb0g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mariozechner/jiti": "^2.6.2",
|
"@mariozechner/jiti": "^2.6.2",
|
||||||
"@mariozechner/pi-agent-core": "^0.62.0",
|
"@mariozechner/pi-agent-core": "^0.64.0",
|
||||||
"@mariozechner/pi-ai": "^0.62.0",
|
"@mariozechner/pi-ai": "^0.64.0",
|
||||||
"@mariozechner/pi-tui": "^0.62.0",
|
"@mariozechner/pi-tui": "^0.64.0",
|
||||||
"@silvia-odwyer/photon-node": "^0.3.4",
|
"@silvia-odwyer/photon-node": "^0.3.4",
|
||||||
|
"ajv": "^8.17.1",
|
||||||
"chalk": "^5.5.0",
|
"chalk": "^5.5.0",
|
||||||
"cli-highlight": "^2.1.11",
|
"cli-highlight": "^2.1.11",
|
||||||
"diff": "^8.0.2",
|
"diff": "^8.0.2",
|
||||||
@@ -1543,9 +1545,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mariozechner/pi-tui": {
|
"node_modules/@mariozechner/pi-tui": {
|
||||||
"version": "0.62.0",
|
"version": "0.64.0",
|
||||||
"resolved": "https://registry.npmjs.org/@mariozechner/pi-tui/-/pi-tui-0.62.0.tgz",
|
"resolved": "https://registry.npmjs.org/@mariozechner/pi-tui/-/pi-tui-0.64.0.tgz",
|
||||||
"integrity": "sha512-/At11PPe8l319MnUoK4wN5L/uVCU6bDdiIUzH8Ez0stOkjSF6isRXScZ+RMM+6iCKsD4muBTX8Cmcif+3/UWHA==",
|
"integrity": "sha512-W1qLry9MAuN/V3YJmMv/BJa0VaYv721NkXPg/DGItdqWxuDc+1VdNbyAnRwxblNkIpXVUWL26x64BlyFXpxmkg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mime-types": "^2.1.4",
|
"@types/mime-types": "^2.1.4",
|
||||||
@@ -2528,9 +2530,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/basic-ftp": {
|
"node_modules/basic-ftp": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.1.tgz",
|
||||||
"integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==",
|
"integrity": "sha512-0yaL8JdxTknKDILitVpfYfV2Ob6yb3udX/hK97M7I3jOeznBNxQPtVvTUtnhUkyHlxFWyr5Lvknmgzoc7jf+1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
@@ -2576,9 +2578,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "5.0.4",
|
"version": "5.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||||
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
|
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^4.0.2"
|
"balanced-match": "^4.0.2"
|
||||||
@@ -3621,9 +3623,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hono": {
|
"node_modules/hono": {
|
||||||
"version": "4.12.9",
|
"version": "4.12.12",
|
||||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.9.tgz",
|
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz",
|
||||||
"integrity": "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA==",
|
"integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.9.0"
|
"node": ">=16.9.0"
|
||||||
@@ -4216,9 +4218,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "8.3.0",
|
"version": "8.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
|
||||||
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
|
"integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
|||||||
28
package.json
28
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@companion-ai/feynman",
|
"name": "@companion-ai/feynman",
|
||||||
"version": "0.2.15",
|
"version": "0.2.17",
|
||||||
"description": "Research-first CLI agent built on Pi and alphaXiv",
|
"description": "Research-first CLI agent built on Pi and alphaXiv",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -60,11 +60,33 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@companion-ai/alpha-hub": "^0.1.2",
|
"@companion-ai/alpha-hub": "^0.1.2",
|
||||||
"@mariozechner/pi-ai": "^0.62.0",
|
"@mariozechner/pi-ai": "^0.64.0",
|
||||||
"@mariozechner/pi-coding-agent": "^0.62.0",
|
"@mariozechner/pi-coding-agent": "^0.64.0",
|
||||||
"@sinclair/typebox": "^0.34.48",
|
"@sinclair/typebox": "^0.34.48",
|
||||||
"dotenv": "^17.3.1"
|
"dotenv": "^17.3.1"
|
||||||
},
|
},
|
||||||
|
"overrides": {
|
||||||
|
"basic-ftp": "5.2.1",
|
||||||
|
"@modelcontextprotocol/sdk": {
|
||||||
|
"@hono/node-server": "1.19.13",
|
||||||
|
"hono": "4.12.12"
|
||||||
|
},
|
||||||
|
"express": {
|
||||||
|
"router": {
|
||||||
|
"path-to-regexp": "8.4.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"proxy-agent": {
|
||||||
|
"pac-proxy-agent": {
|
||||||
|
"get-uri": {
|
||||||
|
"basic-ftp": "5.2.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimatch": {
|
||||||
|
"brace-expansion": "5.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^25.5.0",
|
"@types/node": "^25.5.0",
|
||||||
"tsx": "^4.21.0",
|
"tsx": "^4.21.0",
|
||||||
|
|||||||
@@ -1,266 +0,0 @@
|
|||||||
---
|
|
||||||
description: Submit a replication as a cryptographically verified ValiChord attestation, discover studies awaiting independent validation, query Harmony Records and reproducibility badges, or assist researchers in preparing a study for the validation pipeline.
|
|
||||||
section: Research Workflows
|
|
||||||
topLevelCli: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# ValiChord Validation Workflow
|
|
||||||
|
|
||||||
ValiChord is a distributed peer-to-peer system for scientific reproducibility verification, built on Holochain. It implements a blind commit-reveal protocol in Rust across four DNAs, producing Harmony Records — immutable, cryptographically verifiable proofs that independent parties reproduced the same findings without coordinating. Verified studies receive automatic reproducibility badges (Gold/Silver/Bronze); validators accumulate a per-discipline reputation score across rounds.
|
|
||||||
|
|
||||||
This workflow integrates Feynman at three levels: as a **validator agent** running the full commit-reveal protocol; as a **researcher's assistant** helping prepare a study for submission; and as a **query tool** surfacing reproducibility status during research.
|
|
||||||
|
|
||||||
**Live demo of the commit-reveal protocol**: https://youtu.be/DQ5wZSD1YEw
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ValiChord's four-DNA architecture
|
|
||||||
|
|
||||||
| DNA | Name | Type | Role |
|
|
||||||
|-----|------|------|------|
|
|
||||||
| 1 | Researcher Repository | Private, single-agent | Researcher's local archive. Stores study, pre-registered protocol, data snapshots, deviation declarations. Only SHA-256 hashes ever leave this DNA. |
|
|
||||||
| 2 | Validator Workspace | Private, single-agent | Feynman's working space. Stores task privately. Seals the blind commitment here — content never propagates to the DHT. |
|
|
||||||
| 3 | Attestation | Shared DHT | Coordination layer. Manages validation requests, validator profiles, study claims, commitment anchors, phase markers, and public attestations. 36 zome functions. |
|
|
||||||
| 4 | Governance | Public DHT | Final record layer. Assembles HarmonyRecords, issues reproducibility badges, tracks validator reputation, records governance decisions. All read functions accessible via HTTP Gateway without running a node. |
|
|
||||||
|
|
||||||
The key guarantee: a validator's findings are cryptographically sealed (`SHA-256(msgpack(attestation) || nonce)`) before the reveal phase opens. Neither party can adjust findings after seeing the other's results. The researcher runs a parallel commit-reveal — locking their expected results before the validators reveal — so no party can adapt to seeing the other's outcome.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Workflow A: Feynman as validator agent
|
|
||||||
|
|
||||||
### Step 0: Publish validator profile (one-time setup)
|
|
||||||
|
|
||||||
On first use, publish Feynman's public profile to DNA 3 so it appears in validator discovery indexes and conflict-of-interest checks:
|
|
||||||
|
|
||||||
```
|
|
||||||
publish_validator_profile(profile: ValidatorProfile)
|
|
||||||
```
|
|
||||||
|
|
||||||
Key fields:
|
|
||||||
- `agent_type` — `AutomatedTool` (AI agents are first-class validators; the protocol makes no distinction between human and machine validators)
|
|
||||||
- `disciplines` — list of disciplines Feynman can validate (e.g. ComputationalBiology, Statistics)
|
|
||||||
- `certification_tier` — starts as `Provisional`; advances to `Certified` after 5+ validations with ≥60% agreement rate, `Senior` after 20+ with ≥80%
|
|
||||||
|
|
||||||
If a profile already exists, use `update_validator_profile` to merge changes.
|
|
||||||
|
|
||||||
### Step 1: Gather inputs or discover study
|
|
||||||
|
|
||||||
**If the user provides a `request_ref`**: use it directly.
|
|
||||||
|
|
||||||
**If Feynman is proactively discovering work**: query the pending queue in DNA 3:
|
|
||||||
|
|
||||||
```
|
|
||||||
get_pending_requests_for_discipline(discipline: Discipline)
|
|
||||||
```
|
|
||||||
|
|
||||||
Returns all unclaimed `ValidationRequest` entries for the discipline. Each contains:
|
|
||||||
- `data_hash` — the ExternalHash identifier (used as `request_ref` throughout)
|
|
||||||
- `num_validators_required` — quorum needed to close the round
|
|
||||||
- `validation_tier` — Basic / Enhanced / Comprehensive
|
|
||||||
- `access_urls` — where to fetch the data and code
|
|
||||||
|
|
||||||
Optionally assess study complexity before committing:
|
|
||||||
|
|
||||||
```
|
|
||||||
assess_difficulty(input: AssessDifficultyInput)
|
|
||||||
```
|
|
||||||
|
|
||||||
Scores code volume, dependency count, documentation quality, data accessibility, and environment complexity. Returns predicted duration and confidence. Use this to decide whether to proceed before claiming.
|
|
||||||
|
|
||||||
If replication results are not yet available, suggest `/replicate` first.
|
|
||||||
|
|
||||||
### Step 2: Claim the study
|
|
||||||
|
|
||||||
Before receiving a formal task assignment, register intent to validate via DNA 3:
|
|
||||||
|
|
||||||
```
|
|
||||||
claim_study(request_ref: ExternalHash)
|
|
||||||
```
|
|
||||||
|
|
||||||
This:
|
|
||||||
- Reserves a validator slot (enforced capacity: no over-subscription)
|
|
||||||
- Triggers conflict-of-interest check — rejects claim if Feynman's institution matches the researcher's
|
|
||||||
- Records a `StudyClaim` entry on the shared DHT
|
|
||||||
|
|
||||||
If a claimed validator goes dark, any other validator can free the slot:
|
|
||||||
|
|
||||||
```
|
|
||||||
reclaim_abandoned_claim(input: ReclaimInput)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Receive task and seal private attestation — Commit phase
|
|
||||||
|
|
||||||
Connect to the ValiChord conductor via AppWebSocket. Using DNA 2 (Validator Workspace):
|
|
||||||
|
|
||||||
```
|
|
||||||
receive_task(request_ref, discipline, deadline_secs, validation_focus, time_cap_secs, compensation_tier)
|
|
||||||
```
|
|
||||||
|
|
||||||
`validation_focus` specifies which aspect Feynman is validating:
|
|
||||||
- `ComputationalReproducibility` — re-run code, check numerical outputs
|
|
||||||
- `PreCommitmentAdherence` — verify results match pre-registered analysis plan
|
|
||||||
- `MethodologicalReview` — assess statistical choices and protocol validity
|
|
||||||
|
|
||||||
Then seal the private attestation — this is the blind commitment:
|
|
||||||
|
|
||||||
```
|
|
||||||
seal_private_attestation(task_hash, attestation)
|
|
||||||
```
|
|
||||||
|
|
||||||
Where `attestation` includes:
|
|
||||||
- `outcome` — `Reproduced` / `PartiallyReproduced` / `FailedToReproduce` / `UnableToAssess`
|
|
||||||
- `outcome_summary` — key metrics, effect direction, confidence interval overlap, overall agreement
|
|
||||||
- `confidence` — High / Medium / Low
|
|
||||||
- `time_invested_secs` and `time_breakdown` — environment_setup, data_acquisition, code_execution, troubleshooting
|
|
||||||
- `computational_resources` — whether personal hardware, HPC, GPU, or cloud was required; estimated cost in pence
|
|
||||||
- `deviation_flags` — any undeclared departures from the original protocol (type, severity, evidence)
|
|
||||||
|
|
||||||
The coordinator computes `commitment_hash = SHA-256(msgpack(attestation) || nonce)` and writes a `CommitmentAnchor` to DNA 3's shared DHT. The attestation content remains private in DNA 2.
|
|
||||||
|
|
||||||
Save `task_hash` and `commitment_hash` to `outputs/<slug>-valichord-commit.json`.
|
|
||||||
|
|
||||||
### Step 4: Wait for RevealOpen phase
|
|
||||||
|
|
||||||
Poll DNA 3 (Attestation) until the phase transitions:
|
|
||||||
|
|
||||||
```
|
|
||||||
get_current_phase(request_ref: ExternalHash)
|
|
||||||
```
|
|
||||||
|
|
||||||
Returns `null` (still commit phase), `"RevealOpen"`, or `"Complete"`. Poll every 30 seconds. The phase opens automatically when the `CommitmentAnchor` count reaches `num_validators_required` — no manual trigger required.
|
|
||||||
|
|
||||||
During this wait, the researcher also runs their parallel commit-reveal: they lock their expected results via `publish_researcher_commitment` before the reveal phase opens, then reveal via `reveal_researcher_result` after all validators have submitted. No party — researcher or validator — can adapt to seeing the other's outcome.
|
|
||||||
|
|
||||||
### Step 5: Submit attestation — Reveal phase
|
|
||||||
|
|
||||||
When phase is `RevealOpen`, publish the full attestation to the shared DHT via DNA 3:
|
|
||||||
|
|
||||||
```
|
|
||||||
submit_attestation(attestation, nonce)
|
|
||||||
```
|
|
||||||
|
|
||||||
The coordinator verifies `SHA-256(msgpack(attestation) || nonce) == CommitmentAnchor.commitment_hash` before writing. This prevents adaptive reveals — the attestation must match exactly what was committed.
|
|
||||||
|
|
||||||
### Step 6: Retrieve Harmony Record and badges
|
|
||||||
|
|
||||||
Call DNA 4 (Governance) explicitly after `submit_attestation` returns — DHT propagation means the ValidatorToAttestation link may not be visible within the same transaction:
|
|
||||||
|
|
||||||
```
|
|
||||||
check_and_create_harmony_record(request_ref)
|
|
||||||
get_harmony_record(request_ref)
|
|
||||||
get_badges_for_study(request_ref)
|
|
||||||
```
|
|
||||||
|
|
||||||
The **Harmony Record** contains:
|
|
||||||
- `outcome` — the majority reproduced/not-reproduced finding
|
|
||||||
- `agreement_level` — ExactMatch / WithinTolerance / DirectionalMatch / Divergent / UnableToAssess
|
|
||||||
- `participating_validators` — array of validator agent keys
|
|
||||||
- `validation_duration_secs`
|
|
||||||
- `ActionHash` — the immutable on-chain identifier
|
|
||||||
|
|
||||||
**Reproducibility badges** are automatically issued when the Harmony Record is created:
|
|
||||||
|
|
||||||
| Badge | Threshold |
|
|
||||||
|-------|-----------|
|
|
||||||
| GoldReproducible | ≥7 validators, ≥90% agreement |
|
|
||||||
| SilverReproducible | ≥5 validators, ≥70% agreement |
|
|
||||||
| BronzeReproducible | ≥3 validators, ≥50% agreement |
|
|
||||||
| FailedReproduction | Divergent outcomes |
|
|
||||||
|
|
||||||
Save the full record and badges to `outputs/<slug>-harmony-record.json`.
|
|
||||||
|
|
||||||
### Step 7: Check updated reputation
|
|
||||||
|
|
||||||
After each validation round, Feynman's reputation record in DNA 4 is updated:
|
|
||||||
|
|
||||||
```
|
|
||||||
get_validator_reputation(validator: AgentPubKey)
|
|
||||||
```
|
|
||||||
|
|
||||||
Returns per-discipline scores: total validations, agreement rate, average time, and current `CertificationTier` (Provisional → Certified → Senior). Reputation is a long-term asset — AI validators accumulate a cryptographically verifiable track record across all ValiChord rounds they participate in.
|
|
||||||
|
|
||||||
### Step 8: Report to user
|
|
||||||
|
|
||||||
Present:
|
|
||||||
- Outcome and agreement level
|
|
||||||
- Reproducibility badge(s) issued to the study
|
|
||||||
- Feynman's updated reputation score for this discipline
|
|
||||||
- ActionHash — the permanent public identifier for this Harmony Record
|
|
||||||
- Confirmation that the record is written to the Governance DHT and accessible via HTTP Gateway without any special infrastructure
|
|
||||||
- Path to saved outputs
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Workflow B: Query existing Harmony Record
|
|
||||||
|
|
||||||
`get_harmony_record` and `get_badges_for_study` in DNA 4 are `Unrestricted` functions — accessible via Holochain's HTTP Gateway without connecting to a conductor or running a node.
|
|
||||||
|
|
||||||
```
|
|
||||||
GET <http_gateway_url>/get_harmony_record/<request_ref_b64>
|
|
||||||
GET <http_gateway_url>/get_badges_for_study/<request_ref_b64>
|
|
||||||
```
|
|
||||||
|
|
||||||
Use this to:
|
|
||||||
- Check reproducibility status of a cited study during `/deepresearch`
|
|
||||||
- Surface Harmony Records and badges in research summaries
|
|
||||||
- Verify whether a study has undergone independent validation before recommending it
|
|
||||||
|
|
||||||
The following read functions are also unrestricted on DNA 3:
|
|
||||||
`get_attestations_for_request`, `get_validators_for_discipline`, `get_pending_requests_for_discipline`, `get_validator_profile`, `get_current_phase`, `get_difficulty_assessment`, `get_researcher_reveal`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Workflow C: Proactive discipline queue monitoring
|
|
||||||
|
|
||||||
Feynman can act as a standing validator for a discipline — periodically checking for new studies that need validation without waiting to be assigned:
|
|
||||||
|
|
||||||
```
|
|
||||||
get_pending_requests_for_discipline(discipline: Discipline)
|
|
||||||
```
|
|
||||||
|
|
||||||
Returns all unclaimed `ValidationRequest` entries. For each, optionally run `assess_difficulty` to estimate workload before claiming.
|
|
||||||
|
|
||||||
This enables Feynman to operate as an autonomous reproducibility agent: polling the queue, assessing difficulty, claiming appropriate studies, and running the full Workflow A cycle unsupervised.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Workflow D: Researcher preparation assistant
|
|
||||||
|
|
||||||
Before a study enters the validation pipeline, Feynman can assist the researcher in preparing it via DNA 1 (Researcher Repository). This workflow runs on the researcher's side, not the validator's.
|
|
||||||
|
|
||||||
**Register the study:**
|
|
||||||
```
|
|
||||||
register_study(study: ResearchStudy)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Pre-register the analysis protocol** (immutable once written — creates a tamper-evident commitment to the analysis plan before data collection or validation begins):
|
|
||||||
```
|
|
||||||
register_protocol(input: RegisterProtocolInput)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Take a cryptographic data snapshot** (records a SHA-256 hash of the dataset at a point in time — proves data was not modified after validation began):
|
|
||||||
```
|
|
||||||
take_data_snapshot(input: TakeDataSnapshotInput)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Declare any deviations** from the pre-registered plan before the commit phase opens (pre-commit transparency):
|
|
||||||
```
|
|
||||||
declare_deviation(input: DeclareDeviationInput)
|
|
||||||
```
|
|
||||||
|
|
||||||
Only hashes ever leave DNA 1 — the raw data and protocol text remain on the researcher's device.
|
|
||||||
|
|
||||||
**Repository Readiness Checker**: ValiChord also ships a standalone audit tool that scans a research repository for 30+ reproducibility failure modes before submission — missing dependency files, absolute paths, undeclared environment requirements, data documentation gaps, human-subjects data exposure risks, and more. Feynman is the natural interface for this tool: running the audit, interpreting findings in plain language, guiding the researcher through fixes, and confirming the repository meets the bar for independent validation. See: https://github.com/topeuph-ai/ValiChord
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- AI agents are first-class participants in ValiChord's protocol. Feynman can autonomously publish profiles, claim studies, seal attestations, wait for phase transitions, and submit reveals — the protocol makes no distinction between human and AI validators.
|
|
||||||
- ValiChord's privacy guarantee is structural, not policy-based. DNA 1 (researcher data) and DNA 2 (validator workspace) are single-agent private DHTs — propagation to the shared network is architecturally impossible, not merely restricted.
|
|
||||||
- All 72 zome functions across the four DNAs are callable via AppWebSocket. The 20+ `Unrestricted` read functions on DNA 3 and DNA 4 are additionally accessible via HTTP Gateway without any Holochain node.
|
|
||||||
- If a validation round stalls due to validator dropout, `force_finalize_round` in DNA 4 closes it after a 7-day timeout with a reduced quorum, preventing indefinite blocking.
|
|
||||||
- Live demo (full commit-reveal cycle, Harmony Record generated): https://youtu.be/DQ5wZSD1YEw
|
|
||||||
- Running the demo: `bash demo/start.sh` in a GitHub Codespace, then open port 8888 publicly
|
|
||||||
- ValiChord repo: https://github.com/topeuph-ai/ValiChord
|
|
||||||
@@ -275,7 +275,8 @@ function writeLauncher(bundleRoot, target) {
|
|||||||
"@echo off",
|
"@echo off",
|
||||||
"setlocal",
|
"setlocal",
|
||||||
'set "ROOT=%~dp0"',
|
'set "ROOT=%~dp0"',
|
||||||
'"%ROOT%node\\node.exe" "%ROOT%app\\bin\\feynman.js" %*',
|
'if "%ROOT:~-1%"=="\\" set "ROOT=%ROOT:~0,-1%"',
|
||||||
|
'"%ROOT%\\node\\node.exe" "%ROOT%\\app\\bin\\feynman.js" %*',
|
||||||
"",
|
"",
|
||||||
].join("\r\n"),
|
].join("\r\n"),
|
||||||
"utf8",
|
"utf8",
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ function Resolve-VersionMetadata {
|
|||||||
return [PSCustomObject]@{
|
return [PSCustomObject]@{
|
||||||
ResolvedVersion = $resolvedVersion
|
ResolvedVersion = $resolvedVersion
|
||||||
GitRef = "v$resolvedVersion"
|
GitRef = "v$resolvedVersion"
|
||||||
DownloadUrl = "https://github.com/getcompanion-ai/feynman/archive/refs/tags/v$resolvedVersion.zip"
|
DownloadUrl = if ($env:FEYNMAN_INSTALL_SKILLS_ARCHIVE_URL) { $env:FEYNMAN_INSTALL_SKILLS_ARCHIVE_URL } else { "https://github.com/getcompanion-ai/feynman/archive/refs/tags/v$resolvedVersion.zip" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,8 +92,9 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$skillsSource = Join-Path $sourceRoot.FullName "skills"
|
$skillsSource = Join-Path $sourceRoot.FullName "skills"
|
||||||
if (-not (Test-Path $skillsSource)) {
|
$promptsSource = Join-Path $sourceRoot.FullName "prompts"
|
||||||
throw "Could not find skills/ in downloaded archive."
|
if (-not (Test-Path $skillsSource) -or -not (Test-Path $promptsSource)) {
|
||||||
|
throw "Could not find the bundled skills resources in the downloaded archive."
|
||||||
}
|
}
|
||||||
|
|
||||||
$installParent = Split-Path $installDir -Parent
|
$installParent = Split-Path $installDir -Parent
|
||||||
@@ -107,6 +108,10 @@ try {
|
|||||||
|
|
||||||
New-Item -ItemType Directory -Path $installDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $installDir -Force | Out-Null
|
||||||
Copy-Item -Path (Join-Path $skillsSource "*") -Destination $installDir -Recurse -Force
|
Copy-Item -Path (Join-Path $skillsSource "*") -Destination $installDir -Recurse -Force
|
||||||
|
New-Item -ItemType Directory -Path (Join-Path $installDir "prompts") -Force | Out-Null
|
||||||
|
Copy-Item -Path (Join-Path $promptsSource "*") -Destination (Join-Path $installDir "prompts") -Recurse -Force
|
||||||
|
Copy-Item -Path (Join-Path $sourceRoot.FullName "AGENTS.md") -Destination (Join-Path $installDir "AGENTS.md") -Force
|
||||||
|
Copy-Item -Path (Join-Path $sourceRoot.FullName "CONTRIBUTING.md") -Destination (Join-Path $installDir "CONTRIBUTING.md") -Force
|
||||||
|
|
||||||
Write-Host "==> Installed skills to $installDir"
|
Write-Host "==> Installed skills to $installDir"
|
||||||
if ($Scope -eq "Repo") {
|
if ($Scope -eq "Repo") {
|
||||||
|
|||||||
@@ -146,15 +146,17 @@ archive_metadata="$(resolve_version)"
|
|||||||
resolved_version="$(printf '%s\n' "$archive_metadata" | sed -n '1p')"
|
resolved_version="$(printf '%s\n' "$archive_metadata" | sed -n '1p')"
|
||||||
git_ref="$(printf '%s\n' "$archive_metadata" | sed -n '2p')"
|
git_ref="$(printf '%s\n' "$archive_metadata" | sed -n '2p')"
|
||||||
|
|
||||||
archive_url=""
|
archive_url="${FEYNMAN_INSTALL_SKILLS_ARCHIVE_URL:-}"
|
||||||
case "$git_ref" in
|
if [ -z "$archive_url" ]; then
|
||||||
|
case "$git_ref" in
|
||||||
main)
|
main)
|
||||||
archive_url="https://github.com/getcompanion-ai/feynman/archive/refs/heads/main.tar.gz"
|
archive_url="https://github.com/getcompanion-ai/feynman/archive/refs/heads/main.tar.gz"
|
||||||
;;
|
;;
|
||||||
v*)
|
v*)
|
||||||
archive_url="https://github.com/getcompanion-ai/feynman/archive/refs/tags/${git_ref}.tar.gz"
|
archive_url="https://github.com/getcompanion-ai/feynman/archive/refs/tags/${git_ref}.tar.gz"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$archive_url" ]; then
|
if [ -z "$archive_url" ]; then
|
||||||
echo "Could not resolve a download URL for ref: $git_ref" >&2
|
echo "Could not resolve a download URL for ref: $git_ref" >&2
|
||||||
@@ -181,8 +183,8 @@ step "Extracting skills"
|
|||||||
tar -xzf "$archive_path" -C "$extract_dir"
|
tar -xzf "$archive_path" -C "$extract_dir"
|
||||||
|
|
||||||
source_root="$(find "$extract_dir" -mindepth 1 -maxdepth 1 -type d | head -n 1)"
|
source_root="$(find "$extract_dir" -mindepth 1 -maxdepth 1 -type d | head -n 1)"
|
||||||
if [ -z "$source_root" ] || [ ! -d "$source_root/skills" ]; then
|
if [ -z "$source_root" ] || [ ! -d "$source_root/skills" ] || [ ! -d "$source_root/prompts" ]; then
|
||||||
echo "Could not find skills/ in downloaded archive." >&2
|
echo "Could not find the bundled skills resources in the downloaded archive." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -190,6 +192,10 @@ mkdir -p "$(dirname "$install_dir")"
|
|||||||
rm -rf "$install_dir"
|
rm -rf "$install_dir"
|
||||||
mkdir -p "$install_dir"
|
mkdir -p "$install_dir"
|
||||||
cp -R "$source_root/skills/." "$install_dir/"
|
cp -R "$source_root/skills/." "$install_dir/"
|
||||||
|
mkdir -p "$install_dir/prompts"
|
||||||
|
cp -R "$source_root/prompts/." "$install_dir/prompts/"
|
||||||
|
cp "$source_root/AGENTS.md" "$install_dir/AGENTS.md"
|
||||||
|
cp "$source_root/CONTRIBUTING.md" "$install_dir/CONTRIBUTING.md"
|
||||||
|
|
||||||
step "Installed skills to $install_dir"
|
step "Installed skills to $install_dir"
|
||||||
case "$SCOPE" in
|
case "$SCOPE" in
|
||||||
|
|||||||
@@ -109,8 +109,8 @@ This usually means the release exists, but not all platform bundles were uploade
|
|||||||
|
|
||||||
Workarounds:
|
Workarounds:
|
||||||
- try again after the release finishes publishing
|
- try again after the release finishes publishing
|
||||||
- install via pnpm instead: pnpm add -g @companion-ai/feynman
|
- pass the latest published version explicitly, e.g.:
|
||||||
- install via bun instead: bun add -g @companion-ai/feynman
|
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.16
|
||||||
"@
|
"@
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,12 +125,18 @@ Workarounds:
|
|||||||
New-Item -ItemType Directory -Path $installBinDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $installBinDir -Force | Out-Null
|
||||||
|
|
||||||
$shimPath = Join-Path $installBinDir "feynman.cmd"
|
$shimPath = Join-Path $installBinDir "feynman.cmd"
|
||||||
|
$shimPs1Path = Join-Path $installBinDir "feynman.ps1"
|
||||||
Write-Host "==> Linking feynman into $installBinDir"
|
Write-Host "==> Linking feynman into $installBinDir"
|
||||||
@"
|
@"
|
||||||
@echo off
|
@echo off
|
||||||
"$bundleDir\feynman.cmd" %*
|
CALL "$bundleDir\feynman.cmd" %*
|
||||||
"@ | Set-Content -Path $shimPath -Encoding ASCII
|
"@ | Set-Content -Path $shimPath -Encoding ASCII
|
||||||
|
|
||||||
|
@"
|
||||||
|
`$BundleDir = "$bundleDir"
|
||||||
|
& "`$BundleDir\node\node.exe" "`$BundleDir\app\bin\feynman.js" @args
|
||||||
|
"@ | Set-Content -Path $shimPs1Path -Encoding UTF8
|
||||||
|
|
||||||
$currentUserPath = [Environment]::GetEnvironmentVariable("Path", "User")
|
$currentUserPath = [Environment]::GetEnvironmentVariable("Path", "User")
|
||||||
$alreadyOnPath = $false
|
$alreadyOnPath = $false
|
||||||
if ($currentUserPath) {
|
if ($currentUserPath) {
|
||||||
@@ -153,9 +159,7 @@ Workarounds:
|
|||||||
Write-Warning "Current shell resolves feynman to $($resolvedCommand.Source)"
|
Write-Warning "Current shell resolves feynman to $($resolvedCommand.Source)"
|
||||||
Write-Host "Run in a new shell, or run: `$env:Path = '$installBinDir;' + `$env:Path"
|
Write-Host "Run in a new shell, or run: `$env:Path = '$installBinDir;' + `$env:Path"
|
||||||
Write-Host "Then run: feynman"
|
Write-Host "Then run: feynman"
|
||||||
if ($resolvedCommand.Source -like "*node_modules*@companion-ai*feynman*") {
|
Write-Host "If that path is an old package-manager install, remove it or put $installBinDir first on PATH."
|
||||||
Write-Host "If that path is an old global npm install, remove it with: npm uninstall -g @companion-ai/feynman"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "Feynman $resolvedVersion installed successfully."
|
Write-Host "Feynman $resolvedVersion installed successfully."
|
||||||
|
|||||||
@@ -177,11 +177,7 @@ warn_command_conflict() {
|
|||||||
step "Run now: export PATH=\"$INSTALL_BIN_DIR:\$PATH\" && hash -r && feynman"
|
step "Run now: export PATH=\"$INSTALL_BIN_DIR:\$PATH\" && hash -r && feynman"
|
||||||
step "Or launch directly: $expected_path"
|
step "Or launch directly: $expected_path"
|
||||||
|
|
||||||
case "$resolved_path" in
|
step "If that path is an old package-manager install, remove it or put $INSTALL_BIN_DIR first on PATH."
|
||||||
*"/node_modules/@companion-ai/feynman/"* | *"/node_modules/.bin/feynman")
|
|
||||||
step "If that path is an old global npm install, remove it with: npm uninstall -g @companion-ai/feynman"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,8 +260,8 @@ This usually means the release exists, but not all platform bundles were uploade
|
|||||||
|
|
||||||
Workarounds:
|
Workarounds:
|
||||||
- try again after the release finishes publishing
|
- try again after the release finishes publishing
|
||||||
- install via pnpm instead: pnpm add -g @companion-ai/feynman
|
- pass the latest published version explicitly, e.g.:
|
||||||
- install via bun instead: bun add -g @companion-ai/feynman
|
curl -fsSL https://feynman.is/install | bash -s -- 0.2.16
|
||||||
EOF
|
EOF
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
1
scripts/lib/pi-extension-loader-patch.d.mts
Normal file
1
scripts/lib/pi-extension-loader-patch.d.mts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export function patchPiExtensionLoaderSource(source: string): string;
|
||||||
32
scripts/lib/pi-extension-loader-patch.mjs
Normal file
32
scripts/lib/pi-extension-loader-patch.mjs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
const PATH_TO_FILE_URL_IMPORT = 'import { fileURLToPath, pathToFileURL } from "node:url";';
|
||||||
|
const FILE_URL_TO_PATH_IMPORT = 'import { fileURLToPath } from "node:url";';
|
||||||
|
|
||||||
|
const IMPORT_CALL = 'const module = await jiti.import(extensionPath, { default: true });';
|
||||||
|
const PATCHED_IMPORT_CALL = [
|
||||||
|
' const extensionSpecifier = process.platform === "win32" && path.isAbsolute(extensionPath)',
|
||||||
|
' ? pathToFileURL(extensionPath).href',
|
||||||
|
' : extensionPath;',
|
||||||
|
' const module = await jiti.import(extensionSpecifier, { default: true });',
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
export function patchPiExtensionLoaderSource(source) {
|
||||||
|
let patched = source;
|
||||||
|
|
||||||
|
if (patched.includes(PATH_TO_FILE_URL_IMPORT) || patched.includes(PATCHED_IMPORT_CALL)) {
|
||||||
|
return patched;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patched.includes(FILE_URL_TO_PATH_IMPORT)) {
|
||||||
|
patched = patched.replace(FILE_URL_TO_PATH_IMPORT, PATH_TO_FILE_URL_IMPORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!patched.includes(PATH_TO_FILE_URL_IMPORT)) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!patched.includes(IMPORT_CALL)) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
return patched.replace(IMPORT_CALL, PATCHED_IMPORT_CALL);
|
||||||
|
}
|
||||||
1
scripts/lib/pi-google-legacy-schema-patch.d.mts
Normal file
1
scripts/lib/pi-google-legacy-schema-patch.d.mts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export function patchPiGoogleLegacySchemaSource(source: string): string;
|
||||||
44
scripts/lib/pi-google-legacy-schema-patch.mjs
Normal file
44
scripts/lib/pi-google-legacy-schema-patch.mjs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
const HELPER = [
|
||||||
|
"function normalizeLegacyToolSchema(schema) {",
|
||||||
|
" if (Array.isArray(schema)) return schema.map((item) => normalizeLegacyToolSchema(item));",
|
||||||
|
' if (!schema || typeof schema !== "object") return schema;',
|
||||||
|
" const normalized = {};",
|
||||||
|
" for (const [key, value] of Object.entries(schema)) {",
|
||||||
|
' if (key === "const") {',
|
||||||
|
" normalized.enum = [value];",
|
||||||
|
" continue;",
|
||||||
|
" }",
|
||||||
|
" normalized[key] = normalizeLegacyToolSchema(value);",
|
||||||
|
" }",
|
||||||
|
" return normalized;",
|
||||||
|
"}",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const ORIGINAL =
|
||||||
|
' ...(useParameters ? { parameters: tool.parameters } : { parametersJsonSchema: tool.parameters }),';
|
||||||
|
const PATCHED = [
|
||||||
|
" ...(useParameters",
|
||||||
|
" ? { parameters: normalizeLegacyToolSchema(tool.parameters) }",
|
||||||
|
" : { parametersJsonSchema: tool.parameters }),",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
export function patchPiGoogleLegacySchemaSource(source) {
|
||||||
|
let patched = source;
|
||||||
|
|
||||||
|
if (patched.includes("function normalizeLegacyToolSchema(schema) {")) {
|
||||||
|
return patched;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!patched.includes(ORIGINAL)) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
patched = patched.replace(ORIGINAL, PATCHED);
|
||||||
|
const marker = "export function convertTools(tools, useParameters = false) {";
|
||||||
|
const markerIndex = patched.indexOf(marker);
|
||||||
|
if (markerIndex === -1) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${patched.slice(0, markerIndex)}${HELPER}\n\n${patched.slice(markerIndex)}`;
|
||||||
|
}
|
||||||
2
scripts/lib/pi-subagents-patch.d.mts
Normal file
2
scripts/lib/pi-subagents-patch.d.mts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export const PI_SUBAGENTS_PATCH_TARGETS: string[];
|
||||||
|
export function patchPiSubagentsSource(relativePath: string, source: string): string;
|
||||||
124
scripts/lib/pi-subagents-patch.mjs
Normal file
124
scripts/lib/pi-subagents-patch.mjs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
export const PI_SUBAGENTS_PATCH_TARGETS = [
|
||||||
|
"index.ts",
|
||||||
|
"agents.ts",
|
||||||
|
"artifacts.ts",
|
||||||
|
"run-history.ts",
|
||||||
|
"skills.ts",
|
||||||
|
"chain-clarify.ts",
|
||||||
|
];
|
||||||
|
|
||||||
|
const RESOLVE_PI_AGENT_DIR_HELPER = [
|
||||||
|
"function resolvePiAgentDir(): string {",
|
||||||
|
' const configured = process.env.PI_CODING_AGENT_DIR?.trim();',
|
||||||
|
' if (!configured) return path.join(os.homedir(), ".pi", "agent");',
|
||||||
|
' return configured.startsWith("~/") ? path.join(os.homedir(), configured.slice(2)) : configured;',
|
||||||
|
"}",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
function injectResolvePiAgentDirHelper(source) {
|
||||||
|
if (source.includes("function resolvePiAgentDir(): string {")) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = source.split("\n");
|
||||||
|
let insertAt = 0;
|
||||||
|
let importSeen = false;
|
||||||
|
let importOpen = false;
|
||||||
|
|
||||||
|
for (let index = 0; index < lines.length; index += 1) {
|
||||||
|
const trimmed = lines[index].trim();
|
||||||
|
if (!importSeen) {
|
||||||
|
if (trimmed === "" || trimmed.startsWith("/**") || trimmed.startsWith("*") || trimmed.startsWith("*/")) {
|
||||||
|
insertAt = index + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (trimmed.startsWith("import ")) {
|
||||||
|
importSeen = true;
|
||||||
|
importOpen = !trimmed.endsWith(";");
|
||||||
|
insertAt = index + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmed.startsWith("import ")) {
|
||||||
|
importOpen = !trimmed.endsWith(";");
|
||||||
|
insertAt = index + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (importOpen) {
|
||||||
|
if (trimmed.endsWith(";")) importOpen = false;
|
||||||
|
insertAt = index + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (trimmed === "") {
|
||||||
|
insertAt = index + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
insertAt = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...lines.slice(0, insertAt), "", RESOLVE_PI_AGENT_DIR_HELPER, "", ...lines.slice(insertAt)].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceAll(source, from, to) {
|
||||||
|
return source.split(from).join(to);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchPiSubagentsSource(relativePath, source) {
|
||||||
|
let patched = source;
|
||||||
|
|
||||||
|
switch (relativePath) {
|
||||||
|
case "index.ts":
|
||||||
|
patched = replaceAll(
|
||||||
|
patched,
|
||||||
|
'const configPath = path.join(os.homedir(), ".pi", "agent", "extensions", "subagent", "config.json");',
|
||||||
|
'const configPath = path.join(resolvePiAgentDir(), "extensions", "subagent", "config.json");',
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "agents.ts":
|
||||||
|
patched = replaceAll(
|
||||||
|
patched,
|
||||||
|
'const userDir = path.join(os.homedir(), ".pi", "agent", "agents");',
|
||||||
|
'const userDir = path.join(resolvePiAgentDir(), "agents");',
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "artifacts.ts":
|
||||||
|
patched = replaceAll(
|
||||||
|
patched,
|
||||||
|
'const sessionsBase = path.join(os.homedir(), ".pi", "agent", "sessions");',
|
||||||
|
'const sessionsBase = path.join(resolvePiAgentDir(), "sessions");',
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "run-history.ts":
|
||||||
|
patched = replaceAll(
|
||||||
|
patched,
|
||||||
|
'const HISTORY_PATH = path.join(os.homedir(), ".pi", "agent", "run-history.jsonl");',
|
||||||
|
'const HISTORY_PATH = path.join(resolvePiAgentDir(), "run-history.jsonl");',
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "skills.ts":
|
||||||
|
patched = replaceAll(
|
||||||
|
patched,
|
||||||
|
'const AGENT_DIR = path.join(os.homedir(), ".pi", "agent");',
|
||||||
|
"const AGENT_DIR = resolvePiAgentDir();",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "chain-clarify.ts":
|
||||||
|
patched = replaceAll(
|
||||||
|
patched,
|
||||||
|
'const dir = path.join(os.homedir(), ".pi", "agent", "agents");',
|
||||||
|
'const dir = path.join(resolvePiAgentDir(), "agents");',
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patched === source) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
return injectResolvePiAgentDirHelper(patched);
|
||||||
|
}
|
||||||
2
scripts/lib/pi-web-access-patch.d.mts
Normal file
2
scripts/lib/pi-web-access-patch.d.mts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export const PI_WEB_ACCESS_PATCH_TARGETS: string[];
|
||||||
|
export function patchPiWebAccessSource(relativePath: string, source: string): string;
|
||||||
32
scripts/lib/pi-web-access-patch.mjs
Normal file
32
scripts/lib/pi-web-access-patch.mjs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
export const PI_WEB_ACCESS_PATCH_TARGETS = [
|
||||||
|
"index.ts",
|
||||||
|
"exa.ts",
|
||||||
|
"gemini-api.ts",
|
||||||
|
"gemini-search.ts",
|
||||||
|
"gemini-web.ts",
|
||||||
|
"github-extract.ts",
|
||||||
|
"perplexity.ts",
|
||||||
|
"video-extract.ts",
|
||||||
|
"youtube-extract.ts",
|
||||||
|
];
|
||||||
|
|
||||||
|
const LEGACY_CONFIG_EXPR = 'join(homedir(), ".pi", "web-search.json")';
|
||||||
|
const PATCHED_CONFIG_EXPR =
|
||||||
|
'process.env.FEYNMAN_WEB_SEARCH_CONFIG ?? process.env.PI_WEB_SEARCH_CONFIG ?? join(homedir(), ".pi", "web-search.json")';
|
||||||
|
|
||||||
|
export function patchPiWebAccessSource(relativePath, source) {
|
||||||
|
let patched = source;
|
||||||
|
|
||||||
|
if (patched.includes(PATCHED_CONFIG_EXPR)) {
|
||||||
|
return patched;
|
||||||
|
}
|
||||||
|
|
||||||
|
patched = patched.split(LEGACY_CONFIG_EXPR).join(PATCHED_CONFIG_EXPR);
|
||||||
|
|
||||||
|
if (relativePath === "index.ts" && patched !== source) {
|
||||||
|
patched = patched.replace('import { join } from "node:path";', 'import { dirname, join } from "node:path";');
|
||||||
|
patched = patched.replace('const dir = join(homedir(), ".pi");', "const dir = dirname(WEB_SEARCH_CONFIG_PATH);");
|
||||||
|
}
|
||||||
|
|
||||||
|
return patched;
|
||||||
|
}
|
||||||
@@ -1,12 +1,22 @@
|
|||||||
import { spawnSync } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||||
import { createRequire } from "node:module";
|
import { createRequire } from "node:module";
|
||||||
|
import { homedir } from "node:os";
|
||||||
import { dirname, resolve } from "node:path";
|
import { dirname, resolve } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { FEYNMAN_LOGO_HTML } from "../logo.mjs";
|
import { FEYNMAN_LOGO_HTML } from "../logo.mjs";
|
||||||
|
import { patchPiExtensionLoaderSource } from "./lib/pi-extension-loader-patch.mjs";
|
||||||
|
import { patchPiGoogleLegacySchemaSource } from "./lib/pi-google-legacy-schema-patch.mjs";
|
||||||
|
import { PI_WEB_ACCESS_PATCH_TARGETS, patchPiWebAccessSource } from "./lib/pi-web-access-patch.mjs";
|
||||||
|
import { PI_SUBAGENTS_PATCH_TARGETS, patchPiSubagentsSource } from "./lib/pi-subagents-patch.mjs";
|
||||||
|
|
||||||
const here = dirname(fileURLToPath(import.meta.url));
|
const here = dirname(fileURLToPath(import.meta.url));
|
||||||
const appRoot = resolve(here, "..");
|
const appRoot = resolve(here, "..");
|
||||||
|
const feynmanHome = resolve(process.env.FEYNMAN_HOME ?? homedir(), ".feynman");
|
||||||
|
const feynmanNpmPrefix = resolve(feynmanHome, "npm-global");
|
||||||
|
process.env.FEYNMAN_NPM_PREFIX = feynmanNpmPrefix;
|
||||||
|
process.env.NPM_CONFIG_PREFIX = feynmanNpmPrefix;
|
||||||
|
process.env.npm_config_prefix = feynmanNpmPrefix;
|
||||||
const appRequire = createRequire(resolve(appRoot, "package.json"));
|
const appRequire = createRequire(resolve(appRoot, "package.json"));
|
||||||
const isGlobalInstall = process.env.npm_config_global === "true" || process.env.npm_config_location === "global";
|
const isGlobalInstall = process.env.npm_config_global === "true" || process.env.npm_config_location === "global";
|
||||||
|
|
||||||
@@ -51,9 +61,20 @@ const cliPath = piPackageRoot ? resolve(piPackageRoot, "dist", "cli.js") : null;
|
|||||||
const bunCliPath = piPackageRoot ? resolve(piPackageRoot, "dist", "bun", "cli.js") : null;
|
const bunCliPath = piPackageRoot ? resolve(piPackageRoot, "dist", "bun", "cli.js") : null;
|
||||||
const interactiveModePath = piPackageRoot ? resolve(piPackageRoot, "dist", "modes", "interactive", "interactive-mode.js") : null;
|
const interactiveModePath = piPackageRoot ? resolve(piPackageRoot, "dist", "modes", "interactive", "interactive-mode.js") : null;
|
||||||
const interactiveThemePath = piPackageRoot ? resolve(piPackageRoot, "dist", "modes", "interactive", "theme", "theme.js") : null;
|
const interactiveThemePath = piPackageRoot ? resolve(piPackageRoot, "dist", "modes", "interactive", "theme", "theme.js") : null;
|
||||||
|
const extensionLoaderPath = piPackageRoot ? resolve(piPackageRoot, "dist", "core", "extensions", "loader.js") : null;
|
||||||
const terminalPath = piTuiRoot ? resolve(piTuiRoot, "dist", "terminal.js") : null;
|
const terminalPath = piTuiRoot ? resolve(piTuiRoot, "dist", "terminal.js") : null;
|
||||||
const editorPath = piTuiRoot ? resolve(piTuiRoot, "dist", "components", "editor.js") : null;
|
const editorPath = piTuiRoot ? resolve(piTuiRoot, "dist", "components", "editor.js") : null;
|
||||||
const workspaceRoot = resolve(appRoot, ".feynman", "npm", "node_modules");
|
const workspaceRoot = resolve(appRoot, ".feynman", "npm", "node_modules");
|
||||||
|
const workspaceExtensionLoaderPath = resolve(
|
||||||
|
workspaceRoot,
|
||||||
|
"@mariozechner",
|
||||||
|
"pi-coding-agent",
|
||||||
|
"dist",
|
||||||
|
"core",
|
||||||
|
"extensions",
|
||||||
|
"loader.js",
|
||||||
|
);
|
||||||
|
const piSubagentsRoot = resolve(workspaceRoot, "pi-subagents");
|
||||||
const webAccessPath = resolve(workspaceRoot, "pi-web-access", "index.ts");
|
const webAccessPath = resolve(workspaceRoot, "pi-web-access", "index.ts");
|
||||||
const sessionSearchIndexerPath = resolve(
|
const sessionSearchIndexerPath = resolve(
|
||||||
workspaceRoot,
|
workspaceRoot,
|
||||||
@@ -71,7 +92,17 @@ const workspaceArchivePath = resolve(appRoot, ".feynman", "runtime-workspace.tgz
|
|||||||
function createInstallCommand(packageManager, packageSpecs) {
|
function createInstallCommand(packageManager, packageSpecs) {
|
||||||
switch (packageManager) {
|
switch (packageManager) {
|
||||||
case "npm":
|
case "npm":
|
||||||
return ["install", "--prefer-offline", "--no-audit", "--no-fund", "--loglevel", "error", ...packageSpecs];
|
return [
|
||||||
|
"install",
|
||||||
|
"--global=false",
|
||||||
|
"--location=project",
|
||||||
|
"--prefer-offline",
|
||||||
|
"--no-audit",
|
||||||
|
"--no-fund",
|
||||||
|
"--loglevel",
|
||||||
|
"error",
|
||||||
|
...packageSpecs,
|
||||||
|
];
|
||||||
case "pnpm":
|
case "pnpm":
|
||||||
return ["add", "--prefer-offline", "--reporter", "silent", ...packageSpecs];
|
return ["add", "--prefer-offline", "--reporter", "silent", ...packageSpecs];
|
||||||
case "bun":
|
case "bun":
|
||||||
@@ -243,6 +274,19 @@ function ensurePandoc() {
|
|||||||
|
|
||||||
ensurePandoc();
|
ensurePandoc();
|
||||||
|
|
||||||
|
if (existsSync(piSubagentsRoot)) {
|
||||||
|
for (const relativePath of PI_SUBAGENTS_PATCH_TARGETS) {
|
||||||
|
const entryPath = resolve(piSubagentsRoot, relativePath);
|
||||||
|
if (!existsSync(entryPath)) continue;
|
||||||
|
|
||||||
|
const source = readFileSync(entryPath, "utf8");
|
||||||
|
const patched = patchPiSubagentsSource(relativePath, source);
|
||||||
|
if (patched !== source) {
|
||||||
|
writeFileSync(entryPath, patched, "utf8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (packageJsonPath && existsSync(packageJsonPath)) {
|
if (packageJsonPath && existsSync(packageJsonPath)) {
|
||||||
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
||||||
if (pkg.piConfig?.name !== "feynman" || pkg.piConfig?.configDir !== ".feynman") {
|
if (pkg.piConfig?.name !== "feynman" || pkg.piConfig?.configDir !== ".feynman") {
|
||||||
@@ -337,6 +381,18 @@ if (interactiveModePath && existsSync(interactiveModePath)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const loaderPath of [extensionLoaderPath, workspaceExtensionLoaderPath].filter(Boolean)) {
|
||||||
|
if (!existsSync(loaderPath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = readFileSync(loaderPath, "utf8");
|
||||||
|
const patched = patchPiExtensionLoaderSource(source);
|
||||||
|
if (patched !== source) {
|
||||||
|
writeFileSync(loaderPath, patched, "utf8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (interactiveThemePath && existsSync(interactiveThemePath)) {
|
if (interactiveThemePath && existsSync(interactiveThemePath)) {
|
||||||
let themeSource = readFileSync(interactiveThemePath, "utf8");
|
let themeSource = readFileSync(interactiveThemePath, "utf8");
|
||||||
const desiredGetEditorTheme = [
|
const desiredGetEditorTheme = [
|
||||||
@@ -512,6 +568,21 @@ if (existsSync(webAccessPath)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const piWebAccessRoot = resolve(workspaceRoot, "pi-web-access");
|
||||||
|
|
||||||
|
if (existsSync(piWebAccessRoot)) {
|
||||||
|
for (const relativePath of PI_WEB_ACCESS_PATCH_TARGETS) {
|
||||||
|
const entryPath = resolve(piWebAccessRoot, relativePath);
|
||||||
|
if (!existsSync(entryPath)) continue;
|
||||||
|
|
||||||
|
const source = readFileSync(entryPath, "utf8");
|
||||||
|
const patched = patchPiWebAccessSource(relativePath, source);
|
||||||
|
if (patched !== source) {
|
||||||
|
writeFileSync(entryPath, patched, "utf8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (existsSync(sessionSearchIndexerPath)) {
|
if (existsSync(sessionSearchIndexerPath)) {
|
||||||
const source = readFileSync(sessionSearchIndexerPath, "utf8");
|
const source = readFileSync(sessionSearchIndexerPath, "utf8");
|
||||||
const original = 'const sessionsDir = path.join(os.homedir(), ".pi", "agent", "sessions");';
|
const original = 'const sessionsDir = path.join(os.homedir(), ".pi", "agent", "sessions");';
|
||||||
@@ -523,6 +594,7 @@ if (existsSync(sessionSearchIndexerPath)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const oauthPagePath = piAiRoot ? resolve(piAiRoot, "dist", "utils", "oauth", "oauth-page.js") : null;
|
const oauthPagePath = piAiRoot ? resolve(piAiRoot, "dist", "utils", "oauth", "oauth-page.js") : null;
|
||||||
|
const googleSharedPath = piAiRoot ? resolve(piAiRoot, "dist", "providers", "google-shared.js") : null;
|
||||||
|
|
||||||
if (oauthPagePath && existsSync(oauthPagePath)) {
|
if (oauthPagePath && existsSync(oauthPagePath)) {
|
||||||
let source = readFileSync(oauthPagePath, "utf8");
|
let source = readFileSync(oauthPagePath, "utf8");
|
||||||
@@ -535,6 +607,14 @@ if (oauthPagePath && existsSync(oauthPagePath)) {
|
|||||||
if (changed) writeFileSync(oauthPagePath, source, "utf8");
|
if (changed) writeFileSync(oauthPagePath, source, "utf8");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (googleSharedPath && existsSync(googleSharedPath)) {
|
||||||
|
const source = readFileSync(googleSharedPath, "utf8");
|
||||||
|
const patched = patchPiGoogleLegacySchemaSource(source);
|
||||||
|
if (patched !== source) {
|
||||||
|
writeFileSync(googleSharedPath, patched, "utf8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const alphaHubAuthPath = findPackageRoot("@companion-ai/alpha-hub")
|
const alphaHubAuthPath = findPackageRoot("@companion-ai/alpha-hub")
|
||||||
? resolve(findPackageRoot("@companion-ai/alpha-hub"), "src", "lib", "auth.js")
|
? resolve(findPackageRoot("@companion-ai/alpha-hub"), "src", "lib", "auth.js")
|
||||||
: null;
|
: null;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description: Autonomous experiment loop that tries ideas, measures results, keep
|
|||||||
|
|
||||||
# Autoresearch
|
# Autoresearch
|
||||||
|
|
||||||
Run the `/autoresearch` workflow. Read the prompt template at `prompts/autoresearch.md` for the full procedure.
|
Run the `/autoresearch` workflow. Read the prompt template at `../prompts/autoresearch.md` for the full procedure.
|
||||||
|
|
||||||
Tools used: `init_experiment`, `run_experiment`, `log_experiment` (from pi-autoresearch)
|
Tools used: `init_experiment`, `run_experiment`, `log_experiment` (from pi-autoresearch)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description: Contribute changes to the Feynman repository itself. Use when the t
|
|||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
Read `CONTRIBUTING.md` first, then `AGENTS.md` for repo-level agent conventions.
|
Read `../CONTRIBUTING.md` first, then `../AGENTS.md` for repo-level agent conventions.
|
||||||
|
|
||||||
Use this skill when working on Feynman itself, especially for:
|
Use this skill when working on Feynman itself, especially for:
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description: Run a thorough, source-heavy investigation on any topic. Use when t
|
|||||||
|
|
||||||
# Deep Research
|
# Deep Research
|
||||||
|
|
||||||
Run the `/deepresearch` workflow. Read the prompt template at `prompts/deepresearch.md` for the full procedure.
|
Run the `/deepresearch` workflow. Read the prompt template at `../prompts/deepresearch.md` for the full procedure.
|
||||||
|
|
||||||
Agents used: `researcher`, `verifier`, `reviewer`
|
Agents used: `researcher`, `verifier`, `reviewer`
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ description: Inspect active background research work including running processes
|
|||||||
|
|
||||||
# Jobs
|
# Jobs
|
||||||
|
|
||||||
Run the `/jobs` workflow. Read the prompt template at `prompts/jobs.md` for the full procedure.
|
Run the `/jobs` workflow. Read the prompt template at `../prompts/jobs.md` for the full procedure.
|
||||||
|
|
||||||
Shows active `pi-processes`, scheduled `pi-schedule-prompt` entries, and running subagent tasks.
|
Shows active `pi-processes`, scheduled `pi-schedule-prompt` entries, and running subagent tasks.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description: Run a literature review using paper search and primary-source synth
|
|||||||
|
|
||||||
# Literature Review
|
# Literature Review
|
||||||
|
|
||||||
Run the `/lit` workflow. Read the prompt template at `prompts/lit.md` for the full procedure.
|
Run the `/lit` workflow. Read the prompt template at `../prompts/lit.md` for the full procedure.
|
||||||
|
|
||||||
Agents used: `researcher`, `verifier`, `reviewer`
|
Agents used: `researcher`, `verifier`, `reviewer`
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description: Compare a paper's claims against its public codebase. Use when the
|
|||||||
|
|
||||||
# Paper-Code Audit
|
# Paper-Code Audit
|
||||||
|
|
||||||
Run the `/audit` workflow. Read the prompt template at `prompts/audit.md` for the full procedure.
|
Run the `/audit` workflow. Read the prompt template at `../prompts/audit.md` for the full procedure.
|
||||||
|
|
||||||
Agents used: `researcher`, `verifier`
|
Agents used: `researcher`, `verifier`
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description: Turn research findings into a polished paper-style draft with secti
|
|||||||
|
|
||||||
# Paper Writing
|
# Paper Writing
|
||||||
|
|
||||||
Run the `/draft` workflow. Read the prompt template at `prompts/draft.md` for the full procedure.
|
Run the `/draft` workflow. Read the prompt template at `../prompts/draft.md` for the full procedure.
|
||||||
|
|
||||||
Agents used: `writer`, `verifier`
|
Agents used: `writer`, `verifier`
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description: Simulate a tough but constructive peer review of an AI research art
|
|||||||
|
|
||||||
# Peer Review
|
# Peer Review
|
||||||
|
|
||||||
Run the `/review` workflow. Read the prompt template at `prompts/review.md` for the full procedure.
|
Run the `/review` workflow. Read the prompt template at `../prompts/review.md` for the full procedure.
|
||||||
|
|
||||||
Agents used: `researcher`, `reviewer`
|
Agents used: `researcher`, `reviewer`
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description: Plan or execute a replication of a paper, claim, or benchmark. Use
|
|||||||
|
|
||||||
# Replication
|
# Replication
|
||||||
|
|
||||||
Run the `/replicate` workflow. Read the prompt template at `prompts/replicate.md` for the full procedure.
|
Run the `/replicate` workflow. Read the prompt template at `../prompts/replicate.md` for the full procedure.
|
||||||
|
|
||||||
Agents used: `researcher`
|
Agents used: `researcher`
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ description: Write a durable session log capturing completed work, findings, ope
|
|||||||
|
|
||||||
# Session Log
|
# Session Log
|
||||||
|
|
||||||
Run the `/log` workflow. Read the prompt template at `prompts/log.md` for the full procedure.
|
Run the `/log` workflow. Read the prompt template at `../prompts/log.md` for the full procedure.
|
||||||
|
|
||||||
Output: session log in `notes/session-logs/`.
|
Output: session log in `notes/session-logs/`.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description: Compare multiple sources on a topic and produce a grounded comparis
|
|||||||
|
|
||||||
# Source Comparison
|
# Source Comparison
|
||||||
|
|
||||||
Run the `/compare` workflow. Read the prompt template at `prompts/compare.md` for the full procedure.
|
Run the `/compare` workflow. Read the prompt template at `../prompts/compare.md` for the full procedure.
|
||||||
|
|
||||||
Agents used: `researcher`, `verifier`
|
Agents used: `researcher`, `verifier`
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
name: valichord-validation
|
|
||||||
description: Integrate with ValiChord to submit a replication as a cryptographically verified validator attestation, discover studies awaiting independent validation, query Harmony Records and reproducibility badges, or assist researchers in preparing a study for the validation pipeline. Feynman operates as a first-class AI validator — publishing a validator profile, claiming studies, running the blind commit-reveal protocol, and accumulating a verifiable per-discipline reputation. Also surfaces reproducibility status during /deepresearch and literature reviews via ValiChord's HTTP Gateway.
|
|
||||||
---
|
|
||||||
|
|
||||||
# ValiChord Validation
|
|
||||||
|
|
||||||
Run the `/valichord` workflow. Read the prompt template at `prompts/valichord.md` for the full procedure.
|
|
||||||
|
|
||||||
ValiChord is a four-DNA Holochain system for scientific reproducibility verification. Feynman integrates at four points:
|
|
||||||
- As a **validator agent** — running `/replicate` then submitting findings as a sealed attestation into the blind commit-reveal protocol, earning reproducibility badges for researchers and building Feynman's own verifiable per-discipline reputation (Provisional → Certified → Senior)
|
|
||||||
- As a **proactive discovery agent** — querying the pending study queue by discipline, assessing difficulty, and autonomously claiming appropriate validation work without waiting to be assigned
|
|
||||||
- As a **researcher's assistant** — helping prepare studies for submission: registering protocols, taking cryptographic data snapshots, and running the Repository Readiness Checker to identify and fix reproducibility failure modes before validation begins
|
|
||||||
- As a **research query tool** — checking whether a study carries a Harmony Record or reproducibility badge (Gold/Silver/Bronze) via ValiChord's HTTP Gateway, for use during `/deepresearch` or literature reviews
|
|
||||||
|
|
||||||
Output: a Harmony Record — an immutable, publicly accessible cryptographic proof of independent reproducibility written to the ValiChord Governance DHT — plus automatic badge issuance and an updated validator reputation score.
|
|
||||||
|
|
||||||
Live demo (commit-reveal cycle end-to-end): https://youtu.be/DQ5wZSD1YEw
|
|
||||||
ValiChord repo: https://github.com/topeuph-ai/ValiChord
|
|
||||||
@@ -5,7 +5,7 @@ description: Set up a recurring research watch on a topic, company, paper area,
|
|||||||
|
|
||||||
# Watch
|
# Watch
|
||||||
|
|
||||||
Run the `/watch` workflow. Read the prompt template at `prompts/watch.md` for the full procedure.
|
Run the `/watch` workflow. Read the prompt template at `../prompts/watch.md` for the full procedure.
|
||||||
|
|
||||||
Agents used: `researcher`
|
Agents used: `researcher`
|
||||||
|
|
||||||
|
|||||||
63
src/cli.ts
63
src/cli.ts
@@ -18,6 +18,8 @@ import { ensureFeynmanHome, getDefaultSessionDir, getFeynmanAgentDir, getFeynman
|
|||||||
import { launchPiChat } from "./pi/launch.js";
|
import { launchPiChat } from "./pi/launch.js";
|
||||||
import { CORE_PACKAGE_SOURCES, getOptionalPackagePresetSources, listOptionalPackagePresets } from "./pi/package-presets.js";
|
import { CORE_PACKAGE_SOURCES, getOptionalPackagePresetSources, listOptionalPackagePresets } from "./pi/package-presets.js";
|
||||||
import { normalizeFeynmanSettings, normalizeThinkingLevel, parseModelSpec } from "./pi/settings.js";
|
import { normalizeFeynmanSettings, normalizeThinkingLevel, parseModelSpec } from "./pi/settings.js";
|
||||||
|
import { applyFeynmanPackageManagerEnv } from "./pi/runtime.js";
|
||||||
|
import { getConfiguredServiceTier, normalizeServiceTier, setConfiguredServiceTier } from "./model/service-tier.js";
|
||||||
import {
|
import {
|
||||||
authenticateModelProvider,
|
authenticateModelProvider,
|
||||||
getCurrentModelSpec,
|
getCurrentModelSpec,
|
||||||
@@ -26,7 +28,8 @@ import {
|
|||||||
printModelList,
|
printModelList,
|
||||||
setDefaultModelSpec,
|
setDefaultModelSpec,
|
||||||
} from "./model/commands.js";
|
} from "./model/commands.js";
|
||||||
import { printSearchStatus } from "./search/commands.js";
|
import { clearSearchConfig, printSearchStatus, setSearchProvider } from "./search/commands.js";
|
||||||
|
import type { PiWebSearchProvider } from "./pi/web-access.js";
|
||||||
import { runDoctor, runStatus } from "./setup/doctor.js";
|
import { runDoctor, runStatus } from "./setup/doctor.js";
|
||||||
import { setupPreviewDependencies } from "./setup/preview.js";
|
import { setupPreviewDependencies } from "./setup/preview.js";
|
||||||
import { runSetup } from "./setup/setup.js";
|
import { runSetup } from "./setup/setup.js";
|
||||||
@@ -150,10 +153,34 @@ async function handleModelCommand(subcommand: string | undefined, args: string[]
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (subcommand === "tier") {
|
||||||
|
const requested = args[0];
|
||||||
|
if (!requested) {
|
||||||
|
console.log(getConfiguredServiceTier(feynmanSettingsPath) ?? "not set");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requested === "unset" || requested === "clear" || requested === "off") {
|
||||||
|
setConfiguredServiceTier(feynmanSettingsPath, undefined);
|
||||||
|
console.log("Cleared service tier override");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tier = normalizeServiceTier(requested);
|
||||||
|
if (!tier) {
|
||||||
|
throw new Error("Usage: feynman model tier <auto|default|flex|priority|standard_only|unset>");
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfiguredServiceTier(feynmanSettingsPath, tier);
|
||||||
|
console.log(`Service tier set to ${tier}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(`Unknown model command: ${subcommand}`);
|
throw new Error(`Unknown model command: ${subcommand}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleUpdateCommand(workingDir: string, feynmanAgentDir: string, source?: string): Promise<void> {
|
async function handleUpdateCommand(workingDir: string, feynmanAgentDir: string, source?: string): Promise<void> {
|
||||||
|
applyFeynmanPackageManagerEnv(feynmanAgentDir);
|
||||||
const settingsManager = SettingsManager.create(workingDir, feynmanAgentDir);
|
const settingsManager = SettingsManager.create(workingDir, feynmanAgentDir);
|
||||||
const packageManager = new DefaultPackageManager({
|
const packageManager = new DefaultPackageManager({
|
||||||
cwd: workingDir,
|
cwd: workingDir,
|
||||||
@@ -177,6 +204,7 @@ async function handleUpdateCommand(workingDir: string, feynmanAgentDir: string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handlePackagesCommand(subcommand: string | undefined, args: string[], workingDir: string, feynmanAgentDir: string): Promise<void> {
|
async function handlePackagesCommand(subcommand: string | undefined, args: string[], workingDir: string, feynmanAgentDir: string): Promise<void> {
|
||||||
|
applyFeynmanPackageManagerEnv(feynmanAgentDir);
|
||||||
const settingsManager = SettingsManager.create(workingDir, feynmanAgentDir);
|
const settingsManager = SettingsManager.create(workingDir, feynmanAgentDir);
|
||||||
const configuredSources = new Set(
|
const configuredSources = new Set(
|
||||||
settingsManager
|
settingsManager
|
||||||
@@ -242,12 +270,27 @@ async function handlePackagesCommand(subcommand: string | undefined, args: strin
|
|||||||
console.log("Optional packages installed.");
|
console.log("Optional packages installed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSearchCommand(subcommand: string | undefined): void {
|
function handleSearchCommand(subcommand: string | undefined, args: string[]): void {
|
||||||
if (!subcommand || subcommand === "status") {
|
if (!subcommand || subcommand === "status") {
|
||||||
printSearchStatus();
|
printSearchStatus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (subcommand === "set") {
|
||||||
|
const provider = args[0] as PiWebSearchProvider | undefined;
|
||||||
|
const validProviders: PiWebSearchProvider[] = ["auto", "perplexity", "exa", "gemini"];
|
||||||
|
if (!provider || !validProviders.includes(provider)) {
|
||||||
|
throw new Error("Usage: feynman search set <auto|perplexity|exa|gemini> [api-key]");
|
||||||
|
}
|
||||||
|
setSearchProvider(provider, args[1]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subcommand === "clear") {
|
||||||
|
clearSearchConfig();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(`Unknown search command: ${subcommand}`);
|
throw new Error(`Unknown search command: ${subcommand}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,9 +348,11 @@ export async function main(): Promise<void> {
|
|||||||
"alpha-login": { type: "boolean" },
|
"alpha-login": { type: "boolean" },
|
||||||
"alpha-logout": { type: "boolean" },
|
"alpha-logout": { type: "boolean" },
|
||||||
"alpha-status": { type: "boolean" },
|
"alpha-status": { type: "boolean" },
|
||||||
|
mode: { type: "string" },
|
||||||
model: { type: "string" },
|
model: { type: "string" },
|
||||||
"new-session": { type: "boolean" },
|
"new-session": { type: "boolean" },
|
||||||
prompt: { type: "string" },
|
prompt: { type: "string" },
|
||||||
|
"service-tier": { type: "string" },
|
||||||
"session-dir": { type: "string" },
|
"session-dir": { type: "string" },
|
||||||
"setup-preview": { type: "boolean" },
|
"setup-preview": { type: "boolean" },
|
||||||
thinking: { type: "string" },
|
thinking: { type: "string" },
|
||||||
@@ -414,7 +459,7 @@ export async function main(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (command === "search") {
|
if (command === "search") {
|
||||||
handleSearchCommand(rest[0]);
|
handleSearchCommand(rest[0], rest.slice(1));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,6 +479,17 @@ export async function main(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const explicitModelSpec = values.model ?? process.env.FEYNMAN_MODEL;
|
const explicitModelSpec = values.model ?? process.env.FEYNMAN_MODEL;
|
||||||
|
const explicitServiceTier = normalizeServiceTier(values["service-tier"] ?? process.env.FEYNMAN_SERVICE_TIER);
|
||||||
|
const mode = values.mode;
|
||||||
|
if (mode !== undefined && mode !== "text" && mode !== "json" && mode !== "rpc") {
|
||||||
|
throw new Error("Unknown mode. Use text, json, or rpc.");
|
||||||
|
}
|
||||||
|
if ((values["service-tier"] ?? process.env.FEYNMAN_SERVICE_TIER) && !explicitServiceTier) {
|
||||||
|
throw new Error("Unknown service tier. Use auto, default, flex, priority, or standard_only.");
|
||||||
|
}
|
||||||
|
if (explicitServiceTier) {
|
||||||
|
process.env.FEYNMAN_SERVICE_TIER = explicitServiceTier;
|
||||||
|
}
|
||||||
if (explicitModelSpec) {
|
if (explicitModelSpec) {
|
||||||
const modelRegistry = createModelRegistry(feynmanAuthPath);
|
const modelRegistry = createModelRegistry(feynmanAuthPath);
|
||||||
const explicitModel = parseModelSpec(explicitModelSpec, modelRegistry);
|
const explicitModel = parseModelSpec(explicitModelSpec, modelRegistry);
|
||||||
@@ -464,6 +520,7 @@ export async function main(): Promise<void> {
|
|||||||
sessionDir,
|
sessionDir,
|
||||||
feynmanAgentDir,
|
feynmanAgentDir,
|
||||||
feynmanVersion,
|
feynmanVersion,
|
||||||
|
mode,
|
||||||
thinkingLevel,
|
thinkingLevel,
|
||||||
explicitModelSpec,
|
explicitModelSpec,
|
||||||
oneShotPrompt: values.prompt,
|
oneShotPrompt: values.prompt,
|
||||||
|
|||||||
@@ -95,6 +95,14 @@ const RESEARCH_MODEL_PREFERENCES = [
|
|||||||
spec: "zai/glm-5",
|
spec: "zai/glm-5",
|
||||||
reason: "good fallback when GLM is the available research model",
|
reason: "good fallback when GLM is the available research model",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
spec: "minimax/minimax-m2.7",
|
||||||
|
reason: "good fallback when MiniMax is the available research model",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spec: "minimax/minimax-m2.7-highspeed",
|
||||||
|
reason: "good fallback when MiniMax is the available research model",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
spec: "kimi-coding/kimi-k2-thinking",
|
spec: "kimi-coding/kimi-k2-thinking",
|
||||||
reason: "good fallback when Kimi is the available research model",
|
reason: "good fallback when Kimi is the available research model",
|
||||||
|
|||||||
@@ -7,6 +7,5 @@ export function getModelsJsonPath(authPath: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createModelRegistry(authPath: string): ModelRegistry {
|
export function createModelRegistry(authPath: string): ModelRegistry {
|
||||||
return new ModelRegistry(AuthStorage.create(authPath), getModelsJsonPath(authPath));
|
return ModelRegistry.create(AuthStorage.create(authPath), getModelsJsonPath(authPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
65
src/model/service-tier.ts
Normal file
65
src/model/service-tier.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||||
|
import { dirname } from "node:path";
|
||||||
|
|
||||||
|
export const FEYNMAN_SERVICE_TIERS = [
|
||||||
|
"auto",
|
||||||
|
"default",
|
||||||
|
"flex",
|
||||||
|
"priority",
|
||||||
|
"standard_only",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type FeynmanServiceTier = (typeof FEYNMAN_SERVICE_TIERS)[number];
|
||||||
|
|
||||||
|
const SERVICE_TIER_SET = new Set<string>(FEYNMAN_SERVICE_TIERS);
|
||||||
|
const OPENAI_SERVICE_TIERS = new Set<FeynmanServiceTier>(["auto", "default", "flex", "priority"]);
|
||||||
|
const ANTHROPIC_SERVICE_TIERS = new Set<FeynmanServiceTier>(["auto", "standard_only"]);
|
||||||
|
|
||||||
|
function readSettings(settingsPath: string): Record<string, unknown> {
|
||||||
|
try {
|
||||||
|
return JSON.parse(readFileSync(settingsPath, "utf8")) as Record<string, unknown>;
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeServiceTier(value: string | undefined): FeynmanServiceTier | undefined {
|
||||||
|
if (!value) return undefined;
|
||||||
|
const normalized = value.trim().toLowerCase();
|
||||||
|
return SERVICE_TIER_SET.has(normalized) ? (normalized as FeynmanServiceTier) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getConfiguredServiceTier(settingsPath: string): FeynmanServiceTier | undefined {
|
||||||
|
const settings = readSettings(settingsPath);
|
||||||
|
return normalizeServiceTier(typeof settings.serviceTier === "string" ? settings.serviceTier : undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setConfiguredServiceTier(settingsPath: string, tier: FeynmanServiceTier | undefined): void {
|
||||||
|
const settings = readSettings(settingsPath);
|
||||||
|
if (tier) {
|
||||||
|
settings.serviceTier = tier;
|
||||||
|
} else {
|
||||||
|
delete settings.serviceTier;
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdirSync(dirname(settingsPath), { recursive: true });
|
||||||
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveActiveServiceTier(settingsPath: string): FeynmanServiceTier | undefined {
|
||||||
|
return normalizeServiceTier(process.env.FEYNMAN_SERVICE_TIER) ?? getConfiguredServiceTier(settingsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveProviderServiceTier(
|
||||||
|
provider: string | undefined,
|
||||||
|
tier: FeynmanServiceTier | undefined,
|
||||||
|
): FeynmanServiceTier | undefined {
|
||||||
|
if (!provider || !tier) return undefined;
|
||||||
|
if ((provider === "openai" || provider === "openai-codex") && OPENAI_SERVICE_TIERS.has(tier)) {
|
||||||
|
return tier;
|
||||||
|
}
|
||||||
|
if (provider === "anthropic" && ANTHROPIC_SERVICE_TIERS.has(tier)) {
|
||||||
|
return tier;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
@@ -1,9 +1,15 @@
|
|||||||
import { spawn } from "node:child_process";
|
import { spawn } from "node:child_process";
|
||||||
import { existsSync } from "node:fs";
|
import { existsSync } from "node:fs";
|
||||||
|
import { constants } from "node:os";
|
||||||
|
|
||||||
import { buildPiArgs, buildPiEnv, type PiRuntimeOptions, resolvePiPaths } from "./runtime.js";
|
import { buildPiArgs, buildPiEnv, type PiRuntimeOptions, resolvePiPaths, toNodeImportSpecifier } from "./runtime.js";
|
||||||
import { ensureSupportedNodeVersion } from "../system/node-version.js";
|
import { ensureSupportedNodeVersion } from "../system/node-version.js";
|
||||||
|
|
||||||
|
export function exitCodeFromSignal(signal: NodeJS.Signals): number {
|
||||||
|
const signalNumber = constants.signals[signal];
|
||||||
|
return typeof signalNumber === "number" ? 128 + signalNumber : 1;
|
||||||
|
}
|
||||||
|
|
||||||
export async function launchPiChat(options: PiRuntimeOptions): Promise<void> {
|
export async function launchPiChat(options: PiRuntimeOptions): Promise<void> {
|
||||||
ensureSupportedNodeVersion();
|
ensureSupportedNodeVersion();
|
||||||
|
|
||||||
@@ -18,13 +24,13 @@ export async function launchPiChat(options: PiRuntimeOptions): Promise<void> {
|
|||||||
throw new Error(`Promise polyfill not found: ${promisePolyfillPath}`);
|
throw new Error(`Promise polyfill not found: ${promisePolyfillPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.stdout.isTTY) {
|
if (process.stdout.isTTY && options.mode !== "rpc") {
|
||||||
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
||||||
}
|
}
|
||||||
|
|
||||||
const importArgs = useDevPolyfill
|
const importArgs = useDevPolyfill
|
||||||
? ["--import", tsxLoaderPath, "--import", promisePolyfillSourcePath]
|
? ["--import", toNodeImportSpecifier(tsxLoaderPath), "--import", toNodeImportSpecifier(promisePolyfillSourcePath)]
|
||||||
: ["--import", promisePolyfillPath];
|
: ["--import", toNodeImportSpecifier(promisePolyfillPath)];
|
||||||
|
|
||||||
const child = spawn(process.execPath, [...importArgs, piCliPath, ...buildPiArgs(options)], {
|
const child = spawn(process.execPath, [...importArgs, piCliPath, ...buildPiArgs(options)], {
|
||||||
cwd: options.workingDir,
|
cwd: options.workingDir,
|
||||||
@@ -36,11 +42,9 @@ export async function launchPiChat(options: PiRuntimeOptions): Promise<void> {
|
|||||||
child.on("error", reject);
|
child.on("error", reject);
|
||||||
child.on("exit", (code, signal) => {
|
child.on("exit", (code, signal) => {
|
||||||
if (signal) {
|
if (signal) {
|
||||||
try {
|
console.error(`feynman terminated because the Pi child exited with ${signal}.`);
|
||||||
process.kill(process.pid, signal);
|
process.exitCode = exitCodeFromSignal(signal);
|
||||||
} catch {
|
resolvePromise();
|
||||||
process.exitCode = 1;
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
process.exitCode = code ?? 0;
|
process.exitCode = code ?? 0;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { existsSync, readFileSync } from "node:fs";
|
import { existsSync, readFileSync } from "node:fs";
|
||||||
import { delimiter, dirname, resolve } from "node:path";
|
import { delimiter, dirname, isAbsolute, resolve } from "node:path";
|
||||||
|
import { pathToFileURL } from "node:url";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BROWSER_FALLBACK_PATHS,
|
BROWSER_FALLBACK_PATHS,
|
||||||
@@ -14,12 +15,25 @@ export type PiRuntimeOptions = {
|
|||||||
sessionDir: string;
|
sessionDir: string;
|
||||||
feynmanAgentDir: string;
|
feynmanAgentDir: string;
|
||||||
feynmanVersion?: string;
|
feynmanVersion?: string;
|
||||||
|
mode?: "text" | "json" | "rpc";
|
||||||
thinkingLevel?: string;
|
thinkingLevel?: string;
|
||||||
explicitModelSpec?: string;
|
explicitModelSpec?: string;
|
||||||
oneShotPrompt?: string;
|
oneShotPrompt?: string;
|
||||||
initialPrompt?: string;
|
initialPrompt?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function getFeynmanNpmPrefixPath(feynmanAgentDir: string): string {
|
||||||
|
return resolve(dirname(feynmanAgentDir), "npm-global");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyFeynmanPackageManagerEnv(feynmanAgentDir: string): string {
|
||||||
|
const feynmanNpmPrefixPath = getFeynmanNpmPrefixPath(feynmanAgentDir);
|
||||||
|
process.env.FEYNMAN_NPM_PREFIX = feynmanNpmPrefixPath;
|
||||||
|
process.env.NPM_CONFIG_PREFIX = feynmanNpmPrefixPath;
|
||||||
|
process.env.npm_config_prefix = feynmanNpmPrefixPath;
|
||||||
|
return feynmanNpmPrefixPath;
|
||||||
|
}
|
||||||
|
|
||||||
export function resolvePiPaths(appRoot: string) {
|
export function resolvePiPaths(appRoot: string) {
|
||||||
return {
|
return {
|
||||||
piPackageRoot: resolve(appRoot, "node_modules", "@mariozechner", "pi-coding-agent"),
|
piPackageRoot: resolve(appRoot, "node_modules", "@mariozechner", "pi-coding-agent"),
|
||||||
@@ -35,6 +49,10 @@ export function resolvePiPaths(appRoot: string) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toNodeImportSpecifier(modulePath: string): string {
|
||||||
|
return isAbsolute(modulePath) ? pathToFileURL(modulePath).href : modulePath;
|
||||||
|
}
|
||||||
|
|
||||||
export function validatePiInstallation(appRoot: string): string[] {
|
export function validatePiInstallation(appRoot: string): string[] {
|
||||||
const paths = resolvePiPaths(appRoot);
|
const paths = resolvePiPaths(appRoot);
|
||||||
const missing: string[] = [];
|
const missing: string[] = [];
|
||||||
@@ -66,6 +84,9 @@ export function buildPiArgs(options: PiRuntimeOptions): string[] {
|
|||||||
args.push("--system-prompt", readFileSync(paths.systemPromptPath, "utf8"));
|
args.push("--system-prompt", readFileSync(paths.systemPromptPath, "utf8"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.mode) {
|
||||||
|
args.push("--mode", options.mode);
|
||||||
|
}
|
||||||
if (options.explicitModelSpec) {
|
if (options.explicitModelSpec) {
|
||||||
args.push("--model", options.explicitModelSpec);
|
args.push("--model", options.explicitModelSpec);
|
||||||
}
|
}
|
||||||
@@ -83,9 +104,9 @@ export function buildPiArgs(options: PiRuntimeOptions): string[] {
|
|||||||
|
|
||||||
export function buildPiEnv(options: PiRuntimeOptions): NodeJS.ProcessEnv {
|
export function buildPiEnv(options: PiRuntimeOptions): NodeJS.ProcessEnv {
|
||||||
const paths = resolvePiPaths(options.appRoot);
|
const paths = resolvePiPaths(options.appRoot);
|
||||||
const feynmanHome = dirname(options.feynmanAgentDir);
|
const feynmanNpmPrefixPath = getFeynmanNpmPrefixPath(options.feynmanAgentDir);
|
||||||
const feynmanNpmPrefixPath = resolve(feynmanHome, "npm-global");
|
|
||||||
const feynmanNpmBinPath = resolve(feynmanNpmPrefixPath, "bin");
|
const feynmanNpmBinPath = resolve(feynmanNpmPrefixPath, "bin");
|
||||||
|
const feynmanWebSearchConfigPath = resolve(dirname(options.feynmanAgentDir), "web-search.json");
|
||||||
|
|
||||||
const currentPath = process.env.PATH ?? "";
|
const currentPath = process.env.PATH ?? "";
|
||||||
const binEntries = [paths.nodeModulesBinPath, resolve(paths.piWorkspaceNodeModulesPath, ".bin"), feynmanNpmBinPath];
|
const binEntries = [paths.nodeModulesBinPath, resolve(paths.piWorkspaceNodeModulesPath, ".bin"), feynmanNpmBinPath];
|
||||||
@@ -97,6 +118,7 @@ export function buildPiEnv(options: PiRuntimeOptions): NodeJS.ProcessEnv {
|
|||||||
FEYNMAN_VERSION: options.feynmanVersion,
|
FEYNMAN_VERSION: options.feynmanVersion,
|
||||||
FEYNMAN_SESSION_DIR: options.sessionDir,
|
FEYNMAN_SESSION_DIR: options.sessionDir,
|
||||||
FEYNMAN_MEMORY_DIR: resolve(dirname(options.feynmanAgentDir), "memory"),
|
FEYNMAN_MEMORY_DIR: resolve(dirname(options.feynmanAgentDir), "memory"),
|
||||||
|
FEYNMAN_WEB_SEARCH_CONFIG: feynmanWebSearchConfigPath,
|
||||||
FEYNMAN_NODE_EXECUTABLE: process.execPath,
|
FEYNMAN_NODE_EXECUTABLE: process.execPath,
|
||||||
FEYNMAN_BIN_PATH: resolve(options.appRoot, "bin", "feynman.js"),
|
FEYNMAN_BIN_PATH: resolve(options.appRoot, "bin", "feynman.js"),
|
||||||
FEYNMAN_NPM_PREFIX: feynmanNpmPrefixPath,
|
FEYNMAN_NPM_PREFIX: feynmanNpmPrefixPath,
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import { existsSync, readFileSync } from "node:fs";
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||||
import { homedir } from "node:os";
|
import { dirname, resolve } from "node:path";
|
||||||
import { resolve } from "node:path";
|
import { getFeynmanHome } from "../config/paths.js";
|
||||||
|
|
||||||
export type PiWebSearchProvider = "auto" | "perplexity" | "gemini";
|
export type PiWebSearchProvider = "auto" | "perplexity" | "exa" | "gemini";
|
||||||
|
|
||||||
export type PiWebAccessConfig = Record<string, unknown> & {
|
export type PiWebAccessConfig = Record<string, unknown> & {
|
||||||
|
route?: PiWebSearchProvider;
|
||||||
provider?: PiWebSearchProvider;
|
provider?: PiWebSearchProvider;
|
||||||
searchProvider?: PiWebSearchProvider;
|
searchProvider?: PiWebSearchProvider;
|
||||||
perplexityApiKey?: string;
|
perplexityApiKey?: string;
|
||||||
|
exaApiKey?: string;
|
||||||
geminiApiKey?: string;
|
geminiApiKey?: string;
|
||||||
chromeProfile?: string;
|
chromeProfile?: string;
|
||||||
};
|
};
|
||||||
@@ -17,18 +19,20 @@ export type PiWebAccessStatus = {
|
|||||||
searchProvider: PiWebSearchProvider;
|
searchProvider: PiWebSearchProvider;
|
||||||
requestProvider: PiWebSearchProvider;
|
requestProvider: PiWebSearchProvider;
|
||||||
perplexityConfigured: boolean;
|
perplexityConfigured: boolean;
|
||||||
|
exaConfigured: boolean;
|
||||||
geminiApiConfigured: boolean;
|
geminiApiConfigured: boolean;
|
||||||
chromeProfile?: string;
|
chromeProfile?: string;
|
||||||
routeLabel: string;
|
routeLabel: string;
|
||||||
note: string;
|
note: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getPiWebSearchConfigPath(home = process.env.HOME ?? homedir()): string {
|
export function getPiWebSearchConfigPath(home?: string): string {
|
||||||
return resolve(home, ".feynman", "web-search.json");
|
const feynmanHome = home ? resolve(home, ".feynman") : getFeynmanHome();
|
||||||
|
return resolve(feynmanHome, "web-search.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeProvider(value: unknown): PiWebSearchProvider | undefined {
|
function normalizeProvider(value: unknown): PiWebSearchProvider | undefined {
|
||||||
return value === "auto" || value === "perplexity" || value === "gemini" ? value : undefined;
|
return value === "auto" || value === "perplexity" || value === "exa" || value === "gemini" ? value : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeNonEmptyString(value: unknown): string | undefined {
|
function normalizeNonEmptyString(value: unknown): string | undefined {
|
||||||
@@ -48,10 +52,29 @@ export function loadPiWebAccessConfig(configPath = getPiWebSearchConfigPath()):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function savePiWebAccessConfig(
|
||||||
|
updates: Partial<Record<keyof PiWebAccessConfig, unknown>>,
|
||||||
|
configPath = getPiWebSearchConfigPath(),
|
||||||
|
): void {
|
||||||
|
const merged: Record<string, unknown> = { ...loadPiWebAccessConfig(configPath) };
|
||||||
|
for (const [key, value] of Object.entries(updates)) {
|
||||||
|
if (value === undefined) {
|
||||||
|
delete merged[key];
|
||||||
|
} else {
|
||||||
|
merged[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdirSync(dirname(configPath), { recursive: true });
|
||||||
|
writeFileSync(configPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
||||||
|
}
|
||||||
|
|
||||||
function formatRouteLabel(provider: PiWebSearchProvider): string {
|
function formatRouteLabel(provider: PiWebSearchProvider): string {
|
||||||
switch (provider) {
|
switch (provider) {
|
||||||
case "perplexity":
|
case "perplexity":
|
||||||
return "Perplexity";
|
return "Perplexity";
|
||||||
|
case "exa":
|
||||||
|
return "Exa";
|
||||||
case "gemini":
|
case "gemini":
|
||||||
return "Gemini";
|
return "Gemini";
|
||||||
default:
|
default:
|
||||||
@@ -63,10 +86,12 @@ function formatRouteNote(provider: PiWebSearchProvider): string {
|
|||||||
switch (provider) {
|
switch (provider) {
|
||||||
case "perplexity":
|
case "perplexity":
|
||||||
return "Pi web-access will use Perplexity for search.";
|
return "Pi web-access will use Perplexity for search.";
|
||||||
|
case "exa":
|
||||||
|
return "Pi web-access will use Exa for search.";
|
||||||
case "gemini":
|
case "gemini":
|
||||||
return "Pi web-access will use Gemini API or Gemini Browser.";
|
return "Pi web-access will use Gemini API or Gemini Browser.";
|
||||||
default:
|
default:
|
||||||
return "Pi web-access will try Perplexity, then Gemini API, then Gemini Browser.";
|
return "Pi web-access will try Perplexity, then Exa, then Gemini API, then Gemini Browser.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,9 +99,11 @@ export function getPiWebAccessStatus(
|
|||||||
config: PiWebAccessConfig = loadPiWebAccessConfig(),
|
config: PiWebAccessConfig = loadPiWebAccessConfig(),
|
||||||
configPath = getPiWebSearchConfigPath(),
|
configPath = getPiWebSearchConfigPath(),
|
||||||
): PiWebAccessStatus {
|
): PiWebAccessStatus {
|
||||||
const searchProvider = normalizeProvider(config.searchProvider) ?? "auto";
|
const searchProvider =
|
||||||
const requestProvider = normalizeProvider(config.provider) ?? searchProvider;
|
normalizeProvider(config.searchProvider) ?? normalizeProvider(config.route) ?? normalizeProvider(config.provider) ?? "auto";
|
||||||
|
const requestProvider = normalizeProvider(config.provider) ?? normalizeProvider(config.route) ?? searchProvider;
|
||||||
const perplexityConfigured = Boolean(normalizeNonEmptyString(config.perplexityApiKey));
|
const perplexityConfigured = Boolean(normalizeNonEmptyString(config.perplexityApiKey));
|
||||||
|
const exaConfigured = Boolean(normalizeNonEmptyString(config.exaApiKey));
|
||||||
const geminiApiConfigured = Boolean(normalizeNonEmptyString(config.geminiApiKey));
|
const geminiApiConfigured = Boolean(normalizeNonEmptyString(config.geminiApiKey));
|
||||||
const chromeProfile = normalizeNonEmptyString(config.chromeProfile);
|
const chromeProfile = normalizeNonEmptyString(config.chromeProfile);
|
||||||
const effectiveProvider = searchProvider;
|
const effectiveProvider = searchProvider;
|
||||||
@@ -86,6 +113,7 @@ export function getPiWebAccessStatus(
|
|||||||
searchProvider,
|
searchProvider,
|
||||||
requestProvider,
|
requestProvider,
|
||||||
perplexityConfigured,
|
perplexityConfigured,
|
||||||
|
exaConfigured,
|
||||||
geminiApiConfigured,
|
geminiApiConfigured,
|
||||||
chromeProfile,
|
chromeProfile,
|
||||||
routeLabel: formatRouteLabel(effectiveProvider),
|
routeLabel: formatRouteLabel(effectiveProvider),
|
||||||
@@ -101,6 +129,7 @@ export function formatPiWebAccessDoctorLines(
|
|||||||
` search route: ${status.routeLabel}`,
|
` search route: ${status.routeLabel}`,
|
||||||
` request route: ${status.requestProvider}`,
|
` request route: ${status.requestProvider}`,
|
||||||
` perplexity api: ${status.perplexityConfigured ? "configured" : "not configured"}`,
|
` perplexity api: ${status.perplexityConfigured ? "configured" : "not configured"}`,
|
||||||
|
` exa api: ${status.exaConfigured ? "configured" : "not configured"}`,
|
||||||
` gemini api: ${status.geminiApiConfigured ? "configured" : "not configured"}`,
|
` gemini api: ${status.geminiApiConfigured ? "configured" : "not configured"}`,
|
||||||
` browser profile: ${status.chromeProfile ?? "default Chromium profile"}`,
|
` browser profile: ${status.chromeProfile ?? "default Chromium profile"}`,
|
||||||
` config path: ${status.configPath}`,
|
` config path: ${status.configPath}`,
|
||||||
|
|||||||
@@ -1,13 +1,58 @@
|
|||||||
import { getPiWebAccessStatus } from "../pi/web-access.js";
|
import {
|
||||||
|
getPiWebAccessStatus,
|
||||||
|
savePiWebAccessConfig,
|
||||||
|
type PiWebAccessConfig,
|
||||||
|
type PiWebSearchProvider,
|
||||||
|
} from "../pi/web-access.js";
|
||||||
import { printInfo } from "../ui/terminal.js";
|
import { printInfo } from "../ui/terminal.js";
|
||||||
|
|
||||||
|
const SEARCH_PROVIDERS: PiWebSearchProvider[] = ["auto", "perplexity", "exa", "gemini"];
|
||||||
|
const PROVIDER_API_KEY_FIELDS: Partial<Record<PiWebSearchProvider, keyof PiWebAccessConfig>> = {
|
||||||
|
perplexity: "perplexityApiKey",
|
||||||
|
exa: "exaApiKey",
|
||||||
|
gemini: "geminiApiKey",
|
||||||
|
};
|
||||||
|
|
||||||
export function printSearchStatus(): void {
|
export function printSearchStatus(): void {
|
||||||
const status = getPiWebAccessStatus();
|
const status = getPiWebAccessStatus();
|
||||||
printInfo("Managed by: pi-web-access");
|
printInfo("Managed by: pi-web-access");
|
||||||
printInfo(`Search route: ${status.routeLabel}`);
|
printInfo(`Search route: ${status.routeLabel}`);
|
||||||
printInfo(`Request route: ${status.requestProvider}`);
|
printInfo(`Request route: ${status.requestProvider}`);
|
||||||
printInfo(`Perplexity API configured: ${status.perplexityConfigured ? "yes" : "no"}`);
|
printInfo(`Perplexity API configured: ${status.perplexityConfigured ? "yes" : "no"}`);
|
||||||
|
printInfo(`Exa API configured: ${status.exaConfigured ? "yes" : "no"}`);
|
||||||
printInfo(`Gemini API configured: ${status.geminiApiConfigured ? "yes" : "no"}`);
|
printInfo(`Gemini API configured: ${status.geminiApiConfigured ? "yes" : "no"}`);
|
||||||
printInfo(`Browser profile: ${status.chromeProfile ?? "default Chromium profile"}`);
|
printInfo(`Browser profile: ${status.chromeProfile ?? "default Chromium profile"}`);
|
||||||
printInfo(`Config path: ${status.configPath}`);
|
printInfo(`Config path: ${status.configPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setSearchProvider(provider: PiWebSearchProvider, apiKey?: string): void {
|
||||||
|
if (!SEARCH_PROVIDERS.includes(provider)) {
|
||||||
|
throw new Error(`Usage: feynman search set <${SEARCH_PROVIDERS.join("|")}> [api-key]`);
|
||||||
|
}
|
||||||
|
if (apiKey !== undefined && provider === "auto") {
|
||||||
|
throw new Error("The auto provider does not use an API key. Usage: feynman search set auto");
|
||||||
|
}
|
||||||
|
|
||||||
|
const updates: Partial<Record<keyof PiWebAccessConfig, unknown>> = {
|
||||||
|
provider,
|
||||||
|
searchProvider: provider,
|
||||||
|
route: undefined,
|
||||||
|
};
|
||||||
|
const apiKeyField = PROVIDER_API_KEY_FIELDS[provider];
|
||||||
|
if (apiKeyField && apiKey !== undefined) {
|
||||||
|
updates[apiKeyField] = apiKey;
|
||||||
|
}
|
||||||
|
savePiWebAccessConfig(updates);
|
||||||
|
|
||||||
|
const status = getPiWebAccessStatus();
|
||||||
|
console.log(`Web search provider set to ${status.routeLabel}.`);
|
||||||
|
console.log(`Config path: ${status.configPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearSearchConfig(): void {
|
||||||
|
savePiWebAccessConfig({ provider: undefined, searchProvider: undefined, route: undefined });
|
||||||
|
|
||||||
|
const status = getPiWebAccessStatus();
|
||||||
|
console.log(`Web search provider reset to ${status.routeLabel}.`);
|
||||||
|
console.log(`Config path: ${status.configPath}`);
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { printInfo, printPanel, printSection } from "../ui/terminal.js";
|
|||||||
import { getCurrentModelSpec } from "../model/commands.js";
|
import { getCurrentModelSpec } from "../model/commands.js";
|
||||||
import { buildModelStatusSnapshotFromRecords, getAvailableModelRecords, getSupportedModelRecords } from "../model/catalog.js";
|
import { buildModelStatusSnapshotFromRecords, getAvailableModelRecords, getSupportedModelRecords } from "../model/catalog.js";
|
||||||
import { createModelRegistry, getModelsJsonPath } from "../model/registry.js";
|
import { createModelRegistry, getModelsJsonPath } from "../model/registry.js";
|
||||||
|
import { getConfiguredServiceTier } from "../model/service-tier.js";
|
||||||
|
|
||||||
function findProvidersMissingApiKey(modelsJsonPath: string): string[] {
|
function findProvidersMissingApiKey(modelsJsonPath: string): string[] {
|
||||||
try {
|
try {
|
||||||
@@ -105,6 +106,7 @@ export function runStatus(options: DoctorOptions): void {
|
|||||||
printInfo(`Recommended model: ${snapshot.recommendedModel ?? "not available"}`);
|
printInfo(`Recommended model: ${snapshot.recommendedModel ?? "not available"}`);
|
||||||
printInfo(`alphaXiv: ${snapshot.alphaLoggedIn ? snapshot.alphaUser ?? "configured" : "not configured"}`);
|
printInfo(`alphaXiv: ${snapshot.alphaLoggedIn ? snapshot.alphaUser ?? "configured" : "not configured"}`);
|
||||||
printInfo(`Web access: pi-web-access (${snapshot.webRouteLabel})`);
|
printInfo(`Web access: pi-web-access (${snapshot.webRouteLabel})`);
|
||||||
|
printInfo(`Service tier: ${getConfiguredServiceTier(options.settingsPath) ?? "not set"}`);
|
||||||
printInfo(`Preview: ${snapshot.previewConfigured ? "configured" : "not configured"}`);
|
printInfo(`Preview: ${snapshot.previewConfigured ? "configured" : "not configured"}`);
|
||||||
|
|
||||||
printSection("Paths");
|
printSection("Paths");
|
||||||
@@ -165,6 +167,7 @@ export function runDoctor(options: DoctorOptions): void {
|
|||||||
console.log(`default model valid: ${modelStatus.modelValid ? "yes" : "no"}`);
|
console.log(`default model valid: ${modelStatus.modelValid ? "yes" : "no"}`);
|
||||||
console.log(`authenticated providers: ${modelStatus.authenticatedProviderCount}`);
|
console.log(`authenticated providers: ${modelStatus.authenticatedProviderCount}`);
|
||||||
console.log(`authenticated models: ${modelStatus.authenticatedModelCount}`);
|
console.log(`authenticated models: ${modelStatus.authenticatedModelCount}`);
|
||||||
|
console.log(`service tier: ${getConfiguredServiceTier(options.settingsPath) ?? "not set"}`);
|
||||||
console.log(`recommended model: ${modelStatus.recommendedModel ?? "not available"}`);
|
console.log(`recommended model: ${modelStatus.recommendedModel ?? "not available"}`);
|
||||||
if (modelStatus.recommendedModelReason) {
|
if (modelStatus.recommendedModelReason) {
|
||||||
console.log(` why: ${modelStatus.recommendedModelReason}`);
|
console.log(` why: ${modelStatus.recommendedModelReason}`);
|
||||||
|
|||||||
110
tests/catalog-snapshot.test.ts
Normal file
110
tests/catalog-snapshot.test.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
|
import { buildModelStatusSnapshotFromRecords } from "../src/model/catalog.js";
|
||||||
|
|
||||||
|
test("buildModelStatusSnapshotFromRecords returns empty guidance when model is set and valid", () => {
|
||||||
|
const snapshot = buildModelStatusSnapshotFromRecords(
|
||||||
|
[{ provider: "anthropic", id: "claude-opus-4-6" }],
|
||||||
|
[{ provider: "anthropic", id: "claude-opus-4-6" }],
|
||||||
|
"anthropic/claude-opus-4-6",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(snapshot.currentValid, true);
|
||||||
|
assert.equal(snapshot.current, "anthropic/claude-opus-4-6");
|
||||||
|
assert.equal(snapshot.guidance.length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("buildModelStatusSnapshotFromRecords emits guidance when no models are available", () => {
|
||||||
|
const snapshot = buildModelStatusSnapshotFromRecords([], [], undefined);
|
||||||
|
|
||||||
|
assert.equal(snapshot.currentValid, false);
|
||||||
|
assert.equal(snapshot.current, undefined);
|
||||||
|
assert.equal(snapshot.recommended, undefined);
|
||||||
|
assert.ok(snapshot.guidance.some((line) => line.includes("No authenticated Pi models")));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("buildModelStatusSnapshotFromRecords emits guidance when no default model is set", () => {
|
||||||
|
const snapshot = buildModelStatusSnapshotFromRecords(
|
||||||
|
[{ provider: "openai", id: "gpt-5.4" }],
|
||||||
|
[{ provider: "openai", id: "gpt-5.4" }],
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(snapshot.currentValid, false);
|
||||||
|
assert.equal(snapshot.current, undefined);
|
||||||
|
assert.ok(snapshot.guidance.some((line) => line.includes("No default research model")));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("buildModelStatusSnapshotFromRecords marks provider as configured only when it has available models", () => {
|
||||||
|
const snapshot = buildModelStatusSnapshotFromRecords(
|
||||||
|
[
|
||||||
|
{ provider: "anthropic", id: "claude-opus-4-6" },
|
||||||
|
{ provider: "openai", id: "gpt-5.4" },
|
||||||
|
],
|
||||||
|
[{ provider: "openai", id: "gpt-5.4" }],
|
||||||
|
"openai/gpt-5.4",
|
||||||
|
);
|
||||||
|
|
||||||
|
const anthropicProvider = snapshot.providers.find((provider) => provider.id === "anthropic");
|
||||||
|
const openaiProvider = snapshot.providers.find((provider) => provider.id === "openai");
|
||||||
|
|
||||||
|
assert.ok(anthropicProvider);
|
||||||
|
assert.equal(anthropicProvider!.configured, false);
|
||||||
|
assert.equal(anthropicProvider!.supportedModels, 1);
|
||||||
|
assert.equal(anthropicProvider!.availableModels, 0);
|
||||||
|
|
||||||
|
assert.ok(openaiProvider);
|
||||||
|
assert.equal(openaiProvider!.configured, true);
|
||||||
|
assert.equal(openaiProvider!.supportedModels, 1);
|
||||||
|
assert.equal(openaiProvider!.availableModels, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("buildModelStatusSnapshotFromRecords marks provider as current when selected model belongs to it", () => {
|
||||||
|
const snapshot = buildModelStatusSnapshotFromRecords(
|
||||||
|
[
|
||||||
|
{ provider: "anthropic", id: "claude-opus-4-6" },
|
||||||
|
{ provider: "openai", id: "gpt-5.4" },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ provider: "anthropic", id: "claude-opus-4-6" },
|
||||||
|
{ provider: "openai", id: "gpt-5.4" },
|
||||||
|
],
|
||||||
|
"anthropic/claude-opus-4-6",
|
||||||
|
);
|
||||||
|
|
||||||
|
const anthropicProvider = snapshot.providers.find((provider) => provider.id === "anthropic");
|
||||||
|
const openaiProvider = snapshot.providers.find((provider) => provider.id === "openai");
|
||||||
|
|
||||||
|
assert.equal(anthropicProvider!.current, true);
|
||||||
|
assert.equal(openaiProvider!.current, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("buildModelStatusSnapshotFromRecords returns available models sorted by research preference", () => {
|
||||||
|
const snapshot = buildModelStatusSnapshotFromRecords(
|
||||||
|
[
|
||||||
|
{ provider: "openai", id: "gpt-5.4" },
|
||||||
|
{ provider: "anthropic", id: "claude-opus-4-6" },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ provider: "openai", id: "gpt-5.4" },
|
||||||
|
{ provider: "anthropic", id: "claude-opus-4-6" },
|
||||||
|
],
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(snapshot.availableModels[0], "anthropic/claude-opus-4-6");
|
||||||
|
assert.equal(snapshot.availableModels[1], "openai/gpt-5.4");
|
||||||
|
assert.equal(snapshot.recommended, "anthropic/claude-opus-4-6");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("buildModelStatusSnapshotFromRecords sets currentValid false when current model is not in available list", () => {
|
||||||
|
const snapshot = buildModelStatusSnapshotFromRecords(
|
||||||
|
[{ provider: "anthropic", id: "claude-opus-4-6" }],
|
||||||
|
[],
|
||||||
|
"anthropic/claude-opus-4-6",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(snapshot.currentValid, false);
|
||||||
|
assert.equal(snapshot.current, "anthropic/claude-opus-4-6");
|
||||||
|
});
|
||||||
92
tests/config-paths.test.ts
Normal file
92
tests/config-paths.test.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { existsSync, mkdtempSync, rmSync } from "node:fs";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join, resolve } from "node:path";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ensureFeynmanHome,
|
||||||
|
getBootstrapStatePath,
|
||||||
|
getDefaultSessionDir,
|
||||||
|
getFeynmanAgentDir,
|
||||||
|
getFeynmanHome,
|
||||||
|
getFeynmanMemoryDir,
|
||||||
|
getFeynmanStateDir,
|
||||||
|
} from "../src/config/paths.js";
|
||||||
|
|
||||||
|
test("getFeynmanHome uses FEYNMAN_HOME env var when set", () => {
|
||||||
|
const previous = process.env.FEYNMAN_HOME;
|
||||||
|
try {
|
||||||
|
process.env.FEYNMAN_HOME = "/custom/home";
|
||||||
|
assert.equal(getFeynmanHome(), resolve("/custom/home", ".feynman"));
|
||||||
|
} finally {
|
||||||
|
if (previous === undefined) {
|
||||||
|
delete process.env.FEYNMAN_HOME;
|
||||||
|
} else {
|
||||||
|
process.env.FEYNMAN_HOME = previous;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getFeynmanHome falls back to homedir when FEYNMAN_HOME is unset", () => {
|
||||||
|
const previous = process.env.FEYNMAN_HOME;
|
||||||
|
try {
|
||||||
|
delete process.env.FEYNMAN_HOME;
|
||||||
|
const home = getFeynmanHome();
|
||||||
|
assert.ok(home.endsWith(".feynman"), `expected path ending in .feynman, got: ${home}`);
|
||||||
|
assert.ok(!home.includes("undefined"), `expected no 'undefined' in path, got: ${home}`);
|
||||||
|
} finally {
|
||||||
|
if (previous === undefined) {
|
||||||
|
delete process.env.FEYNMAN_HOME;
|
||||||
|
} else {
|
||||||
|
process.env.FEYNMAN_HOME = previous;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getFeynmanAgentDir resolves to <home>/agent", () => {
|
||||||
|
assert.equal(getFeynmanAgentDir("/some/home"), resolve("/some/home", "agent"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getFeynmanMemoryDir resolves to <home>/memory", () => {
|
||||||
|
assert.equal(getFeynmanMemoryDir("/some/home"), resolve("/some/home", "memory"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getFeynmanStateDir resolves to <home>/.state", () => {
|
||||||
|
assert.equal(getFeynmanStateDir("/some/home"), resolve("/some/home", ".state"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getDefaultSessionDir resolves to <home>/sessions", () => {
|
||||||
|
assert.equal(getDefaultSessionDir("/some/home"), resolve("/some/home", "sessions"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getBootstrapStatePath resolves to <home>/.state/bootstrap.json", () => {
|
||||||
|
assert.equal(getBootstrapStatePath("/some/home"), resolve("/some/home", ".state", "bootstrap.json"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("ensureFeynmanHome creates all required subdirectories", () => {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), "feynman-paths-"));
|
||||||
|
try {
|
||||||
|
const home = join(root, "home");
|
||||||
|
ensureFeynmanHome(home);
|
||||||
|
|
||||||
|
assert.ok(existsSync(home), "home dir should exist");
|
||||||
|
assert.ok(existsSync(join(home, "agent")), "agent dir should exist");
|
||||||
|
assert.ok(existsSync(join(home, "memory")), "memory dir should exist");
|
||||||
|
assert.ok(existsSync(join(home, ".state")), ".state dir should exist");
|
||||||
|
assert.ok(existsSync(join(home, "sessions")), "sessions dir should exist");
|
||||||
|
} finally {
|
||||||
|
rmSync(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("ensureFeynmanHome is idempotent when dirs already exist", () => {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), "feynman-paths-"));
|
||||||
|
try {
|
||||||
|
const home = join(root, "home");
|
||||||
|
ensureFeynmanHome(home);
|
||||||
|
assert.doesNotThrow(() => ensureFeynmanHome(home));
|
||||||
|
} finally {
|
||||||
|
rmSync(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
32
tests/content-policy.test.ts
Normal file
32
tests/content-policy.test.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { readdirSync, readFileSync } from "node:fs";
|
||||||
|
import { dirname, join, resolve } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||||
|
const bannedPatterns = [/ValiChord/i, /Harmony Record/i, /harmony_record_/i];
|
||||||
|
|
||||||
|
function collectMarkdownFiles(root: string): string[] {
|
||||||
|
const files: string[] = [];
|
||||||
|
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
||||||
|
const fullPath = join(root, entry.name);
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
files.push(...collectMarkdownFiles(fullPath));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (entry.isFile() && fullPath.endsWith(".md")) {
|
||||||
|
files.push(fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
test("bundled prompts and skills do not contain blocked promotional product content", () => {
|
||||||
|
for (const filePath of [...collectMarkdownFiles(join(repoRoot, "prompts")), ...collectMarkdownFiles(join(repoRoot, "skills"))]) {
|
||||||
|
const content = readFileSync(filePath, "utf8");
|
||||||
|
for (const pattern of bannedPatterns) {
|
||||||
|
assert.doesNotMatch(content, pattern, `${filePath} contains blocked promotional pattern ${pattern}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -57,6 +57,16 @@ test("buildModelStatusSnapshotFromRecords flags an invalid current model and sug
|
|||||||
assert.ok(snapshot.guidance.some((line) => line.includes("Configured default model is unavailable")));
|
assert.ok(snapshot.guidance.some((line) => line.includes("Configured default model is unavailable")));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("chooseRecommendedModel prefers MiniMax M2.7 over highspeed when that is the authenticated provider", () => {
|
||||||
|
const authPath = createAuthPath({
|
||||||
|
minimax: { type: "api_key", key: "minimax-test-key" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const recommendation = chooseRecommendedModel(authPath);
|
||||||
|
|
||||||
|
assert.equal(recommendation?.spec, "minimax/MiniMax-M2.7");
|
||||||
|
});
|
||||||
|
|
||||||
test("resolveInitialPrompt maps top-level research commands to Pi slash workflows", () => {
|
test("resolveInitialPrompt maps top-level research commands to Pi slash workflows", () => {
|
||||||
const workflows = new Set(["lit", "watch", "jobs", "deepresearch"]);
|
const workflows = new Set(["lit", "watch", "jobs", "deepresearch"]);
|
||||||
assert.equal(resolveInitialPrompt("lit", ["tool-using", "agents"], undefined, workflows), "/lit tool-using agents");
|
assert.equal(resolveInitialPrompt("lit", ["tool-using", "agents"], undefined, workflows), "/lit tool-using agents");
|
||||||
@@ -65,4 +75,3 @@ test("resolveInitialPrompt maps top-level research commands to Pi slash workflow
|
|||||||
assert.equal(resolveInitialPrompt("chat", ["hello"], undefined, workflows), "hello");
|
assert.equal(resolveInitialPrompt("chat", ["hello"], undefined, workflows), "hello");
|
||||||
assert.equal(resolveInitialPrompt("unknown", ["topic"], undefined, workflows), "unknown topic");
|
assert.equal(resolveInitialPrompt("unknown", ["topic"], undefined, workflows), "unknown topic");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
42
tests/pi-extension-loader-patch.test.ts
Normal file
42
tests/pi-extension-loader-patch.test.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
|
import { patchPiExtensionLoaderSource } from "../scripts/lib/pi-extension-loader-patch.mjs";
|
||||||
|
|
||||||
|
test("patchPiExtensionLoaderSource rewrites Windows extension imports to file URLs", () => {
|
||||||
|
const input = [
|
||||||
|
'import * as path from "node:path";',
|
||||||
|
'import { fileURLToPath } from "node:url";',
|
||||||
|
"async function loadExtensionModule(extensionPath) {",
|
||||||
|
" const jiti = createJiti(import.meta.url);",
|
||||||
|
' const module = await jiti.import(extensionPath, { default: true });',
|
||||||
|
" return module;",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const patched = patchPiExtensionLoaderSource(input);
|
||||||
|
|
||||||
|
assert.match(patched, /pathToFileURL/);
|
||||||
|
assert.match(patched, /process\.platform === "win32"/);
|
||||||
|
assert.match(patched, /path\.isAbsolute\(extensionPath\)/);
|
||||||
|
assert.match(patched, /jiti\.import\(extensionSpecifier, \{ default: true \}\)/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("patchPiExtensionLoaderSource is idempotent", () => {
|
||||||
|
const input = [
|
||||||
|
'import * as path from "node:path";',
|
||||||
|
'import { fileURLToPath } from "node:url";',
|
||||||
|
"async function loadExtensionModule(extensionPath) {",
|
||||||
|
" const jiti = createJiti(import.meta.url);",
|
||||||
|
' const module = await jiti.import(extensionPath, { default: true });',
|
||||||
|
" return module;",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const once = patchPiExtensionLoaderSource(input);
|
||||||
|
const twice = patchPiExtensionLoaderSource(once);
|
||||||
|
|
||||||
|
assert.equal(twice, once);
|
||||||
|
});
|
||||||
42
tests/pi-google-legacy-schema-patch.test.ts
Normal file
42
tests/pi-google-legacy-schema-patch.test.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
|
import { patchPiGoogleLegacySchemaSource } from "../scripts/lib/pi-google-legacy-schema-patch.mjs";
|
||||||
|
|
||||||
|
test("patchPiGoogleLegacySchemaSource rewrites legacy parameters conversion to normalize const", () => {
|
||||||
|
const input = [
|
||||||
|
"export function convertTools(tools, useParameters = false) {",
|
||||||
|
" if (tools.length === 0) return undefined;",
|
||||||
|
" return [",
|
||||||
|
" {",
|
||||||
|
" functionDeclarations: tools.map((tool) => ({",
|
||||||
|
" name: tool.name,",
|
||||||
|
" description: tool.description,",
|
||||||
|
' ...(useParameters ? { parameters: tool.parameters } : { parametersJsonSchema: tool.parameters }),',
|
||||||
|
" })),",
|
||||||
|
" },",
|
||||||
|
" ];",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const patched = patchPiGoogleLegacySchemaSource(input);
|
||||||
|
|
||||||
|
assert.match(patched, /function normalizeLegacyToolSchema\(schema\)/);
|
||||||
|
assert.match(patched, /normalized\.enum = \[value\]/);
|
||||||
|
assert.match(patched, /parameters: normalizeLegacyToolSchema\(tool\.parameters\)/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("patchPiGoogleLegacySchemaSource is idempotent", () => {
|
||||||
|
const input = [
|
||||||
|
"export function convertTools(tools, useParameters = false) {",
|
||||||
|
' ...(useParameters ? { parameters: tool.parameters } : { parametersJsonSchema: tool.parameters }),',
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const once = patchPiGoogleLegacySchemaSource(input);
|
||||||
|
const twice = patchPiGoogleLegacySchemaSource(once);
|
||||||
|
|
||||||
|
assert.equal(twice, once);
|
||||||
|
});
|
||||||
9
tests/pi-launch.test.ts
Normal file
9
tests/pi-launch.test.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
|
import { exitCodeFromSignal } from "../src/pi/launch.js";
|
||||||
|
|
||||||
|
test("exitCodeFromSignal maps POSIX signals to conventional shell exit codes", () => {
|
||||||
|
assert.equal(exitCodeFromSignal("SIGTERM"), 143);
|
||||||
|
assert.equal(exitCodeFromSignal("SIGSEGV"), 139);
|
||||||
|
});
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import test from "node:test";
|
import test from "node:test";
|
||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
|
import { pathToFileURL } from "node:url";
|
||||||
|
|
||||||
import { buildPiArgs, buildPiEnv, resolvePiPaths } from "../src/pi/runtime.js";
|
import { applyFeynmanPackageManagerEnv, buildPiArgs, buildPiEnv, resolvePiPaths, toNodeImportSpecifier } from "../src/pi/runtime.js";
|
||||||
|
|
||||||
test("buildPiArgs includes configured runtime paths and prompt", () => {
|
test("buildPiArgs includes configured runtime paths and prompt", () => {
|
||||||
const args = buildPiArgs({
|
const args = buildPiArgs({
|
||||||
@@ -9,6 +10,7 @@ test("buildPiArgs includes configured runtime paths and prompt", () => {
|
|||||||
workingDir: "/workspace",
|
workingDir: "/workspace",
|
||||||
sessionDir: "/sessions",
|
sessionDir: "/sessions",
|
||||||
feynmanAgentDir: "/home/.feynman/agent",
|
feynmanAgentDir: "/home/.feynman/agent",
|
||||||
|
mode: "rpc",
|
||||||
initialPrompt: "hello",
|
initialPrompt: "hello",
|
||||||
explicitModelSpec: "openai:gpt-5.4",
|
explicitModelSpec: "openai:gpt-5.4",
|
||||||
thinkingLevel: "medium",
|
thinkingLevel: "medium",
|
||||||
@@ -21,6 +23,8 @@ test("buildPiArgs includes configured runtime paths and prompt", () => {
|
|||||||
"/repo/feynman/extensions/research-tools.ts",
|
"/repo/feynman/extensions/research-tools.ts",
|
||||||
"--prompt-template",
|
"--prompt-template",
|
||||||
"/repo/feynman/prompts",
|
"/repo/feynman/prompts",
|
||||||
|
"--mode",
|
||||||
|
"rpc",
|
||||||
"--model",
|
"--model",
|
||||||
"openai:gpt-5.4",
|
"openai:gpt-5.4",
|
||||||
"--thinking",
|
"--thinking",
|
||||||
@@ -70,8 +74,47 @@ test("buildPiEnv wires Feynman paths into the Pi environment", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("applyFeynmanPackageManagerEnv pins npm globals to the Feynman prefix", () => {
|
||||||
|
const previousFeynmanPrefix = process.env.FEYNMAN_NPM_PREFIX;
|
||||||
|
const previousUppercasePrefix = process.env.NPM_CONFIG_PREFIX;
|
||||||
|
const previousLowercasePrefix = process.env.npm_config_prefix;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const prefix = applyFeynmanPackageManagerEnv("/home/.feynman/agent");
|
||||||
|
|
||||||
|
assert.equal(prefix, "/home/.feynman/npm-global");
|
||||||
|
assert.equal(process.env.FEYNMAN_NPM_PREFIX, "/home/.feynman/npm-global");
|
||||||
|
assert.equal(process.env.NPM_CONFIG_PREFIX, "/home/.feynman/npm-global");
|
||||||
|
assert.equal(process.env.npm_config_prefix, "/home/.feynman/npm-global");
|
||||||
|
} finally {
|
||||||
|
if (previousFeynmanPrefix === undefined) {
|
||||||
|
delete process.env.FEYNMAN_NPM_PREFIX;
|
||||||
|
} else {
|
||||||
|
process.env.FEYNMAN_NPM_PREFIX = previousFeynmanPrefix;
|
||||||
|
}
|
||||||
|
if (previousUppercasePrefix === undefined) {
|
||||||
|
delete process.env.NPM_CONFIG_PREFIX;
|
||||||
|
} else {
|
||||||
|
process.env.NPM_CONFIG_PREFIX = previousUppercasePrefix;
|
||||||
|
}
|
||||||
|
if (previousLowercasePrefix === undefined) {
|
||||||
|
delete process.env.npm_config_prefix;
|
||||||
|
} else {
|
||||||
|
process.env.npm_config_prefix = previousLowercasePrefix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test("resolvePiPaths includes the Promise.withResolvers polyfill path", () => {
|
test("resolvePiPaths includes the Promise.withResolvers polyfill path", () => {
|
||||||
const paths = resolvePiPaths("/repo/feynman");
|
const paths = resolvePiPaths("/repo/feynman");
|
||||||
|
|
||||||
assert.equal(paths.promisePolyfillPath, "/repo/feynman/dist/system/promise-polyfill.js");
|
assert.equal(paths.promisePolyfillPath, "/repo/feynman/dist/system/promise-polyfill.js");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("toNodeImportSpecifier converts absolute preload paths to file URLs", () => {
|
||||||
|
assert.equal(
|
||||||
|
toNodeImportSpecifier("/repo/feynman/dist/system/promise-polyfill.js"),
|
||||||
|
pathToFileURL("/repo/feynman/dist/system/promise-polyfill.js").href,
|
||||||
|
);
|
||||||
|
assert.equal(toNodeImportSpecifier("tsx"), "tsx");
|
||||||
|
});
|
||||||
|
|||||||
104
tests/pi-subagents-patch.test.ts
Normal file
104
tests/pi-subagents-patch.test.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
|
import { patchPiSubagentsSource } from "../scripts/lib/pi-subagents-patch.mjs";
|
||||||
|
|
||||||
|
const CASES = [
|
||||||
|
{
|
||||||
|
name: "index.ts config path",
|
||||||
|
file: "index.ts",
|
||||||
|
input: [
|
||||||
|
'import * as os from "node:os";',
|
||||||
|
'import * as path from "node:path";',
|
||||||
|
'const configPath = path.join(os.homedir(), ".pi", "agent", "extensions", "subagent", "config.json");',
|
||||||
|
"",
|
||||||
|
].join("\n"),
|
||||||
|
original: 'const configPath = path.join(os.homedir(), ".pi", "agent", "extensions", "subagent", "config.json");',
|
||||||
|
expected: 'const configPath = path.join(resolvePiAgentDir(), "extensions", "subagent", "config.json");',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "agents.ts user agents dir",
|
||||||
|
file: "agents.ts",
|
||||||
|
input: [
|
||||||
|
'import * as os from "node:os";',
|
||||||
|
'import * as path from "node:path";',
|
||||||
|
'const userDir = path.join(os.homedir(), ".pi", "agent", "agents");',
|
||||||
|
"",
|
||||||
|
].join("\n"),
|
||||||
|
original: 'const userDir = path.join(os.homedir(), ".pi", "agent", "agents");',
|
||||||
|
expected: 'const userDir = path.join(resolvePiAgentDir(), "agents");',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "artifacts.ts sessions dir",
|
||||||
|
file: "artifacts.ts",
|
||||||
|
input: [
|
||||||
|
'import * as os from "node:os";',
|
||||||
|
'import * as path from "node:path";',
|
||||||
|
'const sessionsBase = path.join(os.homedir(), ".pi", "agent", "sessions");',
|
||||||
|
"",
|
||||||
|
].join("\n"),
|
||||||
|
original: 'const sessionsBase = path.join(os.homedir(), ".pi", "agent", "sessions");',
|
||||||
|
expected: 'const sessionsBase = path.join(resolvePiAgentDir(), "sessions");',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "run-history.ts history file",
|
||||||
|
file: "run-history.ts",
|
||||||
|
input: [
|
||||||
|
'import * as os from "node:os";',
|
||||||
|
'import * as path from "node:path";',
|
||||||
|
'const HISTORY_PATH = path.join(os.homedir(), ".pi", "agent", "run-history.jsonl");',
|
||||||
|
"",
|
||||||
|
].join("\n"),
|
||||||
|
original: 'const HISTORY_PATH = path.join(os.homedir(), ".pi", "agent", "run-history.jsonl");',
|
||||||
|
expected: 'const HISTORY_PATH = path.join(resolvePiAgentDir(), "run-history.jsonl");',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "skills.ts agent dir",
|
||||||
|
file: "skills.ts",
|
||||||
|
input: [
|
||||||
|
'import * as os from "node:os";',
|
||||||
|
'import * as path from "node:path";',
|
||||||
|
'const AGENT_DIR = path.join(os.homedir(), ".pi", "agent");',
|
||||||
|
"",
|
||||||
|
].join("\n"),
|
||||||
|
original: 'const AGENT_DIR = path.join(os.homedir(), ".pi", "agent");',
|
||||||
|
expected: "const AGENT_DIR = resolvePiAgentDir();",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "chain-clarify.ts chain save dir",
|
||||||
|
file: "chain-clarify.ts",
|
||||||
|
input: [
|
||||||
|
'import * as os from "node:os";',
|
||||||
|
'import * as path from "node:path";',
|
||||||
|
'const dir = path.join(os.homedir(), ".pi", "agent", "agents");',
|
||||||
|
"",
|
||||||
|
].join("\n"),
|
||||||
|
original: 'const dir = path.join(os.homedir(), ".pi", "agent", "agents");',
|
||||||
|
expected: 'const dir = path.join(resolvePiAgentDir(), "agents");',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const scenario of CASES) {
|
||||||
|
test(`patchPiSubagentsSource rewrites ${scenario.name}`, () => {
|
||||||
|
const patched = patchPiSubagentsSource(scenario.file, scenario.input);
|
||||||
|
|
||||||
|
assert.match(patched, /function resolvePiAgentDir\(\): string \{/);
|
||||||
|
assert.match(patched, /process\.env\.PI_CODING_AGENT_DIR\?\.trim\(\)/);
|
||||||
|
assert.ok(patched.includes(scenario.expected));
|
||||||
|
assert.ok(!patched.includes(scenario.original));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test("patchPiSubagentsSource is idempotent", () => {
|
||||||
|
const input = [
|
||||||
|
'import * as os from "node:os";',
|
||||||
|
'import * as path from "node:path";',
|
||||||
|
'const configPath = path.join(os.homedir(), ".pi", "agent", "extensions", "subagent", "config.json");',
|
||||||
|
"",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const once = patchPiSubagentsSource("index.ts", input);
|
||||||
|
const twice = patchPiSubagentsSource("index.ts", once);
|
||||||
|
|
||||||
|
assert.equal(twice, once);
|
||||||
|
});
|
||||||
48
tests/pi-web-access-patch.test.ts
Normal file
48
tests/pi-web-access-patch.test.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
|
import { patchPiWebAccessSource } from "../scripts/lib/pi-web-access-patch.mjs";
|
||||||
|
|
||||||
|
test("patchPiWebAccessSource rewrites legacy Pi web-search config paths", () => {
|
||||||
|
const input = [
|
||||||
|
'import { join } from "node:path";',
|
||||||
|
'import { homedir } from "node:os";',
|
||||||
|
'const CONFIG_PATH = join(homedir(), ".pi", "web-search.json");',
|
||||||
|
"",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const patched = patchPiWebAccessSource("perplexity.ts", input);
|
||||||
|
|
||||||
|
assert.match(patched, /FEYNMAN_WEB_SEARCH_CONFIG/);
|
||||||
|
assert.match(patched, /PI_WEB_SEARCH_CONFIG/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("patchPiWebAccessSource updates index.ts directory handling", () => {
|
||||||
|
const input = [
|
||||||
|
'import { existsSync, mkdirSync } from "node:fs";',
|
||||||
|
'import { join } from "node:path";',
|
||||||
|
'import { homedir } from "node:os";',
|
||||||
|
'const WEB_SEARCH_CONFIG_PATH = join(homedir(), ".pi", "web-search.json");',
|
||||||
|
'const dir = join(homedir(), ".pi");',
|
||||||
|
"",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const patched = patchPiWebAccessSource("index.ts", input);
|
||||||
|
|
||||||
|
assert.match(patched, /import \{ dirname, join \} from "node:path";/);
|
||||||
|
assert.match(patched, /const dir = dirname\(WEB_SEARCH_CONFIG_PATH\);/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("patchPiWebAccessSource is idempotent", () => {
|
||||||
|
const input = [
|
||||||
|
'import { join } from "node:path";',
|
||||||
|
'import { homedir } from "node:os";',
|
||||||
|
'const CONFIG_PATH = join(homedir(), ".pi", "web-search.json");',
|
||||||
|
"",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const once = patchPiWebAccessSource("perplexity.ts", input);
|
||||||
|
const twice = patchPiWebAccessSource("perplexity.ts", once);
|
||||||
|
|
||||||
|
assert.equal(twice, once);
|
||||||
|
});
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
getPiWebAccessStatus,
|
getPiWebAccessStatus,
|
||||||
getPiWebSearchConfigPath,
|
getPiWebSearchConfigPath,
|
||||||
loadPiWebAccessConfig,
|
loadPiWebAccessConfig,
|
||||||
|
savePiWebAccessConfig,
|
||||||
} from "../src/pi/web-access.js";
|
} from "../src/pi/web-access.js";
|
||||||
|
|
||||||
test("loadPiWebAccessConfig returns empty config when Pi web config is missing", () => {
|
test("loadPiWebAccessConfig returns empty config when Pi web config is missing", () => {
|
||||||
@@ -18,7 +19,56 @@ test("loadPiWebAccessConfig returns empty config when Pi web config is missing",
|
|||||||
assert.deepEqual(loadPiWebAccessConfig(configPath), {});
|
assert.deepEqual(loadPiWebAccessConfig(configPath), {});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("getPiWebSearchConfigPath respects FEYNMAN_HOME semantics", () => {
|
||||||
|
assert.equal(getPiWebSearchConfigPath("/tmp/custom-home"), "/tmp/custom-home/.feynman/web-search.json");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("savePiWebAccessConfig merges updates and deletes undefined values", () => {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), "feynman-pi-web-"));
|
||||||
|
const configPath = getPiWebSearchConfigPath(root);
|
||||||
|
|
||||||
|
savePiWebAccessConfig({
|
||||||
|
provider: "perplexity",
|
||||||
|
searchProvider: "perplexity",
|
||||||
|
perplexityApiKey: "pplx_...",
|
||||||
|
}, configPath);
|
||||||
|
savePiWebAccessConfig({
|
||||||
|
provider: undefined,
|
||||||
|
searchProvider: undefined,
|
||||||
|
route: undefined,
|
||||||
|
}, configPath);
|
||||||
|
|
||||||
|
assert.deepEqual(loadPiWebAccessConfig(configPath), {
|
||||||
|
perplexityApiKey: "pplx_...",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("getPiWebAccessStatus reads Pi web-access config directly", () => {
|
test("getPiWebAccessStatus reads Pi web-access config directly", () => {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), "feynman-pi-web-"));
|
||||||
|
const configPath = getPiWebSearchConfigPath(root);
|
||||||
|
mkdirSync(join(root, ".feynman"), { recursive: true });
|
||||||
|
writeFileSync(
|
||||||
|
configPath,
|
||||||
|
JSON.stringify({
|
||||||
|
provider: "exa",
|
||||||
|
searchProvider: "exa",
|
||||||
|
exaApiKey: "exa_...",
|
||||||
|
chromeProfile: "Profile 2",
|
||||||
|
geminiApiKey: "AIza...",
|
||||||
|
}),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
|
||||||
|
const status = getPiWebAccessStatus(loadPiWebAccessConfig(configPath), configPath);
|
||||||
|
assert.equal(status.routeLabel, "Exa");
|
||||||
|
assert.equal(status.requestProvider, "exa");
|
||||||
|
assert.equal(status.exaConfigured, true);
|
||||||
|
assert.equal(status.geminiApiConfigured, true);
|
||||||
|
assert.equal(status.perplexityConfigured, false);
|
||||||
|
assert.equal(status.chromeProfile, "Profile 2");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getPiWebAccessStatus reads Gemini routes directly", () => {
|
||||||
const root = mkdtempSync(join(tmpdir(), "feynman-pi-web-"));
|
const root = mkdtempSync(join(tmpdir(), "feynman-pi-web-"));
|
||||||
const configPath = getPiWebSearchConfigPath(root);
|
const configPath = getPiWebSearchConfigPath(root);
|
||||||
mkdirSync(join(root, ".feynman"), { recursive: true });
|
mkdirSync(join(root, ".feynman"), { recursive: true });
|
||||||
@@ -36,11 +86,23 @@ test("getPiWebAccessStatus reads Pi web-access config directly", () => {
|
|||||||
const status = getPiWebAccessStatus(loadPiWebAccessConfig(configPath), configPath);
|
const status = getPiWebAccessStatus(loadPiWebAccessConfig(configPath), configPath);
|
||||||
assert.equal(status.routeLabel, "Gemini");
|
assert.equal(status.routeLabel, "Gemini");
|
||||||
assert.equal(status.requestProvider, "gemini");
|
assert.equal(status.requestProvider, "gemini");
|
||||||
|
assert.equal(status.exaConfigured, false);
|
||||||
assert.equal(status.geminiApiConfigured, true);
|
assert.equal(status.geminiApiConfigured, true);
|
||||||
assert.equal(status.perplexityConfigured, false);
|
assert.equal(status.perplexityConfigured, false);
|
||||||
assert.equal(status.chromeProfile, "Profile 2");
|
assert.equal(status.chromeProfile, "Profile 2");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("getPiWebAccessStatus supports the legacy route key", () => {
|
||||||
|
const status = getPiWebAccessStatus({
|
||||||
|
route: "perplexity",
|
||||||
|
perplexityApiKey: "pplx_...",
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(status.routeLabel, "Perplexity");
|
||||||
|
assert.equal(status.requestProvider, "perplexity");
|
||||||
|
assert.equal(status.perplexityConfigured, true);
|
||||||
|
});
|
||||||
|
|
||||||
test("formatPiWebAccessDoctorLines reports Pi-managed web access", () => {
|
test("formatPiWebAccessDoctorLines reports Pi-managed web access", () => {
|
||||||
const lines = formatPiWebAccessDoctorLines(
|
const lines = formatPiWebAccessDoctorLines(
|
||||||
getPiWebAccessStatus({
|
getPiWebAccessStatus({
|
||||||
|
|||||||
41
tests/service-tier.test.ts
Normal file
41
tests/service-tier.test.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { mkdtempSync, readFileSync } from "node:fs";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
|
|
||||||
|
import {
|
||||||
|
getConfiguredServiceTier,
|
||||||
|
normalizeServiceTier,
|
||||||
|
resolveProviderServiceTier,
|
||||||
|
setConfiguredServiceTier,
|
||||||
|
} from "../src/model/service-tier.js";
|
||||||
|
|
||||||
|
test("normalizeServiceTier accepts supported values only", () => {
|
||||||
|
assert.equal(normalizeServiceTier("priority"), "priority");
|
||||||
|
assert.equal(normalizeServiceTier("standard_only"), "standard_only");
|
||||||
|
assert.equal(normalizeServiceTier("FAST"), undefined);
|
||||||
|
assert.equal(normalizeServiceTier(undefined), undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("setConfiguredServiceTier persists and clears settings.json values", () => {
|
||||||
|
const dir = mkdtempSync(join(tmpdir(), "feynman-service-tier-"));
|
||||||
|
const settingsPath = join(dir, "settings.json");
|
||||||
|
|
||||||
|
setConfiguredServiceTier(settingsPath, "priority");
|
||||||
|
assert.equal(getConfiguredServiceTier(settingsPath), "priority");
|
||||||
|
|
||||||
|
const persisted = JSON.parse(readFileSync(settingsPath, "utf8")) as { serviceTier?: string };
|
||||||
|
assert.equal(persisted.serviceTier, "priority");
|
||||||
|
|
||||||
|
setConfiguredServiceTier(settingsPath, undefined);
|
||||||
|
assert.equal(getConfiguredServiceTier(settingsPath), undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("resolveProviderServiceTier filters unsupported provider+tier pairs", () => {
|
||||||
|
assert.equal(resolveProviderServiceTier("openai", "priority"), "priority");
|
||||||
|
assert.equal(resolveProviderServiceTier("openai-codex", "flex"), "flex");
|
||||||
|
assert.equal(resolveProviderServiceTier("anthropic", "standard_only"), "standard_only");
|
||||||
|
assert.equal(resolveProviderServiceTier("anthropic", "priority"), undefined);
|
||||||
|
assert.equal(resolveProviderServiceTier("google", "priority"), undefined);
|
||||||
|
});
|
||||||
28
tests/skill-paths.test.ts
Normal file
28
tests/skill-paths.test.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
||||||
|
import { dirname, join, resolve } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||||
|
const skillsRoot = join(repoRoot, "skills");
|
||||||
|
const markdownPathPattern = /`((?:\.\.?\/)(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+\.md)`/g;
|
||||||
|
const simulatedInstallRoot = join(repoRoot, "__skill-install-root__");
|
||||||
|
|
||||||
|
test("all local markdown references in bundled skills resolve in the installed skill layout", () => {
|
||||||
|
for (const entry of readdirSync(skillsRoot, { withFileTypes: true })) {
|
||||||
|
if (!entry.isDirectory()) continue;
|
||||||
|
|
||||||
|
const skillPath = join(skillsRoot, entry.name, "SKILL.md");
|
||||||
|
if (!existsSync(skillPath)) continue;
|
||||||
|
|
||||||
|
const content = readFileSync(skillPath, "utf8");
|
||||||
|
for (const match of content.matchAll(markdownPathPattern)) {
|
||||||
|
const reference = match[1];
|
||||||
|
const installedSkillDir = join(simulatedInstallRoot, entry.name);
|
||||||
|
const installedTarget = resolve(installedSkillDir, reference);
|
||||||
|
const repoTarget = installedTarget.replace(simulatedInstallRoot, repoRoot);
|
||||||
|
assert.ok(existsSync(repoTarget), `${skillPath} references missing installed markdown file ${reference}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
814
website/package-lock.json
generated
814
website/package-lock.json
generated
@@ -7,9 +7,6 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "website",
|
"name": "website",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"engines": {
|
|
||||||
"node": ">=20.19.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/react": "^4.4.2",
|
"@astrojs/react": "^4.4.2",
|
||||||
"@fontsource-variable/ibm-plex-sans": "^5.2.8",
|
"@fontsource-variable/ibm-plex-sans": "^5.2.8",
|
||||||
@@ -29,6 +26,7 @@
|
|||||||
"tw-animate-css": "^1.4.0"
|
"tw-animate-css": "^1.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@astrojs/check": "^0.9.8",
|
||||||
"@eslint/js": "^9.39.4",
|
"@eslint/js": "^9.39.4",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "^9.39.4",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
@@ -39,6 +37,68 @@
|
|||||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"typescript-eslint": "^8.57.1"
|
"typescript-eslint": "^8.57.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.19.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@astrojs/check": {
|
||||||
|
"version": "0.9.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.9.8.tgz",
|
||||||
|
"integrity": "sha512-LDng8446QLS5ToKjRHd3bgUdirvemVVExV7nRyJfW2wV36xuv7vDxwy5NWN9zqeSEDgg0Tv84sP+T3yEq+Zlkw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/language-server": "^2.16.5",
|
||||||
|
"chokidar": "^4.0.3",
|
||||||
|
"kleur": "^4.1.5",
|
||||||
|
"yargs": "^17.7.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"astro-check": "bin/astro-check.js"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@astrojs/check/node_modules/chokidar": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"readdirp": "^4.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.16.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@astrojs/check/node_modules/kleur": {
|
||||||
|
"version": "4.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
|
||||||
|
"integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@astrojs/check/node_modules/readdirp": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.18.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@astrojs/compiler": {
|
"node_modules/@astrojs/compiler": {
|
||||||
@@ -53,6 +113,48 @@
|
|||||||
"integrity": "sha512-GOle7smBWKfMSP8osUIGOlB5kaHdQLV3foCsf+5Q9Wsuu+C6Fs3Ez/ttXmhjZ1HkSgsogcM1RXSjjOVieHq16Q==",
|
"integrity": "sha512-GOle7smBWKfMSP8osUIGOlB5kaHdQLV3foCsf+5Q9Wsuu+C6Fs3Ez/ttXmhjZ1HkSgsogcM1RXSjjOVieHq16Q==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@astrojs/language-server": {
|
||||||
|
"version": "2.16.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@astrojs/language-server/-/language-server-2.16.6.tgz",
|
||||||
|
"integrity": "sha512-N990lu+HSFiG57owR0XBkr02BYMgiLCshLf+4QG4v6jjSWkBeQGnzqi+E1L08xFPPJ7eEeXnxPXGLaVv5pa4Ug==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/compiler": "^2.13.1",
|
||||||
|
"@astrojs/yaml2ts": "^0.2.3",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.5.5",
|
||||||
|
"@volar/kit": "~2.4.28",
|
||||||
|
"@volar/language-core": "~2.4.28",
|
||||||
|
"@volar/language-server": "~2.4.28",
|
||||||
|
"@volar/language-service": "~2.4.28",
|
||||||
|
"muggle-string": "^0.4.1",
|
||||||
|
"tinyglobby": "^0.2.15",
|
||||||
|
"volar-service-css": "0.0.70",
|
||||||
|
"volar-service-emmet": "0.0.70",
|
||||||
|
"volar-service-html": "0.0.70",
|
||||||
|
"volar-service-prettier": "0.0.70",
|
||||||
|
"volar-service-typescript": "0.0.70",
|
||||||
|
"volar-service-typescript-twoslash-queries": "0.0.70",
|
||||||
|
"volar-service-yaml": "0.0.70",
|
||||||
|
"vscode-html-languageservice": "^5.6.2",
|
||||||
|
"vscode-uri": "^3.1.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"astro-ls": "bin/nodeServer.js"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"prettier": "^3.0.0",
|
||||||
|
"prettier-plugin-astro": ">=0.11.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"prettier": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-astro": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@astrojs/markdown-remark": {
|
"node_modules/@astrojs/markdown-remark": {
|
||||||
"version": "6.3.11",
|
"version": "6.3.11",
|
||||||
"resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.11.tgz",
|
"resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.11.tgz",
|
||||||
@@ -132,6 +234,16 @@
|
|||||||
"node": "18.20.8 || ^20.3.0 || >=22.0.0"
|
"node": "18.20.8 || ^20.3.0 || >=22.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@astrojs/yaml2ts": {
|
||||||
|
"version": "0.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@astrojs/yaml2ts/-/yaml2ts-0.2.3.tgz",
|
||||||
|
"integrity": "sha512-PJzRmgQzUxI2uwpdX2lXSHtP4G8ocp24/t+bZyf5Fy0SZLSF9f9KXZoMlFM/XCGue+B0nH/2IZ7FpBYQATBsCg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"yaml": "^2.8.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.29.0",
|
"version": "7.29.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||||
@@ -738,6 +850,68 @@
|
|||||||
"@noble/ciphers": "^1.0.0"
|
"@noble/ciphers": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@emmetio/abbreviation": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@emmetio/scanner": "^1.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emmetio/css-abbreviation": {
|
||||||
|
"version": "2.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emmetio/css-abbreviation/-/css-abbreviation-2.1.8.tgz",
|
||||||
|
"integrity": "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@emmetio/scanner": "^1.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emmetio/css-parser": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emmetio/css-parser/-/css-parser-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-2bC6m0MV/voF4CTZiAbG5MWKbq5EBmDPKu9Sb7s7nVcEzNQlrZP6mFFFlIaISM8X6514H9shWMme1fCm8cWAfQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@emmetio/stream-reader": "^2.2.0",
|
||||||
|
"@emmetio/stream-reader-utils": "^0.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emmetio/html-matcher": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emmetio/html-matcher/-/html-matcher-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@emmetio/scanner": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emmetio/scanner": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emmetio/scanner/-/scanner-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emmetio/stream-reader": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emmetio/stream-reader/-/stream-reader-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emmetio/stream-reader-utils": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emmetio/stream-reader-utils/-/stream-reader-utils-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@emnapi/runtime": {
|
"node_modules/@emnapi/runtime": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz",
|
||||||
@@ -1369,9 +1543,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@hono/node-server": {
|
"node_modules/@hono/node-server": {
|
||||||
"version": "1.19.11",
|
"version": "1.19.13",
|
||||||
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz",
|
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.13.tgz",
|
||||||
"integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==",
|
"integrity": "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.14.1"
|
"node": ">=18.14.1"
|
||||||
@@ -4487,27 +4661,6 @@
|
|||||||
"path-browserify": "^1.0.1"
|
"path-browserify": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ts-morph/common/node_modules/balanced-match": {
|
|
||||||
"version": "4.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
|
||||||
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": "18 || 20 || >=22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@ts-morph/common/node_modules/brace-expansion": {
|
|
||||||
"version": "5.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
|
||||||
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"balanced-match": "^4.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "18 || 20 || >=22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@ts-morph/common/node_modules/minimatch": {
|
"node_modules/@ts-morph/common/node_modules/minimatch": {
|
||||||
"version": "10.2.4",
|
"version": "10.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
|
||||||
@@ -4523,6 +4676,22 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ts-morph/common/node_modules/minimatch/node_modules/balanced-match": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@ts-morph/common/node_modules/minimatch/node_modules/brace-expansion": {
|
||||||
|
"version": "1.1.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
|
||||||
|
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||||
@@ -4843,29 +5012,6 @@
|
|||||||
"typescript": ">=4.8.4 <6.0.0"
|
"typescript": ">=4.8.4 <6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
|
|
||||||
"version": "4.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
|
||||||
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": "18 || 20 || >=22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
|
||||||
"version": "5.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
|
||||||
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"balanced-match": "^4.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "18 || 20 || >=22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
|
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
|
||||||
"version": "10.2.4",
|
"version": "10.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
|
||||||
@@ -4882,6 +5028,24 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch/node_modules/balanced-match": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch/node_modules/brace-expansion": {
|
||||||
|
"version": "1.1.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
|
||||||
|
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
|
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
|
||||||
"version": "7.7.4",
|
"version": "7.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||||
@@ -4976,6 +5140,104 @@
|
|||||||
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@volar/kit": {
|
||||||
|
"version": "2.4.28",
|
||||||
|
"resolved": "https://registry.npmjs.org/@volar/kit/-/kit-2.4.28.tgz",
|
||||||
|
"integrity": "sha512-cKX4vK9dtZvDRaAzeoUdaAJEew6IdxHNCRrdp5Kvcl6zZOqb6jTOfk3kXkIkG3T7oTFXguEMt5+9ptyqYR84Pg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@volar/language-service": "2.4.28",
|
||||||
|
"@volar/typescript": "2.4.28",
|
||||||
|
"typesafe-path": "^0.2.2",
|
||||||
|
"vscode-languageserver-textdocument": "^1.0.11",
|
||||||
|
"vscode-uri": "^3.0.8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@volar/language-core": {
|
||||||
|
"version": "2.4.28",
|
||||||
|
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz",
|
||||||
|
"integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@volar/source-map": "2.4.28"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@volar/language-server": {
|
||||||
|
"version": "2.4.28",
|
||||||
|
"resolved": "https://registry.npmjs.org/@volar/language-server/-/language-server-2.4.28.tgz",
|
||||||
|
"integrity": "sha512-NqcLnE5gERKuS4PUFwlhMxf6vqYo7hXtbMFbViXcbVkbZ905AIVWhnSo0ZNBC2V127H1/2zP7RvVOVnyITFfBw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@volar/language-core": "2.4.28",
|
||||||
|
"@volar/language-service": "2.4.28",
|
||||||
|
"@volar/typescript": "2.4.28",
|
||||||
|
"path-browserify": "^1.0.1",
|
||||||
|
"request-light": "^0.7.0",
|
||||||
|
"vscode-languageserver": "^9.0.1",
|
||||||
|
"vscode-languageserver-protocol": "^3.17.5",
|
||||||
|
"vscode-languageserver-textdocument": "^1.0.11",
|
||||||
|
"vscode-uri": "^3.0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@volar/language-service": {
|
||||||
|
"version": "2.4.28",
|
||||||
|
"resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.28.tgz",
|
||||||
|
"integrity": "sha512-Rh/wYCZJrI5vCwMk9xyw/Z+MsWxlJY1rmMZPsxUoJKfzIRjS/NF1NmnuEcrMbEVGja00aVpCsInJfixQTMdvLw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@volar/language-core": "2.4.28",
|
||||||
|
"vscode-languageserver-protocol": "^3.17.5",
|
||||||
|
"vscode-languageserver-textdocument": "^1.0.11",
|
||||||
|
"vscode-uri": "^3.0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@volar/source-map": {
|
||||||
|
"version": "2.4.28",
|
||||||
|
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz",
|
||||||
|
"integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@volar/typescript": {
|
||||||
|
"version": "2.4.28",
|
||||||
|
"resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz",
|
||||||
|
"integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@volar/language-core": "2.4.28",
|
||||||
|
"path-browserify": "^1.0.1",
|
||||||
|
"vscode-uri": "^3.0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vscode/emmet-helper": {
|
||||||
|
"version": "2.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.11.0.tgz",
|
||||||
|
"integrity": "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"emmet": "^2.4.3",
|
||||||
|
"jsonc-parser": "^2.3.0",
|
||||||
|
"vscode-languageserver-textdocument": "^1.0.1",
|
||||||
|
"vscode-languageserver-types": "^3.15.1",
|
||||||
|
"vscode-uri": "^3.0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vscode/l10n": {
|
||||||
|
"version": "0.0.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz",
|
||||||
|
"integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/accepts": {
|
"node_modules/accepts": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
||||||
@@ -5419,9 +5681,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.12",
|
"version": "1.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
|
||||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -5859,7 +6121,6 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/content-disposition": {
|
"node_modules/content-disposition": {
|
||||||
@@ -6186,9 +6447,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/defu": {
|
"node_modules/defu": {
|
||||||
"version": "6.1.4",
|
"version": "6.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz",
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
"integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
@@ -6407,6 +6668,23 @@
|
|||||||
"integrity": "sha512-vFU34OcrvMcH66T+dYC3G4nURmgfDVewMIu6Q2urXpumAPSMmzvcn04KVVV8Opikq8Vs5nUbO/8laNhNRqSzYw==",
|
"integrity": "sha512-vFU34OcrvMcH66T+dYC3G4nURmgfDVewMIu6Q2urXpumAPSMmzvcn04KVVV8Opikq8Vs5nUbO/8laNhNRqSzYw==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/emmet": {
|
||||||
|
"version": "2.4.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/emmet/-/emmet-2.4.11.tgz",
|
||||||
|
"integrity": "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"./packages/scanner",
|
||||||
|
"./packages/abbreviation",
|
||||||
|
"./packages/css-abbreviation",
|
||||||
|
"./"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@emmetio/abbreviation": "^2.3.3",
|
||||||
|
"@emmetio/css-abbreviation": "^2.1.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
"version": "10.6.0",
|
"version": "10.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
|
||||||
@@ -7677,9 +7955,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hono": {
|
"node_modules/hono": {
|
||||||
"version": "4.12.9",
|
"version": "4.12.12",
|
||||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.9.tgz",
|
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz",
|
||||||
"integrity": "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA==",
|
"integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.9.0"
|
"node": ">=16.9.0"
|
||||||
@@ -8131,6 +8409,13 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsonc-parser": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/jsonfile": {
|
"node_modules/jsonfile": {
|
||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
|
||||||
@@ -9558,6 +9843,13 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/muggle-string": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/mute-stream": {
|
"node_modules/mute-stream": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz",
|
||||||
@@ -10813,6 +11105,13 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/request-light": {
|
||||||
|
"version": "0.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/request-light/-/request-light-0.7.0.tgz",
|
||||||
|
"integrity": "sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/require-directory": {
|
"node_modules/require-directory": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
@@ -10994,9 +11293,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/router/node_modules/path-to-regexp": {
|
"node_modules/router/node_modules/path-to-regexp": {
|
||||||
"version": "8.3.0",
|
"version": "8.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
|
||||||
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
|
"integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -11856,6 +12155,13 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typesafe-path": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/typesafe-path/-/typesafe-path-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
@@ -11869,6 +12175,29 @@
|
|||||||
"node": ">=14.17"
|
"node": ">=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typescript-auto-import-cache": {
|
||||||
|
"version": "0.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript-auto-import-cache/-/typescript-auto-import-cache-0.3.6.tgz",
|
||||||
|
"integrity": "sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"semver": "^7.3.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/typescript-auto-import-cache/node_modules/semver": {
|
||||||
|
"version": "7.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||||
|
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/typescript-eslint": {
|
"node_modules/typescript-eslint": {
|
||||||
"version": "8.57.2",
|
"version": "8.57.2",
|
||||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.2.tgz",
|
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.2.tgz",
|
||||||
@@ -12367,9 +12696,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.4.1",
|
"version": "6.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
|
||||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
"integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
@@ -12916,6 +13245,274 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/volar-service-css": {
|
||||||
|
"version": "0.0.70",
|
||||||
|
"resolved": "https://registry.npmjs.org/volar-service-css/-/volar-service-css-0.0.70.tgz",
|
||||||
|
"integrity": "sha512-K1qyOvBpE3rzdAv3e4/6Rv5yizrYPy5R/ne3IWCAzLBuMO4qBMV3kSqWzj6KUVe6S0AnN6wxF7cRkiaKfYMYJw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-css-languageservice": "^6.3.0",
|
||||||
|
"vscode-languageserver-textdocument": "^1.0.11",
|
||||||
|
"vscode-uri": "^3.0.8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@volar/language-service": "~2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@volar/language-service": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/volar-service-emmet": {
|
||||||
|
"version": "0.0.70",
|
||||||
|
"resolved": "https://registry.npmjs.org/volar-service-emmet/-/volar-service-emmet-0.0.70.tgz",
|
||||||
|
"integrity": "sha512-xi5bC4m/VyE3zy/n2CXspKeDZs3qA41tHLTw275/7dNWM/RqE2z3BnDICQybHIVp/6G1iOQj5c1qXMgQC08TNg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@emmetio/css-parser": "^0.4.1",
|
||||||
|
"@emmetio/html-matcher": "^1.3.0",
|
||||||
|
"@vscode/emmet-helper": "^2.9.3",
|
||||||
|
"vscode-uri": "^3.0.8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@volar/language-service": "~2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@volar/language-service": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/volar-service-html": {
|
||||||
|
"version": "0.0.70",
|
||||||
|
"resolved": "https://registry.npmjs.org/volar-service-html/-/volar-service-html-0.0.70.tgz",
|
||||||
|
"integrity": "sha512-eR6vCgMdmYAo4n+gcT7DSyBQbwB8S3HZZvSagTf0sxNaD4WppMCFfpqWnkrlGStPKMZvMiejRRVmqsX9dYcTvQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-html-languageservice": "^5.3.0",
|
||||||
|
"vscode-languageserver-textdocument": "^1.0.11",
|
||||||
|
"vscode-uri": "^3.0.8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@volar/language-service": "~2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@volar/language-service": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/volar-service-prettier": {
|
||||||
|
"version": "0.0.70",
|
||||||
|
"resolved": "https://registry.npmjs.org/volar-service-prettier/-/volar-service-prettier-0.0.70.tgz",
|
||||||
|
"integrity": "sha512-Z6BCFSpGVCd8BPAsZ785Kce1BGlWd5ODqmqZGVuB14MJvrR4+CYz6cDy4F+igmE1gMifqfvMhdgT8Aud4M5ngg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-uri": "^3.0.8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@volar/language-service": "~2.4.0",
|
||||||
|
"prettier": "^2.2 || ^3.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@volar/language-service": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/volar-service-typescript": {
|
||||||
|
"version": "0.0.70",
|
||||||
|
"resolved": "https://registry.npmjs.org/volar-service-typescript/-/volar-service-typescript-0.0.70.tgz",
|
||||||
|
"integrity": "sha512-l46Bx4cokkUedTd74ojO5H/zqHZJ8SUuyZ0IB8JN4jfRqUM3bQFBHoOwlZCyZmOeO0A3RQNkMnFclxO4c++gsg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"path-browserify": "^1.0.1",
|
||||||
|
"semver": "^7.6.2",
|
||||||
|
"typescript-auto-import-cache": "^0.3.5",
|
||||||
|
"vscode-languageserver-textdocument": "^1.0.11",
|
||||||
|
"vscode-nls": "^5.2.0",
|
||||||
|
"vscode-uri": "^3.0.8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@volar/language-service": "~2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@volar/language-service": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/volar-service-typescript-twoslash-queries": {
|
||||||
|
"version": "0.0.70",
|
||||||
|
"resolved": "https://registry.npmjs.org/volar-service-typescript-twoslash-queries/-/volar-service-typescript-twoslash-queries-0.0.70.tgz",
|
||||||
|
"integrity": "sha512-IdD13Z9N2Bu8EM6CM0fDV1E69olEYGHDU25X51YXmq8Y0CmJ2LNj6gOiBJgpS5JGUqFzECVhMNBW7R0sPdRTMQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-uri": "^3.0.8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@volar/language-service": "~2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@volar/language-service": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/volar-service-typescript/node_modules/semver": {
|
||||||
|
"version": "7.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||||
|
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/volar-service-yaml": {
|
||||||
|
"version": "0.0.70",
|
||||||
|
"resolved": "https://registry.npmjs.org/volar-service-yaml/-/volar-service-yaml-0.0.70.tgz",
|
||||||
|
"integrity": "sha512-0c8bXDBeoATF9F6iPIlOuYTuZAC4c+yi0siQo920u7eiBJk8oQmUmg9cDUbR4+Gl++bvGP4plj3fErbJuPqdcQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-uri": "^3.0.8",
|
||||||
|
"yaml-language-server": "~1.20.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@volar/language-service": "~2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@volar/language-service": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-css-languageservice": {
|
||||||
|
"version": "6.3.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.3.10.tgz",
|
||||||
|
"integrity": "sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vscode/l10n": "^0.0.18",
|
||||||
|
"vscode-languageserver-textdocument": "^1.0.12",
|
||||||
|
"vscode-languageserver-types": "3.17.5",
|
||||||
|
"vscode-uri": "^3.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-html-languageservice": {
|
||||||
|
"version": "5.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-5.6.2.tgz",
|
||||||
|
"integrity": "sha512-ulCrSnFnfQ16YzvwnYUgEbUEl/ZG7u2eV27YhvLObSHKkb8fw1Z9cgsnUwjTEeDIdJDoTDTDpxuhQwoenoLNMg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vscode/l10n": "^0.0.18",
|
||||||
|
"vscode-languageserver-textdocument": "^1.0.12",
|
||||||
|
"vscode-languageserver-types": "^3.17.5",
|
||||||
|
"vscode-uri": "^3.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-json-languageservice": {
|
||||||
|
"version": "4.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-4.1.8.tgz",
|
||||||
|
"integrity": "sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"jsonc-parser": "^3.0.0",
|
||||||
|
"vscode-languageserver-textdocument": "^1.0.1",
|
||||||
|
"vscode-languageserver-types": "^3.16.0",
|
||||||
|
"vscode-nls": "^5.0.0",
|
||||||
|
"vscode-uri": "^3.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"npm": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-json-languageservice/node_modules/jsonc-parser": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/vscode-jsonrpc": {
|
||||||
|
"version": "8.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
|
||||||
|
"integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageserver": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-languageserver-protocol": "3.17.5"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"installServerIntoExtension": "bin/installServerIntoExtension"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageserver-protocol": {
|
||||||
|
"version": "3.17.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz",
|
||||||
|
"integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-jsonrpc": "8.2.0",
|
||||||
|
"vscode-languageserver-types": "3.17.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageserver-textdocument": {
|
||||||
|
"version": "1.0.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz",
|
||||||
|
"integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageserver-types": {
|
||||||
|
"version": "3.17.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz",
|
||||||
|
"integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/vscode-nls": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/vscode-uri": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/web-namespaces": {
|
"node_modules/web-namespaces": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
|
||||||
@@ -13044,6 +13641,91 @@
|
|||||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/yaml": {
|
||||||
|
"version": "2.8.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
|
||||||
|
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"yaml": "bin.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/eemeli"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yaml-language-server": {
|
||||||
|
"version": "1.20.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml-language-server/-/yaml-language-server-1.20.0.tgz",
|
||||||
|
"integrity": "sha512-qhjK/bzSRZ6HtTvgeFvjNPJGWdZ0+x5NREV/9XZWFjIGezew2b4r5JPy66IfOhd5OA7KeFwk1JfmEbnTvev0cA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vscode/l10n": "^0.0.18",
|
||||||
|
"ajv": "^8.17.1",
|
||||||
|
"ajv-draft-04": "^1.0.0",
|
||||||
|
"prettier": "^3.5.0",
|
||||||
|
"request-light": "^0.5.7",
|
||||||
|
"vscode-json-languageservice": "4.1.8",
|
||||||
|
"vscode-languageserver": "^9.0.0",
|
||||||
|
"vscode-languageserver-textdocument": "^1.0.1",
|
||||||
|
"vscode-languageserver-types": "^3.16.0",
|
||||||
|
"vscode-uri": "^3.0.2",
|
||||||
|
"yaml": "2.7.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"yaml-language-server": "bin/yaml-language-server"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yaml-language-server/node_modules/ajv": {
|
||||||
|
"version": "8.18.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
|
||||||
|
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"fast-uri": "^3.0.1",
|
||||||
|
"json-schema-traverse": "^1.0.0",
|
||||||
|
"require-from-string": "^2.0.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yaml-language-server/node_modules/ajv-draft-04": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"ajv": "^8.5.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"ajv": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yaml-language-server/node_modules/json-schema-traverse": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/yaml-language-server/node_modules/request-light": {
|
||||||
|
"version": "0.5.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/request-light/-/request-light-0.5.8.tgz",
|
||||||
|
"integrity": "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/yargs": {
|
"node_modules/yargs": {
|
||||||
"version": "17.7.2",
|
"version": "17.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||||
|
|||||||
@@ -33,7 +33,21 @@
|
|||||||
"tailwindcss": "^4.2.1",
|
"tailwindcss": "^4.2.1",
|
||||||
"tw-animate-css": "^1.4.0"
|
"tw-animate-css": "^1.4.0"
|
||||||
},
|
},
|
||||||
|
"overrides": {
|
||||||
|
"@modelcontextprotocol/sdk": {
|
||||||
|
"@hono/node-server": "1.19.13",
|
||||||
|
"hono": "4.12.12"
|
||||||
|
},
|
||||||
|
"router": {
|
||||||
|
"path-to-regexp": "8.4.2"
|
||||||
|
},
|
||||||
|
"defu": "6.1.7",
|
||||||
|
"vite": "6.4.2",
|
||||||
|
"brace-expansion": "1.1.13",
|
||||||
|
"yaml": "2.8.3"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@astrojs/check": "^0.9.8",
|
||||||
"@eslint/js": "^9.39.4",
|
"@eslint/js": "^9.39.4",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "^9.39.4",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
|
|||||||
@@ -177,11 +177,7 @@ warn_command_conflict() {
|
|||||||
step "Run now: export PATH=\"$INSTALL_BIN_DIR:\$PATH\" && hash -r && feynman"
|
step "Run now: export PATH=\"$INSTALL_BIN_DIR:\$PATH\" && hash -r && feynman"
|
||||||
step "Or launch directly: $expected_path"
|
step "Or launch directly: $expected_path"
|
||||||
|
|
||||||
case "$resolved_path" in
|
step "If that path is an old package-manager install, remove it or put $INSTALL_BIN_DIR first on PATH."
|
||||||
*"/node_modules/@companion-ai/feynman/"* | *"/node_modules/.bin/feynman")
|
|
||||||
step "If that path is an old global npm install, remove it with: npm uninstall -g @companion-ai/feynman"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,8 +260,8 @@ This usually means the release exists, but not all platform bundles were uploade
|
|||||||
|
|
||||||
Workarounds:
|
Workarounds:
|
||||||
- try again after the release finishes publishing
|
- try again after the release finishes publishing
|
||||||
- install via pnpm instead: pnpm add -g @companion-ai/feynman
|
- pass the latest published version explicitly, e.g.:
|
||||||
- install via bun instead: bun add -g @companion-ai/feynman
|
curl -fsSL https://feynman.is/install | bash -s -- 0.2.16
|
||||||
EOF
|
EOF
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -146,15 +146,17 @@ archive_metadata="$(resolve_version)"
|
|||||||
resolved_version="$(printf '%s\n' "$archive_metadata" | sed -n '1p')"
|
resolved_version="$(printf '%s\n' "$archive_metadata" | sed -n '1p')"
|
||||||
git_ref="$(printf '%s\n' "$archive_metadata" | sed -n '2p')"
|
git_ref="$(printf '%s\n' "$archive_metadata" | sed -n '2p')"
|
||||||
|
|
||||||
archive_url=""
|
archive_url="${FEYNMAN_INSTALL_SKILLS_ARCHIVE_URL:-}"
|
||||||
case "$git_ref" in
|
if [ -z "$archive_url" ]; then
|
||||||
|
case "$git_ref" in
|
||||||
main)
|
main)
|
||||||
archive_url="https://github.com/getcompanion-ai/feynman/archive/refs/heads/main.tar.gz"
|
archive_url="https://github.com/getcompanion-ai/feynman/archive/refs/heads/main.tar.gz"
|
||||||
;;
|
;;
|
||||||
v*)
|
v*)
|
||||||
archive_url="https://github.com/getcompanion-ai/feynman/archive/refs/tags/${git_ref}.tar.gz"
|
archive_url="https://github.com/getcompanion-ai/feynman/archive/refs/tags/${git_ref}.tar.gz"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$archive_url" ]; then
|
if [ -z "$archive_url" ]; then
|
||||||
echo "Could not resolve a download URL for ref: $git_ref" >&2
|
echo "Could not resolve a download URL for ref: $git_ref" >&2
|
||||||
@@ -181,8 +183,8 @@ step "Extracting skills"
|
|||||||
tar -xzf "$archive_path" -C "$extract_dir"
|
tar -xzf "$archive_path" -C "$extract_dir"
|
||||||
|
|
||||||
source_root="$(find "$extract_dir" -mindepth 1 -maxdepth 1 -type d | head -n 1)"
|
source_root="$(find "$extract_dir" -mindepth 1 -maxdepth 1 -type d | head -n 1)"
|
||||||
if [ -z "$source_root" ] || [ ! -d "$source_root/skills" ]; then
|
if [ -z "$source_root" ] || [ ! -d "$source_root/skills" ] || [ ! -d "$source_root/prompts" ]; then
|
||||||
echo "Could not find skills/ in downloaded archive." >&2
|
echo "Could not find the bundled skills resources in the downloaded archive." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -190,6 +192,10 @@ mkdir -p "$(dirname "$install_dir")"
|
|||||||
rm -rf "$install_dir"
|
rm -rf "$install_dir"
|
||||||
mkdir -p "$install_dir"
|
mkdir -p "$install_dir"
|
||||||
cp -R "$source_root/skills/." "$install_dir/"
|
cp -R "$source_root/skills/." "$install_dir/"
|
||||||
|
mkdir -p "$install_dir/prompts"
|
||||||
|
cp -R "$source_root/prompts/." "$install_dir/prompts/"
|
||||||
|
cp "$source_root/AGENTS.md" "$install_dir/AGENTS.md"
|
||||||
|
cp "$source_root/CONTRIBUTING.md" "$install_dir/CONTRIBUTING.md"
|
||||||
|
|
||||||
step "Installed skills to $install_dir"
|
step "Installed skills to $install_dir"
|
||||||
case "$SCOPE" in
|
case "$SCOPE" in
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ function Resolve-VersionMetadata {
|
|||||||
return [PSCustomObject]@{
|
return [PSCustomObject]@{
|
||||||
ResolvedVersion = $resolvedVersion
|
ResolvedVersion = $resolvedVersion
|
||||||
GitRef = "v$resolvedVersion"
|
GitRef = "v$resolvedVersion"
|
||||||
DownloadUrl = "https://github.com/getcompanion-ai/feynman/archive/refs/tags/v$resolvedVersion.zip"
|
DownloadUrl = if ($env:FEYNMAN_INSTALL_SKILLS_ARCHIVE_URL) { $env:FEYNMAN_INSTALL_SKILLS_ARCHIVE_URL } else { "https://github.com/getcompanion-ai/feynman/archive/refs/tags/v$resolvedVersion.zip" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,8 +92,9 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$skillsSource = Join-Path $sourceRoot.FullName "skills"
|
$skillsSource = Join-Path $sourceRoot.FullName "skills"
|
||||||
if (-not (Test-Path $skillsSource)) {
|
$promptsSource = Join-Path $sourceRoot.FullName "prompts"
|
||||||
throw "Could not find skills/ in downloaded archive."
|
if (-not (Test-Path $skillsSource) -or -not (Test-Path $promptsSource)) {
|
||||||
|
throw "Could not find the bundled skills resources in the downloaded archive."
|
||||||
}
|
}
|
||||||
|
|
||||||
$installParent = Split-Path $installDir -Parent
|
$installParent = Split-Path $installDir -Parent
|
||||||
@@ -107,6 +108,10 @@ try {
|
|||||||
|
|
||||||
New-Item -ItemType Directory -Path $installDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $installDir -Force | Out-Null
|
||||||
Copy-Item -Path (Join-Path $skillsSource "*") -Destination $installDir -Recurse -Force
|
Copy-Item -Path (Join-Path $skillsSource "*") -Destination $installDir -Recurse -Force
|
||||||
|
New-Item -ItemType Directory -Path (Join-Path $installDir "prompts") -Force | Out-Null
|
||||||
|
Copy-Item -Path (Join-Path $promptsSource "*") -Destination (Join-Path $installDir "prompts") -Recurse -Force
|
||||||
|
Copy-Item -Path (Join-Path $sourceRoot.FullName "AGENTS.md") -Destination (Join-Path $installDir "AGENTS.md") -Force
|
||||||
|
Copy-Item -Path (Join-Path $sourceRoot.FullName "CONTRIBUTING.md") -Destination (Join-Path $installDir "CONTRIBUTING.md") -Force
|
||||||
|
|
||||||
Write-Host "==> Installed skills to $installDir"
|
Write-Host "==> Installed skills to $installDir"
|
||||||
if ($Scope -eq "Repo") {
|
if ($Scope -eq "Repo") {
|
||||||
|
|||||||
@@ -109,8 +109,8 @@ This usually means the release exists, but not all platform bundles were uploade
|
|||||||
|
|
||||||
Workarounds:
|
Workarounds:
|
||||||
- try again after the release finishes publishing
|
- try again after the release finishes publishing
|
||||||
- install via pnpm instead: pnpm add -g @companion-ai/feynman
|
- pass the latest published version explicitly, e.g.:
|
||||||
- install via bun instead: bun add -g @companion-ai/feynman
|
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.16
|
||||||
"@
|
"@
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,12 +125,18 @@ Workarounds:
|
|||||||
New-Item -ItemType Directory -Path $installBinDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $installBinDir -Force | Out-Null
|
||||||
|
|
||||||
$shimPath = Join-Path $installBinDir "feynman.cmd"
|
$shimPath = Join-Path $installBinDir "feynman.cmd"
|
||||||
|
$shimPs1Path = Join-Path $installBinDir "feynman.ps1"
|
||||||
Write-Host "==> Linking feynman into $installBinDir"
|
Write-Host "==> Linking feynman into $installBinDir"
|
||||||
@"
|
@"
|
||||||
@echo off
|
@echo off
|
||||||
"$bundleDir\feynman.cmd" %*
|
CALL "$bundleDir\feynman.cmd" %*
|
||||||
"@ | Set-Content -Path $shimPath -Encoding ASCII
|
"@ | Set-Content -Path $shimPath -Encoding ASCII
|
||||||
|
|
||||||
|
@"
|
||||||
|
`$BundleDir = "$bundleDir"
|
||||||
|
& "`$BundleDir\node\node.exe" "`$BundleDir\app\bin\feynman.js" @args
|
||||||
|
"@ | Set-Content -Path $shimPs1Path -Encoding UTF8
|
||||||
|
|
||||||
$currentUserPath = [Environment]::GetEnvironmentVariable("Path", "User")
|
$currentUserPath = [Environment]::GetEnvironmentVariable("Path", "User")
|
||||||
$alreadyOnPath = $false
|
$alreadyOnPath = $false
|
||||||
if ($currentUserPath) {
|
if ($currentUserPath) {
|
||||||
@@ -153,9 +159,7 @@ Workarounds:
|
|||||||
Write-Warning "Current shell resolves feynman to $($resolvedCommand.Source)"
|
Write-Warning "Current shell resolves feynman to $($resolvedCommand.Source)"
|
||||||
Write-Host "Run in a new shell, or run: `$env:Path = '$installBinDir;' + `$env:Path"
|
Write-Host "Run in a new shell, or run: `$env:Path = '$installBinDir;' + `$env:Path"
|
||||||
Write-Host "Then run: feynman"
|
Write-Host "Then run: feynman"
|
||||||
if ($resolvedCommand.Source -like "*node_modules*@companion-ai*feynman*") {
|
Write-Host "If that path is an old package-manager install, remove it or put $installBinDir first on PATH."
|
||||||
Write-Host "If that path is an old global npm install, remove it with: npm uninstall -g @companion-ai/feynman"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "Feynman $resolvedVersion installed successfully."
|
Write-Host "Feynman $resolvedVersion installed successfully."
|
||||||
|
|||||||
@@ -46,4 +46,4 @@ function Badge({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Badge, badgeVariants }
|
export { Badge }
|
||||||
|
|||||||
@@ -64,4 +64,4 @@ function Button({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Button, buttonVariants }
|
export { Button }
|
||||||
|
|||||||
@@ -41,6 +41,36 @@ To see all models you have configured:
|
|||||||
feynman model list
|
feynman model list
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Only authenticated/configured providers appear in `feynman model list`. If you only see OpenAI models, it usually means only OpenAI auth is configured so far.
|
||||||
|
|
||||||
|
To add another provider, authenticate it first:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
feynman model login anthropic
|
||||||
|
feynman model login google
|
||||||
|
```
|
||||||
|
|
||||||
|
Then switch the default model:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
feynman model set anthropic/claude-opus-4-6
|
||||||
|
```
|
||||||
|
|
||||||
|
## Subagent model overrides
|
||||||
|
|
||||||
|
Feynman's bundled subagents inherit the main default model unless you override them explicitly. Inside the REPL, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/feynman-model
|
||||||
|
```
|
||||||
|
|
||||||
|
This opens an interactive picker where you can either:
|
||||||
|
|
||||||
|
- change the main default model for the session environment
|
||||||
|
- assign a different model to a specific bundled subagent such as `researcher`, `reviewer`, `writer`, or `verifier`
|
||||||
|
|
||||||
|
Per-subagent overrides are persisted in the synced agent files under `~/.feynman/agent/agents/` with a `model:` frontmatter field. Removing that field makes the subagent inherit the main default model again.
|
||||||
|
|
||||||
## Thinking levels
|
## Thinking levels
|
||||||
|
|
||||||
The `thinkingLevel` field controls how much reasoning the model does before responding. Available levels are `off`, `minimal`, `low`, `medium`, `high`, and `xhigh`. Higher levels produce more thorough analysis at the cost of latency and token usage. You can override per-session:
|
The `thinkingLevel` field controls how much reasoning the model does before responding. Available levels are `off`, `minimal`, `low`, `medium`, `high`, and `xhigh`. Higher levels produce more thorough analysis at the cost of latency and token usage. You can override per-session:
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Installation
|
title: Installation
|
||||||
description: Install Feynman on macOS, Linux, or Windows using curl, pnpm, or bun.
|
description: Install Feynman on macOS, Linux, or Windows using the standalone installer.
|
||||||
section: Getting Started
|
section: Getting Started
|
||||||
order: 1
|
order: 1
|
||||||
---
|
---
|
||||||
|
|
||||||
Feynman ships as a standalone runtime bundle for macOS, Linux, and Windows, and as a package-manager install for environments where Node.js is already installed. The recommended approach is the one-line installer, which downloads a prebuilt native bundle with zero external runtime dependencies.
|
Feynman ships as a standalone runtime bundle for macOS, Linux, and Windows. The one-line installer downloads a prebuilt native bundle with zero external runtime dependencies.
|
||||||
|
|
||||||
## One-line installer (recommended)
|
## One-line installer (recommended)
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ curl -fsSL https://feynman.is/install | bash
|
|||||||
|
|
||||||
The installer detects your OS and architecture automatically. On macOS it supports both Intel and Apple Silicon. On Linux it supports x64 and arm64. The launcher is installed to `~/.local/bin`, the bundled runtime is unpacked into `~/.local/share/feynman`, and your `PATH` is updated when needed.
|
The installer detects your OS and architecture automatically. On macOS it supports both Intel and Apple Silicon. On Linux it supports x64 and arm64. The launcher is installed to `~/.local/bin`, the bundled runtime is unpacked into `~/.local/share/feynman`, and your `PATH` is updated when needed.
|
||||||
|
|
||||||
If you previously installed Feynman via `npm`, `pnpm`, or `bun` and still see local Node.js errors after a curl install, your shell is probably still resolving the older global binary first. Run `which -a feynman`, then `hash -r`, or launch the standalone shim directly with `~/.local/bin/feynman`.
|
If you previously installed Feynman through a package manager and still see local Node.js errors after a curl install, your shell is probably still resolving the older global binary first. Run `which -a feynman`, then `hash -r`, or launch the standalone shim directly with `~/.local/bin/feynman`.
|
||||||
|
|
||||||
On **Windows**, open PowerShell as Administrator and run:
|
On **Windows**, open PowerShell as Administrator and run:
|
||||||
|
|
||||||
@@ -55,52 +55,22 @@ Or install them repo-locally:
|
|||||||
& ([scriptblock]::Create((irm https://feynman.is/install-skills.ps1))) -Scope Repo
|
& ([scriptblock]::Create((irm https://feynman.is/install-skills.ps1))) -Scope Repo
|
||||||
```
|
```
|
||||||
|
|
||||||
These installers download only the `skills/` tree from the Feynman repository. They do not install the Feynman terminal, bundled Node runtime, auth storage, or Pi packages.
|
These installers download the bundled `skills/` and `prompts/` trees plus the repo guidance files referenced by those skills. They do not install the Feynman terminal, bundled Node runtime, auth storage, or Pi packages.
|
||||||
|
|
||||||
## Pinned releases
|
## Pinned releases
|
||||||
|
|
||||||
The one-line installer already targets the latest tagged release. To pin an exact version, pass it explicitly:
|
The one-line installer already targets the latest tagged release. To pin an exact version, pass it explicitly:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://feynman.is/install | bash -s -- 0.2.15
|
curl -fsSL https://feynman.is/install | bash -s -- 0.2.17
|
||||||
```
|
```
|
||||||
|
|
||||||
On Windows:
|
On Windows:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.15
|
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.17
|
||||||
```
|
```
|
||||||
|
|
||||||
## pnpm
|
|
||||||
|
|
||||||
If you already have Node.js `20.19.0` or newer installed, you can install Feynman globally via `pnpm`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm add -g @companion-ai/feynman
|
|
||||||
```
|
|
||||||
|
|
||||||
Or run it directly without installing:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm dlx @companion-ai/feynman
|
|
||||||
```
|
|
||||||
|
|
||||||
## bun
|
|
||||||
|
|
||||||
`bun add -g` and `bunx` still use your local Node runtime for Feynman itself, so the same Node.js `20.19.0+` requirement applies.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bun add -g @companion-ai/feynman
|
|
||||||
```
|
|
||||||
|
|
||||||
Or run it directly without installing:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bunx @companion-ai/feynman
|
|
||||||
```
|
|
||||||
|
|
||||||
Both package-manager distributions ship the same core application but depend on Node.js being present on your system. The standalone installer is preferred because it bundles its own Node runtime and works without a separate Node installation.
|
|
||||||
|
|
||||||
## Post-install setup
|
## Post-install setup
|
||||||
|
|
||||||
After installation, run the guided setup wizard to configure your model provider and API keys:
|
After installation, run the guided setup wizard to configure your model provider and API keys:
|
||||||
|
|||||||
@@ -42,6 +42,33 @@ For API key providers, you are prompted to paste your key directly:
|
|||||||
|
|
||||||
Keys are encrypted at rest and never sent anywhere except the provider's API endpoint.
|
Keys are encrypted at rest and never sent anywhere except the provider's API endpoint.
|
||||||
|
|
||||||
|
### Local models: Ollama, LM Studio, vLLM
|
||||||
|
|
||||||
|
If you want to use a model running locally, choose the API-key flow and then select:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Custom provider (baseUrl + API key)
|
||||||
|
```
|
||||||
|
|
||||||
|
For Ollama, the typical settings are:
|
||||||
|
|
||||||
|
```text
|
||||||
|
API mode: openai-completions
|
||||||
|
Base URL: http://localhost:11434/v1
|
||||||
|
Authorization header: No
|
||||||
|
Model ids: llama3.1:8b
|
||||||
|
API key: local
|
||||||
|
```
|
||||||
|
|
||||||
|
That same custom-provider flow also works for other OpenAI-compatible local servers such as LM Studio or vLLM. After saving the provider, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
feynman model list
|
||||||
|
feynman model set <provider>/<model-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
to confirm the local model is available and make it the default.
|
||||||
|
|
||||||
## Stage 3: Optional packages
|
## Stage 3: Optional packages
|
||||||
|
|
||||||
Feynman's core ships with the essentials, but some features require additional packages. The wizard asks if you want to install optional presets:
|
Feynman's core ships with the essentials, but some features require additional packages. The wizard asks if you want to install optional presets:
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ These are the primary commands you will use day-to-day. Each workflow dispatches
|
|||||||
| `/log` | Write a durable session log with completed work, findings, open questions, and next steps |
|
| `/log` | Write a durable session log with completed work, findings, open questions, and next steps |
|
||||||
| `/jobs` | Inspect active background work: running processes, scheduled follow-ups, and active watches |
|
| `/jobs` | Inspect active background work: running processes, scheduled follow-ups, and active watches |
|
||||||
| `/help` | Show grouped Feynman commands and prefill the editor with a selected command |
|
| `/help` | Show grouped Feynman commands and prefill the editor with a selected command |
|
||||||
|
| `/feynman-model` | Open the model picker for the main default model and per-subagent overrides |
|
||||||
| `/init` | Bootstrap `AGENTS.md` and session-log folders for a new research project |
|
| `/init` | Bootstrap `AGENTS.md` and session-log folders for a new research project |
|
||||||
| `/outputs` | Browse all research artifacts (papers, outputs, experiments, notes) |
|
| `/outputs` | Browse all research artifacts (papers, outputs, experiments, notes) |
|
||||||
| `/search` | Search prior session transcripts for past research and findings |
|
| `/search` | Search prior session transcripts for past research and findings |
|
||||||
@@ -37,6 +38,8 @@ These are the primary commands you will use day-to-day. Each workflow dispatches
|
|||||||
|
|
||||||
Session management commands help you organize ongoing work. The `/log` command is particularly useful at the end of a research session to capture what was accomplished and what remains.
|
Session management commands help you organize ongoing work. The `/log` command is particularly useful at the end of a research session to capture what was accomplished and what remains.
|
||||||
|
|
||||||
|
The `/feynman-model` command opens an interactive picker that lets you either change the main default model or assign a different model to a bundled subagent like `researcher`, `reviewer`, `writer`, or `verifier`.
|
||||||
|
|
||||||
## Running workflows from the CLI
|
## Running workflows from the CLI
|
||||||
|
|
||||||
All research workflow slash commands can also be run directly from the command line:
|
All research workflow slash commands can also be run directly from the command line:
|
||||||
|
|||||||
@@ -45,8 +45,6 @@ const terminalCommands = [
|
|||||||
|
|
||||||
const installCommands = [
|
const installCommands = [
|
||||||
{ label: "curl", command: "curl -fsSL https://feynman.is/install | bash" },
|
{ label: "curl", command: "curl -fsSL https://feynman.is/install | bash" },
|
||||||
{ label: "pnpm", command: "pnpm add -g @companion-ai/feynman" },
|
|
||||||
{ label: "bun", command: "bun add -g @companion-ai/feynman" },
|
|
||||||
]
|
]
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user