Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions examples/framework-demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# The Framework — end-to-end demo

One prompt, taken all the way to a **running, deployed app** — offline and
deterministic, in a couple of seconds.

This runs the real product (`@gemstack/framework`'s `runFramework`) with the
built-in **fake driver**: no Claude Code, no model, no API keys. Same code a live
run executes — preset detection, architect decisions, the full-fledged
production-grade loop, deploy — just with scripted agent turns so it is instant
and repeatable. Two things are genuinely real, not narrated:

- **the app boots and serves** — the serve gate starts a real HTTP server and the
run leaves it running, so the demo `fetch`es it and prints what it served;
- **deploy runs the real `cloudflareTarget` adapter** (over a simulated wrangler),
so it ends at a real-looking `workers.dev` URL.

## Run it

```bash
pnpm --filter @gemstack/example-framework-demo start
```

## What you see

```
Prompt: "A paginated orders page backed by an orders table, with sign-in."

--- live narration ---
◆ fake in /tmp/framework-demo-xxxx
Detected Vike (confidence 2); framing with 3 persona(s)
▶ scope: full — "A paginated orders page backed by an orders table, with sign-in."
▶ architect: Vike + universal-orm on Postgres, with vike-auth
· universal-orm on Postgres — the orders catalog is relational and needs typed queries
· SSR over SPA — orders need per-request data and auth on the server
Building on Vike + universal-orm on Postgres, with vike-auth
Checking the app is production-grade
serve: start: node server.js
serve: fetch http://localhost:50106/ -> 200
✗ checklist pass 1: No authentication on the orders page yet
→ improving: No authentication on the orders page yet
serve: start: node server.js
serve: fetch http://localhost:50106/ -> 200
✓ checklist pass 2: production-grade
▶ deploy: SSR → cloudflare (per-request orders data + server-side auth)
✓ production-grade in 2 pass(es)
▶ your app is running at http://localhost:50106
✓ done

--- outcome ---
preset detected: Vike
production-grade: true (in 2 pass(es))
deployed to: cloudflare → https://orders-app.gemstack.workers.dev
running locally: http://localhost:50106
it served: <!doctype html><meta charset=utf-8><title>Orders</title> <h1>Orders</h1>…
```

The loop does not take the agent's word for "production-grade": it **boots the app
and fetches it** every pass (the `serve:` lines), and blocks until both the review
and the real server pass. When it finishes, the app is left running so you can open
it — that is the localhost link, live until the run stops.

## The real thing

Same flow, driven against Claude Code instead of the fake driver:

```bash
npx @gemstack/framework "a paginated orders page with sign-in"
```

That opens the localhost dashboard, wraps Claude Code as the coding agent, and runs
the identical scope → architect → build → production-grade loop → deploy — writing
real files and, with `--serve`, leaving the real app running behind a preview link.
22 changes: 22 additions & 0 deletions examples/framework-demo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@gemstack/example-framework-demo",
"version": "0.0.0",
"private": true,
"description": "Showable end-to-end demo of @gemstack/framework: one prompt → scope → architect → build → full-fledged loop → deploy → a real running app, offline and deterministic via the fake driver.",
"type": "module",
"scripts": {
"typecheck": "tsc --noEmit",
"test": "tsc -p tsconfig.test.json && cd dist-test && node --test",
"clean": "rm -rf dist-test",
"start": "tsx src/main.ts"
},
"dependencies": {
"@gemstack/framework": "workspace:^",
"@gemstack/ai-autopilot": "workspace:^"
},
"devDependencies": {
"@types/node": "^20.0.0",
"tsx": "^4.19.0",
"typescript": "^5.4.0"
}
}
31 changes: 31 additions & 0 deletions examples/framework-demo/src/demo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import { DEMO_INTENT, runDemo } from './demo.js'

describe('framework demo: one prompt → a running, deployed app (offline)', () => {
it('runs the whole product flow and ends at a real serving app', async () => {
const lines: string[] = []
const out = await runDemo(line => lines.push(line))

// Preset detection picked Vike from the demo's deps signals.
assert.equal(out.framework, 'Vike')

// The full-fledged loop blocked once (no auth) then cleared → production-grade.
assert.equal(out.productionGrade, true)
assert.equal(out.passes, 2)

// Deploy ran the real cloudflareTarget adapter → a live workers.dev URL.
assert.equal(out.deployTarget, 'cloudflare')
assert.equal(out.deployUrl, 'https://orders-app.gemstack.workers.dev')

// The app was actually booted and served (not just narrated).
assert.match(out.previewUrl ?? '', /^http:\/\/localhost:\d+$/)
assert.match(out.served, /Orders/)
assert.match(out.served, /Ada Lovelace/)

// The narration told the whole story: the prompt, then a production-grade end.
assert.ok(lines.some(l => l.includes(DEMO_INTENT)))
assert.ok(lines.some(l => l.includes('production-grade')))
assert.ok(lines.some(l => l.includes('your app is running at')))
})
})
139 changes: 139 additions & 0 deletions examples/framework-demo/src/demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { createServer } from 'node:net'
import { cloudflareTarget } from '@gemstack/ai-autopilot'
import {
FAKE_DEPLOY,
FAKE_INTENT,
FAKE_SIGNALS,
fakeDriver,
formatFrameworkEvent,
runFramework,
type AppPreview,
} from '@gemstack/framework'

