@rexvane/piweb 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/assets/showcase.png +0 -0
- package/bin/pi.js +50 -0
- package/bin/piweb.js +12 -0
- package/next.config.ts +21 -0
- package/package.json +88 -0
- package/postcss.config.mjs +7 -0
- package/public/file-icons/3d.svg +1 -0
- package/public/file-icons/LICENSE +8 -0
- package/public/file-icons/adobe-illustrator.svg +1 -0
- package/public/file-icons/adobe-photoshop.svg +1 -0
- package/public/file-icons/agent.svg +1 -0
- package/public/file-icons/asciidoc.svg +1 -0
- package/public/file-icons/assembly.svg +1 -0
- package/public/file-icons/astro.svg +1 -0
- package/public/file-icons/audio.svg +1 -0
- package/public/file-icons/authors.svg +1 -0
- package/public/file-icons/babel.svg +1 -0
- package/public/file-icons/bibliography.svg +1 -0
- package/public/file-icons/bicep.svg +1 -0
- package/public/file-icons/blender.svg +1 -0
- package/public/file-icons/bun.svg +1 -0
- package/public/file-icons/c.svg +1 -0
- package/public/file-icons/certificate.svg +1 -0
- package/public/file-icons/changelog.svg +1 -0
- package/public/file-icons/clojure.svg +1 -0
- package/public/file-icons/cmake.svg +1 -0
- package/public/file-icons/codeowners.svg +1 -0
- package/public/file-icons/commitlint.svg +1 -0
- package/public/file-icons/conduct.svg +1 -0
- package/public/file-icons/console.svg +1 -0
- package/public/file-icons/contributing.svg +1 -0
- package/public/file-icons/cpp.svg +1 -0
- package/public/file-icons/csharp.svg +1 -0
- package/public/file-icons/css.svg +1 -0
- package/public/file-icons/cursor.svg +1 -0
- package/public/file-icons/dart.svg +1 -0
- package/public/file-icons/database.svg +1 -0
- package/public/file-icons/deno.svg +1 -0
- package/public/file-icons/diff.svg +1 -0
- package/public/file-icons/docker.svg +1 -0
- package/public/file-icons/document.svg +1 -0
- package/public/file-icons/editorconfig.svg +1 -0
- package/public/file-icons/ejs.svg +1 -0
- package/public/file-icons/elixir.svg +1 -0
- package/public/file-icons/elm.svg +1 -0
- package/public/file-icons/email.svg +1 -0
- package/public/file-icons/erlang.svg +1 -0
- package/public/file-icons/eslint.svg +1 -0
- package/public/file-icons/favicon.svg +1 -0
- package/public/file-icons/file.svg +1 -0
- package/public/file-icons/folder-admin-open.svg +1 -0
- package/public/file-icons/folder-admin.svg +1 -0
- package/public/file-icons/folder-android-open.svg +1 -0
- package/public/file-icons/folder-android.svg +1 -0
- package/public/file-icons/folder-api-open.svg +1 -0
- package/public/file-icons/folder-api.svg +1 -0
- package/public/file-icons/folder-app-open.svg +1 -0
- package/public/file-icons/folder-app.svg +1 -0
- package/public/file-icons/folder-archive-open.svg +1 -0
- package/public/file-icons/folder-archive.svg +1 -0
- package/public/file-icons/folder-atom-open.svg +1 -0
- package/public/file-icons/folder-atom.svg +1 -0
- package/public/file-icons/folder-audio-open.svg +1 -0
- package/public/file-icons/folder-audio.svg +1 -0
- package/public/file-icons/folder-aws-open.svg +1 -0
- package/public/file-icons/folder-aws.svg +1 -0
- package/public/file-icons/folder-backup-open.svg +1 -0
- package/public/file-icons/folder-backup.svg +1 -0
- package/public/file-icons/folder-benchmark-open.svg +1 -0
- package/public/file-icons/folder-benchmark.svg +1 -0
- package/public/file-icons/folder-cart-open.svg +1 -0
- package/public/file-icons/folder-cart.svg +1 -0
- package/public/file-icons/folder-ci-open.svg +1 -0
- package/public/file-icons/folder-ci.svg +1 -0
- package/public/file-icons/folder-circleci-open.svg +1 -0
- package/public/file-icons/folder-circleci.svg +1 -0
- package/public/file-icons/folder-class-open.svg +1 -0
- package/public/file-icons/folder-class.svg +1 -0
- package/public/file-icons/folder-client-open.svg +1 -0
- package/public/file-icons/folder-client.svg +1 -0
- package/public/file-icons/folder-command-open.svg +1 -0
- package/public/file-icons/folder-command.svg +1 -0
- package/public/file-icons/folder-components-open.svg +1 -0
- package/public/file-icons/folder-components.svg +1 -0
- package/public/file-icons/folder-config-open.svg +1 -0
- package/public/file-icons/folder-config.svg +1 -0
- package/public/file-icons/folder-console-open.svg +1 -0
- package/public/file-icons/folder-console.svg +1 -0
- package/public/file-icons/folder-content-open.svg +1 -0
- package/public/file-icons/folder-content.svg +1 -0
- package/public/file-icons/folder-context-open.svg +1 -0
- package/public/file-icons/folder-context.svg +1 -0
- package/public/file-icons/folder-contract-open.svg +1 -0
- package/public/file-icons/folder-contract.svg +1 -0
- package/public/file-icons/folder-controller-open.svg +1 -0
- package/public/file-icons/folder-controller.svg +1 -0
- package/public/file-icons/folder-core-open.svg +1 -0
- package/public/file-icons/folder-core.svg +1 -0
- package/public/file-icons/folder-coverage-open.svg +1 -0
- package/public/file-icons/folder-coverage.svg +1 -0
- package/public/file-icons/folder-css-open.svg +1 -0
- package/public/file-icons/folder-css.svg +1 -0
- package/public/file-icons/folder-database-open.svg +1 -0
- package/public/file-icons/folder-database.svg +1 -0
- package/public/file-icons/folder-debug-open.svg +1 -0
- package/public/file-icons/folder-debug.svg +1 -0
- package/public/file-icons/folder-deprecated-open.svg +1 -0
- package/public/file-icons/folder-deprecated.svg +1 -0
- package/public/file-icons/folder-desktop-open.svg +1 -0
- package/public/file-icons/folder-desktop.svg +1 -0
- package/public/file-icons/folder-dist-open.svg +1 -0
- package/public/file-icons/folder-dist.svg +1 -0
- package/public/file-icons/folder-docker-open.svg +1 -0
- package/public/file-icons/folder-docker.svg +1 -0
- package/public/file-icons/folder-docs-open.svg +1 -0
- package/public/file-icons/folder-docs.svg +1 -0
- package/public/file-icons/folder-element-open.svg +1 -0
- package/public/file-icons/folder-element.svg +1 -0
- package/public/file-icons/folder-environment-open.svg +1 -0
- package/public/file-icons/folder-environment.svg +1 -0
- package/public/file-icons/folder-event-open.svg +1 -0
- package/public/file-icons/folder-event.svg +1 -0
- package/public/file-icons/folder-examples-open.svg +1 -0
- package/public/file-icons/folder-examples.svg +1 -0
- package/public/file-icons/folder-features-open.svg +1 -0
- package/public/file-icons/folder-features.svg +1 -0
- package/public/file-icons/folder-font-open.svg +1 -0
- package/public/file-icons/folder-font.svg +1 -0
- package/public/file-icons/folder-functions-open.svg +1 -0
- package/public/file-icons/folder-functions.svg +1 -0
- package/public/file-icons/folder-generator-open.svg +1 -0
- package/public/file-icons/folder-generator.svg +1 -0
- package/public/file-icons/folder-git-open.svg +1 -0
- package/public/file-icons/folder-git.svg +1 -0
- package/public/file-icons/folder-github-open.svg +1 -0
- package/public/file-icons/folder-github.svg +1 -0
- package/public/file-icons/folder-gitlab-open.svg +1 -0
- package/public/file-icons/folder-gitlab.svg +1 -0
- package/public/file-icons/folder-graphql-open.svg +1 -0
- package/public/file-icons/folder-graphql.svg +1 -0
- package/public/file-icons/folder-helper-open.svg +1 -0
- package/public/file-icons/folder-helper.svg +1 -0
- package/public/file-icons/folder-hook-open.svg +1 -0
- package/public/file-icons/folder-hook.svg +1 -0
- package/public/file-icons/folder-husky-open.svg +1 -0
- package/public/file-icons/folder-husky.svg +1 -0
- package/public/file-icons/folder-i18n-open.svg +1 -0
- package/public/file-icons/folder-i18n.svg +1 -0
- package/public/file-icons/folder-images-open.svg +1 -0
- package/public/file-icons/folder-images.svg +1 -0
- package/public/file-icons/folder-include-open.svg +1 -0
- package/public/file-icons/folder-include.svg +1 -0
- package/public/file-icons/folder-intellij-open.svg +1 -0
- package/public/file-icons/folder-intellij.svg +1 -0
- package/public/file-icons/folder-interface-open.svg +1 -0
- package/public/file-icons/folder-interface.svg +1 -0
- package/public/file-icons/folder-ios-open.svg +1 -0
- package/public/file-icons/folder-ios.svg +1 -0
- package/public/file-icons/folder-java-open.svg +1 -0
- package/public/file-icons/folder-java.svg +1 -0
- package/public/file-icons/folder-job-open.svg +1 -0
- package/public/file-icons/folder-job.svg +1 -0
- package/public/file-icons/folder-jupyter-open.svg +1 -0
- package/public/file-icons/folder-jupyter.svg +1 -0
- package/public/file-icons/folder-keys-open.svg +1 -0
- package/public/file-icons/folder-keys.svg +1 -0
- package/public/file-icons/folder-kubernetes-open.svg +1 -0
- package/public/file-icons/folder-kubernetes.svg +1 -0
- package/public/file-icons/folder-layout-open.svg +1 -0
- package/public/file-icons/folder-layout.svg +1 -0
- package/public/file-icons/folder-less-open.svg +1 -0
- package/public/file-icons/folder-less.svg +1 -0
- package/public/file-icons/folder-lib-open.svg +1 -0
- package/public/file-icons/folder-lib.svg +1 -0
- package/public/file-icons/folder-log-open.svg +1 -0
- package/public/file-icons/folder-log.svg +1 -0
- package/public/file-icons/folder-messages-open.svg +1 -0
- package/public/file-icons/folder-messages.svg +1 -0
- package/public/file-icons/folder-meta-open.svg +1 -0
- package/public/file-icons/folder-meta.svg +1 -0
- package/public/file-icons/folder-middleware-open.svg +1 -0
- package/public/file-icons/folder-middleware.svg +1 -0
- package/public/file-icons/folder-migrations-open.svg +1 -0
- package/public/file-icons/folder-migrations.svg +1 -0
- package/public/file-icons/folder-mobile-open.svg +1 -0
- package/public/file-icons/folder-mobile.svg +1 -0
- package/public/file-icons/folder-mock-open.svg +1 -0
- package/public/file-icons/folder-mock.svg +1 -0
- package/public/file-icons/folder-molecule-open.svg +1 -0
- package/public/file-icons/folder-molecule.svg +1 -0
- package/public/file-icons/folder-next-open.svg +1 -0
- package/public/file-icons/folder-next.svg +1 -0
- package/public/file-icons/folder-node-open.svg +1 -0
- package/public/file-icons/folder-node.svg +1 -0
- package/public/file-icons/folder-nuxt-open.svg +1 -0
- package/public/file-icons/folder-nuxt.svg +1 -0
- package/public/file-icons/folder-open.svg +1 -0
- package/public/file-icons/folder-organism-open.svg +1 -0
- package/public/file-icons/folder-organism.svg +1 -0
- package/public/file-icons/folder-packages-open.svg +1 -0
- package/public/file-icons/folder-packages.svg +1 -0
- package/public/file-icons/folder-plugin-open.svg +1 -0
- package/public/file-icons/folder-plugin.svg +1 -0
- package/public/file-icons/folder-prisma-open.svg +1 -0
- package/public/file-icons/folder-prisma.svg +1 -0
- package/public/file-icons/folder-prompts-open.svg +1 -0
- package/public/file-icons/folder-prompts.svg +1 -0
- package/public/file-icons/folder-proto-open.svg +1 -0
- package/public/file-icons/folder-proto.svg +1 -0
- package/public/file-icons/folder-public-open.svg +1 -0
- package/public/file-icons/folder-public.svg +1 -0
- package/public/file-icons/folder-python-open.svg +1 -0
- package/public/file-icons/folder-python.svg +1 -0
- package/public/file-icons/folder-queue-open.svg +1 -0
- package/public/file-icons/folder-queue.svg +1 -0
- package/public/file-icons/folder-resource-open.svg +1 -0
- package/public/file-icons/folder-resource.svg +1 -0
- package/public/file-icons/folder-review-open.svg +1 -0
- package/public/file-icons/folder-review.svg +1 -0
- package/public/file-icons/folder-robot-open.svg +1 -0
- package/public/file-icons/folder-robot.svg +1 -0
- package/public/file-icons/folder-routes-open.svg +1 -0
- package/public/file-icons/folder-routes.svg +1 -0
- package/public/file-icons/folder-rust-open.svg +1 -0
- package/public/file-icons/folder-rust.svg +1 -0
- package/public/file-icons/folder-sass-open.svg +1 -0
- package/public/file-icons/folder-sass.svg +1 -0
- package/public/file-icons/folder-scripts-open.svg +1 -0
- package/public/file-icons/folder-scripts.svg +1 -0
- package/public/file-icons/folder-secure-open.svg +1 -0
- package/public/file-icons/folder-secure.svg +1 -0
- package/public/file-icons/folder-seeders-open.svg +1 -0
- package/public/file-icons/folder-seeders.svg +1 -0
- package/public/file-icons/folder-server-open.svg +1 -0
- package/public/file-icons/folder-server.svg +1 -0
- package/public/file-icons/folder-shared-open.svg +1 -0
- package/public/file-icons/folder-shared.svg +1 -0
- package/public/file-icons/folder-skills-open.svg +1 -0
- package/public/file-icons/folder-skills.svg +1 -0
- package/public/file-icons/folder-src-open.svg +1 -0
- package/public/file-icons/folder-src.svg +1 -0
- package/public/file-icons/folder-store-open.svg +1 -0
- package/public/file-icons/folder-store.svg +1 -0
- package/public/file-icons/folder-storybook-open.svg +1 -0
- package/public/file-icons/folder-storybook.svg +1 -0
- package/public/file-icons/folder-supabase-open.svg +1 -0
- package/public/file-icons/folder-supabase.svg +1 -0
- package/public/file-icons/folder-svelte-open.svg +1 -0
- package/public/file-icons/folder-svelte.svg +1 -0
- package/public/file-icons/folder-target-open.svg +1 -0
- package/public/file-icons/folder-target.svg +1 -0
- package/public/file-icons/folder-tasks-open.svg +1 -0
- package/public/file-icons/folder-tasks.svg +1 -0
- package/public/file-icons/folder-temp-open.svg +1 -0
- package/public/file-icons/folder-temp.svg +1 -0
- package/public/file-icons/folder-template-open.svg +1 -0
- package/public/file-icons/folder-template.svg +1 -0
- package/public/file-icons/folder-terraform-open.svg +1 -0
- package/public/file-icons/folder-terraform.svg +1 -0
- package/public/file-icons/folder-test-open.svg +1 -0
- package/public/file-icons/folder-test.svg +1 -0
- package/public/file-icons/folder-theme-open.svg +1 -0
- package/public/file-icons/folder-theme.svg +1 -0
- package/public/file-icons/folder-tools-open.svg +1 -0
- package/public/file-icons/folder-tools.svg +1 -0
- package/public/file-icons/folder-turborepo-open.svg +1 -0
- package/public/file-icons/folder-turborepo.svg +1 -0
- package/public/file-icons/folder-typescript-open.svg +1 -0
- package/public/file-icons/folder-typescript.svg +1 -0
- package/public/file-icons/folder-ui-open.svg +1 -0
- package/public/file-icons/folder-ui.svg +1 -0
- package/public/file-icons/folder-utils-open.svg +1 -0
- package/public/file-icons/folder-utils.svg +1 -0
- package/public/file-icons/folder-video-open.svg +1 -0
- package/public/file-icons/folder-video.svg +1 -0
- package/public/file-icons/folder-views-open.svg +1 -0
- package/public/file-icons/folder-views.svg +1 -0
- package/public/file-icons/folder-vscode-open.svg +1 -0
- package/public/file-icons/folder-vscode.svg +1 -0
- package/public/file-icons/folder.svg +1 -0
- package/public/file-icons/font.svg +1 -0
- package/public/file-icons/fsharp.svg +1 -0
- package/public/file-icons/gemfile.svg +1 -0
- package/public/file-icons/git.svg +1 -0
- package/public/file-icons/go-mod.svg +1 -0
- package/public/file-icons/go.svg +1 -0
- package/public/file-icons/gradle.svg +1 -0
- package/public/file-icons/graphql.svg +1 -0
- package/public/file-icons/groovy.svg +1 -0
- package/public/file-icons/h.svg +1 -0
- package/public/file-icons/handlebars.svg +1 -0
- package/public/file-icons/haskell.svg +1 -0
- package/public/file-icons/hcl.svg +1 -0
- package/public/file-icons/heroku.svg +1 -0
- package/public/file-icons/hpp.svg +1 -0
- package/public/file-icons/html.svg +1 -0
- package/public/file-icons/husky.svg +1 -0
- package/public/file-icons/i18n.svg +1 -0
- package/public/file-icons/image.svg +1 -0
- package/public/file-icons/java.svg +1 -0
- package/public/file-icons/javascript.svg +1 -0
- package/public/file-icons/jenkins.svg +1 -0
- package/public/file-icons/jest.svg +1 -0
- package/public/file-icons/jsconfig.svg +1 -0
- package/public/file-icons/json.svg +1 -0
- package/public/file-icons/julia.svg +1 -0
- package/public/file-icons/jupyter.svg +1 -0
- package/public/file-icons/just.svg +1 -0
- package/public/file-icons/key.svg +1 -0
- package/public/file-icons/kotlin.svg +1 -0
- package/public/file-icons/lerna.svg +1 -0
- package/public/file-icons/less.svg +1 -0
- package/public/file-icons/license.svg +1 -0
- package/public/file-icons/lintstaged.svg +1 -0
- package/public/file-icons/liquid.svg +1 -0
- package/public/file-icons/lock.svg +1 -0
- package/public/file-icons/log.svg +1 -0
- package/public/file-icons/lua.svg +1 -0
- package/public/file-icons/makefile.svg +1 -0
- package/public/file-icons/markdown.svg +1 -0
- package/public/file-icons/maven.svg +1 -0
- package/public/file-icons/mdx.svg +1 -0
- package/public/file-icons/netlify.svg +1 -0
- package/public/file-icons/next.svg +1 -0
- package/public/file-icons/nim.svg +1 -0
- package/public/file-icons/nix.svg +1 -0
- package/public/file-icons/nodejs.svg +1 -0
- package/public/file-icons/nodemon.svg +1 -0
- package/public/file-icons/npm.svg +1 -0
- package/public/file-icons/nx.svg +1 -0
- package/public/file-icons/objective-c.svg +1 -0
- package/public/file-icons/objective-cpp.svg +1 -0
- package/public/file-icons/ocaml.svg +1 -0
- package/public/file-icons/pdf.svg +1 -0
- package/public/file-icons/php.svg +1 -0
- package/public/file-icons/pm2-ecosystem.svg +1 -0
- package/public/file-icons/pnpm.svg +1 -0
- package/public/file-icons/poetry.svg +1 -0
- package/public/file-icons/postcss.svg +1 -0
- package/public/file-icons/powerpoint.svg +1 -0
- package/public/file-icons/powershell.svg +1 -0
- package/public/file-icons/prettier.svg +1 -0
- package/public/file-icons/prisma.svg +1 -0
- package/public/file-icons/prolog.svg +1 -0
- package/public/file-icons/proto.svg +1 -0
- package/public/file-icons/pug.svg +1 -0
- package/public/file-icons/python-misc.svg +1 -0
- package/public/file-icons/python.svg +1 -0
- package/public/file-icons/r.svg +1 -0
- package/public/file-icons/razor.svg +1 -0
- package/public/file-icons/react.svg +1 -0
- package/public/file-icons/react_ts.svg +1 -0
- package/public/file-icons/readme.svg +1 -0
- package/public/file-icons/renovate.svg +1 -0
- package/public/file-icons/robots.svg +1 -0
- package/public/file-icons/rollup.svg +1 -0
- package/public/file-icons/ruby.svg +1 -0
- package/public/file-icons/rust.svg +1 -0
- package/public/file-icons/sass.svg +1 -0
- package/public/file-icons/scala.svg +1 -0
- package/public/file-icons/semantic-release.svg +1 -0
- package/public/file-icons/settings.svg +1 -0
- package/public/file-icons/shader.svg +1 -0
- package/public/file-icons/sml.svg +1 -0
- package/public/file-icons/solidity.svg +1 -0
- package/public/file-icons/stylus.svg +1 -0
- package/public/file-icons/svelte.svg +1 -0
- package/public/file-icons/svg.svg +1 -0
- package/public/file-icons/swift.svg +1 -0
- package/public/file-icons/table.svg +1 -0
- package/public/file-icons/tailwindcss.svg +1 -0
- package/public/file-icons/taskfile.svg +1 -0
- package/public/file-icons/terraform.svg +1 -0
- package/public/file-icons/test-js.svg +1 -0
- package/public/file-icons/test-jsx.svg +1 -0
- package/public/file-icons/test-ts.svg +1 -0
- package/public/file-icons/tex.svg +1 -0
- package/public/file-icons/toml.svg +1 -0
- package/public/file-icons/travis.svg +1 -0
- package/public/file-icons/tsconfig.svg +1 -0
- package/public/file-icons/tune.svg +1 -0
- package/public/file-icons/turborepo.svg +1 -0
- package/public/file-icons/typescript-def.svg +1 -0
- package/public/file-icons/typescript.svg +1 -0
- package/public/file-icons/unity.svg +1 -0
- package/public/file-icons/uv.svg +1 -0
- package/public/file-icons/vala.svg +1 -0
- package/public/file-icons/vercel.svg +1 -0
- package/public/file-icons/video.svg +1 -0
- package/public/file-icons/vim.svg +1 -0
- package/public/file-icons/vite.svg +1 -0
- package/public/file-icons/vitest.svg +1 -0
- package/public/file-icons/vlang.svg +1 -0
- package/public/file-icons/vue.svg +1 -0
- package/public/file-icons/webassembly.svg +1 -0
- package/public/file-icons/webpack.svg +1 -0
- package/public/file-icons/word.svg +1 -0
- package/public/file-icons/xml.svg +1 -0
- package/public/file-icons/yaml.svg +1 -0
- package/public/file-icons/yarn.svg +1 -0
- package/public/file-icons/zig.svg +1 -0
- package/public/file-icons/zip.svg +1 -0
- package/public/provider-icons/antgroup-color.svg +1 -0
- package/public/provider-icons/anthropic.svg +1 -0
- package/public/provider-icons/azure-color.svg +1 -0
- package/public/provider-icons/baseten.svg +1 -0
- package/public/provider-icons/bedrock-color.svg +1 -0
- package/public/provider-icons/cerebras-color.svg +1 -0
- package/public/provider-icons/cloudflare-color.svg +1 -0
- package/public/provider-icons/deepseek-color.svg +1 -0
- package/public/provider-icons/fireworks-color.svg +1 -0
- package/public/provider-icons/githubcopilot.svg +1 -0
- package/public/provider-icons/google-color.svg +1 -0
- package/public/provider-icons/googlecloud-color.svg +1 -0
- package/public/provider-icons/groq.svg +1 -0
- package/public/provider-icons/huggingface-color.svg +1 -0
- package/public/provider-icons/kimi-color.svg +1 -0
- package/public/provider-icons/minimax-color.svg +1 -0
- package/public/provider-icons/mistral-color.svg +1 -0
- package/public/provider-icons/moonshot.svg +1 -0
- package/public/provider-icons/nvidia-color.svg +1 -0
- package/public/provider-icons/openai.svg +1 -0
- package/public/provider-icons/opencode.svg +1 -0
- package/public/provider-icons/openrouter-color.svg +1 -0
- package/public/provider-icons/qwen-color.svg +1 -0
- package/public/provider-icons/together-color.svg +1 -0
- package/public/provider-icons/vercel.svg +1 -0
- package/public/provider-icons/xai.svg +1 -0
- package/public/provider-icons/xiaomimimo.svg +1 -0
- package/public/provider-icons/zai.svg +1 -0
- package/scripts/build-output.mjs +131 -0
- package/scripts/dev.mjs +18 -0
- package/scripts/entrypoint.mjs +8 -0
- package/scripts/install-build.mjs +134 -0
- package/scripts/launcher.mjs +249 -0
- package/scripts/pick-folder.ps1 +110 -0
- package/scripts/postinstall.mjs +12 -0
- package/scripts/process-runner.mjs +60 -0
- package/scripts/release.mjs +172 -0
- package/scripts/sync-file-icons.mjs +118 -0
- package/scripts/sync-provider-icons.mjs +41 -0
- package/src/app/api/agent/[id]/events/route.ts +81 -0
- package/src/app/api/agent/[id]/route.ts +24 -0
- package/src/app/api/agent/[id]/tree/route.ts +49 -0
- package/src/app/api/agent/new/route.ts +19 -0
- package/src/app/api/config/route.ts +22 -0
- package/src/app/api/files/route.ts +63 -0
- package/src/app/api/git/route.ts +32 -0
- package/src/app/api/growth/route.ts +50 -0
- package/src/app/api/health/route.ts +7 -0
- package/src/app/api/models/route.ts +129 -0
- package/src/app/api/pi-settings/route.ts +22 -0
- package/src/app/api/plugins/route.ts +79 -0
- package/src/app/api/security/route.ts +41 -0
- package/src/app/api/sessions/[id]/export/route.ts +25 -0
- package/src/app/api/sessions/[id]/route.ts +37 -0
- package/src/app/api/sessions/route.ts +23 -0
- package/src/app/api/skills/route.ts +39 -0
- package/src/app/api/update/route.ts +33 -0
- package/src/app/api/version/route.ts +8 -0
- package/src/app/api/web-auth/route.ts +80 -0
- package/src/app/api/workspaces/route.ts +78 -0
- package/src/app/globals.css +2039 -0
- package/src/app/icon.svg +21 -0
- package/src/app/layout.tsx +29 -0
- package/src/app/login/page.tsx +111 -0
- package/src/app/page.tsx +10 -0
- package/src/components/AppShell.tsx +1171 -0
- package/src/components/ChatInput.tsx +729 -0
- package/src/components/ChatWindow.tsx +1320 -0
- package/src/components/ContextMeter.tsx +131 -0
- package/src/components/DiffView.tsx +139 -0
- package/src/components/ExtensionUI.tsx +181 -0
- package/src/components/FileViewer.tsx +555 -0
- package/src/components/FullDiff.tsx +220 -0
- package/src/components/GitPanel.tsx +357 -0
- package/src/components/GrowthTree.tsx +317 -0
- package/src/components/ModelSelector.tsx +286 -0
- package/src/components/OutlineRail.tsx +166 -0
- package/src/components/PiMark.tsx +79 -0
- package/src/components/ProjectPanel.tsx +309 -0
- package/src/components/ProviderBrand.tsx +28 -0
- package/src/components/ProviderSetupModal.module.css +560 -0
- package/src/components/ProviderSetupModal.tsx +559 -0
- package/src/components/SessionSidebar.tsx +1066 -0
- package/src/components/SettingsPanel.tsx +2216 -0
- package/src/components/TrajectoryAnalyzer.module.css +225 -0
- package/src/components/TrajectoryView.tsx +289 -0
- package/src/components/icons.tsx +443 -0
- package/src/hooks/useFileViewer.ts +58 -0
- package/src/hooks/useGrowth.ts +635 -0
- package/src/hooks/usePiWeb.ts +900 -0
- package/src/i18n.tsx +1043 -0
- package/src/lib/agent-manager.ts +1348 -0
- package/src/lib/app-root.ts +4 -0
- package/src/lib/auth.ts +51 -0
- package/src/lib/command-validation.ts +116 -0
- package/src/lib/enter-behavior.ts +40 -0
- package/src/lib/extension-ui.ts +134 -0
- package/src/lib/file-icons.json +557 -0
- package/src/lib/file-icons.ts +39 -0
- package/src/lib/files-service.ts +280 -0
- package/src/lib/git-service.ts +255 -0
- package/src/lib/growth-rounds.ts +84 -0
- package/src/lib/growth-service.ts +586 -0
- package/src/lib/growth-tracker.ts +328 -0
- package/src/lib/growth-tree.ts +245 -0
- package/src/lib/growth-turns.ts +23 -0
- package/src/lib/highlight.ts +59 -0
- package/src/lib/model-draft.ts +103 -0
- package/src/lib/models-service.ts +852 -0
- package/src/lib/path-security.ts +101 -0
- package/src/lib/pi-settings.ts +115 -0
- package/src/lib/pi.ts +367 -0
- package/src/lib/plugins-service.ts +312 -0
- package/src/lib/process-format.ts +261 -0
- package/src/lib/provider-display.ts +57 -0
- package/src/lib/security-service.ts +52 -0
- package/src/lib/semver.ts +39 -0
- package/src/lib/session-reader.ts +204 -0
- package/src/lib/settings-write-lock.ts +45 -0
- package/src/lib/skills-service.ts +97 -0
- package/src/lib/text-sanitize.ts +20 -0
- package/src/lib/theme.ts +137 -0
- package/src/lib/trajectory.ts +294 -0
- package/src/lib/types.ts +300 -0
- package/src/lib/update-service.ts +129 -0
- package/src/lib/version.ts +6 -0
- package/src/lib/web-auth-shared.ts +35 -0
- package/src/lib/web-auth.ts +53 -0
- package/src/lib/workspace-store.ts +261 -0
- package/src/proxy.ts +53 -0
- package/tsconfig.json +49 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { decodeSessionId, getAgentDir, isSessionId } from "./pi";
|
|
4
|
+
|
|
5
|
+
export class BoundaryError extends Error {
|
|
6
|
+
constructor(message: string) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "BoundaryError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function comparable(filePath: string): string {
|
|
13
|
+
const resolved = path.resolve(filePath);
|
|
14
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isPathInside(root: string, candidate: string): boolean {
|
|
18
|
+
const relative = path.relative(comparable(root), comparable(candidate));
|
|
19
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function resolveSessionPath(id: string, options?: { allowPending?: boolean }): Promise<string> {
|
|
23
|
+
if (!isSessionId(id)) throw new BoundaryError("invalid session id");
|
|
24
|
+
const decoded = decodeSessionId(id);
|
|
25
|
+
if (!path.isAbsolute(decoded) || path.extname(decoded).toLowerCase() !== ".jsonl") {
|
|
26
|
+
throw new BoundaryError("invalid session path");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const sessionsRoot = path.join(getAgentDir(), "sessions");
|
|
30
|
+
const realRoot = await fs.realpath(sessionsRoot).catch(() => path.resolve(sessionsRoot));
|
|
31
|
+
if (!isPathInside(realRoot, path.resolve(decoded))) throw new BoundaryError("session path is outside the Pi session store");
|
|
32
|
+
|
|
33
|
+
// A freshly created session may not be materialized on disk yet
|
|
34
|
+
// (SessionManager is lazy); allow pending sessions through so setup
|
|
35
|
+
// commands can apply first. The real file is materialized by ensureSession.
|
|
36
|
+
|
|
37
|
+
if (options?.allowPending) {
|
|
38
|
+
const existing = await fs.realpath(decoded).catch((error: NodeJS.ErrnoException) => {
|
|
39
|
+
if (error.code === "ENOENT") return null;
|
|
40
|
+
throw error;
|
|
41
|
+
});
|
|
42
|
+
if (!existing) {
|
|
43
|
+
let parent = path.dirname(decoded);
|
|
44
|
+
while (isPathInside(sessionsRoot, parent)) {
|
|
45
|
+
const realParent = await fs.realpath(parent).catch((error: NodeJS.ErrnoException) => {
|
|
46
|
+
if (error.code === "ENOENT") return null;
|
|
47
|
+
throw error;
|
|
48
|
+
});
|
|
49
|
+
if (realParent) {
|
|
50
|
+
if (!isPathInside(realRoot, realParent)) throw new BoundaryError("session path escapes the Pi session store");
|
|
51
|
+
return decoded;
|
|
52
|
+
}
|
|
53
|
+
if (samePath(parent, sessionsRoot)) break;
|
|
54
|
+
parent = path.dirname(parent);
|
|
55
|
+
}
|
|
56
|
+
throw new BoundaryError("session store not found");
|
|
57
|
+
}
|
|
58
|
+
if (!isPathInside(realRoot, existing)) throw new BoundaryError("session path escapes the Pi session store");
|
|
59
|
+
return decoded;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const realFile = await fs.realpath(decoded).catch(() => {
|
|
63
|
+
throw new BoundaryError("session not found");
|
|
64
|
+
});
|
|
65
|
+
if (!isPathInside(realRoot, realFile)) throw new BoundaryError("session path escapes the Pi session store");
|
|
66
|
+
const stat = await fs.stat(realFile);
|
|
67
|
+
if (!stat.isFile()) throw new BoundaryError("session is not a file");
|
|
68
|
+
return realFile;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function resolveWorkspacePath(value: unknown): Promise<string> {
|
|
72
|
+
if (typeof value !== "string" || value.trim().length === 0 || value.length > 4096) {
|
|
73
|
+
throw new BoundaryError("invalid workspace path");
|
|
74
|
+
}
|
|
75
|
+
const real = await fs.realpath(path.resolve(value.trim())).catch(() => {
|
|
76
|
+
throw new BoundaryError("workspace not found");
|
|
77
|
+
});
|
|
78
|
+
const stat = await fs.stat(real);
|
|
79
|
+
if (!stat.isDirectory()) throw new BoundaryError("workspace is not a directory");
|
|
80
|
+
return real;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function samePath(left: string, right: string): boolean {
|
|
84
|
+
return comparable(left) === comparable(right);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function resolveDiscoveredPath(requested: string, allowed: string[]): Promise<string> {
|
|
88
|
+
if (typeof requested !== "string" || !path.isAbsolute(requested) || requested.length > 4096) {
|
|
89
|
+
throw new BoundaryError("invalid resource path");
|
|
90
|
+
}
|
|
91
|
+
const realRequested = await fs.realpath(requested).catch(() => {
|
|
92
|
+
throw new BoundaryError("resource not found");
|
|
93
|
+
});
|
|
94
|
+
for (const candidate of allowed) {
|
|
95
|
+
if (!candidate) continue;
|
|
96
|
+
const realCandidate = await fs.realpath(candidate).catch(() => null);
|
|
97
|
+
if (realCandidate && samePath(realRequested, realCandidate)) return realRequested;
|
|
98
|
+
|
|
99
|
+
}
|
|
100
|
+
throw new BoundaryError("resource is not part of the discovered Pi configuration");
|
|
101
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-settings:读写 pi settings.json 的数值化配置(压缩/自动重试)。
|
|
3
|
+
* 与项目信任同一个外科手术式 JSON patch 方式。
|
|
4
|
+
*/
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
import { getAgentDir, reloadSettingsManagers } from "./pi";
|
|
9
|
+
import { withExternalSettingsLock, withSettingsWriteLock } from "./settings-write-lock";
|
|
10
|
+
|
|
11
|
+
export interface CompactionSettings {
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
reserveTokens: number;
|
|
14
|
+
keepRecentTokens: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface RetrySettings {
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
maxRetries: number;
|
|
20
|
+
baseDelayMs: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PiSettings {
|
|
24
|
+
compaction: CompactionSettings;
|
|
25
|
+
retry: RetrySettings;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const DEFAULTS: PiSettings = {
|
|
29
|
+
compaction: { enabled: true, reserveTokens: 16384, keepRecentTokens: 20000 },
|
|
30
|
+
retry: { enabled: true, maxRetries: 3, baseDelayMs: 2000 },
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
async function readSettingsFileStrict(): Promise<Record<string, unknown>> {
|
|
34
|
+
let text: string;
|
|
35
|
+
try {
|
|
36
|
+
text = await fs.readFile(path.join(getAgentDir(), "settings.json"), "utf8");
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return {};
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
const parsed: unknown = JSON.parse(text.replace(/^\uFEFF/, ""));
|
|
42
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("settings.json must contain a JSON object");
|
|
43
|
+
return parsed as Record<string, unknown>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function section(raw: Record<string, unknown>, key: "compaction" | "retry"): Record<string, unknown> {
|
|
47
|
+
const value = raw[key];
|
|
48
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function getPiSettings(): Promise<PiSettings> {
|
|
52
|
+
const raw = await readSettingsFileStrict();
|
|
53
|
+
const c = (raw.compaction ?? {}) as Record<string, unknown>;
|
|
54
|
+
const r = (raw.retry ?? {}) as Record<string, unknown>;
|
|
55
|
+
const num = (v: unknown, d: number) => (typeof v === "number" && Number.isSafeInteger(v) && v >= 0 ? v : d);
|
|
56
|
+
return {
|
|
57
|
+
compaction: {
|
|
58
|
+
enabled: c.enabled !== false,
|
|
59
|
+
reserveTokens: num(c.reserveTokens, DEFAULTS.compaction.reserveTokens),
|
|
60
|
+
keepRecentTokens: num(c.keepRecentTokens, DEFAULTS.compaction.keepRecentTokens),
|
|
61
|
+
},
|
|
62
|
+
retry: {
|
|
63
|
+
enabled: r.enabled !== false,
|
|
64
|
+
maxRetries: num(r.maxRetries, DEFAULTS.retry.maxRetries),
|
|
65
|
+
baseDelayMs: num(r.baseDelayMs, DEFAULTS.retry.baseDelayMs),
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class PiSettingsValidationError extends Error {}
|
|
71
|
+
|
|
72
|
+
export function validatePiSettingsPatch(value: unknown): { compaction?: Partial<CompactionSettings>; retry?: Partial<RetrySettings> } {
|
|
73
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new PiSettingsValidationError("invalid settings patch");
|
|
74
|
+
const input = value as Record<string, unknown>;
|
|
75
|
+
if (Object.keys(input).some((key) => key !== "compaction" && key !== "retry")) throw new PiSettingsValidationError("unknown settings field");
|
|
76
|
+
const result: { compaction?: Partial<CompactionSettings>; retry?: Partial<RetrySettings> } = {};
|
|
77
|
+
for (const section of ["compaction", "retry"] as const) {
|
|
78
|
+
if (input[section] === undefined) continue;
|
|
79
|
+
const part = input[section];
|
|
80
|
+
if (!part || typeof part !== "object" || Array.isArray(part)) throw new PiSettingsValidationError(`invalid ${section} settings`);
|
|
81
|
+
const fields = part as Record<string, unknown>;
|
|
82
|
+
const allowed = section === "compaction" ? ["enabled", "reserveTokens", "keepRecentTokens"] : ["enabled", "maxRetries", "baseDelayMs"];
|
|
83
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
84
|
+
if (!allowed.includes(key)) throw new PiSettingsValidationError(`unknown ${section} field: ${key}`);
|
|
85
|
+
if (key === "enabled") {
|
|
86
|
+
if (typeof field !== "boolean") throw new PiSettingsValidationError(`${section}.${key} must be boolean`);
|
|
87
|
+
} else if (typeof field !== "number" || !Number.isSafeInteger(field) || field < 0 || field > 1_000_000_000) {
|
|
88
|
+
throw new PiSettingsValidationError(`${section}.${key} must be a non-negative integer`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (section === "compaction") result.compaction = fields as Partial<CompactionSettings>;
|
|
92
|
+
else result.retry = fields as Partial<RetrySettings>;
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function patchPiSettings(value: unknown): Promise<void> {
|
|
98
|
+
const patch = validatePiSettingsPatch(value);
|
|
99
|
+
const file = path.join(getAgentDir(), "settings.json");
|
|
100
|
+
await withSettingsWriteLock(file, () => withExternalSettingsLock(file, async () => {
|
|
101
|
+
const raw = await readSettingsFileStrict();
|
|
102
|
+
if (patch.compaction) raw.compaction = { ...section(raw, "compaction"), ...patch.compaction };
|
|
103
|
+
if (patch.retry) raw.retry = { ...section(raw, "retry"), ...patch.retry };
|
|
104
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
105
|
+
const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
106
|
+
try {
|
|
107
|
+
await fs.writeFile(temporary, JSON.stringify(raw, null, "\t"), "utf8");
|
|
108
|
+
await fs.rename(temporary, file);
|
|
109
|
+
} finally {
|
|
110
|
+
await fs.rm(temporary, { force: true }).catch(() => undefined);
|
|
111
|
+
}
|
|
112
|
+
}));
|
|
113
|
+
// 正在运行的会话每次压缩/重试都从 SettingsManager 读:让它们重新读盘,改动立即生效
|
|
114
|
+
await reloadSettingsManagers();
|
|
115
|
+
}
|
package/src/lib/pi.ts
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi SDK 单例与共享资源:ModelRuntime、SettingsManager、DefaultResourceLoader、PackageManager。
|
|
3
|
+
* 全部走 @earendil-works/pi-coding-agent 公开导出面,pi 零改动。
|
|
4
|
+
*/
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import {
|
|
8
|
+
type ModelRuntime as PiModelRuntime,
|
|
9
|
+
type SettingsManager,
|
|
10
|
+
DefaultPackageManager,
|
|
11
|
+
DefaultResourceLoader,
|
|
12
|
+
ModelRuntime,
|
|
13
|
+
ProjectTrustStore,
|
|
14
|
+
SessionManager,
|
|
15
|
+
SettingsManager as SettingsManagerClass,
|
|
16
|
+
createAgentSession,
|
|
17
|
+
getAgentDir,
|
|
18
|
+
hasTrustRequiringProjectResources,
|
|
19
|
+
loadProjectContextFiles,
|
|
20
|
+
} from "@earendil-works/pi-coding-agent";
|
|
21
|
+
|
|
22
|
+
export { createAgentSession, getAgentDir, loadProjectContextFiles, SessionManager };
|
|
23
|
+
|
|
24
|
+
// Child tools inherit these defaults; Node itself already decodes source and JSON as UTF-8.
|
|
25
|
+
process.env.PYTHONUTF8 ??= "1";
|
|
26
|
+
process.env.PYTHONIOENCODING ??= "utf-8";
|
|
27
|
+
|
|
28
|
+
let modelRuntimePromise: Promise<PiModelRuntime> | null = null;
|
|
29
|
+
|
|
30
|
+
export function getModelRuntime(): Promise<PiModelRuntime> {
|
|
31
|
+
if (!modelRuntimePromise) {
|
|
32
|
+
modelRuntimePromise = ModelRuntime.create().catch((err) => {
|
|
33
|
+
modelRuntimePromise = null;
|
|
34
|
+
throw err;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return modelRuntimePromise;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 重建 ModelRuntime(models.json / auth.json 落盘后调用,让目录与认证立即生效) */
|
|
41
|
+
export function resetModelRuntime(): void {
|
|
42
|
+
modelRuntimePromise = null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const settingsCache = new Map<string, SettingsManager>();
|
|
46
|
+
|
|
47
|
+
// ---------- 项目信任(与 pi 终端同一份 ~/.pi/agent/trust.json) ----------
|
|
48
|
+
|
|
49
|
+
let trustStore: ProjectTrustStore | null = null;
|
|
50
|
+
function getTrustStore(): ProjectTrustStore {
|
|
51
|
+
if (!trustStore) trustStore = new ProjectTrustStore(getAgentDir());
|
|
52
|
+
return trustStore;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type ProjectTrustState = {
|
|
56
|
+
/** 该目录是否含有需要信任才能加载的项目资源(.pi/settings.json、.pi/extensions、.pi/skills、.agents/skills) */
|
|
57
|
+
required: boolean;
|
|
58
|
+
/** 本次会不会按受信任加载 */
|
|
59
|
+
trusted: boolean;
|
|
60
|
+
/** 结论来源:无需信任 / trust.json 里记住的决定 / settings.json 的 defaultProjectTrust / 还没决定(按不信任处理) */
|
|
61
|
+
source: "not-required" | "remembered" | "default-always" | "default-never" | "undecided";
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function readDefaultProjectTrust(): "ask" | "always" | "never" {
|
|
65
|
+
try {
|
|
66
|
+
const raw = JSON.parse(fs.readFileSync(path.join(getAgentDir(), "settings.json"), "utf8")) as { defaultProjectTrust?: string };
|
|
67
|
+
return raw.defaultProjectTrust === "always" || raw.defaultProjectTrust === "never" ? raw.defaultProjectTrust : "ask";
|
|
68
|
+
} catch {
|
|
69
|
+
return "ask";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 与 pi 终端 main.ts 同一套判定:目录里没有需要信任的项目资源 → 直接信任;
|
|
75
|
+
* 否则看 trust.json 里记住的决定,再看 defaultProjectTrust;「每次询问」在 Web 里没有阻塞式弹窗,
|
|
76
|
+
* 先按不信任加载,并把状态给界面显示一个可点的横幅(用户点信任后写 trust.json 再重载)。
|
|
77
|
+
*/
|
|
78
|
+
export function resolveProjectTrust(cwd: string): ProjectTrustState {
|
|
79
|
+
const resolved = path.resolve(cwd);
|
|
80
|
+
let required = false;
|
|
81
|
+
try {
|
|
82
|
+
required = hasTrustRequiringProjectResources(resolved);
|
|
83
|
+
} catch {
|
|
84
|
+
required = false;
|
|
85
|
+
}
|
|
86
|
+
if (!required) return { required: false, trusted: true, source: "not-required" };
|
|
87
|
+
const remembered = getTrustStore().get(resolved);
|
|
88
|
+
if (remembered !== null) return { required: true, trusted: remembered, source: "remembered" };
|
|
89
|
+
const fallback = readDefaultProjectTrust();
|
|
90
|
+
if (fallback === "always") return { required: true, trusted: true, source: "default-always" };
|
|
91
|
+
if (fallback === "never") return { required: true, trusted: false, source: "default-never" };
|
|
92
|
+
return { required: true, trusted: false, source: "undecided" };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 记住某目录的信任决定(null = 忘记)。已缓存的 SettingsManager 就地切换信任态
|
|
97
|
+
* (活跃会话、加载器、包管理器持有的都是它),随后由调用方 reloadLoader + session.reload 让扩展真正加载/卸载。
|
|
98
|
+
*/
|
|
99
|
+
export function setProjectTrust(cwd: string, decision: boolean | null): ProjectTrustState {
|
|
100
|
+
const resolved = path.resolve(cwd);
|
|
101
|
+
getTrustStore().set(resolved, decision);
|
|
102
|
+
const state = resolveProjectTrust(resolved);
|
|
103
|
+
const key = process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
104
|
+
const sm = settingsCache.get(key);
|
|
105
|
+
if (sm) sm.setProjectTrusted(state.trusted);
|
|
106
|
+
return state;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** 按当前 trust.json / defaultProjectTrust 重新计算每个已缓存目录的信任态并就地写入其 SettingsManager */
|
|
110
|
+
export function syncProjectTrust(): void {
|
|
111
|
+
trustStore = null;
|
|
112
|
+
for (const [key, sm] of settingsCache) {
|
|
113
|
+
try {
|
|
114
|
+
sm.setProjectTrusted(resolveProjectTrust(key).trusted);
|
|
115
|
+
} catch {
|
|
116
|
+
/* ignore */
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** 每个 cwd 一个 SettingsManager(project 级发现依赖 cwd;global 级共享 agentDir),按项目信任状态创建 */
|
|
122
|
+
export function getSettingsManager(cwd: string): SettingsManager {
|
|
123
|
+
const resolved = path.resolve(cwd);
|
|
124
|
+
const key = process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
125
|
+
let sm = settingsCache.get(key);
|
|
126
|
+
if (!sm) {
|
|
127
|
+
sm = SettingsManagerClass.create(resolved, getAgentDir(), { projectTrusted: resolveProjectTrust(resolved).trusted });
|
|
128
|
+
settingsCache.set(key, sm);
|
|
129
|
+
}
|
|
130
|
+
return sm;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
interface LoaderEntry {
|
|
134
|
+
loader: DefaultResourceLoader;
|
|
135
|
+
ready: Promise<void>;
|
|
136
|
+
/** 发现缓存的版本;每个热会话分别追踪自己已应用的版本。 */
|
|
137
|
+
version: number;
|
|
138
|
+
fingerprint: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const loaderCache = new Map<string, LoaderEntry>();
|
|
142
|
+
|
|
143
|
+
let resourceLock: Promise<void> = Promise.resolve();
|
|
144
|
+
/** 资源加载/重载(含 AgentSession.reload)全局排队:SDK 的扩展模块缓存是进程级且按 cwd 切换时清空,不能交错 */
|
|
145
|
+
export function withResourceLock<T>(fn: () => Promise<T>): Promise<T> {
|
|
146
|
+
const run = resourceLock.then(fn, fn);
|
|
147
|
+
resourceLock = run.then(
|
|
148
|
+
() => undefined,
|
|
149
|
+
() => undefined,
|
|
150
|
+
);
|
|
151
|
+
return run;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function loaderKey(cwd: string): string {
|
|
155
|
+
const resolved = path.resolve(cwd);
|
|
156
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** 每个热会话必须拥有自己的扩展 runtime;这里只创建,不加入发现缓存。 */
|
|
160
|
+
export async function createSessionResourceLoader(cwd: string): Promise<DefaultResourceLoader> {
|
|
161
|
+
const settingsManager = getSettingsManager(cwd);
|
|
162
|
+
settingsManager.setProjectTrusted(resolveProjectTrust(cwd).trusted);
|
|
163
|
+
const loader = new DefaultResourceLoader({ cwd: path.resolve(cwd), agentDir: getAgentDir(), settingsManager });
|
|
164
|
+
const reload = loader.reload.bind(loader);
|
|
165
|
+
loader.reload = async (options) => {
|
|
166
|
+
settingsManager.setProjectTrusted(resolveProjectTrust(cwd).trusted);
|
|
167
|
+
await reload(options);
|
|
168
|
+
};
|
|
169
|
+
try {
|
|
170
|
+
await withResourceLock(() => loader.reload());
|
|
171
|
+
return loader;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
loader.getExtensions().runtime.invalidate();
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 每 cwd 的只读发现缓存(技能/扩展/prompt 面板),绝不能传给 AgentSession。
|
|
180
|
+
* SDK 缓存的是扩展工厂,DefaultResourceLoader 的 runtime 却会被 bindCore 就地绑定到会话。
|
|
181
|
+
*/
|
|
182
|
+
export function getResourceLoader(cwd: string): DefaultResourceLoader {
|
|
183
|
+
const key = loaderKey(cwd);
|
|
184
|
+
let entry = loaderCache.get(key);
|
|
185
|
+
if (!entry) {
|
|
186
|
+
const loader = new DefaultResourceLoader({ cwd: path.resolve(cwd), agentDir: getAgentDir(), settingsManager: getSettingsManager(cwd) });
|
|
187
|
+
entry = { loader, ready: Promise.resolve(), version: 0, fingerprint: "" };
|
|
188
|
+
loaderCache.set(key, entry);
|
|
189
|
+
queueLoaderReload(cwd, entry);
|
|
190
|
+
}
|
|
191
|
+
return entry.loader;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function queueLoaderReload(cwd: string, entry: LoaderEntry): Promise<void> {
|
|
195
|
+
const run = withResourceLock(async () => {
|
|
196
|
+
getSettingsManager(cwd).setProjectTrusted(resolveProjectTrust(cwd).trusted);
|
|
197
|
+
// 发现缓存从未被会话绑定;替换前释放它自己的 event-bus 订阅。
|
|
198
|
+
entry.loader.getExtensions().runtime.invalidate();
|
|
199
|
+
const fingerprint = resourceFingerprint(cwd);
|
|
200
|
+
await entry.loader.reload();
|
|
201
|
+
entry.fingerprint = fingerprint;
|
|
202
|
+
entry.version += 1;
|
|
203
|
+
});
|
|
204
|
+
entry.ready = run;
|
|
205
|
+
// getResourceLoader 是同步兼容接口;错误仍由 ready 的 await 方接收。
|
|
206
|
+
void run.catch(() => undefined);
|
|
207
|
+
return run;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function resourceLoaderVersion(cwd: string): number {
|
|
211
|
+
return loaderCache.get(loaderKey(cwd))?.version ?? 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** 等待某 cwd 的加载器完成发现;消费 loader 前必须 await(ensureSession / 快照 / 技能面板等)。 */
|
|
215
|
+
export async function resourceLoaderReady(cwd: string): Promise<void> {
|
|
216
|
+
getResourceLoader(cwd);
|
|
217
|
+
await loaderCache.get(loaderKey(cwd))!.ready;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** 用户可能随手新建 .md / SKILL.md 的目录:全局与项目的 prompts、skills、extensions */
|
|
221
|
+
function watchedResourceDirs(cwd: string): string[] {
|
|
222
|
+
const agentDir = getAgentDir();
|
|
223
|
+
const project = path.join(path.resolve(cwd), ".pi");
|
|
224
|
+
return [
|
|
225
|
+
path.join(agentDir, "prompts"),
|
|
226
|
+
path.join(agentDir, "skills"),
|
|
227
|
+
path.join(agentDir, "extensions"),
|
|
228
|
+
path.join(project, "prompts"),
|
|
229
|
+
path.join(project, "skills"),
|
|
230
|
+
path.join(project, "extensions"),
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function resourceFingerprint(cwd: string): string {
|
|
235
|
+
const records: string[] = [String(resolveProjectTrust(cwd).trusted)];
|
|
236
|
+
const visit = (file: string, depth: number) => {
|
|
237
|
+
try {
|
|
238
|
+
const stat = fs.statSync(file);
|
|
239
|
+
records.push(`${file}:${stat.mtimeMs}:${stat.ctimeMs}:${stat.size}`);
|
|
240
|
+
if (stat.isDirectory() && depth < 2) {
|
|
241
|
+
for (const entry of fs.readdirSync(file, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
242
|
+
if (entry.name !== "node_modules") visit(path.join(file, entry.name), depth + 1);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} catch {
|
|
246
|
+
records.push(`${file}:missing`);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
for (const dir of watchedResourceDirs(cwd)) visit(dir, 0);
|
|
250
|
+
for (const file of [path.join(getAgentDir(), "settings.json"), path.join(cwd, ".pi", "settings.json")]) visit(file, 2);
|
|
251
|
+
return records.join("\n");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 资源目录里有比上次加载更新的文件(用户刚新建了 prompt / 技能 / 扩展)就重载一次。
|
|
256
|
+
* 只 stat 几个目录,不递归项目;返回是否真的重载了。
|
|
257
|
+
*/
|
|
258
|
+
export function resourceLoaderIsStale(cwd: string): boolean {
|
|
259
|
+
const entry = loaderCache.get(loaderKey(cwd));
|
|
260
|
+
return !entry || resourceFingerprint(cwd) !== entry.fingerprint;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export async function refreshResourceLoaderIfStale(cwd: string): Promise<boolean> {
|
|
264
|
+
await resourceLoaderReady(cwd);
|
|
265
|
+
const entry = loaderCache.get(loaderKey(cwd));
|
|
266
|
+
if (!entry) return false;
|
|
267
|
+
if (resourceFingerprint(cwd) === entry.fingerprint) return false;
|
|
268
|
+
await reloadLoader(cwd);
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** 已缓存的加载器(不存在时不创建) */
|
|
273
|
+
export function peekResourceLoader(cwd: string): DefaultResourceLoader | undefined {
|
|
274
|
+
return loaderCache.get(loaderKey(cwd))?.loader;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** 仅重载某 cwd 的发现缓存;热会话由 reloadSessionsForCwd 分别更新,不能共享 runtime。 */
|
|
278
|
+
export async function reloadLoader(cwd: string): Promise<void> {
|
|
279
|
+
const entry = loaderCache.get(loaderKey(cwd));
|
|
280
|
+
if (!entry) {
|
|
281
|
+
await resourceLoaderReady(cwd);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
await queueLoaderReload(cwd, entry);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** 让所有已缓存的 SettingsManager 就地重新读盘(活跃会话持有的就是这些实例) */
|
|
288
|
+
export async function reloadSettingsManagers(): Promise<void> {
|
|
289
|
+
for (const sm of settingsCache.values()) {
|
|
290
|
+
try {
|
|
291
|
+
await sm.reload();
|
|
292
|
+
} catch {
|
|
293
|
+
/* ignore */
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** 保留活跃加载器持有的 SettingsManager,重新读盘而不是留下脱离缓存的实例。 */
|
|
299
|
+
export function invalidateSettingsManagers(): void {
|
|
300
|
+
for (const sm of settingsCache.values()) void sm.reload().catch(() => undefined);
|
|
301
|
+
packageManagerCache.clear();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** 热重载全部已缓存加载器(对应 pi 的 /reload);就地刷新,不清缓存;返回重载过的 cwd 键 */
|
|
305
|
+
export async function reloadAllLoaders(): Promise<string[]> {
|
|
306
|
+
// 先让 SettingsManager 重新读盘(加载器 reload 内部也会 reload 同一个实例),包缓存清掉
|
|
307
|
+
for (const sm of settingsCache.values()) {
|
|
308
|
+
try {
|
|
309
|
+
await sm.reload();
|
|
310
|
+
} catch {
|
|
311
|
+
/* ignore */
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
packageManagerCache.clear();
|
|
315
|
+
const reloaded: string[] = [];
|
|
316
|
+
for (const [key, entry] of loaderCache) {
|
|
317
|
+
try {
|
|
318
|
+
await queueLoaderReload(key, entry);
|
|
319
|
+
reloaded.push(key);
|
|
320
|
+
} catch {
|
|
321
|
+
/* 单个失败不影响其他 */
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return reloaded;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const packageManagerCache = new Map<string, DefaultPackageManager>();
|
|
328
|
+
|
|
329
|
+
export function getPackageManager(cwd: string): DefaultPackageManager {
|
|
330
|
+
const resolved = path.resolve(cwd);
|
|
331
|
+
const key = process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
332
|
+
let packageManager = packageManagerCache.get(key);
|
|
333
|
+
if (!packageManager) {
|
|
334
|
+
packageManager = new DefaultPackageManager({
|
|
335
|
+
cwd: resolved,
|
|
336
|
+
agentDir: getAgentDir(),
|
|
337
|
+
settingsManager: getSettingsManager(resolved),
|
|
338
|
+
});
|
|
339
|
+
packageManagerCache.set(key, packageManager);
|
|
340
|
+
}
|
|
341
|
+
return packageManager;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** 会话 JSONL 绝对路径 ↔ URL 安全 id */
|
|
345
|
+
export function encodeSessionId(sessionPath: string): string {
|
|
346
|
+
return Buffer.from(sessionPath, "utf8").toString("base64url");
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export function decodeSessionId(id: string): string {
|
|
350
|
+
return Buffer.from(id, "base64url").toString("utf8");
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function isSessionId(id: string): boolean {
|
|
354
|
+
return /^[A-Za-z0-9_-]+$/.test(id) && id.length > 8;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** 打开已有会话(不启动 agent,仅读文件树) */
|
|
358
|
+
export function openSessionManager(sessionPath: string): SessionManager {
|
|
359
|
+
return SessionManager.open(sessionPath);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** 工具预设 → pi 工具白名单 */
|
|
363
|
+
export const TOOL_PRESETS: Record<"readonly" | "standard" | "full", string[]> = {
|
|
364
|
+
readonly: ["read", "grep", "find", "ls"],
|
|
365
|
+
standard: ["read", "grep", "find", "ls", "bash", "edit", "write"],
|
|
366
|
+
full: [], // 空 = 不限制(全部可用工具)
|
|
367
|
+
};
|