@captra-ai/cli 0.0.0-stage → 0.2.0
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/bin/captra.js +71 -0
- package/package.json +20 -4
- package/templates/fullstack/index.html +2 -0
- package/templates/fullstack/package.json +28 -0
- package/templates/fullstack/public/captra-mark.svg +6 -0
- package/templates/fullstack/src/App.tsx +14 -0
- package/templates/fullstack/src/BrandIntro.tsx +12 -0
- package/templates/fullstack/src/capabilities/hello.test.ts +7 -0
- package/templates/fullstack/src/capabilities/hello.tsx +16 -0
- package/templates/fullstack/src/captra.app.ts +4 -0
- package/templates/fullstack/src/client.ts +4 -0
- package/templates/fullstack/src/main.tsx +6 -0
- package/templates/fullstack/src/server.ts +10 -0
- package/templates/fullstack/src/styles.css +3 -0
- package/templates/fullstack/tsconfig.app.json +5 -0
- package/templates/fullstack/tsconfig.server.json +4 -0
- package/templates/fullstack/vite.config.ts +7 -0
- package/README.md +0 -3
package/bin/captra.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { basename, dirname, resolve } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { spawnSync } from 'node:child_process'
|
|
6
|
+
|
|
7
|
+
const [, , command='help', ...arguments_] = process.argv
|
|
8
|
+
const argument=arguments_[0],root=resolve(argument??'.')
|
|
9
|
+
const templateRoot=resolve(dirname(fileURLToPath(import.meta.url)),'../templates/fullstack')
|
|
10
|
+
|
|
11
|
+
if(command==='create'||command==='init') createProject(root)
|
|
12
|
+
else if(command==='dev') run('npm',['run','dev'],root)
|
|
13
|
+
else if(command==='build') run('npm',['run','build'],root)
|
|
14
|
+
else if(command==='test') run('npm',['test'],root)
|
|
15
|
+
else if(command==='inspect') inspect(root)
|
|
16
|
+
else if(command==='doctor') doctor(root)
|
|
17
|
+
else if(command==='add') add(arguments_[0],arguments_[1],resolve(arguments_[2]??'.'))
|
|
18
|
+
else help()
|
|
19
|
+
|
|
20
|
+
function createProject(directory){
|
|
21
|
+
if(existsSync(directory)) fail(`Target already exists: ${directory}`)
|
|
22
|
+
const projectName=packageName(basename(directory))
|
|
23
|
+
copyTemplate(templateRoot,directory,{'{{projectName}}':projectName,'{{appName}}':title(projectName)})
|
|
24
|
+
console.log(`\n Captra app created at ${directory}\n\n cd ${basename(directory)}\n npm install\n npm run dev\n`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function inspect(directory){
|
|
28
|
+
const fullStack=existsSync(resolve(directory,'src/server.ts'))||existsSync(resolve(directory,'src/client.ts'))
|
|
29
|
+
const required=['package.json','src/captra.app.ts','src/capabilities',...(fullStack?['src/server.ts','src/client.ts']:[])]
|
|
30
|
+
console.log(`\nCaptra project: ${directory}`)
|
|
31
|
+
for(const item of required)console.log(`${existsSync(resolve(directory,item))?'✓':'×'} ${item}`)
|
|
32
|
+
const pkgPath=resolve(directory,'package.json')
|
|
33
|
+
if(existsSync(pkgPath)){const pkg=JSON.parse(readFileSync(pkgPath,'utf8'));const deps={...pkg.dependencies,...pkg.devDependencies},frameworks=deps.captra?['captra']:['@captra-ai/core','@captra-ai/react',...(fullStack?['@captra-ai/client','@captra-ai/server']:[])];for(const name of frameworks)console.log(`${deps[name]?'✓':'×'} ${name}${deps[name]?` ${deps[name]}`:''}`)}
|
|
34
|
+
console.log('')
|
|
35
|
+
}
|
|
36
|
+
function doctor(directory){
|
|
37
|
+
const findings=[]
|
|
38
|
+
const check=(label,passed,help)=>{findings.push({label,passed,help});console.log(`${passed?'✓':'×'} ${label}${passed?'':` — ${help}`}`)}
|
|
39
|
+
console.log(`\nCaptra doctor: ${directory}`)
|
|
40
|
+
const major=Number(process.versions.node.split('.')[0]);check(`Node.js ${process.versions.node}`,major>=20,'install Node.js 20 or newer')
|
|
41
|
+
const pkgPath=resolve(directory,'package.json'),hasPackage=existsSync(pkgPath);check('package.json',hasPackage,'run this command from a Captra project')
|
|
42
|
+
let dependencies={};if(hasPackage){const pkg=JSON.parse(readFileSync(pkgPath,'utf8'));dependencies={...pkg.dependencies,...pkg.devDependencies}}
|
|
43
|
+
check('Captra dependency',Boolean(dependencies.captra||dependencies['@captra-ai/core']),'install captra')
|
|
44
|
+
check('Application contract',existsSync(resolve(directory,'src/captra.app.ts')),'create src/captra.app.ts')
|
|
45
|
+
check('Capability directory',existsSync(resolve(directory,'src/capabilities')),'create src/capabilities')
|
|
46
|
+
const configs=['tsconfig.json','tsconfig.app.json','tsconfig.server.json'].some(file=>existsSync(resolve(directory,file)));check('TypeScript configuration',configs,'add a tsconfig.json')
|
|
47
|
+
const exposed=[];for(const file of ['.env','.env.local','.env.development','.env.production']){const path=resolve(directory,file);if(!existsSync(path))continue;for(const line of readFileSync(path,'utf8').split(/\r?\n/)){const name=line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/)?.[1];if(name&&/^(VITE_|NEXT_PUBLIC_|PUBLIC_).*(OPENAI|ANTHROPIC|API_KEY|SECRET|TOKEN)/i.test(name))exposed.push(`${file}:${name}`)}}
|
|
48
|
+
check('No browser-exposed AI secrets',exposed.length===0,exposed.length?`rename ${exposed.join(', ')} and keep it server-only`:'keep provider keys server-only')
|
|
49
|
+
console.log(`\n${findings.every(item=>item.passed)?'Ready — no blocking issues found.':`${findings.filter(item=>!item.passed).length} issue(s) need attention.`}\n`)
|
|
50
|
+
if(findings.some(item=>!item.passed))process.exitCode=1
|
|
51
|
+
}
|
|
52
|
+
function add(kind,name,directory){
|
|
53
|
+
if(!['capability','tool','plugin'].includes(kind??''))fail('Usage: captra add <capability|tool|plugin> <name> [directory]')
|
|
54
|
+
if(!name)fail(`A ${kind} name is required`)
|
|
55
|
+
const slug=packageName(name),identifier=javascriptName(slug),templates={
|
|
56
|
+
capability:{folder:'capabilities',extension:'tsx',content:`import { capability,type CapabilityViewProps } from 'captra'\nimport { z } from 'zod'\n\nconst ${identifier}Result=z.object({message:z.string()})\ntype ${identifier}Result=z.infer<typeof ${identifier}Result>\n\nexport function ${title(identifier)}Card({value}:CapabilityViewProps<${identifier}Result>){return <article><h2>{value.message}</h2></article>}\n${title(identifier)}Card.displayName='${title(identifier)}Card'\n\nexport const ${identifier}=capability({\n name:'${slug}',description:'Describe what this capability does.',\n input:z.object({prompt:z.string().min(1)}),output:${identifier}Result,\n memory:['user'],approval:'never',\n async run({prompt}){return {message:prompt}},\n view:${title(identifier)}Card,\n})\n`},
|
|
57
|
+
tool:{folder:'tools',extension:'ts',content:`import { defineTool } from 'captra'\nimport { z } from 'zod'\n\nexport const ${identifier}=defineTool({\n name:'${slug}',description:'Describe what this tool does.',\n input:z.object({value:z.string()}),output:z.object({value:z.string()}),\n permissions:[],\n async execute({value}){return {value}},\n})\n`},
|
|
58
|
+
plugin:{folder:'plugins',extension:'ts',content:`import { definePlugin } from 'captra'\n\nexport const ${identifier}=definePlugin({\n name:'${slug}',version:'1.0.0',\n tools:[],hooks:[],\n})\n`},
|
|
59
|
+
}[kind]
|
|
60
|
+
const target=resolve(directory,'src',templates.folder,`${slug}.${templates.extension}`)
|
|
61
|
+
if(existsSync(target))fail(`Target already exists: ${target}`)
|
|
62
|
+
mkdirSync(dirname(target),{recursive:true});writeFileSync(target,templates.content)
|
|
63
|
+
console.log(`\n✓ Created ${target}\n Register ${identifier} in src/captra.app.ts.\n`)
|
|
64
|
+
}
|
|
65
|
+
function copyTemplate(source,destination,replacements){for(const entry of readdirSync(source)){const from=resolve(source,entry),to=resolve(destination,entry),stats=statSync(from);if(stats.isDirectory()){mkdirSync(to,{recursive:true});copyTemplate(from,to,replacements)}else{mkdirSync(dirname(to),{recursive:true});let content=readFileSync(from,'utf8');for(const [token,value] of Object.entries(replacements))content=content.replaceAll(token,value);writeFileSync(to,content)}}}
|
|
66
|
+
function packageName(value){return value.toLowerCase().replace(/[^a-z0-9._-]+/g,'-').replace(/^-+|-+$/g,'')||'captra-app'}
|
|
67
|
+
function title(value){return value.split(/[-_.]+/).filter(Boolean).map(word=>word[0].toUpperCase()+word.slice(1)).join(' ')}
|
|
68
|
+
function javascriptName(value){const words=value.split(/[-_.]+/).filter(Boolean);const name=words[0]+words.slice(1).map(word=>word[0].toUpperCase()+word.slice(1)).join('');return /^\d/.test(name)?`captra${name}`:name}
|
|
69
|
+
function run(binary,args,cwd){const result=spawnSync(binary,args,{cwd,stdio:'inherit',shell:process.platform==='win32'});process.exit(result.status??1)}
|
|
70
|
+
function fail(message){console.error(message);process.exit(1)}
|
|
71
|
+
function help(){console.log(`\nCaptra — the capability-first AI application framework\n\n captra create <directory>\n captra add capability <name> [directory]\n captra add tool <name> [directory]\n captra add plugin <name> [directory]\n captra doctor [directory]\n captra dev [directory]\n captra build [directory]\n captra test [directory]\n captra inspect [directory]\n`)}
|
package/package.json
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@captra-ai/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Create, inspect, develop, test, and build Captra applications.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/VaibhavBansal26/captra.git",
|
|
9
|
+
"directory": "packages/cli"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"bin": {
|
|
13
|
+
"captra": "bin/captra.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"bin",
|
|
17
|
+
"templates"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="description" content="A full-stack Captra application"/><link rel="icon" href="/captra-mark.svg"/><title>{{appName}}</title></head><body><main id="root"></main><script type="module" src="/src/main.tsx"></script></body></html>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "concurrently -k \"vite\" \"tsx watch src/server.ts\"",
|
|
7
|
+
"build": "tsc -p tsconfig.app.json --noEmit && vite build && tsc -p tsconfig.server.json",
|
|
8
|
+
"start": "node dist-server/server.js",
|
|
9
|
+
"test": "vitest run"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"captra": "^0.1.0",
|
|
13
|
+
"react": "^19.0.0",
|
|
14
|
+
"react-dom": "^19.0.0",
|
|
15
|
+
"zod": "^4.0.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^24.0.0",
|
|
19
|
+
"@types/react": "^19.0.0",
|
|
20
|
+
"@types/react-dom": "^19.0.0",
|
|
21
|
+
"@vitejs/plugin-react": "^5.0.0",
|
|
22
|
+
"concurrently": "^9.0.0",
|
|
23
|
+
"tsx": "^4.0.0",
|
|
24
|
+
"typescript": "^5.9.0",
|
|
25
|
+
"vite": "^7.0.0",
|
|
26
|
+
"vitest": "^4.0.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" role="img" aria-labelledby="title">
|
|
2
|
+
<title id="title">Captra</title>
|
|
3
|
+
<rect width="100" height="100" rx="22" fill="#0B1220"/>
|
|
4
|
+
<path d="M74.51 29.43 A32 32 0 1 0 74.51 70.57" fill="none" stroke="#F8FAFC" stroke-width="12" stroke-linecap="round"/>
|
|
5
|
+
<circle cx="68" cy="50" r="8" fill="#22D3EE"/>
|
|
6
|
+
</svg>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useCallback,useState } from 'react'
|
|
2
|
+
import { CapabilityOutput } from 'captra/react'
|
|
3
|
+
import type { RemoteCapabilityResult } from 'captra'
|
|
4
|
+
import { HelloCard } from './capabilities/hello.js'
|
|
5
|
+
import { client } from './client.js'
|
|
6
|
+
import { BrandIntro } from './BrandIntro.js'
|
|
7
|
+
|
|
8
|
+
export default function App(){
|
|
9
|
+
const [intro,setIntro]=useState(true)
|
|
10
|
+
const [name,setName]=useState('Captra'),[result,setResult]=useState<RemoteCapabilityResult|null>(null),[pending,setPending]=useState(false),[error,setError]=useState('')
|
|
11
|
+
const run=async()=>{setPending(true);setError('');try{setResult(await client.execute('hello',{name}))}catch(caught){setError(caught instanceof Error?caught.message:'Run failed')}finally{setPending(false)}}
|
|
12
|
+
const closeIntro=useCallback(()=>setIntro(false),[])
|
|
13
|
+
return <>{intro&&<BrandIntro onComplete={closeIntro}/>}<main className="shell"><div className="topline"><div className="eyebrow">CAPTRA / FULL-STACK STARTER</div><button onClick={()=>setIntro(true)}>Replay intro</button></div><h1>One contract.<br/>Runtime to UI.</h1><p className="intro">Edit <code>src/capabilities/hello.tsx</code>. Its schema drives validation, execution, memory, typed transport, and this component.</p><section className="runner"><label htmlFor="name">Who should Captra greet?</label><div><input id="name" value={name} onChange={event=>setName(event.target.value)}/><button disabled={pending||!name} onClick={run}>{pending?'Running…':'Run capability →'}</button></div></section>{error&&<p className="error">{error}</p>}{result&&<CapabilityOutput result={result} views={{HelloCard}}/>}<footer><span>React client :5173</span><span>Captra runtime :8787</span><span>Protocol 1.0</span></footer></main></>
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
export function BrandIntro({onComplete}:{onComplete:()=>void}){
|
|
4
|
+
useEffect(()=>{const timer=window.setTimeout(onComplete,3800);return()=>window.clearTimeout(timer)},[onComplete])
|
|
5
|
+
return <div className="brand-intro" role="dialog" aria-label="Welcome to Captra">
|
|
6
|
+
<div className="brand-grid" aria-hidden="true"/>
|
|
7
|
+
<button className="brand-skip" onClick={onComplete}>Skip intro</button>
|
|
8
|
+
<div className="brand-lockup"><svg width="124" height="124" viewBox="0 0 100 100" role="img" aria-label="Captra mark"><circle className="brand-ring" cx="68" cy="50" r="8" fill="none" stroke="#22D3EE" strokeWidth="2"/><path className="brand-arc" d="M74.51 29.43 A32 32 0 1 0 74.51 70.57" fill="none" stroke="#F8FAFC" strokeWidth="12" strokeLinecap="round"/><circle className="brand-dot" cx="68" cy="50" r="8" fill="#22D3EE"/></svg><div className="brand-word" aria-label="captra">{'captra'.split('').map((letter,index)=><span key={index}>{letter}</span>)}</div></div>
|
|
9
|
+
<p className="brand-tag">The AI-native JavaScript framework. Build apps where models are first-class, not bolted on.</p>
|
|
10
|
+
<div className="brand-command"><b>$</b> npm install captra<i/></div>
|
|
11
|
+
</div>
|
|
12
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { describe,expect,it } from 'vitest'
|
|
2
|
+
import { createRuntime,capabilityViews,defineApp } from 'captra'
|
|
3
|
+
import { hello } from './hello.js'
|
|
4
|
+
|
|
5
|
+
const runtime=createRuntime(defineApp({name:'Test',scopes:['user'],capabilities:[hello],surface:capabilityViews()}))
|
|
6
|
+
const identity={tenantId:'test',userId:'u1',projectId:'p1'}
|
|
7
|
+
describe('hello',()=>{it('remembers the user between runs',async()=>{expect((await runtime.execute({capability:'hello',input:{name:'Ada'},identity})).value.remembered).toBe(false);expect((await runtime.execute({capability:'hello',input:{name:'Ada'},identity})).value.remembered).toBe(true)})})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { capability,type CapabilityViewProps } from 'captra'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
const HelloResult=z.object({message:z.string(),remembered:z.boolean()})
|
|
5
|
+
type HelloResult=z.infer<typeof HelloResult>
|
|
6
|
+
|
|
7
|
+
export function HelloCard({value}:CapabilityViewProps<HelloResult>){return <article className="result"><span>CAPABILITY COMPLETED</span><h2>{value.message}</h2><p>{value.remembered?'Your preference was recalled.':'This is your first run.'}</p></article>}
|
|
8
|
+
HelloCard.displayName='HelloCard'
|
|
9
|
+
|
|
10
|
+
export const hello=capability({
|
|
11
|
+
name:'hello',description:'Greet a person with scoped memory.',
|
|
12
|
+
input:z.object({name:z.string().min(1)}),output:HelloResult,
|
|
13
|
+
memory:['user'],approval:'never',
|
|
14
|
+
async run({name},context){const remembered=context.memories.some(item=>item.content===name);if(!remembered)await context.memory.remember({tenantId:context.tenantId,scope:'user',ownerId:context.userId,type:'preference',content:name,confidence:1});return {message:`Hello ${name}`,remembered}},
|
|
15
|
+
view:HelloCard,
|
|
16
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createServer } from 'node:http'
|
|
2
|
+
import { createRuntime } from 'captra'
|
|
3
|
+
import { createCaptraHandler } from 'captra/server'
|
|
4
|
+
import { createNodeHandler } from 'captra/server/node'
|
|
5
|
+
import { app } from './captra.app.js'
|
|
6
|
+
|
|
7
|
+
const runtime=createRuntime(app)
|
|
8
|
+
const handler=createCaptraHandler(runtime,{basePath:'/api/captra',auth:async()=>({tenantId:'local',userId:'developer',projectId:'starter'})})
|
|
9
|
+
const port=Number(process.env.PORT??8787)
|
|
10
|
+
createServer(createNodeHandler(handler)).listen(port,'127.0.0.1',()=>console.log(`Captra backend ready on http://127.0.0.1:${port}/api/captra`))
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
@import url('https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Manrope:wght@500;600;700;800&family=Space+Grotesk:wght@500;700&display=swap');
|
|
2
|
+
:root{font-family:Manrope,system-ui,sans-serif;color:#f5f1e8;background:#171916;font-synthesis:none}*{box-sizing:border-box}body{margin:0;min-width:320px;min-height:100vh;background:radial-gradient(circle at 80% 10%,#62332655,transparent 35%),#171916}.shell{width:min(780px,calc(100% - 40px));margin:auto;padding:12vh 0 40px}.topline{display:flex;align-items:center;justify-content:space-between}.topline button,.brand-skip{border:0;background:transparent;color:#94a3b8;font:11px 'DM Mono';cursor:pointer}.eyebrow{font:12px 'DM Mono';letter-spacing:.14em;color:#22d3ee}h1{font-size:clamp(48px,9vw,92px);line-height:.92;letter-spacing:-.055em;margin:24px 0}.intro{max-width:620px;color:#b8b8af;font-size:17px;line-height:1.7}.intro code{color:#22d3ee}.runner{margin:54px 0 24px;padding:24px;border:1px solid #3c3f38;border-radius:18px;background:#20221f}.runner label{display:block;font:11px 'DM Mono';letter-spacing:.08em;color:#a5a89f;margin-bottom:12px}.runner div{display:flex;gap:10px}.runner input{flex:1;border:1px solid #4b4e46;border-radius:10px;background:#171916;color:white;padding:14px;font:16px inherit;outline:none}.runner input:focus{border-color:#22d3ee}.runner button{border:0;border-radius:10px;background:#22d3ee;color:#0b1220;font-weight:800;padding:0 22px}.runner button:disabled{opacity:.55}.result{border-radius:18px;background:#f3efe4;color:#1b1d19;padding:28px}.result span{font:10px 'DM Mono';letter-spacing:.1em;color:#0891b2}.result h2{font-size:30px;margin:10px 0}.result p{color:#676960}.error{color:#ff8a72}footer{display:flex;gap:18px;margin-top:50px;padding-top:18px;border-top:1px solid #383a34;color:#777b72;font:10px 'DM Mono'}
|
|
3
|
+
.brand-intro{position:fixed;inset:0;z-index:100;display:flex;flex-direction:column;align-items:center;justify-content:center;overflow:hidden;background:#0b1220;color:#f8fafc;font-family:'Space Grotesk',Manrope,sans-serif;text-align:center;animation:brand-exit .55s ease 3.35s forwards}.brand-grid{position:absolute;inset:0;background-image:linear-gradient(#94a3b812 1px,transparent 1px),linear-gradient(90deg,#94a3b812 1px,transparent 1px);background-size:72px 72px;mask-image:radial-gradient(ellipse 70% 60% at 50% 45%,#000 30%,transparent 100%);opacity:0;animation:brand-fade 1.6s ease-out 2.2s forwards}.brand-skip{position:absolute;z-index:2;right:32px;top:28px;min-height:44px}.brand-lockup{position:relative;display:flex;align-items:center;gap:28px;animation:brand-lift .8s cubic-bezier(.2,.8,.2,1) 2.1s both}.brand-lockup svg{display:block;overflow:visible}.brand-arc{stroke-dasharray:160;stroke-dashoffset:160;animation:brand-draw .9s cubic-bezier(.65,0,.35,1) .2s forwards}.brand-dot{transform-origin:68px 50px;transform:scale(0);opacity:0;animation:brand-pop .5s cubic-bezier(.34,1.56,.64,1) 1.05s forwards}.brand-ring{transform-origin:68px 50px;transform:scale(.4);opacity:0;animation:brand-ring .9s ease-out 1.15s forwards}.brand-word{font-size:clamp(70px,9vw,112px);font-weight:700;letter-spacing:-.045em;line-height:1;padding-bottom:8px}.brand-word span{display:inline-block;opacity:0;transform:translateY(28px);animation:brand-rise .55s cubic-bezier(.2,.8,.2,1) forwards}.brand-word span:nth-child(1){animation-delay:1.25s}.brand-word span:nth-child(2){animation-delay:1.31s}.brand-word span:nth-child(3){animation-delay:1.37s}.brand-word span:nth-child(4){animation-delay:1.43s}.brand-word span:nth-child(5){animation-delay:1.49s}.brand-word span:nth-child(6){animation-delay:1.55s}.brand-tag{position:relative;max-width:640px;margin:36px 24px 0;color:#94a3b8;font-size:24px;line-height:1.4;opacity:0;animation:brand-rise .6s cubic-bezier(.2,.8,.2,1) 2.3s forwards}.brand-command{position:relative;margin-top:36px;padding:16px 24px;border:1px solid #1e293b;border-radius:12px;background:#111a2e;color:#cbd5e1;font:500 17px 'DM Mono';opacity:0;animation:brand-rise .6s cubic-bezier(.2,.8,.2,1) 2.7s forwards}.brand-command b{color:#22d3ee}.brand-command i{display:inline-block;width:9px;height:18px;margin-left:6px;vertical-align:text-bottom;background:#cbd5e1;animation:brand-cursor 1s step-end 3.3s infinite}@keyframes brand-draw{to{stroke-dashoffset:0}}@keyframes brand-pop{70%{transform:scale(1.35);opacity:1}to{transform:scale(1);opacity:1}}@keyframes brand-ring{from{transform:scale(.4);opacity:.9}to{transform:scale(3.2);opacity:0}}@keyframes brand-rise{to{transform:translateY(0);opacity:1}}@keyframes brand-lift{from{transform:translateY(120px)}to{transform:translateY(0)}}@keyframes brand-fade{to{opacity:1}}@keyframes brand-cursor{0%,49%{opacity:1}50%,to{opacity:0}}@keyframes brand-exit{to{opacity:0;visibility:hidden}}@media(max-width:620px){.runner div{display:grid}.runner button{padding:14px}footer{display:grid}.brand-lockup{gap:10px}.brand-lockup svg{width:76px;height:76px}.brand-tag{font-size:17px}.brand-command{font-size:13px}.brand-skip{right:16px}}@media(prefers-reduced-motion:reduce){.brand-intro{animation:none}.brand-grid,.brand-arc,.brand-dot,.brand-ring,.brand-word span,.brand-lockup,.brand-tag,.brand-command,.brand-command i{animation:none;opacity:1;transform:none;stroke-dashoffset:0}}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions":{"target":"ES2022","lib":["ES2022","DOM","DOM.Iterable"],"module":"ESNext","moduleResolution":"Bundler","strict":true,"jsx":"react-jsx","noEmit":true,"skipLibCheck":true,"types":["vite/client"]},
|
|
3
|
+
"include":["src/**/*.ts","src/**/*.tsx"],
|
|
4
|
+
"exclude":["src/server.ts","src/**/*.test.ts"]
|
|
5
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions":{"target":"ES2022","lib":["ES2022","DOM"],"module":"NodeNext","moduleResolution":"NodeNext","strict":true,"jsx":"react-jsx","skipLibCheck":true,"types":["node"],"outDir":"dist-server","rootDir":"src"},
|
|
3
|
+
"include":["src/server.ts","src/captra.app.ts","src/capabilities/**/*.tsx"]
|
|
4
|
+
}
|
package/README.md
DELETED