/**
* The showable end-to-end demo for `@gemstack/framework`: one prompt taken all
* the way to a *running, deployed* app, offline and deterministic.
*
* It drives the real product (`runFramework`) with the built-in **fake driver**
* (no Claude Code, no model, no keys) so the whole flow — preset detection,
* architect decisions, build, the full-fledged production-grade loop, and deploy
* — runs the same code a live run does, just with scripted agent turns. Two
* things are genuinely real, not narrated:
*
* - the app **boots and serves**: the serve gate starts a real HTTP server and
* the run leaves it running, so the demo can `fetch` it and prove it works;
* - the **deploy** runs the real `cloudflareTarget` adapter over a simulated
* wrangler, so it ends at a real-looking `workers.dev` URL deterministically.
*
* This is the artifact to point people at (README / the-framework.ai / Discord):
* a single command that goes from an idea to an app you can open.
*/

/** The one prompt the demo builds from. */
export const DEMO_INTENT = FAKE_INTENT

/** What {@link runDemo} reports back after the flow completes. */
export interface DemoOutcome {
/** The detected framework preset (Vike, from the demo's deps signals). */
framework: string | undefined
/** Whether the loop reached production-grade, and in how many passes. */
productionGrade: boolean
passes: number
/** The deploy result: the target and the (simulated) live URL. */
deployTarget: string | undefined
deployUrl: string | undefined
/** The localhost URL the app was left running at. */
previewUrl: string | undefined
/** The first bytes the running app actually served (proof it works). */
served: string
}

/** An ephemeral free port so the demo never collides with something already bound. */
function freePort(): Promise<number> {
return new Promise((resolvePromise, rejectPromise) => {
const srv = createServer()
srv.on('error', rejectPromise)
srv.listen(0, '127.0.0.1', () => {
const addr = srv.address()
const port = typeof addr === 'object' && addr ? addr.port : 0
srv.close(() => resolvePromise(port))
})
})
}

/** The tiny but real "orders app" the serve gate boots — a stand-in for what the agent builds. */
function ordersAppSource(port: number): string {
return [
`const http = require('http')`,
`const page = \`<!doctype html><meta charset=utf-8><title>Orders</title>`,
`<h1>Orders</h1><table><tr><th>#</th><th>Customer</th><th>Total</th></tr>`,
`<tr><td>1001</td><td>Ada Lovelace</td><td>$42.00</td></tr>`,
`<tr><td>1002</td><td>Alan Turing</td><td>$17.50</td></tr></table>\``,
`http.createServer((_, res) => res.end(page)).listen(${port})`,
].join('\n')
}

/** The simulated Cloudflare: every command succeeds; the deploy prints a workers.dev URL. */
const simulatedCloudflare = {
exec: async (command: string) => ({
stdout: /wrangler|deploy/.test(command)
? 'Published framework-demo\nhttps://orders-app.gemstack.workers.dev'
: '',
stderr: '',
exitCode: 0,
}),
}

/**
* Run the whole demo and stream one narration line per phase to `onLine`.
* Deterministic and offline; leaves no processes or temp files behind.
*/
export async function runDemo(onLine: (line: string) => void): Promise<DemoOutcome> {
const dir = await mkdtemp(join(tmpdir(), 'framework-demo-'))
const port = await freePort()
await writeFile(join(dir, 'server.js'), ordersAppSource(port) + '\n')
await writeFile(join(dir, 'package.json'), JSON.stringify({ name: 'orders-app', private: true }) + '\n')

let preview: AppPreview | undefined
try {
const run = await runFramework({
intent: DEMO_INTENT,
driver: fakeDriver(),
cwd: dir,
signals: FAKE_SIGNALS,
serve: { command: 'node server.js', port, waitMs: 8000, keepAlive: true },
deploy: FAKE_DEPLOY,
deployTarget: cloudflareTarget({
session: simulatedCloudflare,
apiToken: 'demo-token',
accountId: 'demo-account',
projectName: 'framework-demo',
}),
onEvent: event => onLine(formatFrameworkEvent(event)),
})
preview = run.preview

// Prove the app the loop signed off on is actually serving right now.
let served = ''
if (preview) {
const res = await fetch(preview.url)
served = (await res.text()).replace(/\s+/g, ' ').trim()
}

return {
framework: run.detection.framework,
productionGrade: run.result.productionGrade,
passes: run.result.passes,
deployTarget: run.result.deploy?.plan.target,
deployUrl: run.result.deploy?.result.url,
previewUrl: preview?.url,
served,
}
} finally {
if (preview) await preview.stop()
await rm(dir, { recursive: true, force: true })
}
}
24 changes: 24 additions & 0 deletions examples/framework-demo/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { DEMO_INTENT, runDemo } from './demo.js'

/** Run the demo and print the whole story: the live narration, then the outcome. */
async function main(): Promise<void> {
console.log('The Framework — end-to-end demo (offline, deterministic)\n')
console.log(`Prompt: "${DEMO_INTENT}"\n`)
console.log('--- live narration ---')
const out = await runDemo(line => console.log(line))

console.log('\n--- outcome ---')
console.log(` preset detected: ${out.framework}`)
console.log(` production-grade: ${out.productionGrade} (in ${out.passes} pass(es))`)
console.log(` deployed to: ${out.deployTarget} → ${out.deployUrl}`)
console.log(` running locally: ${out.previewUrl}`)
console.log(` it served: ${out.served.slice(0, 72)}${out.served.length > 72 ? '…' : ''}`)

console.log('\nThat is the fake driver. To do it for real against Claude Code:')
console.log(' npx @gemstack/framework "a paginated orders page with sign-in"')
}

main().catch(err => {
console.error(err)
process.exitCode = 1
})
5 changes: 5 additions & 0 deletions examples/framework-demo/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "noEmit": true, "rootDir": "src" },
"include": ["src"]
}
5 changes: 5 additions & 0 deletions examples/framework-demo/tsconfig.test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "outDir": "dist-test", "rootDir": "src" },
"include": ["src"]
}
19 changes: 19 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading