@dhzh/foundry 0.0.3-beta.1 → 0.0.3-beta.2
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 +28 -7
- package/dist/cli/index.mjs +25 -1
- package/dist/web/index.html +1 -1
- package/dist/web/static/css/index.400108c990.css +1 -0
- package/dist/web/static/js/304.ae01979a4b.js +3 -0
- package/dist/web/static/js/index.93ad4f2a33.js +1 -0
- package/dist/web/static/js/lib-react.8689a73639.js +2 -0
- package/dist/web/static/js/lib-router.7aa1b84d62.js +2 -0
- package/dist/web/static/js/lib-router.7aa1b84d62.js.LICENSE.txt +10 -0
- package/package.json +6 -2
- package/dist/web/static/css/index.ef46db3751.css +0 -0
- package/dist/web/static/js/488.ff29f65580.js +0 -1
- package/dist/web/static/js/async/973.3e764f383c.js +0 -1
- package/dist/web/static/js/index.8be18902e4.js +0 -1
- package/dist/web/static/js/lib-react.bf6574ea51.js +0 -2
- /package/dist/web/static/css/{488.399a9465c7.css → 304.399a9465c7.css} +0 -0
- /package/dist/web/static/js/{lib-react.bf6574ea51.js.LICENSE.txt → lib-react.8689a73639.js.LICENSE.txt} +0 -0
package/README.md
CHANGED
|
@@ -5,9 +5,8 @@ An AI-native local developer runtime for tools, skills, agents, and workflows.
|
|
|
5
5
|
Foundry is distributed as a CLI package. It starts a local Hono server and
|
|
6
6
|
serves the bundled React Web UI from `dist/web/`.
|
|
7
7
|
|
|
8
|
-
The Web UI is built with Astryx and
|
|
9
|
-
|
|
10
|
-
described in the implementation plans under [`specs/plans/`](specs/plans/).
|
|
8
|
+
The Web UI is built with Astryx and provides the local Foundry administration
|
|
9
|
+
shell, including the Settings surface.
|
|
11
10
|
|
|
12
11
|
## Requirements
|
|
13
12
|
|
|
@@ -50,10 +49,7 @@ pnpm run build:cli # Build only dist/cli/
|
|
|
50
49
|
pnpm run build:web # Build only dist/web/
|
|
51
50
|
pnpm run dev:cli # Run the CLI from source in watch mode
|
|
52
51
|
pnpm run dev:web # Start the Rsbuild Web UI development server
|
|
53
|
-
pnpm run preview:web # Preview the production Web UI build
|
|
54
52
|
pnpm run lint # Run ESLint
|
|
55
|
-
pnpm run test # Run Vitest once
|
|
56
|
-
pnpm run test:coverage # Run Vitest with coverage
|
|
57
53
|
```
|
|
58
54
|
|
|
59
55
|
## Run A Production Build
|
|
@@ -74,6 +70,24 @@ pnpm dlx @dhzh/foundry
|
|
|
74
70
|
The `foundry` executable listens on `127.0.0.1:7777` and prints a clickable
|
|
75
71
|
local URL when it starts.
|
|
76
72
|
|
|
73
|
+
## Settings
|
|
74
|
+
|
|
75
|
+
The Settings capability is available through the Web UI and the module-level
|
|
76
|
+
CLI command:
|
|
77
|
+
|
|
78
|
+
Current registered settings are `ui.theme` and `ui.pointer`.
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
foundry settings list
|
|
82
|
+
foundry settings get ui.theme
|
|
83
|
+
foundry settings set ui.theme dark
|
|
84
|
+
foundry settings reset ui.theme
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Use `--raw` when only command values or boolean mutation results are needed.
|
|
88
|
+
The installable [`foundry-settings`](skills/foundry-settings/SKILL.md) Skill is
|
|
89
|
+
the automation wrapper for this CLI surface.
|
|
90
|
+
|
|
77
91
|
## Web UI
|
|
78
92
|
|
|
79
93
|
The private [`web`](packages/web/package.json) workspace uses:
|
|
@@ -99,7 +113,10 @@ the CLI can serve them next to its bundled output.
|
|
|
99
113
|
|
|
100
114
|
| Path | Role |
|
|
101
115
|
|------|------|
|
|
116
|
+
| `specs/plans/` | Numbered implementation plans and their naming convention |
|
|
102
117
|
| `src/cli/` | CLI entrypoint and Hono static server |
|
|
118
|
+
| `src/modules/settings/` | Settings registry, repository, service, routes, and CLI command |
|
|
119
|
+
| `skills/foundry-settings/` | Installable Skill wrapping the Settings CLI command |
|
|
103
120
|
| `packages/web/` | Rsbuild + React Web UI workspace |
|
|
104
121
|
| `packages/web/src/index.tsx` | Browser entrypoint |
|
|
105
122
|
| `packages/web/src/app.tsx` | Root React component |
|
|
@@ -108,7 +125,11 @@ the CLI can serve them next to its bundled output.
|
|
|
108
125
|
| `bin/index.js` | Published `foundry` executable wrapper |
|
|
109
126
|
| `dist/cli/` | Bundled CLI output generated by tsdown |
|
|
110
127
|
| `dist/web/` | Web UI production output served by the CLI |
|
|
111
|
-
|
|
128
|
+
|
|
129
|
+
## Implementation Plans
|
|
130
|
+
|
|
131
|
+
See [`specs/plans/README.md`](specs/plans/README.md) for the canonical plan
|
|
132
|
+
location, naming, numbering, and content conventions.
|
|
112
133
|
|
|
113
134
|
## Learn More
|
|
114
135
|
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1 +1,25 @@
|
|
|
1
|
-
import e from"cac";import t from"terminal-link";import{existsSync as
|
|
1
|
+
import e from"cac";import t,{exit as n}from"node:process";import r from"terminal-link";import{z as i}from"zod";import{existsSync as a,mkdirSync as o}from"node:fs";import{DatabaseSync as s}from"node:sqlite";import{homedir as c}from"node:os";import l from"node:path";import u from"cli-table3";import{consola as d}from"consola";import{serve as f}from"@hono/node-server";import{serveStatic as p}from"@hono/node-server/serve-static";import{zValidator as m}from"@hono/zod-validator";import{Hono as h}from"hono";function g(){return l.join(c(),`.foundry`)}function _(){return l.join(g(),`foundry.sqlite`)}function v(){let e=g();return a(e)||o(e,{recursive:!0}),e}function y(){let e=_();return a(e)||new s(e).close(),e}function b(){return{storageRootPath:v(),databasePath:y()}}function x(e){let t=e.prepare(`PRAGMA integrity_check`).all().flatMap(e=>Object.values(e));if(t.length!==1||t[0]!==`ok`)throw Error(`SQLite database integrity check failed`)}function S(e,t){if(!e.isOpen)throw Error(`Cannot start a transaction on a closed database`);if(e.isTransaction)throw Error(`Nested SQLite transactions are not supported`);e.exec(`BEGIN`);try{let n=t();return e.exec(`COMMIT`),n}catch(t){throw e.exec(`ROLLBACK`),t}}function C(){let e=_(),t=new s(e,{enableForeignKeyConstraints:!0,timeout:5e3});t.exec(`
|
|
2
|
+
PRAGMA journal_mode = DELETE;
|
|
3
|
+
PRAGMA synchronous = FULL;
|
|
4
|
+
`);let n=!1;return{database:t,databasePath:e,assertIntegrity:()=>x(t),transaction:e=>S(t,e),close:()=>{n||=(t.close(),!0)}}}const w=[{group:`ui`,name:`theme`,defaultValue:`system`,schema:i.enum([`system`,`light`,`dark`]),secret:!1},{group:`ui`,name:`pointer`,defaultValue:!0,schema:i.boolean(),secret:!1}];function T(e,t){return w.find(n=>n.group===e&&n.name===t)}function E(e,t){return`${e}.${t}`}const D=`settings`;function O(e){e.database.exec(`
|
|
5
|
+
CREATE TABLE IF NOT EXISTS ${D} (
|
|
6
|
+
"group" TEXT NOT NULL,
|
|
7
|
+
name TEXT NOT NULL,
|
|
8
|
+
payload TEXT NOT NULL,
|
|
9
|
+
created_at INTEGER NOT NULL,
|
|
10
|
+
updated_at INTEGER NOT NULL,
|
|
11
|
+
PRIMARY KEY ("group", name)
|
|
12
|
+
)
|
|
13
|
+
`);let t=e.database.prepare(`
|
|
14
|
+
INSERT OR IGNORE INTO ${D} ("group", name, payload, created_at, updated_at)
|
|
15
|
+
VALUES (?, ?, ?, ?, ?)
|
|
16
|
+
`),n=Date.now();e.transaction(()=>{for(let e of w)t.run(e.group,e.name,JSON.stringify({value:e.defaultValue}),n,n)})}function k(e){let t=(e=[])=>`
|
|
17
|
+
SELECT * FROM ${D}
|
|
18
|
+
${e.length>0?`WHERE ${e.map(e=>`${e} = ?`).join(` AND `)}`:``}
|
|
19
|
+
`,n=e.database.prepare(t()),r=e.database.prepare(t([`"group"`,`name`])),i=e.database.prepare(`
|
|
20
|
+
INSERT INTO ${D} ("group", name, payload, created_at, updated_at)
|
|
21
|
+
VALUES (?, ?, ?, ?, ?)
|
|
22
|
+
ON CONFLICT("group", name) DO UPDATE SET
|
|
23
|
+
payload = excluded.payload,
|
|
24
|
+
updated_at = excluded.updated_at
|
|
25
|
+
`);return{get({group:e,name:t}){return r.get(e,t)},getAll(){return n.all()},upsert(e,t,n){let r=Date.now();return i.run(e,t,n,r,r).changes===1}}}function A(e,t){let n=T(e,t);if(!n)throw Error(`Unknown setting: ${e}.${t}`);return n}function j(e,t){let n;try{n=JSON.parse(e)}catch{return{structurallyValid:!1}}let r=i.object({value:t}).safeParse(n);return r.success?{value:r.data.value,structurallyValid:!0}:{structurallyValid:!1}}function M(e){O(e);let t=k(e);return{get(e,n){let r=t.get({group:e,name:n}),i=A(e,n),a=E(e,n);if(r){let t=j(r.payload,i.schema);if(t.structurallyValid)return{key:a,group:e,name:n,value:t.value}}return{key:a,group:e,name:n,value:i.defaultValue}},list(){return t.getAll().map(({group:e,name:t,payload:n})=>{let r=A(e,t),i=j(n,r.schema);return{key:E(e,t),group:e,name:t,value:i.structurallyValid?i.value:r.defaultValue}})},setMany(n){e.transaction(()=>{for(let{group:e,name:r,value:i}of n)t.upsert(e,r,JSON.stringify({value:i}))})},resetMany(n){let r=n.map(({group:e,name:t})=>A(e,t));e.transaction(()=>{for(let e of r)t.upsert(e.group,e.name,JSON.stringify({value:e.defaultValue}))})}}}function N(){let e=b(),t=C();try{return t.assertIntegrity(),{storage:t,storagePaths:e,settingsService:M(t),close:t.close}}catch(e){throw t.close(),e}}const P={log(...e){d.log(...e)},raw(...e){d.log.raw(...e)},table(e,t){let n=new u({head:[...e]});for(let e of t)n.push([...e]);d.log(n.toString())},info(...e){d.info(...e)},error(...e){d.error(...e)}},F=i.enum([`get`,`list`,`set`,`reset`]),I=[`Key`,`Value`];function L(e){return typeof e==`string`?e:JSON.stringify(e)}function R(e,t,n){if(e.length!==t)throw Error(`Usage: foundry settings ${n}`)}function z(e){let t=e.indexOf(`.`);if(t<=0||t>=e.length-1)throw Error(`Invalid setting key: ${e}`);return{group:e.slice(0,t),name:e.slice(t+1)}}function B(e){e.command(`settings <action> [...values]`,`Get, list, set, or reset Foundry settings`).option(`--raw`,`Print raw output`).action((e,t,n)=>{let r=F.safeParse(e);if(!r.success)throw Error(`Unknown settings action: ${e}, expected one of: ${F.options.join(`, `)}`);let{raw:i=!1}=n,a=N();try{switch(r.data){case`get`:{R(t,1,`get <key>`);let{group:e,name:n}=z(t[0]),r=a.settingsService.get(e,n);i?P.raw(L(r.value)):P.table(I,[[r.key,L(r.value)]]);return}case`list`:{R(t,0,`list`);let e=a.settingsService.list();if(i)for(let t of e)P.raw(L(t.value));else P.table(I,e.map(e=>[e.key,L(e.value)]));return}case`reset`:{R(t,1,`reset <key>`);let{group:e,name:n}=z(t[0]);try{a.settingsService.resetMany([{group:e,name:n}]),i?P.raw(`true`):P.log(!0)}catch{i?P.raw(`false`):P.log(!1)}return}case`set`:{R(t,2,`set <key> <value>`);let{group:e,name:n}=z(t[0]);try{a.settingsService.setMany([{group:e,name:n,value:t[1]}]),i?P.raw(`true`):P.log(!0)}catch{i?P.raw(`false`):P.log(!1)}}}}finally{a.close()}})}const V=e=>new h().get(`/api/settings`,t=>t.json(e.list())).post(`/api/settings`,m(`json`,i.array(i.object({group:i.string().min(1),name:i.string().min(1),value:i.json()})),(e,t)=>{if(!e.success)return t.json({error:`Invalid settings input`,issues:e.error.issues},400)}),t=>{let n=t.req.valid(`json`);try{return e.setMany(n),t.json(!0)}catch{return t.json({error:`Failed to update settings`},500)}}).post(`/api/settings/reset`,m(`json`,i.object({keys:i.array(i.object({group:i.string().min(1),name:i.string().min(1)}))}),(e,t)=>{if(!e.success)return t.json({error:`Invalid settings input`,issues:e.error.issues},400)}),t=>{let n=t.req.valid(`json`);try{return e.resetMany(n.keys),t.json(!0)}catch{return t.json({error:`Failed to reset settings`},500)}});function H(){let e=l.resolve(import.meta.dirname,`../web`);a(l.join(e,`index.html`))||(P.error(`Web UI assets missing. Please reinstall Foundry: npm install -g @dhzh/foundry`),n(1));let t=N(),r=V(t.settingsService);r.use(`/*`,p({root:e}));try{let e=f({fetch:r.fetch,hostname:`127.0.0.1`,port:7777});return{server:e,url:`http://127.0.0.1:7777`,close:()=>{e.close(),t.close()}}}catch(e){throw t.close(),e}}const U=e(`foundry`);B(U),U.command(`[...args]`,`Open a local Foundry workspace`).action(()=>{let e=H(),t=r(e.url,e.url,{fallback:!1});P.info(`Foundry workspace is running at ${t}`)}),U.help();try{U.parse()}catch(e){P.error(e instanceof Error?e.message:String(e)),t.exitCode=1}export{};
|
package/dist/web/index.html
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><html><head><title>Foundry</title><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><script defer src="/static/js/lib-react.
|
|
1
|
+
<!DOCTYPE html><html><head><title>Foundry</title><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><script defer src="/static/js/lib-react.8689a73639.js"></script><script defer src="/static/js/lib-router.7aa1b84d62.js"></script><script defer src="/static/js/304.ae01979a4b.js"></script><script defer src="/static/js/index.93ad4f2a33.js"></script><link href="/static/css/304.399a9465c7.css" rel="stylesheet"><link href="/static/css/index.400108c990.css" rel="stylesheet"></head><body><div id="root"></div></body></html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
html,body,#root{overscroll-behavior:none;height:100%;overflow:hidden}.astryx-layout-content,.astryx-layout-panel,.astryx-side-nav>*,.astryx-mobile-nav,.astryx-mobile-nav *{overscroll-behavior:none}
|