@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.
Files changed (535) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +128 -0
  3. package/assets/showcase.png +0 -0
  4. package/bin/pi.js +50 -0
  5. package/bin/piweb.js +12 -0
  6. package/next.config.ts +21 -0
  7. package/package.json +88 -0
  8. package/postcss.config.mjs +7 -0
  9. package/public/file-icons/3d.svg +1 -0
  10. package/public/file-icons/LICENSE +8 -0
  11. package/public/file-icons/adobe-illustrator.svg +1 -0
  12. package/public/file-icons/adobe-photoshop.svg +1 -0
  13. package/public/file-icons/agent.svg +1 -0
  14. package/public/file-icons/asciidoc.svg +1 -0
  15. package/public/file-icons/assembly.svg +1 -0
  16. package/public/file-icons/astro.svg +1 -0
  17. package/public/file-icons/audio.svg +1 -0
  18. package/public/file-icons/authors.svg +1 -0
  19. package/public/file-icons/babel.svg +1 -0
  20. package/public/file-icons/bibliography.svg +1 -0
  21. package/public/file-icons/bicep.svg +1 -0
  22. package/public/file-icons/blender.svg +1 -0
  23. package/public/file-icons/bun.svg +1 -0
  24. package/public/file-icons/c.svg +1 -0
  25. package/public/file-icons/certificate.svg +1 -0
  26. package/public/file-icons/changelog.svg +1 -0
  27. package/public/file-icons/clojure.svg +1 -0
  28. package/public/file-icons/cmake.svg +1 -0
  29. package/public/file-icons/codeowners.svg +1 -0
  30. package/public/file-icons/commitlint.svg +1 -0
  31. package/public/file-icons/conduct.svg +1 -0
  32. package/public/file-icons/console.svg +1 -0
  33. package/public/file-icons/contributing.svg +1 -0
  34. package/public/file-icons/cpp.svg +1 -0
  35. package/public/file-icons/csharp.svg +1 -0
  36. package/public/file-icons/css.svg +1 -0
  37. package/public/file-icons/cursor.svg +1 -0
  38. package/public/file-icons/dart.svg +1 -0
  39. package/public/file-icons/database.svg +1 -0
  40. package/public/file-icons/deno.svg +1 -0
  41. package/public/file-icons/diff.svg +1 -0
  42. package/public/file-icons/docker.svg +1 -0
  43. package/public/file-icons/document.svg +1 -0
  44. package/public/file-icons/editorconfig.svg +1 -0
  45. package/public/file-icons/ejs.svg +1 -0
  46. package/public/file-icons/elixir.svg +1 -0
  47. package/public/file-icons/elm.svg +1 -0
  48. package/public/file-icons/email.svg +1 -0
  49. package/public/file-icons/erlang.svg +1 -0
  50. package/public/file-icons/eslint.svg +1 -0
  51. package/public/file-icons/favicon.svg +1 -0
  52. package/public/file-icons/file.svg +1 -0
  53. package/public/file-icons/folder-admin-open.svg +1 -0
  54. package/public/file-icons/folder-admin.svg +1 -0
  55. package/public/file-icons/folder-android-open.svg +1 -0
  56. package/public/file-icons/folder-android.svg +1 -0
  57. package/public/file-icons/folder-api-open.svg +1 -0
  58. package/public/file-icons/folder-api.svg +1 -0
  59. package/public/file-icons/folder-app-open.svg +1 -0
  60. package/public/file-icons/folder-app.svg +1 -0
  61. package/public/file-icons/folder-archive-open.svg +1 -0
  62. package/public/file-icons/folder-archive.svg +1 -0
  63. package/public/file-icons/folder-atom-open.svg +1 -0
  64. package/public/file-icons/folder-atom.svg +1 -0
  65. package/public/file-icons/folder-audio-open.svg +1 -0
  66. package/public/file-icons/folder-audio.svg +1 -0
  67. package/public/file-icons/folder-aws-open.svg +1 -0
  68. package/public/file-icons/folder-aws.svg +1 -0
  69. package/public/file-icons/folder-backup-open.svg +1 -0
  70. package/public/file-icons/folder-backup.svg +1 -0
  71. package/public/file-icons/folder-benchmark-open.svg +1 -0
  72. package/public/file-icons/folder-benchmark.svg +1 -0
  73. package/public/file-icons/folder-cart-open.svg +1 -0
  74. package/public/file-icons/folder-cart.svg +1 -0
  75. package/public/file-icons/folder-ci-open.svg +1 -0
  76. package/public/file-icons/folder-ci.svg +1 -0
  77. package/public/file-icons/folder-circleci-open.svg +1 -0
  78. package/public/file-icons/folder-circleci.svg +1 -0
  79. package/public/file-icons/folder-class-open.svg +1 -0
  80. package/public/file-icons/folder-class.svg +1 -0
  81. package/public/file-icons/folder-client-open.svg +1 -0
  82. package/public/file-icons/folder-client.svg +1 -0
  83. package/public/file-icons/folder-command-open.svg +1 -0
  84. package/public/file-icons/folder-command.svg +1 -0
  85. package/public/file-icons/folder-components-open.svg +1 -0
  86. package/public/file-icons/folder-components.svg +1 -0
  87. package/public/file-icons/folder-config-open.svg +1 -0
  88. package/public/file-icons/folder-config.svg +1 -0
  89. package/public/file-icons/folder-console-open.svg +1 -0
  90. package/public/file-icons/folder-console.svg +1 -0
  91. package/public/file-icons/folder-content-open.svg +1 -0
  92. package/public/file-icons/folder-content.svg +1 -0
  93. package/public/file-icons/folder-context-open.svg +1 -0
  94. package/public/file-icons/folder-context.svg +1 -0
  95. package/public/file-icons/folder-contract-open.svg +1 -0
  96. package/public/file-icons/folder-contract.svg +1 -0
  97. package/public/file-icons/folder-controller-open.svg +1 -0
  98. package/public/file-icons/folder-controller.svg +1 -0
  99. package/public/file-icons/folder-core-open.svg +1 -0
  100. package/public/file-icons/folder-core.svg +1 -0
  101. package/public/file-icons/folder-coverage-open.svg +1 -0
  102. package/public/file-icons/folder-coverage.svg +1 -0
  103. package/public/file-icons/folder-css-open.svg +1 -0
  104. package/public/file-icons/folder-css.svg +1 -0
  105. package/public/file-icons/folder-database-open.svg +1 -0
  106. package/public/file-icons/folder-database.svg +1 -0
  107. package/public/file-icons/folder-debug-open.svg +1 -0
  108. package/public/file-icons/folder-debug.svg +1 -0
  109. package/public/file-icons/folder-deprecated-open.svg +1 -0
  110. package/public/file-icons/folder-deprecated.svg +1 -0
  111. package/public/file-icons/folder-desktop-open.svg +1 -0
  112. package/public/file-icons/folder-desktop.svg +1 -0
  113. package/public/file-icons/folder-dist-open.svg +1 -0
  114. package/public/file-icons/folder-dist.svg +1 -0
  115. package/public/file-icons/folder-docker-open.svg +1 -0
  116. package/public/file-icons/folder-docker.svg +1 -0
  117. package/public/file-icons/folder-docs-open.svg +1 -0
  118. package/public/file-icons/folder-docs.svg +1 -0
  119. package/public/file-icons/folder-element-open.svg +1 -0
  120. package/public/file-icons/folder-element.svg +1 -0
  121. package/public/file-icons/folder-environment-open.svg +1 -0
  122. package/public/file-icons/folder-environment.svg +1 -0
  123. package/public/file-icons/folder-event-open.svg +1 -0
  124. package/public/file-icons/folder-event.svg +1 -0
  125. package/public/file-icons/folder-examples-open.svg +1 -0
  126. package/public/file-icons/folder-examples.svg +1 -0
  127. package/public/file-icons/folder-features-open.svg +1 -0
  128. package/public/file-icons/folder-features.svg +1 -0
  129. package/public/file-icons/folder-font-open.svg +1 -0
  130. package/public/file-icons/folder-font.svg +1 -0
  131. package/public/file-icons/folder-functions-open.svg +1 -0
  132. package/public/file-icons/folder-functions.svg +1 -0
  133. package/public/file-icons/folder-generator-open.svg +1 -0
  134. package/public/file-icons/folder-generator.svg +1 -0
  135. package/public/file-icons/folder-git-open.svg +1 -0
  136. package/public/file-icons/folder-git.svg +1 -0
  137. package/public/file-icons/folder-github-open.svg +1 -0
  138. package/public/file-icons/folder-github.svg +1 -0
  139. package/public/file-icons/folder-gitlab-open.svg +1 -0
  140. package/public/file-icons/folder-gitlab.svg +1 -0
  141. package/public/file-icons/folder-graphql-open.svg +1 -0
  142. package/public/file-icons/folder-graphql.svg +1 -0
  143. package/public/file-icons/folder-helper-open.svg +1 -0
  144. package/public/file-icons/folder-helper.svg +1 -0
  145. package/public/file-icons/folder-hook-open.svg +1 -0
  146. package/public/file-icons/folder-hook.svg +1 -0
  147. package/public/file-icons/folder-husky-open.svg +1 -0
  148. package/public/file-icons/folder-husky.svg +1 -0
  149. package/public/file-icons/folder-i18n-open.svg +1 -0
  150. package/public/file-icons/folder-i18n.svg +1 -0
  151. package/public/file-icons/folder-images-open.svg +1 -0
  152. package/public/file-icons/folder-images.svg +1 -0
  153. package/public/file-icons/folder-include-open.svg +1 -0
  154. package/public/file-icons/folder-include.svg +1 -0
  155. package/public/file-icons/folder-intellij-open.svg +1 -0
  156. package/public/file-icons/folder-intellij.svg +1 -0
  157. package/public/file-icons/folder-interface-open.svg +1 -0
  158. package/public/file-icons/folder-interface.svg +1 -0
  159. package/public/file-icons/folder-ios-open.svg +1 -0
  160. package/public/file-icons/folder-ios.svg +1 -0
  161. package/public/file-icons/folder-java-open.svg +1 -0
  162. package/public/file-icons/folder-java.svg +1 -0
  163. package/public/file-icons/folder-job-open.svg +1 -0
  164. package/public/file-icons/folder-job.svg +1 -0
  165. package/public/file-icons/folder-jupyter-open.svg +1 -0
  166. package/public/file-icons/folder-jupyter.svg +1 -0
  167. package/public/file-icons/folder-keys-open.svg +1 -0
  168. package/public/file-icons/folder-keys.svg +1 -0
  169. package/public/file-icons/folder-kubernetes-open.svg +1 -0
  170. package/public/file-icons/folder-kubernetes.svg +1 -0
  171. package/public/file-icons/folder-layout-open.svg +1 -0
  172. package/public/file-icons/folder-layout.svg +1 -0
  173. package/public/file-icons/folder-less-open.svg +1 -0
  174. package/public/file-icons/folder-less.svg +1 -0
  175. package/public/file-icons/folder-lib-open.svg +1 -0
  176. package/public/file-icons/folder-lib.svg +1 -0
  177. package/public/file-icons/folder-log-open.svg +1 -0
  178. package/public/file-icons/folder-log.svg +1 -0
  179. package/public/file-icons/folder-messages-open.svg +1 -0
  180. package/public/file-icons/folder-messages.svg +1 -0
  181. package/public/file-icons/folder-meta-open.svg +1 -0
  182. package/public/file-icons/folder-meta.svg +1 -0
  183. package/public/file-icons/folder-middleware-open.svg +1 -0
  184. package/public/file-icons/folder-middleware.svg +1 -0
  185. package/public/file-icons/folder-migrations-open.svg +1 -0
  186. package/public/file-icons/folder-migrations.svg +1 -0
  187. package/public/file-icons/folder-mobile-open.svg +1 -0
  188. package/public/file-icons/folder-mobile.svg +1 -0
  189. package/public/file-icons/folder-mock-open.svg +1 -0
  190. package/public/file-icons/folder-mock.svg +1 -0
  191. package/public/file-icons/folder-molecule-open.svg +1 -0
  192. package/public/file-icons/folder-molecule.svg +1 -0
  193. package/public/file-icons/folder-next-open.svg +1 -0
  194. package/public/file-icons/folder-next.svg +1 -0
  195. package/public/file-icons/folder-node-open.svg +1 -0
  196. package/public/file-icons/folder-node.svg +1 -0
  197. package/public/file-icons/folder-nuxt-open.svg +1 -0
  198. package/public/file-icons/folder-nuxt.svg +1 -0
  199. package/public/file-icons/folder-open.svg +1 -0
  200. package/public/file-icons/folder-organism-open.svg +1 -0
  201. package/public/file-icons/folder-organism.svg +1 -0
  202. package/public/file-icons/folder-packages-open.svg +1 -0
  203. package/public/file-icons/folder-packages.svg +1 -0
  204. package/public/file-icons/folder-plugin-open.svg +1 -0
  205. package/public/file-icons/folder-plugin.svg +1 -0
  206. package/public/file-icons/folder-prisma-open.svg +1 -0
  207. package/public/file-icons/folder-prisma.svg +1 -0
  208. package/public/file-icons/folder-prompts-open.svg +1 -0
  209. package/public/file-icons/folder-prompts.svg +1 -0
  210. package/public/file-icons/folder-proto-open.svg +1 -0
  211. package/public/file-icons/folder-proto.svg +1 -0
  212. package/public/file-icons/folder-public-open.svg +1 -0
  213. package/public/file-icons/folder-public.svg +1 -0
  214. package/public/file-icons/folder-python-open.svg +1 -0
  215. package/public/file-icons/folder-python.svg +1 -0
  216. package/public/file-icons/folder-queue-open.svg +1 -0
  217. package/public/file-icons/folder-queue.svg +1 -0
  218. package/public/file-icons/folder-resource-open.svg +1 -0
  219. package/public/file-icons/folder-resource.svg +1 -0
  220. package/public/file-icons/folder-review-open.svg +1 -0
  221. package/public/file-icons/folder-review.svg +1 -0
  222. package/public/file-icons/folder-robot-open.svg +1 -0
  223. package/public/file-icons/folder-robot.svg +1 -0
  224. package/public/file-icons/folder-routes-open.svg +1 -0
  225. package/public/file-icons/folder-routes.svg +1 -0
  226. package/public/file-icons/folder-rust-open.svg +1 -0
  227. package/public/file-icons/folder-rust.svg +1 -0
  228. package/public/file-icons/folder-sass-open.svg +1 -0
  229. package/public/file-icons/folder-sass.svg +1 -0
  230. package/public/file-icons/folder-scripts-open.svg +1 -0
  231. package/public/file-icons/folder-scripts.svg +1 -0
  232. package/public/file-icons/folder-secure-open.svg +1 -0
  233. package/public/file-icons/folder-secure.svg +1 -0
  234. package/public/file-icons/folder-seeders-open.svg +1 -0
  235. package/public/file-icons/folder-seeders.svg +1 -0
  236. package/public/file-icons/folder-server-open.svg +1 -0
  237. package/public/file-icons/folder-server.svg +1 -0
  238. package/public/file-icons/folder-shared-open.svg +1 -0
  239. package/public/file-icons/folder-shared.svg +1 -0
  240. package/public/file-icons/folder-skills-open.svg +1 -0
  241. package/public/file-icons/folder-skills.svg +1 -0
  242. package/public/file-icons/folder-src-open.svg +1 -0
  243. package/public/file-icons/folder-src.svg +1 -0
  244. package/public/file-icons/folder-store-open.svg +1 -0
  245. package/public/file-icons/folder-store.svg +1 -0
  246. package/public/file-icons/folder-storybook-open.svg +1 -0
  247. package/public/file-icons/folder-storybook.svg +1 -0
  248. package/public/file-icons/folder-supabase-open.svg +1 -0
  249. package/public/file-icons/folder-supabase.svg +1 -0
  250. package/public/file-icons/folder-svelte-open.svg +1 -0
  251. package/public/file-icons/folder-svelte.svg +1 -0
  252. package/public/file-icons/folder-target-open.svg +1 -0
  253. package/public/file-icons/folder-target.svg +1 -0
  254. package/public/file-icons/folder-tasks-open.svg +1 -0
  255. package/public/file-icons/folder-tasks.svg +1 -0
  256. package/public/file-icons/folder-temp-open.svg +1 -0
  257. package/public/file-icons/folder-temp.svg +1 -0
  258. package/public/file-icons/folder-template-open.svg +1 -0
  259. package/public/file-icons/folder-template.svg +1 -0
  260. package/public/file-icons/folder-terraform-open.svg +1 -0
  261. package/public/file-icons/folder-terraform.svg +1 -0
  262. package/public/file-icons/folder-test-open.svg +1 -0
  263. package/public/file-icons/folder-test.svg +1 -0
  264. package/public/file-icons/folder-theme-open.svg +1 -0
  265. package/public/file-icons/folder-theme.svg +1 -0
  266. package/public/file-icons/folder-tools-open.svg +1 -0
  267. package/public/file-icons/folder-tools.svg +1 -0
  268. package/public/file-icons/folder-turborepo-open.svg +1 -0
  269. package/public/file-icons/folder-turborepo.svg +1 -0
  270. package/public/file-icons/folder-typescript-open.svg +1 -0
  271. package/public/file-icons/folder-typescript.svg +1 -0
  272. package/public/file-icons/folder-ui-open.svg +1 -0
  273. package/public/file-icons/folder-ui.svg +1 -0
  274. package/public/file-icons/folder-utils-open.svg +1 -0
  275. package/public/file-icons/folder-utils.svg +1 -0
  276. package/public/file-icons/folder-video-open.svg +1 -0
  277. package/public/file-icons/folder-video.svg +1 -0
  278. package/public/file-icons/folder-views-open.svg +1 -0
  279. package/public/file-icons/folder-views.svg +1 -0
  280. package/public/file-icons/folder-vscode-open.svg +1 -0
  281. package/public/file-icons/folder-vscode.svg +1 -0
  282. package/public/file-icons/folder.svg +1 -0
  283. package/public/file-icons/font.svg +1 -0
  284. package/public/file-icons/fsharp.svg +1 -0
  285. package/public/file-icons/gemfile.svg +1 -0
  286. package/public/file-icons/git.svg +1 -0
  287. package/public/file-icons/go-mod.svg +1 -0
  288. package/public/file-icons/go.svg +1 -0
  289. package/public/file-icons/gradle.svg +1 -0
  290. package/public/file-icons/graphql.svg +1 -0
  291. package/public/file-icons/groovy.svg +1 -0
  292. package/public/file-icons/h.svg +1 -0
  293. package/public/file-icons/handlebars.svg +1 -0
  294. package/public/file-icons/haskell.svg +1 -0
  295. package/public/file-icons/hcl.svg +1 -0
  296. package/public/file-icons/heroku.svg +1 -0
  297. package/public/file-icons/hpp.svg +1 -0
  298. package/public/file-icons/html.svg +1 -0
  299. package/public/file-icons/husky.svg +1 -0
  300. package/public/file-icons/i18n.svg +1 -0
  301. package/public/file-icons/image.svg +1 -0
  302. package/public/file-icons/java.svg +1 -0
  303. package/public/file-icons/javascript.svg +1 -0
  304. package/public/file-icons/jenkins.svg +1 -0
  305. package/public/file-icons/jest.svg +1 -0
  306. package/public/file-icons/jsconfig.svg +1 -0
  307. package/public/file-icons/json.svg +1 -0
  308. package/public/file-icons/julia.svg +1 -0
  309. package/public/file-icons/jupyter.svg +1 -0
  310. package/public/file-icons/just.svg +1 -0
  311. package/public/file-icons/key.svg +1 -0
  312. package/public/file-icons/kotlin.svg +1 -0
  313. package/public/file-icons/lerna.svg +1 -0
  314. package/public/file-icons/less.svg +1 -0
  315. package/public/file-icons/license.svg +1 -0
  316. package/public/file-icons/lintstaged.svg +1 -0
  317. package/public/file-icons/liquid.svg +1 -0
  318. package/public/file-icons/lock.svg +1 -0
  319. package/public/file-icons/log.svg +1 -0
  320. package/public/file-icons/lua.svg +1 -0
  321. package/public/file-icons/makefile.svg +1 -0
  322. package/public/file-icons/markdown.svg +1 -0
  323. package/public/file-icons/maven.svg +1 -0
  324. package/public/file-icons/mdx.svg +1 -0
  325. package/public/file-icons/netlify.svg +1 -0
  326. package/public/file-icons/next.svg +1 -0
  327. package/public/file-icons/nim.svg +1 -0
  328. package/public/file-icons/nix.svg +1 -0
  329. package/public/file-icons/nodejs.svg +1 -0
  330. package/public/file-icons/nodemon.svg +1 -0
  331. package/public/file-icons/npm.svg +1 -0
  332. package/public/file-icons/nx.svg +1 -0
  333. package/public/file-icons/objective-c.svg +1 -0
  334. package/public/file-icons/objective-cpp.svg +1 -0
  335. package/public/file-icons/ocaml.svg +1 -0
  336. package/public/file-icons/pdf.svg +1 -0
  337. package/public/file-icons/php.svg +1 -0
  338. package/public/file-icons/pm2-ecosystem.svg +1 -0
  339. package/public/file-icons/pnpm.svg +1 -0
  340. package/public/file-icons/poetry.svg +1 -0
  341. package/public/file-icons/postcss.svg +1 -0
  342. package/public/file-icons/powerpoint.svg +1 -0
  343. package/public/file-icons/powershell.svg +1 -0
  344. package/public/file-icons/prettier.svg +1 -0
  345. package/public/file-icons/prisma.svg +1 -0
  346. package/public/file-icons/prolog.svg +1 -0
  347. package/public/file-icons/proto.svg +1 -0
  348. package/public/file-icons/pug.svg +1 -0
  349. package/public/file-icons/python-misc.svg +1 -0
  350. package/public/file-icons/python.svg +1 -0
  351. package/public/file-icons/r.svg +1 -0
  352. package/public/file-icons/razor.svg +1 -0
  353. package/public/file-icons/react.svg +1 -0
  354. package/public/file-icons/react_ts.svg +1 -0
  355. package/public/file-icons/readme.svg +1 -0
  356. package/public/file-icons/renovate.svg +1 -0
  357. package/public/file-icons/robots.svg +1 -0
  358. package/public/file-icons/rollup.svg +1 -0
  359. package/public/file-icons/ruby.svg +1 -0
  360. package/public/file-icons/rust.svg +1 -0
  361. package/public/file-icons/sass.svg +1 -0
  362. package/public/file-icons/scala.svg +1 -0
  363. package/public/file-icons/semantic-release.svg +1 -0
  364. package/public/file-icons/settings.svg +1 -0
  365. package/public/file-icons/shader.svg +1 -0
  366. package/public/file-icons/sml.svg +1 -0
  367. package/public/file-icons/solidity.svg +1 -0
  368. package/public/file-icons/stylus.svg +1 -0
  369. package/public/file-icons/svelte.svg +1 -0
  370. package/public/file-icons/svg.svg +1 -0
  371. package/public/file-icons/swift.svg +1 -0
  372. package/public/file-icons/table.svg +1 -0
  373. package/public/file-icons/tailwindcss.svg +1 -0
  374. package/public/file-icons/taskfile.svg +1 -0
  375. package/public/file-icons/terraform.svg +1 -0
  376. package/public/file-icons/test-js.svg +1 -0
  377. package/public/file-icons/test-jsx.svg +1 -0
  378. package/public/file-icons/test-ts.svg +1 -0
  379. package/public/file-icons/tex.svg +1 -0
  380. package/public/file-icons/toml.svg +1 -0
  381. package/public/file-icons/travis.svg +1 -0
  382. package/public/file-icons/tsconfig.svg +1 -0
  383. package/public/file-icons/tune.svg +1 -0
  384. package/public/file-icons/turborepo.svg +1 -0
  385. package/public/file-icons/typescript-def.svg +1 -0
  386. package/public/file-icons/typescript.svg +1 -0
  387. package/public/file-icons/unity.svg +1 -0
  388. package/public/file-icons/uv.svg +1 -0
  389. package/public/file-icons/vala.svg +1 -0
  390. package/public/file-icons/vercel.svg +1 -0
  391. package/public/file-icons/video.svg +1 -0
  392. package/public/file-icons/vim.svg +1 -0
  393. package/public/file-icons/vite.svg +1 -0
  394. package/public/file-icons/vitest.svg +1 -0
  395. package/public/file-icons/vlang.svg +1 -0
  396. package/public/file-icons/vue.svg +1 -0
  397. package/public/file-icons/webassembly.svg +1 -0
  398. package/public/file-icons/webpack.svg +1 -0
  399. package/public/file-icons/word.svg +1 -0
  400. package/public/file-icons/xml.svg +1 -0
  401. package/public/file-icons/yaml.svg +1 -0
  402. package/public/file-icons/yarn.svg +1 -0
  403. package/public/file-icons/zig.svg +1 -0
  404. package/public/file-icons/zip.svg +1 -0
  405. package/public/provider-icons/antgroup-color.svg +1 -0
  406. package/public/provider-icons/anthropic.svg +1 -0
  407. package/public/provider-icons/azure-color.svg +1 -0
  408. package/public/provider-icons/baseten.svg +1 -0
  409. package/public/provider-icons/bedrock-color.svg +1 -0
  410. package/public/provider-icons/cerebras-color.svg +1 -0
  411. package/public/provider-icons/cloudflare-color.svg +1 -0
  412. package/public/provider-icons/deepseek-color.svg +1 -0
  413. package/public/provider-icons/fireworks-color.svg +1 -0
  414. package/public/provider-icons/githubcopilot.svg +1 -0
  415. package/public/provider-icons/google-color.svg +1 -0
  416. package/public/provider-icons/googlecloud-color.svg +1 -0
  417. package/public/provider-icons/groq.svg +1 -0
  418. package/public/provider-icons/huggingface-color.svg +1 -0
  419. package/public/provider-icons/kimi-color.svg +1 -0
  420. package/public/provider-icons/minimax-color.svg +1 -0
  421. package/public/provider-icons/mistral-color.svg +1 -0
  422. package/public/provider-icons/moonshot.svg +1 -0
  423. package/public/provider-icons/nvidia-color.svg +1 -0
  424. package/public/provider-icons/openai.svg +1 -0
  425. package/public/provider-icons/opencode.svg +1 -0
  426. package/public/provider-icons/openrouter-color.svg +1 -0
  427. package/public/provider-icons/qwen-color.svg +1 -0
  428. package/public/provider-icons/together-color.svg +1 -0
  429. package/public/provider-icons/vercel.svg +1 -0
  430. package/public/provider-icons/xai.svg +1 -0
  431. package/public/provider-icons/xiaomimimo.svg +1 -0
  432. package/public/provider-icons/zai.svg +1 -0
  433. package/scripts/build-output.mjs +131 -0
  434. package/scripts/dev.mjs +18 -0
  435. package/scripts/entrypoint.mjs +8 -0
  436. package/scripts/install-build.mjs +134 -0
  437. package/scripts/launcher.mjs +249 -0
  438. package/scripts/pick-folder.ps1 +110 -0
  439. package/scripts/postinstall.mjs +12 -0
  440. package/scripts/process-runner.mjs +60 -0
  441. package/scripts/release.mjs +172 -0
  442. package/scripts/sync-file-icons.mjs +118 -0
  443. package/scripts/sync-provider-icons.mjs +41 -0
  444. package/src/app/api/agent/[id]/events/route.ts +81 -0
  445. package/src/app/api/agent/[id]/route.ts +24 -0
  446. package/src/app/api/agent/[id]/tree/route.ts +49 -0
  447. package/src/app/api/agent/new/route.ts +19 -0
  448. package/src/app/api/config/route.ts +22 -0
  449. package/src/app/api/files/route.ts +63 -0
  450. package/src/app/api/git/route.ts +32 -0
  451. package/src/app/api/growth/route.ts +50 -0
  452. package/src/app/api/health/route.ts +7 -0
  453. package/src/app/api/models/route.ts +129 -0
  454. package/src/app/api/pi-settings/route.ts +22 -0
  455. package/src/app/api/plugins/route.ts +79 -0
  456. package/src/app/api/security/route.ts +41 -0
  457. package/src/app/api/sessions/[id]/export/route.ts +25 -0
  458. package/src/app/api/sessions/[id]/route.ts +37 -0
  459. package/src/app/api/sessions/route.ts +23 -0
  460. package/src/app/api/skills/route.ts +39 -0
  461. package/src/app/api/update/route.ts +33 -0
  462. package/src/app/api/version/route.ts +8 -0
  463. package/src/app/api/web-auth/route.ts +80 -0
  464. package/src/app/api/workspaces/route.ts +78 -0
  465. package/src/app/globals.css +2039 -0
  466. package/src/app/icon.svg +21 -0
  467. package/src/app/layout.tsx +29 -0
  468. package/src/app/login/page.tsx +111 -0
  469. package/src/app/page.tsx +10 -0
  470. package/src/components/AppShell.tsx +1171 -0
  471. package/src/components/ChatInput.tsx +729 -0
  472. package/src/components/ChatWindow.tsx +1320 -0
  473. package/src/components/ContextMeter.tsx +131 -0
  474. package/src/components/DiffView.tsx +139 -0
  475. package/src/components/ExtensionUI.tsx +181 -0
  476. package/src/components/FileViewer.tsx +555 -0
  477. package/src/components/FullDiff.tsx +220 -0
  478. package/src/components/GitPanel.tsx +357 -0
  479. package/src/components/GrowthTree.tsx +317 -0
  480. package/src/components/ModelSelector.tsx +286 -0
  481. package/src/components/OutlineRail.tsx +166 -0
  482. package/src/components/PiMark.tsx +79 -0
  483. package/src/components/ProjectPanel.tsx +309 -0
  484. package/src/components/ProviderBrand.tsx +28 -0
  485. package/src/components/ProviderSetupModal.module.css +560 -0
  486. package/src/components/ProviderSetupModal.tsx +559 -0
  487. package/src/components/SessionSidebar.tsx +1066 -0
  488. package/src/components/SettingsPanel.tsx +2216 -0
  489. package/src/components/TrajectoryAnalyzer.module.css +225 -0
  490. package/src/components/TrajectoryView.tsx +289 -0
  491. package/src/components/icons.tsx +443 -0
  492. package/src/hooks/useFileViewer.ts +58 -0
  493. package/src/hooks/useGrowth.ts +635 -0
  494. package/src/hooks/usePiWeb.ts +900 -0
  495. package/src/i18n.tsx +1043 -0
  496. package/src/lib/agent-manager.ts +1348 -0
  497. package/src/lib/app-root.ts +4 -0
  498. package/src/lib/auth.ts +51 -0
  499. package/src/lib/command-validation.ts +116 -0
  500. package/src/lib/enter-behavior.ts +40 -0
  501. package/src/lib/extension-ui.ts +134 -0
  502. package/src/lib/file-icons.json +557 -0
  503. package/src/lib/file-icons.ts +39 -0
  504. package/src/lib/files-service.ts +280 -0
  505. package/src/lib/git-service.ts +255 -0
  506. package/src/lib/growth-rounds.ts +84 -0
  507. package/src/lib/growth-service.ts +586 -0
  508. package/src/lib/growth-tracker.ts +328 -0
  509. package/src/lib/growth-tree.ts +245 -0
  510. package/src/lib/growth-turns.ts +23 -0
  511. package/src/lib/highlight.ts +59 -0
  512. package/src/lib/model-draft.ts +103 -0
  513. package/src/lib/models-service.ts +852 -0
  514. package/src/lib/path-security.ts +101 -0
  515. package/src/lib/pi-settings.ts +115 -0
  516. package/src/lib/pi.ts +367 -0
  517. package/src/lib/plugins-service.ts +312 -0
  518. package/src/lib/process-format.ts +261 -0
  519. package/src/lib/provider-display.ts +57 -0
  520. package/src/lib/security-service.ts +52 -0
  521. package/src/lib/semver.ts +39 -0
  522. package/src/lib/session-reader.ts +204 -0
  523. package/src/lib/settings-write-lock.ts +45 -0
  524. package/src/lib/skills-service.ts +97 -0
  525. package/src/lib/text-sanitize.ts +20 -0
  526. package/src/lib/theme.ts +137 -0
  527. package/src/lib/trajectory.ts +294 -0
  528. package/src/lib/types.ts +300 -0
  529. package/src/lib/update-service.ts +129 -0
  530. package/src/lib/version.ts +6 -0
  531. package/src/lib/web-auth-shared.ts +35 -0
  532. package/src/lib/web-auth.ts +53 -0
  533. package/src/lib/workspace-store.ts +261 -0
  534. package/src/proxy.ts +53 -0
  535. package/tsconfig.json +49 -0
