@dhzh/foundry 1.2.0 → 1.2.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/README.md CHANGED
@@ -1,61 +1,150 @@
1
1
  # Foundry
2
2
 
3
- Foundry is an AI-native local developer runtime for managing Skills, MCP Servers, Prompts, model Providers, and local agent Runtimes. It provides a CLI-managed web interface that runs entirely on the user's machine.
3
+ Foundry is a local control plane for AI development tools. It provides a CLI-managed web interface for saving model Providers, applying them to installed agent Runtimes, and moving user-owned configuration between Foundry installations.
4
4
 
5
- ## Usage
5
+ Foundry currently supports [OpenAI Codex](https://github.com/openai/codex) and [Claude Code](https://docs.anthropic.com/en/docs/claude-code). The Dashboard, Skills, MCPs, and Prompts screens are present as placeholders; their management workflows are not connected to the Local Web UI yet.
6
6
 
7
- Start the local web interface with:
7
+ ## Requirements
8
+
9
+ - Node.js `24.18.0` or newer within the Node 24 release line
10
+ - pnpm `11.22.0` or newer within the pnpm 11 release line for repository development
11
+
12
+ ## Quick Start
13
+
14
+ Start the latest published Local Web UI without installing Foundry globally:
8
15
 
9
16
  ```bash
10
- pnpm dlx @dhzh/foundry ui
17
+ pnpm dlx @dhzh/foundry@latest ui
11
18
  ```
12
19
 
13
- The server listens on `http://127.0.0.1:54321` and opens the interface in the default browser. Use a different port or keep the browser closed when needed:
20
+ Foundry listens only on `127.0.0.1`, uses port `54321` by default, and opens the interface in the default browser. Stop it with `Ctrl+C`.
14
21
 
15
22
  ```bash
16
- pnpm dlx @dhzh/foundry ui --port 61234 --no-open
23
+ # Use another port.
24
+ pnpm dlx @dhzh/foundry@latest ui --port 61234
25
+
26
+ # Start without opening a browser.
27
+ pnpm dlx @dhzh/foundry@latest ui --no-open
28
+
29
+ # Show command help or the installed version.
30
+ pnpm dlx @dhzh/foundry@latest ui --help
31
+ pnpm dlx @dhzh/foundry@latest --version
17
32
  ```
18
33
 
19
- ## Local Web Interface
34
+ | Option | Default | Description |
35
+ | --- | --- | --- |
36
+ | `--port <port>` | `54321` | Listen on a port from `1` through `65535`. |
37
+ | `--open` | enabled | Open the Local Web UI after the server starts. |
38
+ | `--no-open` | - | Keep the browser closed. |
20
39
 
21
- The Local Web UI uses hash-based URLs so navigation does not require server-side route fallbacks. It currently provides working management flows for Providers, Runtimes, and Application Settings, while the remaining capability surfaces are placeholders for future workflows.
40
+ Running `foundry` without a subcommand prints help and does not start a server.
22
41
 
23
- - `/#/dashboard` - Dashboard placeholder and application entry point.
24
- - `/#/skills`, `/#/mcps`, and `/#/prompts` - Capability management placeholders.
25
- - `/#/providers` - Browse saved Codex and Claude Code Providers by Runtime.
26
- - `/#/providers/new` - Create a Provider with connection, authentication, model, and optional presentation details.
27
- - `/#/runtimes` - Detect installed Codex and Claude Code Runtimes, select a saved Provider or Official Default, preview managed configuration changes, and apply them to the Runtime configuration file.
28
- - `/#/settings` - Choose a system, light, or dark Color Mode persisted by the Foundry Server.
42
+ ## Current Capabilities
29
43
 
30
- Provider records, Runtime assignments, and Application Settings are stored in Foundry's local SQLite database. The browser tab title reports Foundry Server connectivity as `Checking…`, `Healthy`, or `Unhealthy`, and unknown routes render an in-app Not Found page.
44
+ ### Providers
31
45
 
32
- ## Development
46
+ A Provider is a saved model-service connection scoped to one Runtime. Foundry can:
33
47
 
34
- The repository is a pnpm workspace containing the published server and CLI package, a private React app, and a private API contract package.
48
+ - create, edit, copy, list, and soft-delete Codex and Claude Code Providers;
49
+ - store Runtime-specific endpoints, API keys, model selections, and optional presentation details;
50
+ - test a saved connection against the Provider's model-listing endpoint;
51
+ - apply or reapply a Provider directly from its card; and
52
+ - prevent deletion while the Provider is assigned to a Runtime.
35
53
 
36
- Install dependencies from the repository root:
54
+ Codex Providers use the Responses protocol and support a default model, optional review model, and optional API key. Claude Code Providers use the Messages protocol and support authentication-header selection, role models, subagent settings, model capabilities, and selected Claude Code feature flags.
37
55
 
38
- ```bash
39
- pnpm install
40
- ```
56
+ ### Runtimes
41
57
 
42
- Start the Hono server:
58
+ Foundry detects the `codex` and `claude` executables from `PATH`, reads their version, and manages these user-level configuration files:
43
59
 
44
- ```bash
45
- pnpm dev:server
46
- ```
60
+ | Runtime | Configuration file |
61
+ | --- | --- |
62
+ | Codex | `~/.codex/config.toml` |
63
+ | Claude Code | `~/.claude/settings.json` |
47
64
 
48
- In another terminal, start the React app at [http://localhost:12345](http://localhost:12345):
65
+ Before applying a saved Provider or Official Default, Foundry shows the proposed field-level changes. Applying a preview:
49
66
 
50
- ```bash
51
- pnpm dev:app
52
- ```
67
+ - preserves fields outside Foundry's managed field set;
68
+ - rejects the apply if the file changed after the preview was created;
69
+ - writes through a temporary file and atomically replaces the target;
70
+ - keeps the latest configuration backup beside the file with a `.foundry-backup` suffix; and
71
+ - records the selected Provider or Official Default as the Runtime Assignment.
72
+
73
+ Official Default removes Foundry-managed active selection fields while preserving unrelated configuration, saved Provider tables, and official account credentials. Foundry does not infer Runtime Assignments from configuration files changed outside the application.
74
+
75
+ ### Settings
76
+
77
+ Application Settings currently contain a persisted Color Mode: System, Light, or Dark. The Settings page also owns Foundry data import and export.
78
+
79
+ ## Local Web Routes
80
+
81
+ The Local Web UI uses hash routing, so browser navigation does not require server-side route fallbacks.
82
+
83
+ | Route | Status | Purpose |
84
+ | --- | --- | --- |
85
+ | `/#/` | Available | Redirects to the Dashboard. |
86
+ | `/#/dashboard` | Placeholder | Application entry point. |
87
+ | `/#/skills` | Placeholder | Future Skill management. |
88
+ | `/#/mcps` | Placeholder | Future MCP Server management. |
89
+ | `/#/prompts` | Placeholder | Future Prompt management. |
90
+ | `/#/providers` | Available | Lists Providers by Runtime; accepts `?runtime=codex` or `?runtime=claude-code`. |
91
+ | `/#/providers/new` | Available | Creates a Provider. |
92
+ | `/#/providers/:providerId/edit` | Available | Edits an existing Provider. |
93
+ | `/#/providers/:providerId/copy` | Available | Creates a new Provider from an existing one. |
94
+ | `/#/runtimes` | Available | Detects and configures supported Runtimes. |
95
+ | `/#/settings` | Available | Changes Color Mode and imports or exports data. |
96
+ | any other hash route | Available | Renders the in-app Not Found page. |
97
+
98
+ The browser title also reports Foundry Server health as `Checking...`, `Healthy`, or `Unhealthy`.
99
+
100
+ ## Data Import and Export
101
+
102
+ The Settings page exports all current Exportable Data as one timestamped `.foundry` file. The file is a ZIP-backed binary container intended for Foundry rather than manual editing; it is not encrypted.
103
+
104
+ | Module | Encoding | Exported data | Import behavior |
105
+ | --- | --- | --- | --- |
106
+ | `settings` | JSON | Application Settings | Overwrites current settings. |
107
+ | `providers` | JSON | All active Providers, including API keys and avatars | Appends new Providers, including duplicates. |
108
+
109
+ Provider database IDs, timestamps, and deletion metadata are omitted so the destination database can generate them. Deleted Providers, Runtime Assignments, and other machine-specific operational state are not exported.
110
+
111
+ Each package contains a manifest with its format, creation time, Foundry version, and per-module path, media type, overwrite policy, byte size, and SHA-256 checksum. Import validates the container and manifest before processing modules. Supported modules are atomic but independent: one module can fail or be unsupported without rolling back another module that imported successfully.
112
+
113
+ Because Provider API keys are stored in the export, keep `.foundry` files private and store them securely.
53
114
 
54
- Rsbuild proxies `/api` requests to the Hono server at `http://127.0.0.1:54321`.
115
+ ## Local Data and Safety
55
116
 
56
- ## Health Endpoint
117
+ Foundry stores Provider records, Runtime Assignments, and Application Settings in one `foundry.sqlite` database in the operating system's application-data directory. The directory is resolved with [`env-paths`](https://github.com/sindresorhus/env-paths); on macOS the default database location is `~/Library/Application Support/foundry/foundry.sqlite`.
57
118
 
58
- `GET /api/health` returns HTTP 200 with the shared response envelope:
119
+ Provider API keys are stored in this local database. Access to the machine and its user account should therefore be treated as access to those credentials.
120
+
121
+ Database migrations run automatically when the server starts. Before applying pending migrations to an existing database, Foundry creates an online SQLite backup under the adjacent `backups/` directory and retains the newest migration backup. A database migrated by a newer or incompatible Foundry version is rejected instead of being silently changed.
122
+
123
+ The HTTP server is loopback-only and has no remote-listening mode or authentication layer. It should not be exposed through a reverse proxy or port-forwarding setup.
124
+
125
+ ## HTTP API
126
+
127
+ The Local Web UI uses relative `/api` requests. The server currently exposes:
128
+
129
+ | Method | Path | Purpose |
130
+ | --- | --- | --- |
131
+ | `GET` | `/api/health` | Reports Foundry Server health. |
132
+ | `GET` | `/api/settings` | Reads Application Settings. |
133
+ | `PATCH` | `/api/settings` | Updates Application Settings. |
134
+ | `GET` | `/api/providers?runtime=:runtime` | Lists Provider summaries for `codex` or `claude-code`. |
135
+ | `POST` | `/api/providers` | Creates a Provider. |
136
+ | `GET` | `/api/providers/:providerId` | Reads full Provider configuration. |
137
+ | `PUT` | `/api/providers/:providerId` | Updates a Provider without changing its Runtime type. |
138
+ | `DELETE` | `/api/providers/:providerId` | Soft-deletes an unassigned Provider. |
139
+ | `POST` | `/api/providers/:providerId/copy` | Creates a Provider copy from submitted data. |
140
+ | `POST` | `/api/providers/:providerId/test-connection` | Runs a non-persistent Provider connection test. |
141
+ | `GET` | `/api/runtimes` | Detects supported Runtimes and returns their assignments. |
142
+ | `POST` | `/api/runtimes/:runtime/preview` | Previews configuration changes for a Provider or Official Default. |
143
+ | `POST` | `/api/runtimes/:runtime/apply` | Applies an unchanged preview and records the Runtime Assignment. |
144
+ | `GET` | `/api/data/export` | Downloads the current `.foundry` export. |
145
+ | `POST` | `/api/data/import` | Imports a `.foundry` file from the raw request body. |
146
+
147
+ JSON success and business-result responses use a shared envelope:
59
148
 
60
149
  ```json
61
150
  {
@@ -65,17 +154,47 @@ Rsbuild proxies `/api` requests to the Hono server at `http://127.0.0.1:54321`.
65
154
  }
66
155
  ```
67
156
 
68
- Unexpected query parameters are rejected with HTTP 400.
157
+ `message` is optional. Request validation failures use HTTP `400`; domain outcomes also carry a machine-readable `status` such as `PROVIDER_NOT_FOUND` or `RUNTIME_CONFIGURATION_CHANGED`. The export endpoint instead returns `application/octet-stream` with an attachment filename. Import requests are limited to 256 MiB.
69
158
 
70
- ## Workspace Layout
159
+ The API is an internal local contract shared by the server and Local Web UI. It is not currently documented as a stable remote integration API.
71
160
 
72
- - `src/cli/` - CLI entry point and the `foundry ui` command.
73
- - `src/server/` - Hono server, health handler, static app serving, and lifecycle management.
74
- - `app/` - React and Rsbuild local web interface with Vitest Browser Mode coverage.
75
- - `packages/api-contract/` - Shared response constants and TypeScript types.
76
- - `test/` - Vitest coverage for the CLI, server, and shared contracts.
161
+ ## Development
77
162
 
78
- ## Verification
163
+ Install dependencies from the repository root:
164
+
165
+ ```bash
166
+ pnpm install
167
+ ```
168
+
169
+ Start the Hono server on `http://127.0.0.1:54321`:
170
+
171
+ ```bash
172
+ pnpm dev:server
173
+ ```
174
+
175
+ In another terminal, start the React app on [http://localhost:12345](http://localhost:12345):
176
+
177
+ ```bash
178
+ pnpm dev:app
179
+ ```
180
+
181
+ Rsbuild proxies `/api` to the Hono development server. Other useful commands are:
182
+
183
+ | Command | Purpose |
184
+ | --- | --- |
185
+ | `pnpm dev:cli` | Runs the source CLI's `ui` command. |
186
+ | `pnpm test` | Runs Node tests and Chromium Browser Mode tests once. |
187
+ | `pnpm test:dev` | Runs Vitest in watch mode. |
188
+ | `pnpm test:coverage` | Runs tests with V8 coverage. |
189
+ | `pnpm lint` | Checks the workspace with ESLint. |
190
+ | `pnpm lint-fix` | Applies ESLint fixes. |
191
+ | `pnpm typecheck` | Type-checks the root TypeScript project. |
192
+ | `pnpm --filter @dhzh/foundry-app typecheck` | Type-checks the React app. |
193
+ | `pnpm build` | Builds the server/CLI package, migrations, and Local Web UI into `dist/`. |
194
+ | `pnpm db:generate` | Generates a new Drizzle migration after an approved schema change. |
195
+ | `pnpm release` | Starts the interactive version-bump workflow for maintainers. |
196
+
197
+ Run the full verification set before release:
79
198
 
80
199
  ```bash
81
200
  pnpm test
@@ -85,6 +204,23 @@ pnpm --filter @dhzh/foundry-app typecheck
85
204
  pnpm build
86
205
  ```
87
206
 
207
+ Pushing a `v*` tag triggers separate GitHub Actions workflows that create the GitHub release and publish `@dhzh/foundry` to npm with provenance.
208
+
209
+ ## Repository Layout
210
+
211
+ - `src/cli/` - Citty CLI and the `foundry ui` command.
212
+ - `src/server/` - Loopback Hono server, persistence, HTTP handlers, Runtime configuration, static assets, and shutdown lifecycle.
213
+ - `app/` - Private React, Rsbuild, Tailwind CSS, shadcn/ui, and TanStack Query workspace for the Local Web UI.
214
+ - `packages/api-contract/` - Private shared runtime constants and TypeScript HTTP contracts; bundled into the published output.
215
+ - `drizzle/` - Forward-only SQLite migrations bundled with the package.
216
+ - `test/` - Node-side Vitest coverage for the CLI, server, persistence, and contracts.
217
+ - `app/test/` - Vitest Browser Mode coverage using Playwright and Chromium.
218
+ - `docs/adr/` - Architecture decision records for routing, server state, persistence, credentials, Runtime management, and export format.
219
+ - `docs/design/` and `docs/research/` - Detailed feature behavior and supporting technical research.
220
+ - `src/main/` - Earlier Skill and Prompt subsystem code that is not wired into the current CLI, server, or Local Web UI entrypoints.
221
+
222
+ The published package contains the CLI, the minimal library entrypoint, bundled migrations, and the built Local Web UI. The private app and API-contract workspaces are not published as separate packages.
223
+
88
224
  ## Open Source Inspiration
89
225
 
90
226
  Foundry is inspired by open-source projects that explore better ways to manage local AI development tools:
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{t as e}from"./package-DreCfN5H.mjs";import{defineCommand as t,renderUsage as n,runMain as r}from"citty";import i from"node:process";import a from"open";import{serve as o}from"@hono/node-server";import{serveStatic as s}from"@hono/node-server/serve-static";import{zValidator as c}from"@hono/zod-validator";import{constants as l,existsSync as u}from"node:fs";import d from"node:path";import{Hono as f}from"hono";import{z as p}from"zod";import{Uint8ArrayReader as m,Uint8ArrayWriter as h,ZipReader as ee,ZipWriter as te}from"@zip.js/zip.js";import{createHash as g,randomUUID as ne,randomUUIDv7 as re}from"node:crypto";import{bodyLimit as ie}from"hono/body-limit";import{TextDecoder as ae,promisify as oe}from"node:util";import{Buffer as se}from"node:buffer";import ce from"better-sqlite3";import{drizzle as le}from"drizzle-orm/better-sqlite3";import{access as ue,chmod as de,lstat as fe,mkdir as _,readFile as pe,readdir as me,realpath as he,rename as ge,rm as v,stat as _e,writeFile as ve}from"node:fs/promises";import{readMigrationFiles as ye}from"drizzle-orm/migrator";import be from"env-paths";import{homedir as xe}from"node:os";import{and as y,desc as Se,eq as b,isNull as x,sql as S}from"drizzle-orm";import{blob as Ce,check as C,index as we,integer as w,sqliteTable as T,text as E,uniqueIndex as Te}from"drizzle-orm/sqlite-core";import{parse as Ee,patch as De,stringify as Oe}from"@decimalturn/toml-patch";import{execFile as ke}from"node:child_process";var Ae=Object.defineProperty,je=(e,t)=>{let n={};for(var r in e)Ae(n,r,{get:e[r],enumerable:!0});return t||Ae(n,Symbol.toStringTag,{value:`Module`}),n};const D={providerConnectionFailed:`PROVIDER_CONNECTION_FAILED`,providerInUse:`PROVIDER_IN_USE`,providerNotFound:`PROVIDER_NOT_FOUND`,runtimeApplyFailed:`RUNTIME_APPLY_FAILED`,runtimeConfigurationChanged:`RUNTIME_CONFIGURATION_CHANGED`,runtimeConfigurationInvalid:`RUNTIME_CONFIGURATION_INVALID`,runtimeNotDetected:`RUNTIME_NOT_DETECTED`,success:`SUCCESS`},O=[`system`,`light`,`dark`],Me=`foundry-export-v1`,Ne=[`settings`,`providers`],k=[`codex`,`claude-code`],Pe=[`image/png`,`image/jpeg`,`image/webp`,`image/svg+xml`],Fe=[`authorization`,`x-api-key`],Ie=[`effort`,`xhigh_effort`,`max_effort`,`thinking`,`adaptive_thinking`,`interleaved_thinking`],Le=p.strictObject({});function Re(e,t){e.get(`/api/data/export`,c(`query`,Le),async()=>{let e=await t.createExport();return new Response(e.content,{headers:{"cache-control":`no-store`,"content-disposition":`attachment; filename="${e.filename}"`,"content-length":String(e.content.byteLength),"content-type":`application/octet-stream`,"x-content-type-options":`nosniff`}})})}const ze=new TextEncoder;function A(e){return String(e).padStart(2,`0`)}function Be(e){let t={avatar:e.avatar,name:e.name,officialWebsite:e.officialWebsite,remark:e.remark};return e.runtime,{...t,configuration:e.configuration,runtime:e.runtime}}function Ve(e,t,n,r){let i=ze.encode(JSON.stringify(r));return{content:i,manifest:{id:e,mediaType:`application/json`,overwrite:n,path:t,sha256:g(`sha256`).update(i).digest(`hex`),size:i.byteLength}}}function He(e){return`foundry-export-${[e.getFullYear(),`-`,A(e.getMonth()+1),`-`,A(e.getDate()),`T`,A(e.getHours()),A(e.getMinutes()),A(e.getSeconds())].join(``)}.foundry`}var Ue=class{providerStore;settingsStore;now;foundryVersion;constructor(t,n,r=()=>new Date,i=e){this.providerStore=t,this.settingsStore=n,this.now=r,this.foundryVersion=i}async createExport(){let e=this.now(),t=this.settingsStore.getApplicationSettings(),n=k.flatMap(e=>this.providerStore.listProviders(e).map(e=>Be(e))),r=[Ve(`settings`,`modules/settings.json`,!0,t),Ve(`providers`,`modules/providers.json`,!1,n)],i={createdAt:e.toISOString(),format:Me,foundryVersion:this.foundryVersion,modules:r.map(e=>e.manifest)},a=new te(new h),o=JSON.stringify(i),s=ze.encode(o);await a.add(`manifest.json`,new m(s));for(let e of r)await a.add(e.manifest.path,new m(e.content));return{content:await a.close(),filename:He(e),manifest:i}}};const We=/^[A-Za-z0-9+/]+={0,2}$/u;function Ge(e,t){for(let n of e){let e=n.codePointAt(0)??0,r=t&&[9,10,13].includes(e);if(e===127||!r&&e<32)return!0}return!1}function j(e){return p.string().trim().max(e).refine(e=>!Ge(e,!0),{message:`Control characters are not allowed.`})}function M(e){return j(e).nullable().transform(e=>e===``?null:e)}function Ke(e,t){try{let n=new URL(e);return[`http:`,`https:`].includes(n.protocol)&&n.username===``&&n.password===``&&(t||n.search===``&&n.hash===``)}catch{return!1}}const qe=j(2048).min(1).refine(e=>Ke(e,!1),{message:`Provide an HTTP or HTTPS URL without credentials, a query, or a fragment.`}),Je=M(2048).refine(e=>e===null||Ke(e,!0),{message:`Provide an HTTP or HTTPS URL without credentials.`}),Ye=j(100).min(1),Xe=M(100),Ze=M(2e3),N=j(200).min(1),Qe=M(200),$e=M(2e3),et=p.string().min(1).max(16384).refine(e=>!Ge(e,!1),{message:`Control characters are not allowed.`}),tt=p.union([et,p.literal(``),p.null()]).transform(e=>e===``?null:e);function nt(e,t){if(e===`image/png`)return[137,80,78,71,13,10,26,10].every((e,n)=>t[n]===e);if(e===`image/jpeg`)return t[0]===255&&t[1]===216&&t[2]===255;if(e===`image/svg+xml`)try{let e=new ae(`utf-8`,{fatal:!0}).decode(t);return/^\s*(?:(?:<\?xml[\s\S]*?\?>|<!--[\s\S]*?-->|<!DOCTYPE[\s\S]*?>)\s*)*<svg(?:\s|>)/u.test(e)}catch{return!1}return t[0]===82&&t[1]===73&&t[2]===70&&t[3]===70&&t[8]===87&&t[9]===69&&t[10]===66&&t[11]===80}const rt=p.strictObject({data:p.string().min(1).max(2796204),mimeType:p.enum(Pe)}).superRefine((e,t)=>{if(!We.test(e.data)){t.addIssue({code:`custom`,message:`Avatar data must be valid Base64.`});return}let n=se.from(e.data,`base64`);(n.byteLength===0||n.byteLength>2097152||n.toString(`base64`)!==e.data||!nt(e.mimeType,n))&&t.addIssue({code:`custom`,message:`Avatar data must be a valid PNG, JPEG, WebP, or SVG image no larger than 2 MB.`})}),it=p.array(p.enum(Ie)).max(Ie.length).refine(e=>new Set(e).size===e.length,{message:`Model capabilities must be unique.`}),P=p.strictObject({description:Ze,displayName:Xe,model:N,supportedCapabilities:it}),at=p.union([N,P.transform(e=>e.model)]),ot=p.strictObject({apiKey:tt,baseUrl:qe,defaultModel:N,protocol:p.literal(`responses`),reviewModel:Qe}),st=p.strictObject({apiKey:et,apiKeyHeader:p.enum(Fe),baseUrl:qe,fableModel:P.nullable(),haikuModel:P.nullable(),opusModel:P.nullable(),defaultModel:at,protocol:p.literal(`messages`),sonnetModel:P.nullable(),subagentModel:Qe,subagentModelForce:p.boolean().default(!1),hideAiAttribution:p.boolean().default(!1),teammatesMode:p.boolean().default(!1),enableToolSearch:p.boolean().default(!1),maxEffortThinking:p.boolean().default(!1),disableAutoUpdater:p.boolean().default(!1)}),ct={avatar:rt.nullable(),name:Ye,officialWebsite:Je,remark:$e},F=p.discriminatedUnion(`runtime`,[p.strictObject({...ct,configuration:ot,runtime:p.literal(`codex`)}),p.strictObject({...ct,configuration:st,runtime:p.literal(`claude-code`)})]),I=p.strictObject({providerId:p.string().min(1)}),lt=p.strictObject({runtime:p.enum(k)});function L(e){return F.parse(e)}function ut(e){return ot.parse(e)}function dt(e){return st.parse(e)}function ft(e){return e===null?null:se.from(rt.parse(e).data,`base64`)}const pt=new ae(`utf-8`,{fatal:!0}),mt=p.string().regex(/^[a-f0-9]{64}$/u),ht=p.strictObject({id:p.string().min(1).max(100).regex(/^[a-z][a-z0-9-]*$/u),mediaType:p.string().min(1).max(100),overwrite:p.boolean(),path:p.string().min(1).max(512),sha256:mt,size:p.number().int().nonnegative().max(2**53-1)}),gt=p.strictObject({createdAt:p.string().min(1).max(64).refine(e=>!Number.isNaN(Date.parse(e))),format:p.literal(Me),foundryVersion:p.string().min(1).max(100),modules:p.array(ht).max(256)}).superRefine((e,t)=>{let n=new Set,r=new Set;for(let[i,a]of e.modules.entries())n.has(a.id)&&t.addIssue({code:`custom`,message:`Export Module identifiers must be unique.`,path:[`modules`,i,`id`]}),r.has(a.path)&&t.addIssue({code:`custom`,message:`Export Module paths must be unique.`,path:[`modules`,i,`path`]}),n.add(a.id),r.add(a.path)}),_t=p.strictObject({colorMode:p.enum(O)}),vt=p.array(F);var R=class extends Error{constructor(e){super(`The selected file is not a valid Foundry Export.`,{cause:e}),this.name=`FoundryImportFileError`}};function yt(e){return Ne.includes(e)}function bt(e,t){let n=e.find(e=>e.filename===t);if(!n||n.directory||n.encrypted||n.symlink)throw Error(`The Export Module entry is unavailable.`);return n}async function xt(e,t){if(e.uncompressedSize>t)throw Error(`The archive entry is too large.`);let n=await e.getData(new h);if(n.byteLength>t)throw Error(`The archive entry is too large.`);return JSON.parse(pt.decode(n))}async function St(e){try{let t=gt.parse(await xt(bt(e,`manifest.json`),1048576)),n=new Set([`manifest.json`,...t.modules.map(e=>e.path)]),r=e.some(e=>!e.directory&&!n.has(e.filename)),i=new Set(t.modules.map(e=>e.id));if(r||Ne.some(e=>!i.has(e)))throw Error(`The Foundry Export package is incomplete.`);return t}catch(e){throw new R(e)}}async function Ct(e,t){if(e.mediaType!==`application/json`||e.size>268435456)throw Error(`The Export Module metadata is invalid.`);let n=bt(t,e.path);if(n.uncompressedSize!==e.size)throw Error(`The Export Module size does not match its manifest.`);let r=await n.getData(new h),i=g(`sha256`).update(r).digest(`hex`);if(r.byteLength!==e.size||i!==e.sha256)throw Error(`The Export Module does not match its manifest.`);return JSON.parse(pt.decode(r))}function wt(e){return{id:e,importedItems:0,message:`${e===`settings`?`Application Settings`:`Providers`} could not be imported.`,status:`failed`}}var Tt=class{providerStore;settingsStore;constructor(e,t){this.providerStore=e,this.settingsStore=t}async importData(e){if(e.byteLength===0)throw new R;let t=new ee(new m(e),{checkCrc32:!0,filenameValidation:`strict`,maxAppendedDataSize:0,strictness:`strict`});try{let e;try{e=await t.getEntries()}catch(e){throw new R(e)}if(e.length>256)throw new R;let n=await St(e),r=[];for(let t of n.modules){if(!yt(t.id)){r.push({id:t.id,importedItems:0,message:`This Export Module is not supported by this Foundry version.`,status:`unsupported`});continue}try{let n=await Ct(t,e);if(t.id===`settings`){if(!t.overwrite)throw Error(`Application Settings must use overwrite import behavior.`);this.settingsStore.updateApplicationSettings(_t.parse(n)),r.push({id:t.id,importedItems:1,status:`imported`})}else{if(t.overwrite)throw Error(`Providers must use append import behavior.`);let e=vt.parse(n);this.providerStore.createProviders(e),r.push({id:t.id,importedItems:e.length,status:`imported`})}}catch{r.push(wt(t.id))}}return{modules:r}}finally{await t.close()}}};const Et=p.strictObject({});function Dt(e,t){e.post(`/api/data/import`,c(`query`,Et),ie({maxSize:268435456}),async e=>{try{let n=await t.importData(new Uint8Array(await e.req.arrayBuffer()));return e.json({status:D.success,data:n})}catch(t){if(t instanceof R)return e.json({message:t.message},400);throw t}})}function z(e){return{avatar:e.avatar,baseUrl:e.configuration.baseUrl,id:e.id,name:e.name,officialWebsite:e.officialWebsite,remark:e.remark,runtime:e.runtime}}function Ot(e,t,n){e.get(`/api/providers`,c(`query`,lt),e=>e.json({status:D.success,data:t.listProviders(e.req.valid(`query`).runtime).map(e=>z(e))})),e.delete(`/api/providers/:providerId`,c(`param`,I),e=>{let n=t.deleteProvider(e.req.valid(`param`).providerId);return n===`in-use`?e.json({status:D.providerInUse,data:!1,message:`A Provider in use cannot be deleted.`}):n===`not-found`?e.json({status:D.providerNotFound,data:!1,message:`The selected Provider is unavailable.`}):e.json({status:D.success,data:!0})}),e.post(`/api/providers/:providerId/test-connection`,c(`param`,I),async e=>{let r=t.getProvider(e.req.valid(`param`).providerId);if(r===null)return e.json({status:D.providerNotFound,data:!1,message:`The selected Provider is unavailable.`});let i=await n.testProvider(r);return i.successful?e.json({status:D.success,data:!0}):e.json({status:D.providerConnectionFailed,data:!1,message:i.message})}),e.post(`/api/providers`,c(`json`,F),e=>{let n=e.req.valid(`json`),r=z(t.createProvider(n));return e.json({status:D.success,data:r},201)}),e.get(`/api/providers/:providerId`,c(`param`,I),e=>{let n=t.getProvider(e.req.valid(`param`).providerId);return n===null?e.json({status:D.providerNotFound,data:null,message:`The selected Provider is unavailable.`}):e.json({status:D.success,data:n})}),e.post(`/api/providers/:providerId/copy`,c(`param`,I),c(`json`,F),e=>{let n=e.req.valid(`param`).providerId,r=t.getProvider(n);if(r===null)return e.json({status:D.providerNotFound,data:null,message:`The selected Provider is unavailable.`});let i=e.req.valid(`json`);if(i.runtime!==r.runtime)return e.json({message:`A Provider Runtime cannot be changed.`},400);let a=t.copyProvider(n,i);return a===null?e.json({status:D.providerNotFound,data:null,message:`The selected Provider is unavailable.`}):e.json({status:D.success,data:z(a)},201)}),e.put(`/api/providers/:providerId`,c(`param`,I),c(`json`,F),e=>{let n=e.req.valid(`param`).providerId,r=t.getProvider(n);if(r===null)return e.json({status:D.providerNotFound,data:null,message:`The selected Provider is unavailable.`});let i=e.req.valid(`json`);if(i.runtime!==r.runtime)return e.json({message:`A Provider Runtime cannot be changed.`},400);let a=t.updateProvider(n,i);return e.json({status:D.success,data:a===null?null:z(a)})})}var B=class extends Error{status;constructor(e,t){super(t),this.status=e}};const kt=p.discriminatedUnion(`kind`,[p.strictObject({kind:p.literal(`official-default`)}),p.strictObject({kind:p.literal(`provider`),providerId:p.string().min(1)})]),At=p.strictObject({runtime:p.enum(k)}),jt=p.strictObject({}),Mt=p.strictObject({providerKey:p.string().min(1).max(200).optional(),target:kt}),Nt=p.strictObject({expectedFileHash:p.string().regex(/^[0-9a-f]{64}$/),providerKey:p.string().min(1).max(200).optional(),target:kt});function Pt(e,t){e.get(`/api/runtimes`,c(`query`,jt),async e=>e.json({status:D.success,data:await t.listRuntimes()})),e.post(`/api/runtimes/:runtime/preview`,c(`param`,At),c(`json`,Mt),async e=>{try{return e.json({status:D.success,data:await t.previewConfiguration(e.req.valid(`param`).runtime,e.req.valid(`json`))})}catch(t){if(t instanceof B)return e.json({status:t.status,data:null,message:t.message});throw t}}),e.post(`/api/runtimes/:runtime/apply`,c(`param`,At),c(`json`,Nt),async e=>{try{return e.json({status:D.success,data:await t.applyConfiguration(e.req.valid(`param`).runtime,e.req.valid(`json`))})}catch(t){if(t instanceof B)return e.json({status:t.status,data:null,message:t.message});throw t}})}const Ft=p.strictObject({}),It=p.strictObject({colorMode:p.enum(O)});function Lt(e,t){e.get(`/api/settings`,c(`query`,Ft),e=>e.json({status:D.success,data:t.getApplicationSettings()})),e.patch(`/api/settings`,c(`json`,It),e=>e.json({status:D.success,data:t.updateApplicationSettings(e.req.valid(`json`))}))}const Rt=p.strictObject({});function zt(){return[d.resolve(import.meta.dirname,`app`),d.resolve(import.meta.dirname,`../../dist/app`)].find(e=>u(e))}function Bt(e){let t=new f;t.get(`/api/health`,c(`query`,Rt),e=>e.json({status:D.success,data:!0,message:`Service is healthy.`})),Re(t,new Ue(e.providerStore,e.settingsStore)),Dt(t,new Tt(e.providerStore,e.settingsStore)),Lt(t,e.settingsStore),Ot(t,e.providerStore,e.providerConnectionTester),Pt(t,e.runtimeService);let n=e.webRoot??zt();return n&&t.use(`*`,s({root:n})),t}const V=`__drizzle_migrations`;function Vt(e){return!!e.prepare(`
2
+ import{t as e}from"./package-CgHJEDYp.mjs";import{defineCommand as t,renderUsage as n,runMain as r}from"citty";import i from"node:process";import a from"open";import{serve as o}from"@hono/node-server";import{serveStatic as s}from"@hono/node-server/serve-static";import{zValidator as c}from"@hono/zod-validator";import{constants as l,existsSync as u}from"node:fs";import d from"node:path";import{Hono as f}from"hono";import{z as p}from"zod";import{Uint8ArrayReader as m,Uint8ArrayWriter as h,ZipReader as ee,ZipWriter as te}from"@zip.js/zip.js";import{createHash as g,randomUUID as ne,randomUUIDv7 as re}from"node:crypto";import{bodyLimit as ie}from"hono/body-limit";import{TextDecoder as ae,promisify as oe}from"node:util";import{Buffer as se}from"node:buffer";import ce from"better-sqlite3";import{drizzle as le}from"drizzle-orm/better-sqlite3";import{access as ue,chmod as de,lstat as fe,mkdir as _,readFile as pe,readdir as me,realpath as he,rename as ge,rm as v,stat as _e,writeFile as ve}from"node:fs/promises";import{readMigrationFiles as ye}from"drizzle-orm/migrator";import be from"env-paths";import{homedir as xe}from"node:os";import{and as y,desc as Se,eq as b,isNull as x,sql as S}from"drizzle-orm";import{blob as Ce,check as C,index as we,integer as w,sqliteTable as T,text as E,uniqueIndex as Te}from"drizzle-orm/sqlite-core";import{parse as Ee,patch as De,stringify as Oe}from"@decimalturn/toml-patch";import{execFile as ke}from"node:child_process";var Ae=Object.defineProperty,je=(e,t)=>{let n={};for(var r in e)Ae(n,r,{get:e[r],enumerable:!0});return t||Ae(n,Symbol.toStringTag,{value:`Module`}),n};const D={providerConnectionFailed:`PROVIDER_CONNECTION_FAILED`,providerInUse:`PROVIDER_IN_USE`,providerNotFound:`PROVIDER_NOT_FOUND`,runtimeApplyFailed:`RUNTIME_APPLY_FAILED`,runtimeConfigurationChanged:`RUNTIME_CONFIGURATION_CHANGED`,runtimeConfigurationInvalid:`RUNTIME_CONFIGURATION_INVALID`,runtimeNotDetected:`RUNTIME_NOT_DETECTED`,success:`SUCCESS`},O=[`system`,`light`,`dark`],Me=`foundry-export-v1`,Ne=[`settings`,`providers`],k=[`codex`,`claude-code`],Pe=[`image/png`,`image/jpeg`,`image/webp`,`image/svg+xml`],Fe=[`authorization`,`x-api-key`],Ie=[`effort`,`xhigh_effort`,`max_effort`,`thinking`,`adaptive_thinking`,`interleaved_thinking`],Le=p.strictObject({});function Re(e,t){e.get(`/api/data/export`,c(`query`,Le),async()=>{let e=await t.createExport();return new Response(e.content,{headers:{"cache-control":`no-store`,"content-disposition":`attachment; filename="${e.filename}"`,"content-length":String(e.content.byteLength),"content-type":`application/octet-stream`,"x-content-type-options":`nosniff`}})})}const ze=new TextEncoder;function A(e){return String(e).padStart(2,`0`)}function Be(e){let t={avatar:e.avatar,name:e.name,officialWebsite:e.officialWebsite,remark:e.remark};return e.runtime,{...t,configuration:e.configuration,runtime:e.runtime}}function Ve(e,t,n,r){let i=ze.encode(JSON.stringify(r));return{content:i,manifest:{id:e,mediaType:`application/json`,overwrite:n,path:t,sha256:g(`sha256`).update(i).digest(`hex`),size:i.byteLength}}}function He(e){return`foundry-export-${[e.getFullYear(),`-`,A(e.getMonth()+1),`-`,A(e.getDate()),`T`,A(e.getHours()),A(e.getMinutes()),A(e.getSeconds())].join(``)}.foundry`}var Ue=class{providerStore;settingsStore;now;foundryVersion;constructor(t,n,r=()=>new Date,i=e){this.providerStore=t,this.settingsStore=n,this.now=r,this.foundryVersion=i}async createExport(){let e=this.now(),t=this.settingsStore.getApplicationSettings(),n=k.flatMap(e=>this.providerStore.listProviders(e).map(e=>Be(e))),r=[Ve(`settings`,`modules/settings.json`,!0,t),Ve(`providers`,`modules/providers.json`,!1,n)],i={createdAt:e.toISOString(),format:Me,foundryVersion:this.foundryVersion,modules:r.map(e=>e.manifest)},a=new te(new h),o=JSON.stringify(i),s=ze.encode(o);await a.add(`manifest.json`,new m(s));for(let e of r)await a.add(e.manifest.path,new m(e.content));return{content:await a.close(),filename:He(e),manifest:i}}};const We=/^[A-Za-z0-9+/]+={0,2}$/u;function Ge(e,t){for(let n of e){let e=n.codePointAt(0)??0,r=t&&[9,10,13].includes(e);if(e===127||!r&&e<32)return!0}return!1}function j(e){return p.string().trim().max(e).refine(e=>!Ge(e,!0),{message:`Control characters are not allowed.`})}function M(e){return j(e).nullable().transform(e=>e===``?null:e)}function Ke(e,t){try{let n=new URL(e);return[`http:`,`https:`].includes(n.protocol)&&n.username===``&&n.password===``&&(t||n.search===``&&n.hash===``)}catch{return!1}}const qe=j(2048).min(1).refine(e=>Ke(e,!1),{message:`Provide an HTTP or HTTPS URL without credentials, a query, or a fragment.`}),Je=M(2048).refine(e=>e===null||Ke(e,!0),{message:`Provide an HTTP or HTTPS URL without credentials.`}),Ye=j(100).min(1),Xe=M(100),Ze=M(2e3),N=j(200).min(1),Qe=M(200),$e=M(2e3),et=p.string().min(1).max(16384).refine(e=>!Ge(e,!1),{message:`Control characters are not allowed.`}),tt=p.union([et,p.literal(``),p.null()]).transform(e=>e===``?null:e);function nt(e,t){if(e===`image/png`)return[137,80,78,71,13,10,26,10].every((e,n)=>t[n]===e);if(e===`image/jpeg`)return t[0]===255&&t[1]===216&&t[2]===255;if(e===`image/svg+xml`)try{let e=new ae(`utf-8`,{fatal:!0}).decode(t);return/^\s*(?:(?:<\?xml[\s\S]*?\?>|<!--[\s\S]*?-->|<!DOCTYPE[\s\S]*?>)\s*)*<svg(?:\s|>)/u.test(e)}catch{return!1}return t[0]===82&&t[1]===73&&t[2]===70&&t[3]===70&&t[8]===87&&t[9]===69&&t[10]===66&&t[11]===80}const rt=p.strictObject({data:p.string().min(1).max(2796204),mimeType:p.enum(Pe)}).superRefine((e,t)=>{if(!We.test(e.data)){t.addIssue({code:`custom`,message:`Avatar data must be valid Base64.`});return}let n=se.from(e.data,`base64`);(n.byteLength===0||n.byteLength>2097152||n.toString(`base64`)!==e.data||!nt(e.mimeType,n))&&t.addIssue({code:`custom`,message:`Avatar data must be a valid PNG, JPEG, WebP, or SVG image no larger than 2 MB.`})}),it=p.array(p.enum(Ie)).max(Ie.length).refine(e=>new Set(e).size===e.length,{message:`Model capabilities must be unique.`}),P=p.strictObject({description:Ze,displayName:Xe,model:N,supportedCapabilities:it}),at=p.union([N,P.transform(e=>e.model)]),ot=p.strictObject({apiKey:tt,baseUrl:qe,defaultModel:N,protocol:p.literal(`responses`),reviewModel:Qe}),st=p.strictObject({apiKey:et,apiKeyHeader:p.enum(Fe),baseUrl:qe,fableModel:P.nullable(),haikuModel:P.nullable(),opusModel:P.nullable(),defaultModel:at,protocol:p.literal(`messages`),sonnetModel:P.nullable(),subagentModel:Qe,subagentModelForce:p.boolean().default(!1),hideAiAttribution:p.boolean().default(!1),teammatesMode:p.boolean().default(!1),enableToolSearch:p.boolean().default(!1),maxEffortThinking:p.boolean().default(!1),disableAutoUpdater:p.boolean().default(!1)}),ct={avatar:rt.nullable(),name:Ye,officialWebsite:Je,remark:$e},F=p.discriminatedUnion(`runtime`,[p.strictObject({...ct,configuration:ot,runtime:p.literal(`codex`)}),p.strictObject({...ct,configuration:st,runtime:p.literal(`claude-code`)})]),I=p.strictObject({providerId:p.string().min(1)}),lt=p.strictObject({runtime:p.enum(k)});function L(e){return F.parse(e)}function ut(e){return ot.parse(e)}function dt(e){return st.parse(e)}function ft(e){return e===null?null:se.from(rt.parse(e).data,`base64`)}const pt=new ae(`utf-8`,{fatal:!0}),mt=p.string().regex(/^[a-f0-9]{64}$/u),ht=p.strictObject({id:p.string().min(1).max(100).regex(/^[a-z][a-z0-9-]*$/u),mediaType:p.string().min(1).max(100),overwrite:p.boolean(),path:p.string().min(1).max(512),sha256:mt,size:p.number().int().nonnegative().max(2**53-1)}),gt=p.strictObject({createdAt:p.string().min(1).max(64).refine(e=>!Number.isNaN(Date.parse(e))),format:p.literal(Me),foundryVersion:p.string().min(1).max(100),modules:p.array(ht).max(256)}).superRefine((e,t)=>{let n=new Set,r=new Set;for(let[i,a]of e.modules.entries())n.has(a.id)&&t.addIssue({code:`custom`,message:`Export Module identifiers must be unique.`,path:[`modules`,i,`id`]}),r.has(a.path)&&t.addIssue({code:`custom`,message:`Export Module paths must be unique.`,path:[`modules`,i,`path`]}),n.add(a.id),r.add(a.path)}),_t=p.strictObject({colorMode:p.enum(O)}),vt=p.array(F);var R=class extends Error{constructor(e){super(`The selected file is not a valid Foundry Export.`,{cause:e}),this.name=`FoundryImportFileError`}};function yt(e){return Ne.includes(e)}function bt(e,t){let n=e.find(e=>e.filename===t);if(!n||n.directory||n.encrypted||n.symlink)throw Error(`The Export Module entry is unavailable.`);return n}async function xt(e,t){if(e.uncompressedSize>t)throw Error(`The archive entry is too large.`);let n=await e.getData(new h);if(n.byteLength>t)throw Error(`The archive entry is too large.`);return JSON.parse(pt.decode(n))}async function St(e){try{let t=gt.parse(await xt(bt(e,`manifest.json`),1048576)),n=new Set([`manifest.json`,...t.modules.map(e=>e.path)]),r=e.some(e=>!e.directory&&!n.has(e.filename)),i=new Set(t.modules.map(e=>e.id));if(r||Ne.some(e=>!i.has(e)))throw Error(`The Foundry Export package is incomplete.`);return t}catch(e){throw new R(e)}}async function Ct(e,t){if(e.mediaType!==`application/json`||e.size>268435456)throw Error(`The Export Module metadata is invalid.`);let n=bt(t,e.path);if(n.uncompressedSize!==e.size)throw Error(`The Export Module size does not match its manifest.`);let r=await n.getData(new h),i=g(`sha256`).update(r).digest(`hex`);if(r.byteLength!==e.size||i!==e.sha256)throw Error(`The Export Module does not match its manifest.`);return JSON.parse(pt.decode(r))}function wt(e){return{id:e,importedItems:0,message:`${e===`settings`?`Application Settings`:`Providers`} could not be imported.`,status:`failed`}}var Tt=class{providerStore;settingsStore;constructor(e,t){this.providerStore=e,this.settingsStore=t}async importData(e){if(e.byteLength===0)throw new R;let t=new ee(new m(e),{checkCrc32:!0,filenameValidation:`strict`,maxAppendedDataSize:0,strictness:`strict`});try{let e;try{e=await t.getEntries()}catch(e){throw new R(e)}if(e.length>256)throw new R;let n=await St(e),r=[];for(let t of n.modules){if(!yt(t.id)){r.push({id:t.id,importedItems:0,message:`This Export Module is not supported by this Foundry version.`,status:`unsupported`});continue}try{let n=await Ct(t,e);if(t.id===`settings`){if(!t.overwrite)throw Error(`Application Settings must use overwrite import behavior.`);this.settingsStore.updateApplicationSettings(_t.parse(n)),r.push({id:t.id,importedItems:1,status:`imported`})}else{if(t.overwrite)throw Error(`Providers must use append import behavior.`);let e=vt.parse(n);this.providerStore.createProviders(e),r.push({id:t.id,importedItems:e.length,status:`imported`})}}catch{r.push(wt(t.id))}}return{modules:r}}finally{await t.close()}}};const Et=p.strictObject({});function Dt(e,t){e.post(`/api/data/import`,c(`query`,Et),ie({maxSize:268435456}),async e=>{try{let n=await t.importData(new Uint8Array(await e.req.arrayBuffer()));return e.json({status:D.success,data:n})}catch(t){if(t instanceof R)return e.json({message:t.message},400);throw t}})}function z(e){return{avatar:e.avatar,baseUrl:e.configuration.baseUrl,id:e.id,name:e.name,officialWebsite:e.officialWebsite,remark:e.remark,runtime:e.runtime}}function Ot(e,t,n){e.get(`/api/providers`,c(`query`,lt),e=>e.json({status:D.success,data:t.listProviders(e.req.valid(`query`).runtime).map(e=>z(e))})),e.delete(`/api/providers/:providerId`,c(`param`,I),e=>{let n=t.deleteProvider(e.req.valid(`param`).providerId);return n===`in-use`?e.json({status:D.providerInUse,data:!1,message:`A Provider in use cannot be deleted.`}):n===`not-found`?e.json({status:D.providerNotFound,data:!1,message:`The selected Provider is unavailable.`}):e.json({status:D.success,data:!0})}),e.post(`/api/providers/:providerId/test-connection`,c(`param`,I),async e=>{let r=t.getProvider(e.req.valid(`param`).providerId);if(r===null)return e.json({status:D.providerNotFound,data:!1,message:`The selected Provider is unavailable.`});let i=await n.testProvider(r);return i.successful?e.json({status:D.success,data:!0}):e.json({status:D.providerConnectionFailed,data:!1,message:i.message})}),e.post(`/api/providers`,c(`json`,F),e=>{let n=e.req.valid(`json`),r=z(t.createProvider(n));return e.json({status:D.success,data:r},201)}),e.get(`/api/providers/:providerId`,c(`param`,I),e=>{let n=t.getProvider(e.req.valid(`param`).providerId);return n===null?e.json({status:D.providerNotFound,data:null,message:`The selected Provider is unavailable.`}):e.json({status:D.success,data:n})}),e.post(`/api/providers/:providerId/copy`,c(`param`,I),c(`json`,F),e=>{let n=e.req.valid(`param`).providerId,r=t.getProvider(n);if(r===null)return e.json({status:D.providerNotFound,data:null,message:`The selected Provider is unavailable.`});let i=e.req.valid(`json`);if(i.runtime!==r.runtime)return e.json({message:`A Provider Runtime cannot be changed.`},400);let a=t.copyProvider(n,i);return a===null?e.json({status:D.providerNotFound,data:null,message:`The selected Provider is unavailable.`}):e.json({status:D.success,data:z(a)},201)}),e.put(`/api/providers/:providerId`,c(`param`,I),c(`json`,F),e=>{let n=e.req.valid(`param`).providerId,r=t.getProvider(n);if(r===null)return e.json({status:D.providerNotFound,data:null,message:`The selected Provider is unavailable.`});let i=e.req.valid(`json`);if(i.runtime!==r.runtime)return e.json({message:`A Provider Runtime cannot be changed.`},400);let a=t.updateProvider(n,i);return e.json({status:D.success,data:a===null?null:z(a)})})}var B=class extends Error{status;constructor(e,t){super(t),this.status=e}};const kt=p.discriminatedUnion(`kind`,[p.strictObject({kind:p.literal(`official-default`)}),p.strictObject({kind:p.literal(`provider`),providerId:p.string().min(1)})]),At=p.strictObject({runtime:p.enum(k)}),jt=p.strictObject({}),Mt=p.strictObject({providerKey:p.string().min(1).max(200).optional(),target:kt}),Nt=p.strictObject({expectedFileHash:p.string().regex(/^[0-9a-f]{64}$/),providerKey:p.string().min(1).max(200).optional(),target:kt});function Pt(e,t){e.get(`/api/runtimes`,c(`query`,jt),async e=>e.json({status:D.success,data:await t.listRuntimes()})),e.post(`/api/runtimes/:runtime/preview`,c(`param`,At),c(`json`,Mt),async e=>{try{return e.json({status:D.success,data:await t.previewConfiguration(e.req.valid(`param`).runtime,e.req.valid(`json`))})}catch(t){if(t instanceof B)return e.json({status:t.status,data:null,message:t.message});throw t}}),e.post(`/api/runtimes/:runtime/apply`,c(`param`,At),c(`json`,Nt),async e=>{try{return e.json({status:D.success,data:await t.applyConfiguration(e.req.valid(`param`).runtime,e.req.valid(`json`))})}catch(t){if(t instanceof B)return e.json({status:t.status,data:null,message:t.message});throw t}})}const Ft=p.strictObject({}),It=p.strictObject({colorMode:p.enum(O)});function Lt(e,t){e.get(`/api/settings`,c(`query`,Ft),e=>e.json({status:D.success,data:t.getApplicationSettings()})),e.patch(`/api/settings`,c(`json`,It),e=>e.json({status:D.success,data:t.updateApplicationSettings(e.req.valid(`json`))}))}const Rt=p.strictObject({});function zt(){return[d.resolve(import.meta.dirname,`app`),d.resolve(import.meta.dirname,`../../dist/app`)].find(e=>u(e))}function Bt(e){let t=new f;t.get(`/api/health`,c(`query`,Rt),e=>e.json({status:D.success,data:!0,message:`Service is healthy.`})),Re(t,new Ue(e.providerStore,e.settingsStore)),Dt(t,new Tt(e.providerStore,e.settingsStore)),Lt(t,e.settingsStore),Ot(t,e.providerStore,e.providerConnectionTester),Pt(t,e.runtimeService);let n=e.webRoot??zt();return n&&t.use(`*`,s({root:n})),t}const V=`__drizzle_migrations`;function Vt(e){return!!e.prepare(`
3
3
  SELECT 1
4
4
  FROM sqlite_master
5
5
  WHERE type = 'table' AND name = ?
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
1
 
2
- import{t as e}from"./package-DreCfN5H.mjs";function t(){return e}export{t as getVersion};
2
+ import{t as e}from"./package-CgHJEDYp.mjs";function t(){return e}export{t as getVersion};
@@ -0,0 +1,2 @@
1
+
2
+ var e=`1.2.1`;export{e as t};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dhzh/foundry",
3
3
  "type": "module",
4
- "version": "1.2.0",
4
+ "version": "1.2.1",
5
5
  "devEngines": {
6
6
  "packageManager": {
7
7
  "name": "pnpm",
@@ -1,2 +0,0 @@
1
-
2
- var e=`1.2.0`;export{e as t};