@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,129 @@
|
|
|
1
|
+
import { gzipSync } from "node:zlib";
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
import {
|
|
4
|
+
answerLogin,
|
|
5
|
+
cancelLogin,
|
|
6
|
+
CustomProvidersConflictError,
|
|
7
|
+
CustomProvidersValidationError,
|
|
8
|
+
discoverModels,
|
|
9
|
+
listModels,
|
|
10
|
+
loginState,
|
|
11
|
+
providerUsage,
|
|
12
|
+
readCustomProviders,
|
|
13
|
+
removeApiKey,
|
|
14
|
+
setApiKey,
|
|
15
|
+
startLogin,
|
|
16
|
+
writeCustomProviders,
|
|
17
|
+
} from "@/lib/models-service";
|
|
18
|
+
|
|
19
|
+
export const dynamic = "force-dynamic";
|
|
20
|
+
|
|
21
|
+
/** 1300+ 个模型的目录有 240KB~420KB;Next 的压缩层实测没有压这条响应,这里自己 gzip(浏览器透明解压) */
|
|
22
|
+
function jsonMaybeGzip(req: Request, payload: unknown): Response {
|
|
23
|
+
const body = JSON.stringify(payload);
|
|
24
|
+
const accept = req.headers.get("accept-encoding") ?? "";
|
|
25
|
+
if (!/\bgzip\b/.test(accept) || body.length < 2048) {
|
|
26
|
+
return new Response(body, { headers: { "Content-Type": "application/json; charset=utf-8" } });
|
|
27
|
+
}
|
|
28
|
+
const gz = gzipSync(Buffer.from(body, "utf8"));
|
|
29
|
+
return new Response(new Uint8Array(gz), {
|
|
30
|
+
headers: { "Content-Type": "application/json; charset=utf-8", "Content-Encoding": "gzip", Vary: "Accept-Encoding", "Cache-Control": "no-store" },
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function GET(req: Request) {
|
|
35
|
+
const params = new URL(req.url).searchParams;
|
|
36
|
+
const custom = params.get("custom") === "1";
|
|
37
|
+
const full = custom || params.get("full") === "1";
|
|
38
|
+
try {
|
|
39
|
+
const customProviders = custom ? await readCustomProviders() : null;
|
|
40
|
+
const data = await listModels();
|
|
41
|
+
// 页面挂载只需要选模型用的字段(1300+ 个模型带 api/baseUrl/cost/input 有 400KB);设置页用 custom=1 拿完整版
|
|
42
|
+
const models = full
|
|
43
|
+
? data.models
|
|
44
|
+
: data.models.map((m) => ({ provider: m.provider, id: m.id, name: m.name, reasoning: m.reasoning, thinkingLevels: m.thinkingLevels, contextWindow: m.contextWindow }));
|
|
45
|
+
const providers = full
|
|
46
|
+
? data.providers
|
|
47
|
+
: data.providers.map((p) => ({ id: p.id, name: p.name, authReady: p.authReady, builtIn: p.builtIn, modelCount: p.modelCount }));
|
|
48
|
+
return jsonMaybeGzip(req, { success: true, data: { providers, models, customProviders } });
|
|
49
|
+
} catch (err: any) {
|
|
50
|
+
return NextResponse.json({ success: false, error: String(err?.message ?? err) }, { status: 500 });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function POST(req: Request) {
|
|
55
|
+
try {
|
|
56
|
+
const body = (await req.json()) as {
|
|
57
|
+
action:
|
|
58
|
+
| "setKey"
|
|
59
|
+
| "removeKey"
|
|
60
|
+
| "saveCustomProviders"
|
|
61
|
+
| "discoverModels"
|
|
62
|
+
| "providerUsage"
|
|
63
|
+
| "loginStart"
|
|
64
|
+
| "loginState"
|
|
65
|
+
| "loginAnswer"
|
|
66
|
+
| "loginCancel";
|
|
67
|
+
providerId?: string;
|
|
68
|
+
apiKey?: string;
|
|
69
|
+
baseUrl?: string;
|
|
70
|
+
api?: string;
|
|
71
|
+
content?: string;
|
|
72
|
+
revision?: string;
|
|
73
|
+
text?: string;
|
|
74
|
+
force?: boolean;
|
|
75
|
+
};
|
|
76
|
+
if (body.action === "setKey") {
|
|
77
|
+
if (!body.providerId || !body.apiKey) return NextResponse.json({ success: false, error: "missing fields" }, { status: 400 });
|
|
78
|
+
await setApiKey(body.providerId, body.apiKey);
|
|
79
|
+
return NextResponse.json({ success: true });
|
|
80
|
+
}
|
|
81
|
+
if (body.action === "removeKey") {
|
|
82
|
+
if (!body.providerId) return NextResponse.json({ success: false, error: "missing providerId" }, { status: 400 });
|
|
83
|
+
await removeApiKey(body.providerId);
|
|
84
|
+
return NextResponse.json({ success: true });
|
|
85
|
+
}
|
|
86
|
+
if (body.action === "saveCustomProviders") {
|
|
87
|
+
if (typeof body.content !== "string") return NextResponse.json({ success: false, error: "missing content" }, { status: 400 });
|
|
88
|
+
if (body.revision !== undefined && (typeof body.revision !== "string" || !/^(?:missing|[a-f0-9]{64})$/.test(body.revision))) {
|
|
89
|
+
return NextResponse.json({ success: false, error: "invalid revision" }, { status: 400 });
|
|
90
|
+
}
|
|
91
|
+
await writeCustomProviders(body.content, body.revision);
|
|
92
|
+
return NextResponse.json({ success: true, data: { requiresSessionReopen: true } });
|
|
93
|
+
}
|
|
94
|
+
if (body.action === "discoverModels") {
|
|
95
|
+
if (!body.baseUrl) return NextResponse.json({ success: false, error: "missing baseUrl" }, { status: 400 });
|
|
96
|
+
const models = await discoverModels({ baseUrl: body.baseUrl, api: body.api, apiKey: body.apiKey, providerId: body.providerId });
|
|
97
|
+
return NextResponse.json({ success: true, data: { models } });
|
|
98
|
+
}
|
|
99
|
+
if (body.action === "providerUsage") {
|
|
100
|
+
if (!body.providerId) return NextResponse.json({ success: false, error: "missing providerId" }, { status: 400 });
|
|
101
|
+
const usage = await providerUsage(body.providerId, body.force === true);
|
|
102
|
+
return NextResponse.json({ success: true, data: { usage } });
|
|
103
|
+
}
|
|
104
|
+
if (body.action === "loginStart") {
|
|
105
|
+
if (!body.providerId) return NextResponse.json({ success: false, error: "missing providerId" }, { status: 400 });
|
|
106
|
+
await startLogin(body.providerId);
|
|
107
|
+
return NextResponse.json({ success: true, data: loginState(body.providerId) });
|
|
108
|
+
}
|
|
109
|
+
if (body.action === "loginState") {
|
|
110
|
+
if (!body.providerId) return NextResponse.json({ success: false, error: "missing providerId" }, { status: 400 });
|
|
111
|
+
return NextResponse.json({ success: true, data: loginState(body.providerId) });
|
|
112
|
+
}
|
|
113
|
+
if (body.action === "loginAnswer") {
|
|
114
|
+
if (!body.providerId || typeof body.text !== "string")
|
|
115
|
+
return NextResponse.json({ success: false, error: "missing fields" }, { status: 400 });
|
|
116
|
+
return NextResponse.json({ success: true, data: { delivered: answerLogin(body.providerId, body.text) } });
|
|
117
|
+
}
|
|
118
|
+
if (body.action === "loginCancel") {
|
|
119
|
+
if (!body.providerId) return NextResponse.json({ success: false, error: "missing providerId" }, { status: 400 });
|
|
120
|
+
cancelLogin(body.providerId);
|
|
121
|
+
return NextResponse.json({ success: true });
|
|
122
|
+
}
|
|
123
|
+
return NextResponse.json({ success: false, error: "unknown action" }, { status: 400 });
|
|
124
|
+
} catch (err: any) {
|
|
125
|
+
const status = err instanceof CustomProvidersConflictError ? 409
|
|
126
|
+
: err instanceof CustomProvidersValidationError || err instanceof SyntaxError ? 400 : 500;
|
|
127
|
+
return NextResponse.json({ success: false, error: String(err?.message ?? err) }, { status });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { getPiSettings, patchPiSettings, PiSettingsValidationError } from "@/lib/pi-settings";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
|
|
6
|
+
export async function GET() {
|
|
7
|
+
try {
|
|
8
|
+
return NextResponse.json({ success: true, data: await getPiSettings() });
|
|
9
|
+
} catch (err: any) {
|
|
10
|
+
return NextResponse.json({ success: false, error: String(err?.message ?? err) }, { status: 500 });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function PUT(req: Request) {
|
|
15
|
+
try {
|
|
16
|
+
const body = await req.json();
|
|
17
|
+
await patchPiSettings(body);
|
|
18
|
+
return NextResponse.json({ success: true, data: await getPiSettings() });
|
|
19
|
+
} catch (err: any) {
|
|
20
|
+
return NextResponse.json({ success: false, error: String(err?.message ?? err) }, { status: err instanceof PiSettingsValidationError || err instanceof SyntaxError ? 400 : 500 });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import {
|
|
3
|
+
installPackage,
|
|
4
|
+
listLoadedExtensions,
|
|
5
|
+
listPackages,
|
|
6
|
+
reloadExtensions,
|
|
7
|
+
removePackage,
|
|
8
|
+
toggleExtension,
|
|
9
|
+
togglePackage,
|
|
10
|
+
updatePackages,
|
|
11
|
+
} from "@/lib/plugins-service";
|
|
12
|
+
import { BoundaryError, resolveWorkspacePath } from "@/lib/path-security";
|
|
13
|
+
|
|
14
|
+
export const dynamic = "force-dynamic";
|
|
15
|
+
|
|
16
|
+
export async function GET(req: Request) {
|
|
17
|
+
try {
|
|
18
|
+
const cwd = await resolveWorkspacePath(new URL(req.url).searchParams.get("cwd") || process.cwd());
|
|
19
|
+
const [packages, extensions] = await Promise.all([listPackages(cwd), listLoadedExtensions(cwd)]);
|
|
20
|
+
return NextResponse.json({ success: true, data: { packages, extensions } });
|
|
21
|
+
} catch (err: any) {
|
|
22
|
+
return NextResponse.json({ success: false, error: String(err?.message ?? err) }, { status: err instanceof BoundaryError ? 400 : 500 });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function POST(req: Request) {
|
|
27
|
+
try {
|
|
28
|
+
const body = (await req.json()) as {
|
|
29
|
+
action: "install" | "remove" | "update" | "reload" | "toggle";
|
|
30
|
+
source?: string;
|
|
31
|
+
path?: string;
|
|
32
|
+
kind?: "package" | "extension";
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
scope?: "user" | "project";
|
|
35
|
+
local?: boolean;
|
|
36
|
+
cwd?: string;
|
|
37
|
+
};
|
|
38
|
+
const cwd = await resolveWorkspacePath(body.cwd || process.cwd());
|
|
39
|
+
if (body.source && body.source.length > 4096) {
|
|
40
|
+
return NextResponse.json({ success: false, error: "package source is too long" }, { status: 400 });
|
|
41
|
+
}
|
|
42
|
+
if (body.action === "reload") {
|
|
43
|
+
const reloaded = await reloadExtensions(cwd);
|
|
44
|
+
return NextResponse.json({ success: true, data: reloaded });
|
|
45
|
+
}
|
|
46
|
+
if (body.action === "toggle") {
|
|
47
|
+
if (body.kind === "package" && body.source) {
|
|
48
|
+
await togglePackage(body.source, body.disabled === true, body.scope === "project" ? "project" : "user", cwd);
|
|
49
|
+
return NextResponse.json({ success: true });
|
|
50
|
+
}
|
|
51
|
+
if (body.kind === "extension" && body.path) {
|
|
52
|
+
await toggleExtension(body.path, body.disabled === true, cwd);
|
|
53
|
+
return NextResponse.json({ success: true });
|
|
54
|
+
}
|
|
55
|
+
return NextResponse.json({ success: false, error: "invalid toggle payload" }, { status: 400 });
|
|
56
|
+
}
|
|
57
|
+
if (body.action === "install") {
|
|
58
|
+
if (!body.source) return NextResponse.json({ success: false, error: "missing source" }, { status: 400 });
|
|
59
|
+
await installPackage(body.source, body.local === true, cwd);
|
|
60
|
+
return NextResponse.json({ success: true });
|
|
61
|
+
}
|
|
62
|
+
if (body.action === "remove") {
|
|
63
|
+
if (!body.source) return NextResponse.json({ success: false, error: "missing source" }, { status: 400 });
|
|
64
|
+
const removed = await removePackage(body.source, body.local === true, cwd);
|
|
65
|
+
if (!removed) return NextResponse.json({ success: false, error: `package not found: ${body.source}` }, { status: 404 });
|
|
66
|
+
return NextResponse.json({ success: true, data: { removed } });
|
|
67
|
+
}
|
|
68
|
+
if (body.action === "update") {
|
|
69
|
+
await updatePackages(body.source, cwd);
|
|
70
|
+
return NextResponse.json({ success: true });
|
|
71
|
+
}
|
|
72
|
+
return NextResponse.json({ success: false, error: "unknown action" }, { status: 400 });
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return NextResponse.json(
|
|
75
|
+
{ success: false, error: error instanceof Error ? error.message : "request failed" },
|
|
76
|
+
{ status: error instanceof BoundaryError || error instanceof SyntaxError ? 400 : 500 },
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import type { ProjectTrust } from "@/lib/security-service";
|
|
3
|
+
import { getSecurity, setProjectTrust } from "@/lib/security-service";
|
|
4
|
+
import { reloadLoader, setProjectTrust as setProjectTrustDecision } from "@/lib/pi";
|
|
5
|
+
import { reloadSessionsForCwd } from "@/lib/agent-manager";
|
|
6
|
+
import { BoundaryError, resolveWorkspacePath } from "@/lib/path-security";
|
|
7
|
+
|
|
8
|
+
export const dynamic = "force-dynamic";
|
|
9
|
+
|
|
10
|
+
export async function GET() {
|
|
11
|
+
try {
|
|
12
|
+
return NextResponse.json({ success: true, data: await getSecurity() });
|
|
13
|
+
} catch (err: any) {
|
|
14
|
+
return NextResponse.json({ success: false, error: String(err?.message ?? err) }, { status: 500 });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function PUT(req: Request) {
|
|
19
|
+
try {
|
|
20
|
+
const body = (await req.json()) as { defaultProjectTrust?: ProjectTrust; projectTrust?: { cwd?: unknown; decision?: unknown } };
|
|
21
|
+
// 按目录记住信任决定(写 ~/.pi/agent/trust.json,与 pi 终端共用)
|
|
22
|
+
if (body.projectTrust) {
|
|
23
|
+
const cwd = await resolveWorkspacePath(body.projectTrust.cwd);
|
|
24
|
+
const decision = body.projectTrust.decision;
|
|
25
|
+
if (decision !== true && decision !== false && decision !== null)
|
|
26
|
+
return NextResponse.json({ success: false, error: "decision must be true / false / null" }, { status: 400 });
|
|
27
|
+
const state = setProjectTrustDecision(cwd, decision);
|
|
28
|
+
// 让该目录的加载器按新信任态重新发现资源,活跃会话重建扩展运行时,页面拿到新清单
|
|
29
|
+
await reloadLoader(cwd);
|
|
30
|
+
await reloadSessionsForCwd(cwd);
|
|
31
|
+
return NextResponse.json({ success: true, data: state });
|
|
32
|
+
}
|
|
33
|
+
const v = body.defaultProjectTrust;
|
|
34
|
+
if (v !== "ask" && v !== "always" && v !== "never")
|
|
35
|
+
return NextResponse.json({ success: false, error: "bad value" }, { status: 400 });
|
|
36
|
+
await setProjectTrust(v);
|
|
37
|
+
return NextResponse.json({ success: true });
|
|
38
|
+
} catch (err: any) {
|
|
39
|
+
return NextResponse.json({ success: false, error: String(err?.message ?? err) }, { status: err instanceof BoundaryError ? 400 : 500 });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { exportSession, getManaged } from "@/lib/agent-manager";
|
|
3
|
+
import { BoundaryError, resolveSessionPath } from "@/lib/path-security";
|
|
4
|
+
|
|
5
|
+
export const dynamic = "force-dynamic";
|
|
6
|
+
|
|
7
|
+
export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
8
|
+
const { id } = await params;
|
|
9
|
+
const format = new URL(req.url).searchParams.get("format") === "html" ? "html" : "jsonl";
|
|
10
|
+
try {
|
|
11
|
+
const m = getManaged(await resolveSessionPath(id));
|
|
12
|
+
const { filename, content, contentType } = await exportSession(m, format);
|
|
13
|
+
return new Response(content, {
|
|
14
|
+
headers: {
|
|
15
|
+
"Content-Type": `${contentType}; charset=utf-8`,
|
|
16
|
+
"Content-Disposition": `attachment; filename="${encodeURIComponent(filename)}"`,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
} catch (error) {
|
|
20
|
+
return NextResponse.json(
|
|
21
|
+
{ success: false, error: error instanceof Error ? error.message : "request failed" },
|
|
22
|
+
{ status: error instanceof BoundaryError ? 400 : 500 },
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { deleteSession, readSession } from "@/lib/session-reader";
|
|
3
|
+
import { disposeSessionPath } from "@/lib/agent-manager";
|
|
4
|
+
import { BoundaryError, resolveSessionPath } from "@/lib/path-security";
|
|
5
|
+
import { forgetSession } from "@/lib/workspace-store";
|
|
6
|
+
|
|
7
|
+
export const dynamic = "force-dynamic";
|
|
8
|
+
|
|
9
|
+
/** 冷读取(不启动 agent) */
|
|
10
|
+
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
11
|
+
const { id } = await params;
|
|
12
|
+
try {
|
|
13
|
+
const data = await readSession(await resolveSessionPath(id));
|
|
14
|
+
return NextResponse.json({ success: true, data });
|
|
15
|
+
} catch (error) {
|
|
16
|
+
return NextResponse.json(
|
|
17
|
+
{ success: false, error: error instanceof Error ? error.message : "request failed" },
|
|
18
|
+
{ status: error instanceof BoundaryError ? 400 : 500 },
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
24
|
+
const { id } = await params;
|
|
25
|
+
try {
|
|
26
|
+
const sessionPath = await resolveSessionPath(id, { allowPending: true });
|
|
27
|
+
await disposeSessionPath(sessionPath);
|
|
28
|
+
await deleteSession(sessionPath);
|
|
29
|
+
await forgetSession(sessionPath);
|
|
30
|
+
return NextResponse.json({ success: true });
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return NextResponse.json(
|
|
33
|
+
{ success: false, error: error instanceof Error ? error.message : "request failed" },
|
|
34
|
+
{ status: error instanceof BoundaryError ? 400 : 500 },
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
import { listSessions } from "@/lib/session-reader";
|
|
4
|
+
import { activeStatus } from "@/lib/agent-manager";
|
|
5
|
+
import { getWorkspaceRegistry, registerCwds } from "@/lib/workspace-store";
|
|
6
|
+
|
|
7
|
+
export const dynamic = "force-dynamic";
|
|
8
|
+
|
|
9
|
+
export async function GET(req: Request) {
|
|
10
|
+
try {
|
|
11
|
+
const q = new URL(req.url).searchParams.get("q") ?? undefined;
|
|
12
|
+
const sessions = await listSessions(q);
|
|
13
|
+
await registerCwds(sessions.map((session) => session.cwd));
|
|
14
|
+
const workspaceRegistry = await getWorkspaceRegistry();
|
|
15
|
+
// 侧栏每 3 秒轮询:内容没变就 304,浏览器复用缓存体(no-cache = 每次都回源校验,不会用陈旧数据)
|
|
16
|
+
const body = JSON.stringify({ success: true, data: { sessions, running: activeStatus(), workspaceRegistry } });
|
|
17
|
+
const etag = `"${createHash("sha1").update(body).digest("base64url")}"`;
|
|
18
|
+
if (req.headers.get("if-none-match") === etag) return new Response(null, { status: 304, headers: { ETag: etag, "Cache-Control": "no-cache" } });
|
|
19
|
+
return new Response(body, { headers: { "Content-Type": "application/json; charset=utf-8", ETag: etag, "Cache-Control": "no-cache" } });
|
|
20
|
+
} catch (err: any) {
|
|
21
|
+
return NextResponse.json({ success: false, error: String(err?.message ?? err) }, { status: 500 });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { listSkills, readSkillFile, setSkillDisabled } from "@/lib/skills-service";
|
|
3
|
+
import { BoundaryError, resolveWorkspacePath } from "@/lib/path-security";
|
|
4
|
+
|
|
5
|
+
export const dynamic = "force-dynamic";
|
|
6
|
+
|
|
7
|
+
export async function GET(req: Request) {
|
|
8
|
+
try {
|
|
9
|
+
const rawCwd = new URL(req.url).searchParams.get("cwd");
|
|
10
|
+
const cwd = await resolveWorkspacePath(rawCwd || process.cwd());
|
|
11
|
+
const skills = await listSkills(cwd);
|
|
12
|
+
return NextResponse.json({ success: true, data: { skills } });
|
|
13
|
+
} catch (err: any) {
|
|
14
|
+
return NextResponse.json({ success: false, error: String(err?.message ?? err) }, { status: err instanceof BoundaryError ? 400 : 500 });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function POST(req: Request) {
|
|
19
|
+
try {
|
|
20
|
+
const body = (await req.json()) as { action: "toggle" | "read"; filePath?: string; disabled?: boolean; cwd?: string };
|
|
21
|
+
const cwd = await resolveWorkspacePath(body.cwd || process.cwd());
|
|
22
|
+
if (body.action === "toggle") {
|
|
23
|
+
if (!body.filePath || typeof body.disabled !== "boolean")
|
|
24
|
+
return NextResponse.json({ success: false, error: "missing fields" }, { status: 400 });
|
|
25
|
+
const r = await setSkillDisabled(body.filePath, body.disabled, cwd);
|
|
26
|
+
return NextResponse.json({ success: true, data: r });
|
|
27
|
+
}
|
|
28
|
+
if (body.action === "read") {
|
|
29
|
+
if (!body.filePath) return NextResponse.json({ success: false, error: "missing filePath" }, { status: 400 });
|
|
30
|
+
return NextResponse.json({ success: true, data: { content: await readSkillFile(body.filePath, cwd) } });
|
|
31
|
+
}
|
|
32
|
+
return NextResponse.json({ success: false, error: "unknown action" }, { status: 400 });
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return NextResponse.json(
|
|
35
|
+
{ success: false, error: error instanceof Error ? error.message : "request failed" },
|
|
36
|
+
{ status: error instanceof BoundaryError ? 400 : 500 },
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { checkForUpdate, runUpdate, UpdateBusyError, type UpdateTarget } from "@/lib/update-service";
|
|
3
|
+
import { activeStatus } from "@/lib/agent-manager";
|
|
4
|
+
|
|
5
|
+
export const dynamic = "force-dynamic";
|
|
6
|
+
|
|
7
|
+
/** Fixed targets/actions only. Runtime authorization and origin checks live in proxy. */
|
|
8
|
+
export async function POST(req: Request) {
|
|
9
|
+
let body: unknown;
|
|
10
|
+
try { body = await req.json(); }
|
|
11
|
+
catch { return NextResponse.json({ success: false, error: "invalid JSON body" }, { status: 400 }); }
|
|
12
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
13
|
+
return NextResponse.json({ success: false, error: "expected an object body" }, { status: 400 });
|
|
14
|
+
}
|
|
15
|
+
const { target, action } = body as { target?: UpdateTarget; action?: string };
|
|
16
|
+
if (target !== "piweb" && target !== "pi") return NextResponse.json({ success: false, error: "unknown target" }, { status: 400 });
|
|
17
|
+
if (action !== "check" && action !== "update") return NextResponse.json({ success: false, error: "unknown action" }, { status: 400 });
|
|
18
|
+
try {
|
|
19
|
+
if (action === "check") return NextResponse.json({ success: true, data: await checkForUpdate(target) });
|
|
20
|
+
const assertIdle = async () => {
|
|
21
|
+
if (Object.values(activeStatus()).some((session) => session.streaming)) {
|
|
22
|
+
throw new UpdateBusyError("An agent session is running. Wait for it to finish and update in a maintenance window; do not start new sessions until the release is ready.");
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
// Check before even taking a release lock; rechecked before each mutating step.
|
|
26
|
+
await assertIdle();
|
|
27
|
+
return NextResponse.json({ success: true, data: await runUpdate(target, { assertIdle }) });
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return NextResponse.json({ success: false, error: error instanceof Error ? error.message : String(error) }, {
|
|
30
|
+
status: error instanceof UpdateBusyError ? 409 : 500,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { getRuntimeVersions } from "@/lib/version";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-dynamic";
|
|
5
|
+
|
|
6
|
+
export async function GET() {
|
|
7
|
+
return NextResponse.json({ success: true, data: { ...await getRuntimeVersions(), node: process.version } }, { headers: { "Cache-Control": "no-store" } });
|
|
8
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 网页认证:GET 状态 / POST 登录(写会话 Cookie)/ DELETE 登出(清 Cookie)。
|
|
3
|
+
* 无密码配置时整体关闭(GET 返回 enabled:false,POST 404)。
|
|
4
|
+
*/
|
|
5
|
+
import { NextResponse } from "next/server";
|
|
6
|
+
import { isSafeHost, isSafeOrigin, validBasicAuthorization } from "@/lib/auth";
|
|
7
|
+
import { SESSION_COOKIE, clearedCookieOptions, createSessionToken, readCookieHeader, safeNextPath, sessionCookieOptions, validPassword, validSessionToken } from "@/lib/web-auth";
|
|
8
|
+
|
|
9
|
+
export const dynamic = "force-dynamic";
|
|
10
|
+
|
|
11
|
+
function untrusted(request: Request): NextResponse | null {
|
|
12
|
+
const password = process.env.PI_WEB_PASSWORD;
|
|
13
|
+
if (!isSafeHost(request, Boolean(password))) {
|
|
14
|
+
return NextResponse.json({ success: false, error: "untrusted host" }, { status: 403 });
|
|
15
|
+
}
|
|
16
|
+
if (request.method !== "GET" && request.method !== "HEAD" && !isSafeOrigin(request)) {
|
|
17
|
+
return NextResponse.json({ success: false, error: "cross-origin request rejected" }, { status: 403 });
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function GET(request: Request) {
|
|
23
|
+
const blocked = untrusted(request);
|
|
24
|
+
if (blocked) return blocked;
|
|
25
|
+
const password = process.env.PI_WEB_PASSWORD;
|
|
26
|
+
const enabled = Boolean(password);
|
|
27
|
+
const authenticated = !enabled
|
|
28
|
+
|| validBasicAuthorization(request.headers.get("authorization"), password!)
|
|
29
|
+
|| validSessionToken(readCookieHeader(request.headers.get("cookie"), SESSION_COOKIE), password!);
|
|
30
|
+
return NextResponse.json({ success: true, data: { enabled, authenticated } }, { headers: { "Cache-Control": "no-store" } });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function POST(request: Request) {
|
|
34
|
+
const blocked = untrusted(request);
|
|
35
|
+
if (blocked) return blocked;
|
|
36
|
+
const password = process.env.PI_WEB_PASSWORD;
|
|
37
|
+
if (!password) return NextResponse.json({ success: false, error: "password authentication is disabled" }, { status: 404 });
|
|
38
|
+
if (!(request.headers.get("content-type") ?? "").includes("application/json")) {
|
|
39
|
+
return NextResponse.json({ success: false, error: "Content-Type must be application/json" }, { status: 415 });
|
|
40
|
+
}
|
|
41
|
+
let body: { password?: unknown; next?: unknown };
|
|
42
|
+
try {
|
|
43
|
+
const reader = request.body?.getReader();
|
|
44
|
+
if (!reader) return NextResponse.json({ success: false, error: "missing login payload" }, { status: 400 });
|
|
45
|
+
const chunks: Uint8Array[] = [];
|
|
46
|
+
let size = 0;
|
|
47
|
+
try {
|
|
48
|
+
for (;;) {
|
|
49
|
+
const { value, done } = await reader.read();
|
|
50
|
+
if (done) break;
|
|
51
|
+
size += value.byteLength;
|
|
52
|
+
if (size > 16 * 1024) return NextResponse.json({ success: false, error: "login payload too large" }, { status: 413 });
|
|
53
|
+
chunks.push(value);
|
|
54
|
+
}
|
|
55
|
+
} finally {
|
|
56
|
+
await reader.cancel().catch(() => undefined);
|
|
57
|
+
reader.releaseLock();
|
|
58
|
+
}
|
|
59
|
+
body = JSON.parse(Buffer.concat(chunks).toString("utf8")) as { password?: unknown; next?: unknown };
|
|
60
|
+
} catch {
|
|
61
|
+
return NextResponse.json({ success: false, error: "invalid JSON body" }, { status: 400 });
|
|
62
|
+
}
|
|
63
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
64
|
+
return NextResponse.json({ success: false, error: "invalid login payload" }, { status: 400 });
|
|
65
|
+
}
|
|
66
|
+
if (typeof body.password !== "string" || !validPassword(body.password, password)) {
|
|
67
|
+
return NextResponse.json({ success: false, error: "invalid password" }, { status: 401 });
|
|
68
|
+
}
|
|
69
|
+
const response = NextResponse.json({ success: true, data: { next: safeNextPath(typeof body.next === "string" ? body.next : null) } }, { headers: { "Cache-Control": "no-store" } });
|
|
70
|
+
response.cookies.set(SESSION_COOKIE, createSessionToken(password), sessionCookieOptions(request));
|
|
71
|
+
return response;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function DELETE(request: Request) {
|
|
75
|
+
const blocked = untrusted(request);
|
|
76
|
+
if (blocked) return blocked;
|
|
77
|
+
const response = NextResponse.json({ success: true }, { headers: { "Cache-Control": "no-store" } });
|
|
78
|
+
response.cookies.set(SESSION_COOKIE, "", clearedCookieOptions(request));
|
|
79
|
+
return response;
|
|
80
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import {
|
|
3
|
+
addWorkspace,
|
|
4
|
+
archiveSession,
|
|
5
|
+
forgetSession,
|
|
6
|
+
getRemovedWorkspaces,
|
|
7
|
+
getWorkspaceRegistry,
|
|
8
|
+
pickFolderNative,
|
|
9
|
+
registerCwds,
|
|
10
|
+
removeWorkspace,
|
|
11
|
+
setAlias,
|
|
12
|
+
} from "@/lib/workspace-store";
|
|
13
|
+
import { encodeSessionId } from "@/lib/pi";
|
|
14
|
+
import { BoundaryError, resolveSessionPath, resolveWorkspacePath } from "@/lib/path-security";
|
|
15
|
+
|
|
16
|
+
export const dynamic = "force-dynamic";
|
|
17
|
+
|
|
18
|
+
export async function GET() {
|
|
19
|
+
try {
|
|
20
|
+
return NextResponse.json({ success: true, data: await getWorkspaceRegistry() });
|
|
21
|
+
} catch (err: any) {
|
|
22
|
+
return NextResponse.json({ success: false, error: String(err?.message ?? err) }, { status: 500 });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function POST(req: Request) {
|
|
27
|
+
try {
|
|
28
|
+
const body = (await req.json()) as {
|
|
29
|
+
action: "add" | "remove" | "pick" | "rename" | "archiveSession" | "forgetSession" | "registerCwds";
|
|
30
|
+
path?: string;
|
|
31
|
+
name?: string;
|
|
32
|
+
cwds?: string[];
|
|
33
|
+
};
|
|
34
|
+
if (body.action === "pick") {
|
|
35
|
+
const r = await pickFolderNative();
|
|
36
|
+
const registry = r.path ? await addWorkspace(await resolveWorkspacePath(r.path)) : null;
|
|
37
|
+
return NextResponse.json({ success: true, data: { ...r, ...(registry ?? {}) } });
|
|
38
|
+
}
|
|
39
|
+
if (body.action === "add") {
|
|
40
|
+
if (!body.path) return NextResponse.json({ success: false, error: "missing path" }, { status: 400 });
|
|
41
|
+
return NextResponse.json({ success: true, data: await addWorkspace(await resolveWorkspacePath(body.path)) });
|
|
42
|
+
}
|
|
43
|
+
if (body.action === "remove") {
|
|
44
|
+
if (!body.path) return NextResponse.json({ success: false, error: "missing path" }, { status: 400 });
|
|
45
|
+
const r = await removeWorkspace(body.path);
|
|
46
|
+
return NextResponse.json({ success: true, data: r });
|
|
47
|
+
}
|
|
48
|
+
if (body.action === "rename") {
|
|
49
|
+
if (!body.path) return NextResponse.json({ success: false, error: "missing path" }, { status: 400 });
|
|
50
|
+
const aliases = await setAlias(body.path, body.name ?? "");
|
|
51
|
+
return NextResponse.json({ success: true, data: { aliases } });
|
|
52
|
+
}
|
|
53
|
+
if (body.action === "archiveSession") {
|
|
54
|
+
if (!body.path) return NextResponse.json({ success: false, error: "missing path" }, { status: 400 });
|
|
55
|
+
const sessionPath = await resolveSessionPath(encodeSessionId(body.path), { allowPending: true });
|
|
56
|
+
const archivedSessions = await archiveSession(sessionPath);
|
|
57
|
+
return NextResponse.json({ success: true, data: { archivedSessions } });
|
|
58
|
+
}
|
|
59
|
+
if (body.action === "forgetSession") {
|
|
60
|
+
if (!body.path) return NextResponse.json({ success: false, error: "missing path" }, { status: 400 });
|
|
61
|
+
const sessionPath = await resolveSessionPath(encodeSessionId(body.path), { allowPending: true });
|
|
62
|
+
const archivedSessions = await forgetSession(sessionPath);
|
|
63
|
+
return NextResponse.json({ success: true, data: { archivedSessions } });
|
|
64
|
+
}
|
|
65
|
+
if (body.action === "registerCwds") {
|
|
66
|
+
if (!Array.isArray(body.cwds)) return NextResponse.json({ success: false, error: "missing cwds" }, { status: 400 });
|
|
67
|
+
const workspaces = await registerCwds(body.cwds);
|
|
68
|
+
const removedWorkspaces = await getRemovedWorkspaces();
|
|
69
|
+
return NextResponse.json({ success: true, data: { workspaces, removedWorkspaces } });
|
|
70
|
+
}
|
|
71
|
+
return NextResponse.json({ success: false, error: "unknown action" }, { status: 400 });
|
|
72
|
+
} catch (err: any) {
|
|
73
|
+
return NextResponse.json(
|
|
74
|
+
{ success: false, error: String(err?.message ?? err) },
|
|
75
|
+
{ status: err instanceof BoundaryError ? 400 : 500 },
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|