@@ -0,0 +1,52 @@
1
+ /**
2
+ * security-service:pi 真实安全能力的 Web 面。
3
+ * - 工具预设(只读/标准/全部)→ pi 的 tools 白名单(agent-manager 应用)
4
+ * - 项目信任(settings.json 的 defaultProjectTrust:ask/always/never)
5
+ * pi 没有 OS 级沙箱与审批系统——不做假开关。
6
+ */
7
+ import fs from "node:fs/promises";
8
+ import path from "node:path";
9
+ import { getAgentDir, getSettingsManager, reloadAllLoaders, reloadSettingsManagers, syncProjectTrust } from "./pi";
10
+ import { reloadSessionsForCwd } from "./agent-manager";
11
+ import { flushSettingsOrThrow, withSettingsWriteLock } from "./settings-write-lock";
12
+
13
+ export type ProjectTrust = "ask" | "always" | "never";
14
+
15
+ export async function getSecurity(): Promise<{ defaultProjectTrust: ProjectTrust }> {
16
+ let raw: string;
17
+ try {
18
+ raw = await fs.readFile(path.join(getAgentDir(), "settings.json"), "utf8");
19
+ } catch (error) {
20
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return { defaultProjectTrust: "ask" };
21
+ throw error;
22
+ }
23
+ const parsed: unknown = JSON.parse(raw.replace(/^\uFEFF/, ""));
24
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("settings.json must contain a JSON object");
25
+ const trust = (parsed as { defaultProjectTrust?: unknown }).defaultProjectTrust;
26
+ return { defaultProjectTrust: trust === "always" || trust === "never" ? trust : "ask" };
27
+ }
28
+
29
+ export async function setProjectTrust(value: ProjectTrust): Promise<void> {
30
+ if (value !== "ask" && value !== "always" && value !== "never") throw new Error("invalid defaultProjectTrust");
31
+ await withSettingsWriteLock(path.join(getAgentDir(), "settings.json"), async () => {
32
+ await getSecurity(); // ENOENT only; malformed or unreadable settings must fail closed.
33
+ const manager = getSettingsManager(process.cwd());
34
+ await flushSettingsOrThrow(manager);
35
+ await manager.reload();
36
+ await flushSettingsOrThrow(manager);
37
+ // SDK 自己持有跨进程锁,只落修改字段;flush 不抛写错,必须显式 drainErrors。
38
+ manager.setDefaultProjectTrust(value);
39
+ await flushSettingsOrThrow(manager);
40
+ if ((await getSecurity()).defaultProjectTrust !== value) {
41
+ throw new Error("defaultProjectTrust was not persisted");
42
+ }
43
+ // Only a confirmed persisted change may alter loaders and active sessions.
44
+ await reloadSettingsManagers();
45
+ await flushSettingsOrThrow(manager);
46
+ syncProjectTrust();
47
+ await flushSettingsOrThrow(manager);
48
+ await reloadAllLoaders();
49
+ await flushSettingsOrThrow(manager);
50
+ await reloadSessionsForCwd();
51
+ });
52
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * 版本号比较(设置页「检查更新」用):主.次.修 按数字比,预发布段按 semver 规则比。
3
+ * 正式版高于同号预发布(0.4.0 > 0.4.0-beta.1);预发布标识符数字按数值、字母按字典序、数字低于字母。
4
+ */
5
+ export interface ParsedVersion {
6
+ nums: [number, number, number];
7
+ pre: string[];
8
+ }
9
+
10
+ export function parseVersion(value: string): ParsedVersion | null {
11
+ const m = value.trim().replace(/^v/i, "").match(/^(\d+)\.(\d+)(?:\.(\d+))?(?:-([0-9A-Za-z.-]+))?/);
12
+ if (!m) return null;
13
+ return { nums: [Number(m[1]), Number(m[2]), Number(m[3] ?? 0)], pre: m[4] ? m[4].split(".") : [] };
14
+ }
15
+
16
+ /** a 比 b 新才返回 true;任一方解析失败按「不比它新」处理 */
17
+ export function isNewer(a: string, b: string): boolean {
18
+ const pa = parseVersion(a);
19
+ const pb = parseVersion(b);
20
+ if (!pa || !pb) return false;
21
+ for (let i = 0; i < 3; i += 1) if (pa.nums[i] !== pb.nums[i]) return pa.nums[i] > pb.nums[i];
22
+ if (!pa.pre.length || !pb.pre.length) return pa.pre.length === 0 && pb.pre.length > 0;
23
+ const n = Math.max(pa.pre.length, pb.pre.length);
24
+ for (let i = 0; i < n; i += 1) {
25
+ const x = pa.pre[i];
26
+ const y = pb.pre[i];
27
+ if (x === undefined) return false;
28
+ if (y === undefined) return true;
29
+ const nx = /^\d+$/.test(x);
30
+ const ny = /^\d+$/.test(y);
31
+ if (nx && ny) {
32
+ if (Number(x) !== Number(y)) return Number(x) > Number(y);
33
+ continue;
34
+ }
35
+ if (nx !== ny) return ny;
36
+ if (x !== y) return x > y;
37
+ }
38
+ return false;
39
+ }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * session-reader:枚举 / 搜索 / 冷读取 ~/.pi/agent/sessions/**.jsonl(pi 零改动)。
3
+ */
4
+ import { createReadStream } from "node:fs";
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import { createInterface } from "node:readline";
8
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
9
+ import type { SessionSummary, WebMessage } from "./types";
10
+ import { toWebMessage } from "./agent-manager";
11
+ import { getAgentDir } from "./pi";
12
+ import { BoundaryError, isPathInside } from "./path-security";
13
+
14
+ function toSummary(info: {
15
+ path: string;
16
+ name?: string;
17
+ cwd: string;
18
+ created: Date;
19
+ modified: Date;
20
+ messageCount: number;
21
+ firstMessage: string;
22
+ }): SessionSummary {
23
+ return {
24
+ path: info.path,
25
+ name: info.name,
26
+ cwd: info.cwd,
27
+ created: info.created.toISOString(),
28
+ modified: info.modified.toISOString(),
29
+ messageCount: info.messageCount,
30
+ firstMessage: info.firstMessage,
31
+ };
32
+ }
33
+
34
+ /**
35
+ * 单个 JSONL → 摘要,与 SDK `SessionManager.listAll()` 的口径一致:
36
+ * 首行必须是 session 头;名字取最后一条 session_info;messageCount 计全部 message 条目;
37
+ * firstMessage 取首条用户消息的文本块;modified 取用户 / 助手消息的最后活动时间(没有就回落头时间戳、再回落 mtime)。
38
+ */
39
+ async function summarize(filePath: string, stat: { mtime: Date }): Promise<SessionSummary | null> {
40
+ try {
41
+ let header: { cwd?: unknown; timestamp?: unknown } | null = null;
42
+ let name: string | undefined;
43
+ let messageCount = 0;
44
+ let firstMessage = "";
45
+ let lastActivity: number | undefined;
46
+ const rl = createInterface({ input: createReadStream(filePath, { encoding: "utf8" }), crlfDelay: Infinity });
47
+ for await (const line of rl) {
48
+ if (!line.trim()) continue;
49
+ let entry: any;
50
+ try {
51
+ entry = JSON.parse(line);
52
+ } catch {
53
+ continue;
54
+ }
55
+ if (!header) {
56
+ if (entry?.type !== "session") return null;
57
+ header = entry;
58
+ continue;
59
+ }
60
+ if (entry?.type === "session_info") name = typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : undefined;
61
+ if (entry?.type !== "message") continue;
62
+ messageCount += 1;
63
+ const message = entry.message;
64
+ if (!message || typeof message.role !== "string" || !("content" in message)) continue;
65
+ if (message.role !== "user" && message.role !== "assistant") continue;
66
+ const at = typeof message.timestamp === "number" ? message.timestamp : new Date(entry.timestamp).getTime();
67
+ if (Number.isFinite(at)) lastActivity = Math.max(lastActivity ?? 0, at);
68
+ if (!firstMessage && message.role === "user") {
69
+ const content = message.content;
70
+ firstMessage = typeof content === "string"
71
+ ? content
72
+ : Array.isArray(content) ? content.filter((b: any) => b?.type === "text").map((b: any) => b.text).join(" ") : "";
73
+ }
74
+ }
75
+ if (!header) return null;
76
+ const headerTime = typeof header.timestamp === "string" ? new Date(header.timestamp).getTime() : NaN;
77
+ const modified = lastActivity && lastActivity > 0 ? new Date(lastActivity) : !Number.isNaN(headerTime) ? new Date(headerTime) : stat.mtime;
78
+ return toSummary({
79
+ path: filePath,
80
+ name,
81
+ cwd: typeof header.cwd === "string" ? header.cwd : "",
82
+ created: new Date(String(header.timestamp)),
83
+ modified,
84
+ messageCount,
85
+ firstMessage: firstMessage || "(no messages)",
86
+ });
87
+ } catch {
88
+ return null;
89
+ }
90
+ }
91
+
92
+ /** 按文件缓存的摘要:mtime 与大小都没变就不重读(侧栏每 3 秒轮询一次,全盘重读几 MB JSONL 太浪费) */
93
+ const summaryCache = new Map<string, { mtimeMs: number; size: number; summary: SessionSummary | null }>();
94
+ /** 并发列表请求共用一次扫描 */
95
+ let scanning: Promise<SessionSummary[]> | null = null;
96
+
97
+ async function scanSessions(): Promise<SessionSummary[]> {
98
+ const root = path.join(getAgentDir(), "sessions");
99
+ const files: string[] = [];
100
+ let dirs: import("node:fs").Dirent[] = [];
101
+ try {
102
+ dirs = await fs.readdir(root, { withFileTypes: true });
103
+ } catch {
104
+ return [];
105
+ }
106
+ await Promise.all(dirs.filter((d) => d.isDirectory() || d.isSymbolicLink()).map(async (d) => {
107
+ try {
108
+ for (const f of await fs.readdir(path.join(root, d.name))) if (f.endsWith(".jsonl")) files.push(path.join(root, d.name, f));
109
+ } catch {
110
+ /* 目录读不了就跳过 */
111
+ }
112
+ }));
113
+ const seen = new Set<string>();
114
+ const out: SessionSummary[] = [];
115
+ const BATCH = 16;
116
+ for (let i = 0; i < files.length; i += BATCH) {
117
+ await Promise.all(files.slice(i, i + BATCH).map(async (file) => {
118
+ seen.add(file);
119
+ const stat = await fs.stat(file).catch(() => null);
120
+ if (!stat?.isFile()) return;
121
+ const cached = summaryCache.get(file);
122
+ if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
123
+ if (cached.summary) out.push(cached.summary);
124
+ return;
125
+ }
126
+ const summary = await summarize(file, stat);
127
+ summaryCache.set(file, { mtimeMs: stat.mtimeMs, size: stat.size, summary });
128
+ if (summary) out.push(summary);
129
+ }));
130
+ }
131
+ for (const key of summaryCache.keys()) if (!seen.has(key)) summaryCache.delete(key);
132
+ return out;
133
+ }
134
+
135
+ /** 列表短缓存:多标签每 3 秒各轮询一次,1.2 秒 TTL 吸收并发请求 */
136
+ let listCache: { at: number; list: SessionSummary[] } | null = null;
137
+
138
+ export async function listSessions(query?: string): Promise<SessionSummary[]> {
139
+ let list: SessionSummary[];
140
+ if (listCache && Date.now() - listCache.at < 1200) {
141
+ list = listCache.list;
142
+ } else {
143
+ scanning ??= scanSessions().finally(() => { scanning = null; });
144
+ list = await scanning;
145
+ listCache = { at: Date.now(), list };
146
+ }
147
+ if (query?.trim()) {
148
+ const q = query.trim().toLowerCase();
149
+ list = list.filter(
150
+ (s) =>
151
+ s.name?.toLowerCase().includes(q) ||
152
+ s.firstMessage.toLowerCase().includes(q) ||
153
+ s.cwd.toLowerCase().includes(q),
154
+ );
155
+ }
156
+ // 同一时间戳的会话按路径定序:扫描是并发的,顺序不固定会让 ETag 抖动、侧栏顺序跳动
157
+ list.sort((a, b) => b.modified.localeCompare(a.modified) || a.path.localeCompare(b.path));
158
+ return list;
159
+ }
160
+
161
+ export async function deleteSession(sessionPath: string): Promise<void> {
162
+ const root = path.join(getAgentDir(), "sessions");
163
+ const realRoot = await fs.realpath(root).catch(() => path.resolve(root));
164
+ const target = path.resolve(sessionPath);
165
+ if (path.extname(target).toLowerCase() !== ".jsonl" || !isPathInside(path.resolve(root), target)) {
166
+ throw new BoundaryError("session path is outside the Pi session store");
167
+ }
168
+ const realTarget = await fs.realpath(target).catch((error: NodeJS.ErrnoException) => {
169
+ if (error.code === "ENOENT") return target;
170
+ throw error;
171
+ });
172
+ if (realTarget !== target && !isPathInside(realRoot, realTarget)) throw new BoundaryError("session path escapes the Pi session store");
173
+ try {
174
+ await fs.unlink(target);
175
+ } catch (error) {
176
+ // 未落盘的惰性会话(还没发过消息)本来就没有文件,删除视为成功
177
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
178
+ }
179
+ }
180
+
181
+ /** 冷读取:还原上下文消息 + 会话名(不启动 agent) */
182
+ export async function readSession(sessionPath: string): Promise<{
183
+ cwd: string;
184
+ name: string;
185
+ messages: WebMessage[];
186
+ }> {
187
+ const sm = SessionManager.open(sessionPath);
188
+ let messages: WebMessage[] = [];
189
+ try {
190
+ const entries = sm.buildContextEntries();
191
+ messages = entries
192
+ .filter((e: any) => e.type === "message" && e.message)
193
+ .map((e: any) => toWebMessage(e.message, e.id));
194
+ } catch {
195
+ messages = [];
196
+ }
197
+ let name = "";
198
+ try {
199
+ name = (sm as unknown as { getSessionName?: () => string }).getSessionName?.() ?? "";
200
+ } catch {
201
+ name = "";
202
+ }
203
+ return { cwd: sm.getCwd(), name, messages };
204
+ }
@@ -0,0 +1,45 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import lockfile from "proper-lockfile";
4
+ import type { SettingsManager } from "@earendil-works/pi-coding-agent";
5
+
6
+ const pending = new Map<string, Promise<void>>();
7
+
8
+ /** Serialize in-process read/modify/write operations on the same Pi settings file. */
9
+ export function withSettingsWriteLock<T>(file: string, operation: () => Promise<T>): Promise<T> {
10
+ const resolved = path.resolve(file);
11
+ const key = process.platform === "win32" ? resolved.toLowerCase() : resolved;
12
+ const previous = pending.get(key) ?? Promise.resolve();
13
+ const result = previous.then(operation, operation);
14
+ const settled = result.then(() => undefined, () => undefined);
15
+ pending.set(key, settled);
16
+ void settled.then(() => {
17
+ if (pending.get(key) === settled) pending.delete(key);
18
+ });
19
+ return result;
20
+ }
21
+
22
+ /** Match the SDK's settings.json.lock protocol for PiWeb's manual JSON writes. */
23
+ export async function withExternalSettingsLock<T>(file: string, operation: () => Promise<T>): Promise<T> {
24
+ await fs.mkdir(path.dirname(file), { recursive: true });
25
+ const release = await lockfile.lock(file, {
26
+ realpath: false,
27
+ retries: { retries: 20, minTimeout: 20, maxTimeout: 100 },
28
+ });
29
+ try {
30
+ return await operation();
31
+ } finally {
32
+ await release();
33
+ }
34
+ }
35
+
36
+ /** SDK setters enqueue writes and record failures instead of rejecting flush(). */
37
+ export async function flushSettingsOrThrow(manager: Pick<SettingsManager, "flush" | "drainErrors">): Promise<void> {
38
+ await manager.flush();
39
+ const errors = manager.drainErrors();
40
+ if (errors.length) {
41
+ throw new Error(`Pi settings failed: ${errors.map(({ scope, error }) => `${scope}: ${error.message}`).join("; ")}`, {
42
+ cause: errors[0].error,
43
+ });
44
+ }
45
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * skills-service:技能发现(全局/项目/pi 包)+ SKILL.md frontmatter 开关。
3
+ * 开关实现:外科手术式改写 `disable-model-invocation`,其余 YAML 原样保留,然后 reload 加载器。
4
+ */
5
+ import fs from "node:fs/promises";
6
+ import os from "node:os";
7
+ import path from "node:path";
8
+ import { getAgentDir, getResourceLoader, reloadAllLoaders, resourceLoaderReady } from "./pi";
9
+ import { reloadSessionsForCwd } from "./agent-manager";
10
+ import { resolveDiscoveredPath } from "./path-security";
11
+
12
+ export interface SkillView {
13
+ name: string;
14
+ description: string;
15
+ filePath: string;
16
+ disabled: boolean;
17
+ scope: "global" | "project" | "package";
18
+ baseDir: string;
19
+ }
20
+
21
+ function classifyScope(filePath: string, cwd?: string): SkillView["scope"] {
22
+ const norm = filePath.replace(/\\/g, "/").toLowerCase();
23
+ const agentDir = getAgentDir().replace(/\\/g, "/").toLowerCase();
24
+ const home = os.homedir().replace(/\\/g, "/").toLowerCase();
25
+ if (norm.startsWith(`${agentDir}/`) || norm.startsWith(`${home}/.agents/`) || norm.startsWith(`${home}/.pi/`))
26
+ return "global";
27
+ if (cwd && norm.startsWith(path.resolve(cwd).replace(/\\/g, "/").toLowerCase())) return "project";
28
+ if (norm.includes("/node_modules/")) return "package";
29
+ return "package";
30
+ }
31
+
32
+ export async function listSkills(cwd?: string): Promise<SkillView[]> {
33
+ const seen = new Set<string>();
34
+ const out: SkillView[] = [];
35
+ const targets: string[] = cwd ? [path.resolve(cwd)] : [process.cwd()];
36
+ for (const dir of targets) {
37
+ await resourceLoaderReady(dir);
38
+ const loader = getResourceLoader(dir);
39
+ const { skills } = loader.getSkills();
40
+ for (const s of skills) {
41
+ if (seen.has(s.filePath)) continue;
42
+ seen.add(s.filePath);
43
+ out.push({
44
+ name: s.name,
45
+ description: s.description,
46
+ filePath: s.filePath,
47
+ disabled: s.disableModelInvocation,
48
+ scope: classifyScope(s.filePath, dir),
49
+ baseDir: s.baseDir,
50
+ });
51
+ }
52
+ }
53
+ out.sort((a, b) => a.name.localeCompare(b.name));
54
+ return out;
55
+ }
56
+
57
+ /** 在 SKILL.md 的 YAML frontmatter 中精确改写一个布尔键,保留其余行 */
58
+ function toggleFrontmatterKey(content: string, key: string, value: boolean): string {
59
+ const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
60
+ if (!m) return content;
61
+ const lines = m[1].split(/\r?\n/);
62
+ const pattern = new RegExp(`^\\s*${key}\\s*:`);
63
+ let replaced = false;
64
+ const next = lines.map((line) => {
65
+ if (pattern.test(line)) {
66
+ replaced = true;
67
+ return value ? `${key}: true` : `${key}: false`;
68
+ }
69
+ return line;
70
+ });
71
+ if (!replaced) {
72
+ if (value) next.push(`${key}: true`);
73
+ }
74
+ if (!replaced && !value) return content; // 本来就没有且要关 → 无需改动
75
+ const body = content.slice(m[0].length);
76
+ return `---\n${next.join("\n")}\n---${body}`;
77
+ }
78
+
79
+ export async function setSkillDisabled(filePath: string, disabled: boolean, cwd?: string): Promise<{ changed: boolean }> {
80
+ const skills = await listSkills(cwd);
81
+ const authorized = await resolveDiscoveredPath(filePath, skills.map((skill) => skill.filePath));
82
+ const content = await fs.readFile(authorized, "utf8");
83
+ const next = toggleFrontmatterKey(content, "disable-model-invocation", disabled);
84
+ if (next === content) return { changed: false };
85
+ await fs.writeFile(authorized, next, "utf8");
86
+ // 就地重载加载器并让活跃会话重建系统提示(技能列表在系统提示里);全局技能影响所有目录
87
+ await reloadAllLoaders();
88
+ const scope = classifyScope(authorized, cwd);
89
+ await reloadSessionsForCwd(scope === "project" ? cwd : undefined);
90
+ return { changed: true };
91
+ }
92
+
93
+ export async function readSkillFile(filePath: string, cwd?: string): Promise<string> {
94
+ const skills = await listSkills(cwd);
95
+ const authorized = await resolveDiscoveredPath(filePath, skills.map((skill) => skill.filePath));
96
+ return fs.readFile(authorized, "utf8");
97
+ }
@@ -0,0 +1,20 @@
1
+ export interface SanitizedToolOutput {
2
+ text: string;
3
+ encodingLoss: boolean;
4
+ }
5
+
6
+ /**
7
+ * Legacy Windows PowerShell sessions may contain U+FFFD because output bytes
8
+ * were decoded before PowerShell switched to UTF-8. The original bytes are no
9
+ * longer recoverable, so retain readable lines and flag the loss for the UI.
10
+ */
11
+ export function sanitizeToolOutput(value: string): SanitizedToolOutput {
12
+ if (!value.includes("\uFFFD")) return { text: value, encodingLoss: false };
13
+ const readableLines = value
14
+ .split(/\r?\n/)
15
+ .filter((line) => !line.includes("\uFFFD"));
16
+ return {
17
+ text: readableLines.join("\n").replace(/\n{3,}/g, "\n\n").trim(),
18
+ encodingLoss: true,
19
+ };
20
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * 外观 = pebrel 配色主题对(https://github.com/Kuddev/pebrel)
3
+ * + dsh 亮暗模式三选一(light / dark / system,见 dsh ui-theme 的 theme-settings.ts)。
4
+ * 两者独立持久化:配色 piweb.pebrelTheme,亮暗 piweb.theme。
5
+ */
6
+
7
+ export type PebrelTheme =
8
+ | 'silver-steel'
9
+ | 'limestone-coal'
10
+ | 'linen-moss'
11
+ | 'nord-paper'
12
+ | 'pebrel'
13
+ | 'dsh';
14
+
15
+ /** dsh 的 THEME_PREFERENCES:浅色/深色/跟随系统 */
16
+ export type ThemeMode = 'light' | 'dark' | 'system';
17
+
18
+ export const PEBREL_THEME_IDS: PebrelTheme[] = [
19
+ 'dsh',
20
+ 'silver-steel',
21
+ 'limestone-coal',
22
+ 'linen-moss',
23
+ 'nord-paper',
24
+ 'pebrel',
25
+ ];
26
+
27
+ export const THEME_MODES: ThemeMode[] = ['light', 'dark', 'system'];
28
+
29
+ export const DEFAULT_PEBREL_THEME: PebrelTheme = 'dsh';
30
+ export const DEFAULT_THEME_MODE: ThemeMode = 'system';
31
+
32
+ const THEME_KEY = 'piweb.pebrelTheme';
33
+ const MODE_KEY = 'piweb.theme';
34
+
35
+ export function isPebrelTheme(value: string | null): value is PebrelTheme {
36
+ return !!value && (PEBREL_THEME_IDS as string[]).includes(value);
37
+ }
38
+
39
+ export function isThemeMode(value: string | null): value is ThemeMode {
40
+ return !!value && (THEME_MODES as string[]).includes(value);
41
+ }
42
+
43
+ export function loadPebrelTheme(): PebrelTheme {
44
+ if (typeof window === 'undefined') return DEFAULT_PEBREL_THEME;
45
+ try {
46
+ const saved = localStorage.getItem(THEME_KEY);
47
+ if (isPebrelTheme(saved)) return saved;
48
+ } catch {
49
+ /* ignore */
50
+ }
51
+ return DEFAULT_PEBREL_THEME;
52
+ }
53
+
54
+ export function loadThemeMode(): ThemeMode {
55
+ if (typeof window === 'undefined') return DEFAULT_THEME_MODE;
56
+ try {
57
+ const saved = localStorage.getItem(MODE_KEY);
58
+ if (isThemeMode(saved)) return saved;
59
+ } catch {
60
+ /* ignore */
61
+ }
62
+ return DEFAULT_THEME_MODE;
63
+ }
64
+
65
+ /** 亮暗模式解析为实际值:system 跟随系统媒体查询(dsh getEffectiveTheme 语义) */
66
+ export function getEffectiveMode(mode: ThemeMode): 'light' | 'dark' {
67
+ if (mode === 'system') {
68
+ if (typeof window === 'undefined' || !window.matchMedia) return 'dark';
69
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
70
+ }
71
+ return mode;
72
+ }
73
+
74
+ function applyLightDark(mode: ThemeMode): void {
75
+ if (typeof window === 'undefined') return;
76
+ const isDark = getEffectiveMode(mode) === 'dark';
77
+ const root = document.documentElement;
78
+ const body = document.body;
79
+ if (isDark) {
80
+ root.setAttribute('data-ds-dark-theme', '');
81
+ root.removeAttribute('data-ds-light-theme');
82
+ body?.setAttribute('data-ds-dark-theme', '');
83
+ body?.removeAttribute('data-ds-light-theme');
84
+ } else {
85
+ root.removeAttribute('data-ds-dark-theme');
86
+ root.setAttribute('data-ds-light-theme', '');
87
+ body?.removeAttribute('data-ds-dark-theme');
88
+ body?.setAttribute('data-ds-light-theme', '');
89
+ }
90
+ root.style.colorScheme = isDark ? 'dark' : 'light';
91
+ }
92
+
93
+ function applyFontSize(): void {
94
+ try {
95
+ // 界面字号:驱动 body/导航/标签等(--dsh-content-font-size + delta)
96
+ const fs = localStorage.getItem('piweb.fontSize');
97
+ if (fs) {
98
+ const n = Math.max(12, Math.min(18, parseInt(fs, 10) || 14));
99
+ document.documentElement.style.setProperty('--dsh-content-font-size', `${n}px`);
100
+ document.documentElement.style.setProperty('--dsh-content-font-delta', `${n - 14}px`);
101
+ }
102
+ // 对话字号:只作用于消息正文/用户气泡/输入框(.md 等),缺省回落界面字号
103
+ const chatFs = localStorage.getItem('piweb.chatFontSize');
104
+ if (chatFs) {
105
+ const n = Math.max(12, Math.min(20, parseInt(chatFs, 10) || 14));
106
+ document.documentElement.style.setProperty('--piweb-chat-font-size', `${n}px`);
107
+ }
108
+ } catch {
109
+ /* ignore */
110
+ }
111
+ }
112
+
113
+ export function applyPebrelTheme(theme: PebrelTheme, mode?: ThemeMode): void {
114
+ if (typeof window === 'undefined') return;
115
+ const effectiveMode = mode ?? loadThemeMode();
116
+ if (theme === 'dsh') {
117
+ // dsh 经典配色 = 项目基础变量,移除 pebrel 覆盖
118
+ document.documentElement.removeAttribute('data-pebrel-theme');
119
+ document.body?.removeAttribute('data-pebrel-theme');
120
+ } else {
121
+ document.documentElement.setAttribute('data-pebrel-theme', theme);
122
+ document.body?.setAttribute('data-pebrel-theme', theme);
123
+ }
124
+ applyLightDark(effectiveMode);
125
+ applyFontSize();
126
+ try {
127
+ localStorage.setItem(THEME_KEY, theme);
128
+ localStorage.setItem(MODE_KEY, effectiveMode);
129
+ } catch {
130
+ /* ignore */
131
+ }
132
+ }
133
+
134
+ /** 系统亮暗变化 / 跨标签同步时重放当前外观 */
135
+ export function syncPebrelTheme(): void {
136
+ applyPebrelTheme(loadPebrelTheme(), loadThemeMode());
137
+ }