zone4code-cmd 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +45 -2
- package/dist/cli.js +59 -0
- package/package.json +36 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sammwy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,46 @@
|
|
|
1
|
-
#
|
|
1
|
+
# zone4code-cmd
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[Zone4build](https://zone4build.com) from your terminal: create projects, prompt the AI agent,
|
|
4
|
+
plan with the capability roadmap, and run the same checks as the IDE's chat commands.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm install -g zone4code-cmd
|
|
8
|
+
zone4code login # email + password, or: zone4code login --key z4p_…
|
|
9
|
+
zone4code keys add openrouter # optional: your own AI key (billed to you, no platform limits)
|
|
10
|
+
zone4code create "Club booking"
|
|
11
|
+
zone4code prompt "Members book training sessions; coaches create them"
|
|
12
|
+
zone4code status issue
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Signed up with Google? Create a CLI key in **Settings › CLI keys** and use `zone4code login --key z4p_…`.
|
|
16
|
+
In CI, set `ZONE4CODE_TOKEN` (and `ZONE4CODE_URL` for another instance).
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
| | |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `login` · `logout` · `whoami` | account (a CLI key is stored in `~/.config/zone4code/config.json`, readable only by you) |
|
|
23
|
+
| `keys` · `keys add <provider> [key]` · `keys rm <id>` | your own AI provider keys |
|
|
24
|
+
| `projects` · `workspaces` · `create <name>` · `use <project>` | projects (`--template react-vite`, `--workspace`, `--no-start`) |
|
|
25
|
+
| `start` · `stop` · `restart` · `open` | the project's app and preview |
|
|
26
|
+
| `prompt "<text>"` · `chat` · `build` | the AI agent: streams its answer, asks before risky actions (`--yes` to allow them, `--model`, `--new`) |
|
|
27
|
+
| `status [topic]` | roadmap, issue, api, ui, data, auth, roles, e2e … (`--json`) |
|
|
28
|
+
| `e2e` · `responsive` | end-to-end tests / screen-size check in the platform sandbox (exit 1 on failure) |
|
|
29
|
+
| `roadmap` · `roadmap push [ids…] [--actors …] [--approve]` | the project's roadmap |
|
|
30
|
+
|
|
31
|
+
### Capabilities (offline, no account needed)
|
|
32
|
+
|
|
33
|
+
The same catalog, prerequisite rules and build plan as the platform's Roadmap.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
zone4code caps list
|
|
37
|
+
zone4code caps check doc pwa # exit 1 + what's missing (use it in CI)
|
|
38
|
+
zone4code caps derive --actors "Guest: browse listings, search; Host: log in, create listing, upload photos"
|
|
39
|
+
zone4code caps plan ui auth data --actors "Member: register, book a session"
|
|
40
|
+
zone4code caps prompt # the brief the agent receives
|
|
41
|
+
zone4code caps init # writes singulary.brief.json interactively
|
|
42
|
+
zone4code caps audit ./my-app # what a folder already implements (files only)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Commands read `./singulary.brief.json` when present; `--actors`, `--summary` and capability ids override it.
|
|
46
|
+
Every command takes `--json` for scripts. Requires Node.js 20+.
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{parseArgs as nt}from"util";var k=class extends Error{constructor(t,s,n){super(n);this.status=t;this.code=s}status;code},P=class{constructor(o){this.config=o}config;get url(){return this.config.url}headers(o){return{Accept:"application/json","User-Agent":"zone4code-cmd",...this.config.token?{Authorization:`Bearer ${this.config.token}`}:{},...o}}async request(o,t,s,n){let i;try{i=await fetch(`${this.config.url}${t}`,{method:o,headers:this.headers(s===void 0?n:{"Content-Type":"application/json",...n}),body:s===void 0?void 0:JSON.stringify(s)})}catch(l){throw new k(0,"network",`Can't reach ${this.config.url} (${l instanceof Error?l.message:l}).`)}let c=await i.text(),r;try{r=c?JSON.parse(c):void 0}catch{r=c}if(!i.ok){let l=r?.error,p=typeof l=="string"?l:l?.message??(typeof r=="string"&&r.length<300?r:i.statusText);throw i.status===401?new k(401,"unauthorized","Not signed in, or the CLI key was deleted: run `zone4code login`."):new k(i.status,typeof l=="object"&&l?.code?l.code:String(i.status),p||`HTTP ${i.status}`)}return r}get(o){return this.request("GET",o)}post(o,t){return this.request("POST",o,t??{})}put(o,t){return this.request("PUT",o,t??{})}delete(o){return this.request("DELETE",o)}async stream(o,t,s){let n=await fetch(`${this.config.url}${o}`,{headers:this.headers({Accept:"text/event-stream"}),signal:s});if(!n.ok||!n.body)throw new k(n.status,String(n.status),`Stream failed (${n.status}).`);let i=new TextDecoder,c="";for await(let r of n.body){c+=i.decode(r,{stream:!0});let l;for(;(l=c.indexOf(`
|
|
3
|
+
|
|
4
|
+
`))>=0;){let p=c.slice(0,l);c=c.slice(l+2);let d=p.split(`
|
|
5
|
+
`).filter(m=>m.startsWith("data:")).map(m=>m.slice(5).trimStart()).join(`
|
|
6
|
+
`);if(d)try{if(t(JSON.parse(d))===!0)return}catch{}}}}};import re from"fs";var v=[{id:"ui",group:"FOUNDATION",name:"UI Components",description:"Pages, forms, tables and dashboards",requires:[],sdkHint:"React pages and components in src/; one page per journey step",keywords:["dashboard","page","home","list","browse","view","search","settings","screen","form"]},{id:"pwa",group:"FOUNDATION",name:"Installable app (PWA)",description:"Users install it from the browser, with its own icon, full screen",requires:["ui"],requiresReason:"Installing needs the app's screens",sdkHint:"public/manifest.webmanifest + icons 192/512 + a small service worker (public/sw.js) registered in production + an Install button (beforeinstallprompt)",keywords:["install","installable","pwa","mobile app","home screen","offline","application"]},{id:"data",group:"FOUNDATION",name:"Data & Schema",description:"Your own entities and CRUD, defined at runtime",requires:[],sdkHint:"client.schema.define({ typeName, schema }) then client.from(type).create/list/update/delete",keywords:["create","add","edit","update","delete","save","record","profile","list","manage","track","store"]},{id:"auth",group:"IDENTITY",name:"Authentication",description:"Sign up, sign in, password reset, sessions",requires:[],sdkHint:"client.auth.register / login / logout / getUser / forgotPassword; My profile page: getUser, updateProfile, changePassword",keywords:["register","signup","sign up","login","log in","sign in","logout","account","password","profile"]},{id:"rbac",group:"IDENTITY",name:"Roles & Permissions",description:"Who can see and do what (admin, manager, user)",requires:["auth"],requiresReason:"Roles are attached to signed-in users",sdkHint:"Realm roles from client.auth.getUser().roles (the platform creates them in Keycloak); never put x-scopes / x-public-scopes in schemas",keywords:["approve","reject","validate","assign","role","admin","manager","moderate","permission","review"]},{id:"doc",group:"PLATFORM",name:"Document Repository",description:"Secure file upload, folders and downloads",requires:["auth","rbac","data"],requiresReason:"Documents must be private to their owner and linked to records",sdkHint:"client.doc.upload(file), client.doc.getFolderFiles(folderId); store the returned id on a record",keywords:["upload","document","file","attach","attachment","download","photo","image","pdf"]},{id:"notification",group:"PLATFORM",name:"Notifications",description:"Email, SMS and push alerts",requires:["data"],requiresReason:"Notifications need stored contact details and events",sdkHint:"client.notification.sendEmail / sendSms / sendPush",keywords:["notify","notified","notification","email","sms","alert","remind","reminder","message"]},{id:"audit",group:"PLATFORM",name:"Audit Trail",description:"History of who changed what and when",requires:["data","auth"],requiresReason:"An audit trail records changes made by known users",sdkHint:"client.from(type).revisions(id); record actor ids on writes",keywords:["history","audit","activity log","track changes","trace","compliance"]},{id:"crm",group:"BUSINESS",name:"Customer Management",description:"Customer and contact records",requires:["data","auth"],requiresReason:"Customer records are private data behind sign-in",sdkHint:"Entities customer/contact with client.from(); relations with link()",keywords:["customer","client","contact","lead","crm"]},{id:"invoices",group:"BUSINESS",name:"Invoice Management",description:"Invoices, statuses and approvals",requires:["data","auth","rbac"],requiresReason:"Invoices are private and approvals need roles",sdkHint:"Entity invoice with a status enum (draft/submitted/approved/paid) via client.schema.define",keywords:["invoice","bill","billing","pay","payment","quote"]}],Q=["FOUNDATION","IDENTITY","PLATFORM","BUSINESS"];function j(e){return v.find(o=>o.id===e)}var Le=new Set(["a","an","the","my","his","her","their","our","your","to","of","for","in","on","and","or","with","new","own","all","status","details","notified"]),Be=new Set(["register","signup","login","log","sign","logout","open","view","see","browse","search","create","add","edit","update","delete","remove","upload","download","review","approve","reject","validate","assign","notify","get","receive","send","pay","book","manage","track","submit","request","invite","share","export","import","comment","rate"]),ee=new Set(["dashboard","page","home","list","settings","screen","inbox","calendar","map"]),te=new Set(["profile","account","password"]);function E(e){return e.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean)}function B(e){return e.endsWith("ies")?`${e.slice(0,-3)}y`:e.endsWith("ses")||e.endsWith("xes")?e.slice(0,-2):e.endsWith("s")&&!e.endsWith("ss")&&e.length>3?e.slice(0,-1):e}function oe(e){return E(e).filter(o=>!Be.has(o)&&!Le.has(o)&&o.length>2).map(B)}function R(e){let o=new Set,t=new Set,s=new Map,n=new Set(e.actors.map(i=>B(i.name.trim().toLowerCase())));for(let i of e.actors)for(let c of i.steps){let r=c.toLowerCase(),l=E(r).filter(p=>!n.has(B(p)));for(let p of oe(c))te.has(p)?t.add("profile"):ee.has(p)?t.add(p):n.has(p)||o.add(p);for(let p of v){if(s.has(p.id))continue;p.keywords.some(m=>m.includes(" ")?r.includes(m):l.includes(m))&&s.set(p.id,`${i.name}: "${c}"`)}}return e.actors.some(i=>i.steps.some(c=>E(c).some(r=>se.has(r))))&&t.add("profile"),o.size&&!s.has("data")&&s.set("data",`entities ${[...o].join(", ")}`),e.actors.some(i=>i.steps.length)&&!s.has("ui")&&s.set("ui","journeys need screens"),e.actors.length>1&&!s.has("rbac")&&s.set("rbac",`${e.actors.map(i=>i.name).join(" and ")} see different things`),{entities:[...o],pages:[...t],suggestions:[...s].map(([i,c])=>({capability:i,because:c}))}}var se=new Set(["register","signup","login","log","sign"]),De=new Set(["open","view","see","browse","search","track","review","download","get","receive","export"]),qe=new Set(["create","add","upload","submit","request","book","invite","import","comment","rate","send"]),ze=new Set(["edit","update"]),Ue=new Set(["delete","remove"]),Fe=new Set(["approve","reject","validate","assign","pay","share"]),H=["view","create","update","delete"];function Me(e){return e.trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"user"}function We(e){let o=new Set;for(let t of E(e))De.has(t)?o.add("view"):qe.has(t)?o.add("create"):ze.has(t)?o.add("update"):Ue.has(t)?o.add("delete"):t==="manage"?["view","create","update","delete"].forEach(s=>o.add(s)):Fe.has(t)&&o.add(t);return o.size&&o.add("view"),[...o]}function Je(e){let o=t=>H.includes(t)?H.indexOf(t):H.length;return[...new Set(e)].sort((t,s)=>o(t)-o(s)||t.localeCompare(s))}function D(e){let o=new Set(e.actors.map(t=>B(t.name.trim().toLowerCase())));return e.actors.map(t=>{let s=new Map,n=new Set,i=[],c=!1;for(let r of t.steps){if(E(r).some(d=>se.has(d))){c=!0;continue}let l=oe(r).filter(d=>!ee.has(d)&&!te.has(d)&&!o.has(d)),p=We(r);if(!(!l.length||!p.length)){if(!c){p.some(d=>d!=="view")?i.push(r):l.forEach(d=>n.add(d));continue}for(let d of l){let m=s.get(d)??new Set;p.forEach(u=>m.add(u)),s.set(d,m)}}}return{actor:t.name,role:Me(t.name),entities:[...s].map(([r,l])=>({entity:r,scopes:Je(l)})),publicViews:[...n],needsSignIn:i}})}var I="src/data/seed",X="src/data/store.ts";function x(e){let o=R(e),t=D(e),s=new Set(e.capabilities),n=d=>d.filter(m=>s.has(m)).map(m=>j(m)?.name??m),i=o.entities,c=i.length>0||["data","crm","invoices"].some(d=>s.has(d)),r=s.has("auth")||s.has("rbac"),l=[];(s.has("ui")||e.actors.some(d=>d.steps.length))&&l.push({id:"ui",title:"Screens with mock data",items:[...e.actors.map(d=>`${d.name||"actor"} journey (${d.steps.length} steps)`),...o.pages.map(d=>`screen: ${d}`)],instructions:[`Build every journey screen. All mock data lives in JSON files only: ${I}/<entity>.json, one array per entity, each record with a stable string "id".`,`Pages read and write data only through ${X} (list/get/create/update/remove per entity). It reads the JSON files for now. Never put mock arrays inside components.`]}),r&&l.push({id:"auth",title:"Sign-in & profile",items:[...n(["auth"]),"screen: profile"],instructions:["Add register/login/logout with client.auth.* and keep the session. Data calls that need a user run only after sign-in, so the token is always sent (a missing token is what returns 401).","Signed-out visitors only see screens whose data is public.",`Every signed-in actor gets a "My profile" page, reachable from the header (avatar or name menu): show name, email and role from client.auth.getUser(); edit first/last name with client.auth.updateProfile(); change password with client.auth.changePassword({ oldPassword, newPassword }); language switch if the app is multilingual; sign out. Show a clear message if a save fails instead of crashing.${s.has("doc")?" Profile photo: upload with client.doc.upload and keep the returned id.":""}`]}),r&&(s.has("rbac")||e.actors.length>1)&&l.push({id:"roles",title:"Roles & access",items:t.map(d=>`role: ${d.role}`),instructions:["One realm role per actor (listed under Roles and access). Read the user's roles from client.auth.getUser().roles and guard routes and actions with them.",'Do not put x-scopes or x-public-scopes in schemas: the production generic-api rejects them (400 "unknown keyword"). Role permissions (entity#scope) are created in Keycloak by the platform when the roadmap is approved; reads before sign-in are not possible yet, so show those screens from the seed data until the user signs in. A 403 means the role lacks that entity#scope.']}),c&&(l.push({id:"schema",title:"Schema from the seed JSON",items:[...n(["data","crm","invoices"]),...i.map(d=>`entity: ${d}`)],instructions:[`Entities are created from ${I}/<entity>.json by the platform (Data tab \u203A Sync seed data). Never call client.schema.define when the app starts (every call creates a new schema version); if a schema must change, define it once from a script.`,"Schemas may only use standard JSON Schema plus translatable, x-index and x-public-actions. Never x-scopes or x-public-scopes (the API rejects the whole schema with 400).","Use natural entity names in code (client.from('payment')): createClient must get entityPrefix: import.meta.env.VITE_ZONE4BUILD_ENTITY_PREFIX (zone4code-sdk ^2.1.0), and the SDK stores them per project. Never write the prefix yourself."]}),l.push({id:"wire",title:"Connect screens to the SDK",items:i.map(d=>`client.from('${d}')`),instructions:[`Switch ${X} from the JSON files to client.from(<entity>) and keep its interface, so pages do not change.`,"Handle 401 by sending the user to sign-in and 403 with a clear 'not allowed' message."]}),l.push({id:"seed",title:"Seed the database",items:i.map(d=>`${I}/${d}.json`),instructions:[`Add scripts/seed.ts that loads ${I}/*.json into the workspace with client.from(<entity>).create, signed in as the owner.`,"Make it idempotent: store each JSON id as seed_key and skip records whose seed_key already exists. Never run it from app pages."]})),s.has("pwa")&&l.push({id:"pwa",title:"Installable app (PWA)",items:["manifest","icons","service worker","Install button"],instructions:[`Add public/manifest.webmanifest: name, short_name, description, start_url "/", scope "/", display "standalone", theme_color and background_color from the app's colors, lang from the app's default language, icons 192x192 and 512x512 (plus one "purpose": "maskable"). Link it in index.html with <meta name="theme-color">, <link rel="apple-touch-icon"> and <meta name="apple-mobile-web-app-capable" content="yes">.`,"Add public/sw.js: network-first for page navigations with the cached home page as offline fallback; never cache API calls (api.zone4build.com) or Vite's hashed assets. Register it from main.jsx only in production (import.meta.env.PROD) so it never interferes with the preview.",'Add an "Install the app" button: listen for beforeinstallprompt, keep the event, call prompt() on click and hide the button once installed (appinstalled, or display-mode: standalone). On iPhone/iPad (no prompt) show the hint "Share \u2192 Add to Home Screen" instead.',"Installing needs HTTPS (the preview and deployed URLs have it)."]});let p=n(["doc","notification","audit"]);return p.length&&l.push({id:"platform",title:"Files, notifications & audit",items:p,instructions:["Add these with zone4code-sdk on top of the connected data."]}),l}function N(e){let o=new Set(e),t=new Map;for(let s of e){let n=j(s);for(let i of n?.requires??[]){if(o.has(i))continue;let c=t.get(i)??{requiredBy:[],reason:n?.requiresReason};c.requiredBy.push(s),t.set(i,c)}}return[...t].map(([s,n])=>({capability:s,...n}))}function V(e){let{brief:o}=e,t=R(o),s=o.capabilities.map(r=>j(r)).filter(Boolean),n=[];if(n.push(`Status: ${e.status==="approved"?"approved":"draft"} (version ${e.version})`),o.summary.trim()&&n.push(`Goal: ${o.summary.trim()}`),o.actors.length&&(n.push("","Actors and journeys (build each journey end to end):"),o.actors.forEach((r,l)=>{n.push(`${l+1}. ${r.name}: ${r.steps.join(" \u2192 ")||"(no steps yet)"}`)})),t.entities.length&&n.push("",`Data entities (define with client.schema.define): ${t.entities.join(", ")}`),t.pages.length&&n.push(`Screens: ${t.pages.join(", ")}`),n.push("",`End-to-end tests: e2e/*.spec.ts with @playwright/test, one file per journey, relative paths (page.goto("/")). They run in the platform's sandbox (run_e2e_tests, or /e2e for the user): never add Playwright to package.json or write a playwright.config. Test logins come from process.env.E2E_USER / E2E_PASSWORD.`),s.length){n.push("","Capabilities (use zone4code-sdk, never a custom backend for these):");for(let r of s)n.push(`- ${r.name}: ${r.sdkHint}`)}let i=D(o);if(i.some(r=>r.entities.length||r.publicViews.length)){n.push("","Roles and access (one realm role per actor):");for(let r of i){let l=r.entities.map(p=>`${p.entity}: ${p.scopes.join(", ")}`).join("; ")||"(no data steps)";n.push(`- ${r.actor} \u2192 role \`${r.role}\`: ${l}`),r.publicViews.length&&n.push(` signed out: view ${r.publicViews.join(", ")} (from seed data until sign-in; public reads not available yet)`),r.needsSignIn.length&&n.push(` needs sign-in first: ${r.needsSignIn.join(", ")}`)}}let c=x(o);return c.length&&(n.push("","Build order:"),c.forEach((r,l)=>{n.push(`${l+1}. ${r.title}`),r.instructions.forEach(p=>n.push(` - ${p}`))})),o.check?.status==="needs_foundations"&&o.check.missing.length&&n.push("",`Unresolved prerequisites: ${o.check.missing.map(r=>j(r.capability)?.name??r.capability).join(", ")}`),n.join(`
|
|
7
|
+
`)}function He(e){return e.ok?`Roles created in the workspace: ${e.roles.join(", ")} (sign-up role: ${e.signupRole}); ${e.permissions} permissions on ${e.entities.join(", ")||"no entities"}.`:e.skipped?`Roles were not created: ${e.error}`:`Creating the roles failed: ${e.error}`}function ne(e,o){let t=["Build this project from its approved roadmap.","Work in the build order, one phase at a time: implement it, verify it runs, then give me a short summary and the next phase before continuing.","",V(e)];return o&&(t.push("",He(o)),!o.ok&&!o.skipped&&t.push("Until this is fixed, signed-in data calls may return 403. Tell me in the Roles & access phase instead of working around it in code.")),t.join(`
|
|
8
|
+
`)}var lt=8*1024*1024;import Ve from"readline";var Ye=process.stdout.isTTY&&!process.env.NO_COLOR,b=e=>o=>Ye?`\x1B[${e}m${o}\x1B[0m`:o,a={bold:b("1"),dim:b("2"),red:b("31"),green:b("32"),yellow:b("33"),blue:b("34"),magenta:b("35"),cyan:b("36"),gray:b("90")},g={ok:a.green("\u2713"),warn:a.yellow("!"),error:a.red("\u2717"),todo:a.gray("\u25CB"),info:a.blue("i")},ie=e=>e.replace(/\x1b\[[0-9;]*m/g,"").length;function C(e,o){let t=o?[o.map(n=>a.bold(n)),...e]:e,s=t[0]?.map((n,i)=>Math.max(...t.map(c=>ie(c[i]??""))))??[];return t.map(n=>n.map((i,c)=>c===n.length-1?i:i+" ".repeat(s[c]-ie(i))).join(" ")).join(`
|
|
9
|
+
`)}function $(e){let o=Ve.createInterface({input:process.stdin,output:process.stdout});return new Promise(t=>o.question(e,s=>(o.close(),t(s.trim()))))}function q(e){return new Promise(o=>{let t=process.stdin;if(process.stdout.write(e),!t.isTTY){$("").then(o);return}let s="";t.setRawMode(!0),t.resume(),t.setEncoding("utf8");let n=i=>{i==="\r"||i===`
|
|
10
|
+
`||i===""?(t.setRawMode(!1),t.pause(),t.off("data",n),process.stdout.write(`
|
|
11
|
+
`),o(s)):i===""?(process.stdout.write(`
|
|
12
|
+
`),process.exit(130)):i==="\x7F"?s=s.slice(0,-1):s+=i};t.on("data",n)})}async function z(e,o=!1){let t=(await $(`${e} ${o?"[Y/n]":"[y/N]"} `)).toLowerCase();return t?t.startsWith("y")||t.startsWith("o"):o}function f(e,o=1){console.error(`${g.error} ${e}`),process.exit(o)}var A="singulary.brief.json",Ge=new Set(v.map(e=>e.id));function U(e){let o=e.flatMap(s=>s.split(/[\s,]+/)).map(s=>s.trim().toLowerCase()).filter(Boolean),t=o.filter(s=>!Ge.has(s));return t.length&&f(`Unknown capabilit${t.length>1?"ies":"y"}: ${t.join(", ")}. Run \`zone4code caps list\`.`),[...new Set(o)]}function Y(e){return e.split(/[;\n]+/).map(o=>o.trim()).filter(Boolean).map((o,t)=>{let[s,n=""]=o.split(/:(.*)/s);return{id:`a${t+1}`,name:(s??"").trim(),steps:n.split(/,|→|->/).map(i=>i.trim()).filter(Boolean)}}).filter(o=>o.name)}function F(e){let o={summary:"",actors:[],capabilities:[]},t=e.file??(re.existsSync(A)?A:void 0);if(t)try{let s=JSON.parse(re.readFileSync(t,"utf8")),n=s.brief??s;o={summary:n.summary??"",actors:n.actors??[],capabilities:U(n.capabilities??[])}}catch(s){f(`Can't read ${t}: ${s instanceof Error?s.message:s}`)}return e.actors&&(o.actors=Y(e.actors)),e.summary&&(o.summary=e.summary),e.caps?.length&&(o.capabilities=U(e.caps)),o}import Ze from"readline";import M from"fs";import ae from"os";import W from"path";var Ke="https://zone4build.com";function ce(){let e=process.platform==="win32"?process.env.APPDATA??W.join(ae.homedir(),"AppData","Roaming"):process.env.XDG_CONFIG_HOME??W.join(ae.homedir(),".config");return W.join(e,"zone4code","config.json")}function O(){let e={};try{e=JSON.parse(M.readFileSync(ce(),"utf8"))}catch{}return{...e,url:(process.env.ZONE4CODE_URL??e.url??Ke).replace(/\/$/,""),token:process.env.ZONE4CODE_TOKEN??e.token}}function S(e){let o=ce(),t={};try{t=JSON.parse(M.readFileSync(o,"utf8"))}catch{}let s={...t,...e};for(let n of Object.keys(s))s[n]===void 0&&delete s[n];return M.mkdirSync(W.dirname(o),{recursive:!0,mode:448}),M.writeFileSync(o,JSON.stringify(s,null,2),{mode:384}),O()}async function Xe(e,o,t,s){let n=o.sessions?.[t.id];if(n&&!s.newSession)return n;let{session:i}=await e.post("/api/agent/sessions",{workspaceId:t.workspaceId,projectId:t.id,approvalMode:s.yes?"auto":"manual",...s.model?{modelProvider:"openrouter",modelName:s.model}:{}});return S({sessions:{...o.sessions??{},[t.id]:i.id}}),i.id}function Qe(e){if(!e||typeof e!="object")return"";let o=e,t=o.path??o.command??o.topic??o.query??o.name;return typeof t=="string"?t.slice(0,120):""}async function T(e,o,t,s,n){let i=await Xe(e,o,t,n),c=new AbortController,r=!1,l=()=>{r&&process.exit(130),r=!0,console.log(a.yellow(`
|
|
13
|
+
Stopping the agent\u2026 (Ctrl+C again to quit)`)),e.post(`/api/agent/sessions/${i}/cancel`).catch(()=>{})};process.on("SIGINT",l);let p=!1,d=()=>{p&&process.stdout.write(`
|
|
14
|
+
`),p=!1},m,u=new Promise(y=>m=y),h=e.stream(`/api/agent/sessions/${i}/stream`,y=>{switch(y.type){case"content_delta":process.stdout.write(String(y.delta??"")),p=!String(y.delta??"").endsWith(`
|
|
15
|
+
`);return;case"tool_call_start":{let w=y.toolCall;d(),n.quiet||console.log(a.gray(` \u203A ${w.toolName} ${Qe(w.arguments)}`));return}case"approval_requested":{let w=y.approval;d(),(async()=>{let J=w.action??w.toolName??"an action";console.log(`${g.warn} The agent wants to run ${a.bold(J)}${w.riskLevel?a.gray(` (${w.riskLevel})`):""}`),w.arguments&&console.log(a.gray(` ${JSON.stringify(w.arguments).slice(0,400)}`));let _e=n.yes||await z("Allow it?");await e.post(`/api/agent/approvals/${w.id}/resolve`,{decision:_e?"approved":"rejected"})})().catch(J=>console.error(a.red(J.message)));return}case"error":d(),console.error(`${g.error} ${String(y.error??"The agent stopped.")}`);return;case"done":return d(),!0;default:}},c.signal).catch(y=>{c.signal.aborted||console.error(a.red(`Stream: ${y.message}`))});setTimeout(m,400),await u,await e.post(`/api/agent/sessions/${i}/messages`,{content:s,...n.yes?{approvalMode:"auto"}:{},...n.model?{modelProvider:"openrouter",modelName:n.model}:{}}),await h,c.abort(),process.off("SIGINT",l)}async function le(e,o,t,s){console.log(`${a.bold(t.name)} \xB7 chat with the agent. ${a.gray("/new new conversation \xB7 /exit quit")}`);let n=o,i=s.newSession;for(;;){let c=Ze.createInterface({input:process.stdin,output:process.stdout}),r=await new Promise(l=>c.question(a.magenta("\u203A "),p=>(c.close(),l(p.trim()))));if(r){if(r==="/exit"||r==="/quit")return;if(r==="/new"){i=!0,console.log(a.gray("New conversation."));continue}await T(e,n,t,r,{...s,newSession:i}),i=!1,n=O(),console.log()}}}import et from"os";async function pe(e,o){let t=(o.url??e.url).replace(/\/$/,""),s=o.key??process.env.ZONE4CODE_TOKEN,n;if(!s){console.log(`Sign in to ${a.bold(t)}`),console.log(a.dim("Signed up with Google? Create a CLI key in Settings \u203A CLI keys, then run: zone4code login --key z4p_\u2026"));let r=o.email??await $("Email: "),l=await q("Password: "),p=await fetch(`${t}/api/auth/login`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":"zone4code-cmd"},body:JSON.stringify({email:r,password:l})}).catch(h=>f(`Can't reach ${t}: ${h.message}`));if(!p.ok){let h=await p.json().catch(()=>({}));f(h.error?.message??`Sign-in failed (${p.status}).`)}let d=p.headers.getSetCookie().map(h=>h.split(";")[0]).join("; "),m=await fetch(`${t}/api/me/cli-keys`,{method:"POST",headers:{"Content-Type":"application/json",Cookie:d,"User-Agent":"zone4code-cmd"},body:JSON.stringify({name:`zone4code CLI on ${et.hostname()}`})});m.ok||f(`Signed in, but couldn't create a CLI key (${m.status}).`);let u=await m.json();s=u.token,n=u.key.id,await fetch(`${t}/api/auth/logout`,{method:"POST",headers:{Cookie:d}}).catch(()=>{})}let c=await new P({...e,url:t,token:s}).get("/api/auth/me").catch(r=>f(r.status===401?"This CLI key is not valid.":r.message));S({url:t,token:s,tokenId:n,email:c.user.email}),console.log(`${g.ok} Signed in as ${a.bold(c.user.email)} on ${t}`),console.log(a.dim('Next: zone4code projects \xB7 zone4code create "My app" \xB7 zone4code keys add openrouter sk-or-\u2026'))}async function de(e){e.token&&e.tokenId&&await new P(e).delete(`/api/me/cli-keys/${e.tokenId}`).catch(()=>{}),S({token:void 0,tokenId:void 0,email:void 0}),console.log(`${g.ok} Signed out${e.tokenId?" (CLI key deleted)":""}.`)}async function ue(e,o){let t=await e.get("/api/auth/me");console.log(`${t.user.email} on ${o.url}`),o.projectName&&console.log(a.dim(`Current project: ${o.projectName} (${o.projectId})`))}import L from"fs";import _ from"path";function me(e){if(e){console.log(JSON.stringify(v,null,2));return}for(let o of Q){console.log(a.bold(o));let t=v.filter(s=>s.group===o).map(s=>[a.magenta(s.id),s.name,s.requires.length?a.gray(`needs ${s.requires.join(", ")}`):""]);console.log(C(t)),console.log()}console.log(a.dim("zone4code caps show <id> for how the agent builds one."))}function ge(e){let o=j(e);if(!o)throw new Error(`Unknown capability "${e}".`);console.log(`${a.bold(o.name)} ${a.gray(`(${o.id} \xB7 ${o.group})`)}`),console.log(o.description),o.requires.length&&console.log(`Needs: ${o.requires.join(", ")}${o.requiresReason?a.gray(` \u2014 ${o.requiresReason}`):""}`),console.log(`How: ${o.sdkHint}`),console.log(a.gray(`Suggested by journey words: ${o.keywords.join(", ")}`))}function fe(e,o){let t=N(e);if(o)return console.log(JSON.stringify({status:t.length?"needs_foundations":"ok",missing:t},null,2)),t.length?1:0;if(!t.length)return console.log(`${g.ok} ${e.join(", ")}: all prerequisites selected.`),0;for(let s of t){let n=j(s.capability);console.log(`${g.error} Missing ${a.bold(s.capability)} (${n?.name}) \u2014 needed by ${s.requiredBy.join(", ")}${s.reason?a.gray(` \xB7 ${s.reason}`):""}`)}return console.log(a.dim(`
|
|
16
|
+
Complete set: ${[...new Set([...e,...t.map(s=>s.capability)])].join(" ")}`)),1}function he(e,o){let t=R(e),s=D(e);if(o){console.log(JSON.stringify({...t,access:s},null,2));return}if(!e.actors.length)throw new Error('Describe the actors: --actors "Guest: browse, search; Host: log in, create listing"');console.log(`${a.bold("Entities")} ${t.entities.join(", ")||a.gray("none found")}`),console.log(`${a.bold("Screens")} ${t.pages.join(", ")||a.gray("none found")}`),console.log(a.bold(`
|
|
17
|
+
Suggested capabilities`));for(let n of t.suggestions)console.log(` ${a.magenta(n.capability.padEnd(13))} ${a.gray(n.because)}`);console.log(a.bold(`
|
|
18
|
+
Roles and access`));for(let n of s){console.log(` ${a.bold(n.role)} (${n.actor})`);for(let i of n.entities)console.log(` ${i.entity}: ${i.scopes.join(", ")}`);n.publicViews.length&&console.log(a.gray(` before sign-in, reads: ${n.publicViews.join(", ")}`)),n.needsSignIn.length&&console.log(a.yellow(` move after sign-in: ${n.needsSignIn.join(", ")}`))}}function ye(e,o){let t=x(e);if(o){console.log(JSON.stringify(t,null,2));return}t.forEach((s,n)=>{console.log(`${a.bold(`${n+1}. ${s.title}`)} ${a.gray(s.items.join(" \xB7 "))}`);for(let i of s.instructions)console.log(` - ${i}`)})}function we(e){console.log(V({brief:e,status:"approved",version:1}))}function be(e,o){let t=_.resolve(e),s=[],n=(u,h)=>{for(let y of L.readdirSync(u,{withFileTypes:!0})){if(["node_modules",".git","dist","build",".vite"].includes(y.name))continue;let w=_.join(u,y.name);y.isDirectory()&&h<8?n(w,h+1):y.isFile()&&s.push(_.relative(t,w).split(_.sep).join("/"))}};if(!L.existsSync(t))throw new Error(`No folder ${t}`);n(t,0);let i=s.filter(u=>/^src\/.*\.(m?[jt]sx?|vue|svelte)$/.test(u)),c=u=>{try{return L.readFileSync(_.join(t,u),"utf8")}catch{return""}},r=u=>i.filter(h=>u.test(c(h))),p=JSON.parse(c("package.json")||"{}").dependencies?.["zone4code-sdk"],d=s.find(u=>/^(public\/)?manifest\.(webmanifest|json)$/.test(u)),m=[{capability:"sdk",state:p?/^[\^~]?[2-9]/.test(p)?"ok":"warn":"todo",detail:p?`zone4code-sdk ${p}${/^[\^~]?[2-9]/.test(p)?"":" (update to ^2)"}`:"zone4code-sdk not installed"},{capability:"ui",state:i.some(u=>/^src\/(pages|views|screens)\//.test(u))?"ok":"todo",detail:`${i.filter(u=>/^src\/(pages|views|screens)\//.test(u)).length} screens`},{capability:"pwa",state:d&&r(/serviceWorker\.register\(/).length?"ok":"todo",detail:d?`${d}${r(/serviceWorker\.register\(/).length?" + service worker":", no service worker registered"}`:"no manifest"},{capability:"auth",state:r(/\.auth\.(login|register|signIn)/).length?"ok":"todo",detail:r(/\.auth\.(login|register|signIn)/).join(", ")||"no sign-in calls"},{capability:"data",state:r(/client\.from\(|useCollection\(|\.from\(["'`]/).length?"ok":"todo",detail:`${r(/client\.from\(|useCollection\(/).length} files use data \xB7 seed ${s.some(u=>u.startsWith(`${I}/`))?"present":"missing"}`},{capability:"doc",state:r(/\.doc\.(upload|createFolder)|documents\./).length?"ok":"todo",detail:r(/\.doc\.(upload|createFolder)/).join(", ")||"no uploads"},{capability:"notification",state:r(/\.notification\.(send|sendEmail)|notifications\./).length?"ok":"todo",detail:r(/\.notification\./).join(", ")||"no notifications"},{capability:"e2e",state:s.some(u=>/^e2e\/.*\.(spec|test)\.[jt]sx?$/.test(u))?"ok":"todo",detail:`${s.filter(u=>/^e2e\/.*\.(spec|test)\./.test(u)).length} test files`}];if(o)console.log(JSON.stringify(m,null,2));else{console.log(`${a.bold(t)} ${a.gray("(files only)")}`);for(let u of m)console.log(`${u.state==="ok"?g.ok:u.state==="warn"?g.warn:g.todo} ${u.capability.padEnd(13)} ${a.gray(u.detail)}`)}return m.some(u=>u.state==="warn")?1:0}async function ke(){console.log(a.bold("New roadmap")+a.gray(" \u2014 who uses your app, and what do they do?"));let e=await $("In one sentence, what is the app for? "),o=await $('Actors and their steps (e.g. "Member: register, book a session; Coach: log in, create session"): '),t={summary:e,actors:Y(o),capabilities:[]},s=R(t),n=[...new Set(["ui",...s.suggestions.map(l=>l.capability)])];console.log(`
|
|
19
|
+
Suggested: ${n.map(l=>a.magenta(l)).join(" ")}`);let i=await $("Add or remove? (e.g. +pwa -crm, Enter to keep) "),c=new Set(n);for(let l of i.split(/\s+/).filter(Boolean)){let[p,d]=[l[0],l.slice(1)],[m]=U([d??""]);m&&p==="-"?c.delete(m):m&&c.add(m)}let r=N([...c]);r.length&&(console.log(a.yellow(`Adding prerequisites: ${r.map(l=>l.capability).join(", ")}`)),c=new Set([...c,...r.map(l=>l.capability)])),t.capabilities=v.map(l=>l.id).filter(l=>c.has(l)),!(L.existsSync(A)&&!await z(`${A} exists. Replace it?`))&&(L.writeFileSync(A,`${JSON.stringify(t,null,2)}
|
|
20
|
+
`),console.log(`${g.ok} Wrote ${A}. Next: zone4code caps plan \xB7 zone4code roadmap push --approve`))}async function ve(e){let{providerKeys:o}=await e.get("/api/provider-keys"),t=o;if(!t.length){console.log("No personal AI key. Add one: zone4code keys add openrouter (the agent then bills your key, without platform limits)");return}console.log(C(t.map(s=>[s.provider,s.label,a.gray(s.id)]),["PROVIDER","LABEL","ID"]))}async function $e(e,o,t,s){let n=t??await q(`${o} API key: `);n&&(await e.post("/api/provider-keys",{scopeType:"user",provider:o,label:s??`${o} (CLI)`,key:n}),console.log(`${g.ok} Saved your ${o} key. The agent uses it from the next message.`))}async function Se(e,o){await e.delete(`/api/provider-keys/${o}`),console.log(`${g.ok} Removed.`)}async function Pe(e){return(await e.get("/api/workspaces")).workspaces}async function tt(e,o,t){let s=await Pe(e);s.length||f("You have no workspace yet: open zone4build.com once to create one.");let n=t??o.workspaceId,i=n?s.find(c=>c.id===n||c.slug===n||c.name.toLowerCase()===n.toLowerCase()):s[0];return i||f(`No workspace "${n}". Yours: ${s.map(c=>c.slug).join(", ")}`),i}async function je(e){let o=await Pe(e);console.log(C(o.map(t=>[t.slug,t.name,a.gray(t.id)]),["SLUG","NAME","ID"]))}async function Ce(e){return(await e.get("/api/projects")).projects}async function G(e,o,t){let s=t??o.projectId;s||f("No project selected: run `zone4code use <project>` or pass --project.");let i=(await Ce(e)).find(c=>c.id===s||c.slug===s||c.name.toLowerCase()===s.toLowerCase());return i||f(`Project "${s}" not found. Run \`zone4code projects\`.`),i}async function Ae(e,o,t){let s=await Ce(e);if(t){console.log(JSON.stringify(s,null,2));return}if(!s.length){console.log('No projects yet. Create one: zone4code create "My app"');return}console.log(C(s.map(n=>[n.id===o.projectId?a.green("\u25CF"):" ",n.slug,n.name,n.lastStatus,n.runTarget?"computer":"cloud",a.gray(n.id)]),["","SLUG","NAME","STATUS","RUNS ON","ID"]))}async function Ie(e,o,t){let s=await G(e,o,t);S({projectId:s.id,projectName:s.name,workspaceId:s.workspaceId}),console.log(`${g.ok} Using ${a.bold(s.name)} (${s.id})`)}async function Ee(e,o,t,s){let n=await tt(e,o,s.workspace),{project:i}=await e.post("/api/projects",{workspaceId:n.id,name:t,templateId:s.template??"react-vite"});S({projectId:i.id,projectName:i.name,workspaceId:i.workspaceId}),console.log(`${g.ok} Created ${a.bold(i.name)} in ${n.name} (${i.id}) \u2014 now the current project.`),s.start!==!1&&await K(e,i,"start"),console.log(a.dim('Next: zone4code caps init \xB7 zone4code prompt "Build a booking app for a sports club"'))}async function K(e,o,t){process.stdout.write(`${t==="start"?"Starting":t==="stop"?"Stopping":"Restarting"} ${o.name}\u2026 `);let{runtime:s}=await e.post(`/api/projects/${o.id}/runtime/actions`,{action:t});console.log(s.status);let n=s.detectedPorts[0]!==void 0?s.preview.urlsByPort[s.detectedPorts[0]]:void 0;n&&console.log(`Preview: ${a.cyan(n)}`)}async function Re(e,o,t){let{runtime:s}=await e.get(`/api/projects/${t.id}/runtime`),n=s.detectedPorts[0]!==void 0?s.preview.urlsByPort[s.detectedPorts[0]]:null;console.log(`IDE: ${a.cyan(`${o.url}/projects/${t.id}`)}`),console.log(`Preview: ${n?a.cyan(n):a.gray("not running (zone4code start)")}`)}async function xe(e,o,t){let{brief:s}=await e.get(`/api/projects/${o.id}/brief`);if(t){console.log(JSON.stringify(s,null,2));return}if(!s){console.log("No roadmap yet. Write one: zone4code caps init, then zone4code roadmap push --approve");return}let n=s.brief;console.log(`${a.bold(o.name)} \xB7 roadmap v${s.version} \xB7 ${s.status==="approved"?a.green("approved"):a.yellow("draft")}`),n.summary&&console.log(n.summary);for(let i of n.actors)console.log(` ${a.bold(i.name)}: ${i.steps.join(" \u2192 ")}`);console.log(`Capabilities: ${n.capabilities.map(i=>a.magenta(i)).join(" ")}`),console.log(`Build order: ${x(n).map(i=>i.title).join(" \u2192 ")}`)}async function Ne(e,o,t,s){let n=F(t);!n.actors.length&&!n.capabilities.length&&f("Nothing to push: run `zone4code caps init` or pass --actors and capabilities.");let i=N(n.capabilities);i.length&&f(`Missing prerequisites: ${i.map(l=>`${l.capability} (for ${l.requiredBy.join(", ")})`).join(", ")}. Add them, then push again.`);let{brief:c,access:r}=await e.put(`/api/projects/${o.id}/brief`,{brief:n,status:s?"approved":"draft"});console.log(`${g.ok} Roadmap v${c.version} saved as ${c.status}.`),r&&console.log(r.ok?`${g.ok} Roles created in the workspace.`:`${g.warn} Roles: ${r.skipped?"skipped":"not created yet"} \u2014 zone4code status roles`),s&&console.log(a.dim("Next: zone4code build"))}async function Oe(e,o,t,s){let{brief:n}=await e.get(`/api/projects/${t.id}/brief`);n||f("No roadmap yet: zone4code caps init, then zone4code roadmap push --approve"),n.status!=="approved"&&f("Approve the roadmap first: zone4code roadmap push --approve"),await T(e,o,t,ne(n,n.access??null),s)}var ot={ok:g.ok,warn:g.warn,error:g.error,todo:g.todo,info:g.info};function st(e){console.log(a.bold(e.title)),e.summary&&console.log(a.dim(e.summary));for(let t of e.items)console.log(`${ot[t.state]??"-"} ${t.label}${t.detail?a.gray(` \u2014 ${t.detail}`):""}`);let o=e.actions.filter(t=>t.kind==="ask-agent");if(o.length){console.log(a.dim(`
|
|
21
|
+
Ask the agent:`));for(let t of o)t.kind==="ask-agent"&&console.log(` zone4code prompt ${JSON.stringify(t.prompt.length>160?`${t.prompt.slice(0,157)}\u2026`:t.prompt)}`)}}async function Te(e,o,t,s){if(!t){let{commands:i}=await e.get(`/api/projects/${o.id}/status`);console.log(`Topics for ${a.bold(o.name)}:`);for(let c of i)console.log(` ${a.magenta(c.name.padEnd(12))} ${c.hint}`);console.log(a.dim(`
|
|
22
|
+
zone4code status <topic>`));return}let{card:n}=t==="issue"||t==="fix"?await e.post(`/api/projects/${o.id}/status/issue`,{issues:[]}):await e.get(`/api/projects/${o.id}/status/${encodeURIComponent(t)}`);s?console.log(JSON.stringify(n,null,2)):st(n)}async function Z(e,o,t,s){let n=t==="responsive"?"Checking every screen":"Running the end-to-end tests";process.stdout.write(`${n} for ${o.name} in the sandbox`);let{run:i}=await e.post(`/api/projects/${o.id}/e2e/run`,{suite:t}),c=Date.now()+7*6e4;for(;i.status==="running"&&Date.now()<c;)await new Promise(r=>setTimeout(r,3e3)),process.stdout.write("."),i=(await e.get(`/api/projects/${o.id}/e2e?suite=${t}`)).run??i;if(console.log(),s)console.log(JSON.stringify(i,null,2));else{for(let r of i.tests){let l=r.issues??[],p=r.status==="skipped"?g.todo:r.status==="passed"&&!l.some(d=>d.level==="problem")?l.length?g.warn:g.ok:g.error;console.log(`${p} ${r.title}${r.error?a.gray(` \u2014 ${r.error.split(`
|
|
23
|
+
`)[0]}`):""}${r.note?a.gray(` \u2014 ${r.note}`):""}`);for(let d of l)console.log(a.gray(` ${d.level}: ${d.text}`))}i.message&&console.log(a.yellow(i.message)),console.log(a.bold(i.status==="passed"?"All good.":i.status==="failed"?`${i.failed} failing.`:i.status))}return i.status==="passed"?0:1}var it=`${a.bold("zone4code")} \u2014 Zone4build from your terminal: projects, the AI agent, roadmap and checks.
|
|
24
|
+
|
|
25
|
+
${a.bold("Account")}
|
|
26
|
+
login [--key z4p_\u2026] [--url URL] sign in (email + password, or a CLI key from Settings)
|
|
27
|
+
logout \xB7 whoami
|
|
28
|
+
keys [list] \xB7 keys add <provider> [key] \xB7 keys rm <id>
|
|
29
|
+
your own AI key (e.g. openrouter): the agent bills it, no platform limits
|
|
30
|
+
|
|
31
|
+
${a.bold("Projects")}
|
|
32
|
+
projects [--json] \xB7 workspaces
|
|
33
|
+
create <name> [--template react-vite] [--workspace slug] [--no-start]
|
|
34
|
+
use <project> set the current project (id, slug or name)
|
|
35
|
+
start \xB7 stop \xB7 restart \xB7 open
|
|
36
|
+
|
|
37
|
+
${a.bold("Agent")} ${a.gray("(current project, or --project)")}
|
|
38
|
+
prompt "<what to build or change>" one message; streams the answer and asks before risky actions
|
|
39
|
+
chat interactive conversation (/new, /exit)
|
|
40
|
+
build build the approved roadmap, phase by phase
|
|
41
|
+
--yes approve actions automatically --model <openrouter model> --new new conversation
|
|
42
|
+
|
|
43
|
+
${a.bold("Roadmap & capabilities")}
|
|
44
|
+
caps list \xB7 caps show <id> the platform capabilities and what they need
|
|
45
|
+
caps check <ids\u2026> prerequisites (exit 1 if missing \u2014 use it in CI)
|
|
46
|
+
caps derive --actors "\u2026" entities, screens, roles and suggested capabilities from journeys
|
|
47
|
+
caps plan [ids\u2026] [--actors "\u2026"] ordered build phases with instructions
|
|
48
|
+
caps prompt [ids\u2026] [--actors "\u2026"] the brief the agent receives
|
|
49
|
+
caps init interactive: write singulary.brief.json
|
|
50
|
+
caps audit [dir] what a project folder already implements (files only)
|
|
51
|
+
roadmap [show] \xB7 roadmap push [--approve] [--file F]
|
|
52
|
+
actors format: "Member: register, book a session; Coach: log in, create session"
|
|
53
|
+
|
|
54
|
+
${a.bold("Checks")} ${a.gray("(same as the chat's slash commands)")}
|
|
55
|
+
status [topic] roadmap, issue, api, ui, data, auth, roles, e2e, \u2026
|
|
56
|
+
e2e \xB7 responsive run in the platform sandbox; exit 1 on failure
|
|
57
|
+
|
|
58
|
+
Options: --project <p> \xB7 --json \xB7 --url <platform> \xB7 -h, --help \xB7 -v, --version
|
|
59
|
+
Env: ZONE4CODE_TOKEN, ZONE4CODE_URL (CI)`;async function rt(){let{values:e,positionals:o}=nt({allowPositionals:!0,strict:!1,options:{help:{type:"boolean",short:"h"},version:{type:"boolean",short:"v"},json:{type:"boolean"},key:{type:"string"},url:{type:"string"},email:{type:"string"},project:{type:"string",short:"p"},workspace:{type:"string"},template:{type:"string"},"no-start":{type:"boolean"},yes:{type:"boolean",short:"y"},model:{type:"string"},new:{type:"boolean"},quiet:{type:"boolean",short:"q"},actors:{type:"string"},summary:{type:"string"},file:{type:"string"},approve:{type:"boolean"},label:{type:"string"}}}),t=u=>typeof u=="string"?u:void 0,s=!!e.json,[n,...i]=o;if(e.version)return console.log("0.1.0"),0;if(!n||e.help||n==="help")return console.log(it),0;let c=O();e.url&&(c.url=String(e.url).replace(/\/$/,""));let r=new P(c),l=()=>{c.token||f("Not signed in: run `zone4code login`.")},p=async()=>(l(),G(r,c,t(e.project))),d={yes:!!e.yes,model:t(e.model),newSession:!!e.new,quiet:!!e.quiet},m=u=>F({file:t(e.file),actors:t(e.actors),summary:t(e.summary),caps:u});switch(n){case"login":return await pe(c,{key:t(e.key),url:t(e.url),email:t(e.email)}),0;case"logout":return await de(c),0;case"whoami":return l(),await ue(r,c),0;case"keys":{l();let[u="list",h,y]=i;return u==="list"?await ve(r):u==="add"?await $e(r,h??f("zone4code keys add <provider> [key]"),y,t(e.label)):u==="rm"||u==="remove"?await Se(r,h??f("zone4code keys rm <id>")):f(`Unknown: keys ${u}`),0}case"workspaces":return l(),await je(r),0;case"projects":case"ls":return l(),await Ae(r,c,s),0;case"create":case"new":return l(),await Ee(r,c,i.join(" ")||f('zone4code create "<name>"'),{template:t(e.template),workspace:t(e.workspace),start:!e["no-start"]}),0;case"use":return l(),await Ie(r,c,i.join(" ")||f("zone4code use <project>")),0;case"start":case"stop":case"restart":return await K(r,await p(),n),0;case"open":return await Re(r,c,await p()),0;case"prompt":case"ask":{let u=i.join(" ")||(process.stdin.isTTY?"":await at());return u.trim()||f('zone4code prompt "<what to build or change>"'),await T(r,c,await p(),u,d),0}case"chat":return await le(r,c,await p(),d),0;case"build":return await Oe(r,c,await p(),d),0;case"status":return await Te(r,await p(),i[0]?.replace(/^\//,""),s),0;case"e2e":return Z(r,await p(),"tests",s);case"responsive":return Z(r,await p(),"responsive",s);case"roadmap":{let[u="show"]=i;return u==="show"?await xe(r,await p(),s):u==="push"?await Ne(r,await p(),{file:t(e.file),actors:t(e.actors),summary:t(e.summary),caps:i.slice(1)},!!e.approve):f(`Unknown: roadmap ${u}`),0}case"caps":case"capabilities":{let[u="list",...h]=i;switch(u){case"list":return me(s),0;case"show":return ge(h[0]??f("zone4code caps show <id>")),0;case"check":return fe(m(h).capabilities,s);case"derive":return he(m(h),s),0;case"plan":return ye(m(h),s),0;case"prompt":return we(m(h)),0;case"init":return await ke(),0;case"audit":return be(h[0]??".",s);default:f(`Unknown: caps ${u}. Try zone4code caps list.`)}return 0}default:f(`Unknown command "${n}". Run zone4code --help.`)}}function at(){return new Promise(e=>{let o="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>o+=t),process.stdin.on("end",()=>e(o))})}rt().then(e=>process.exit(e),e=>{e instanceof k&&f(e.message),f(e instanceof Error?e.message:String(e))});
|
package/package.json
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zone4code-cmd",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Zone4build from your terminal: create projects, prompt the AI agent, plan with the capability roadmap and run checks.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"zone4build",
|
|
7
|
+
"zone4code",
|
|
8
|
+
"singulary",
|
|
9
|
+
"ai",
|
|
10
|
+
"app-builder",
|
|
11
|
+
"cli",
|
|
12
|
+
"agent",
|
|
13
|
+
"roadmap"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://zone4build.com",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"type": "module",
|
|
18
|
+
"bin": {
|
|
19
|
+
"zone4code": "dist/cli.js"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^22.10.2",
|
|
30
|
+
"tsup": "^8.0.2",
|
|
31
|
+
"typescript": "^5.7.3",
|
|
32
|
+
"@singulary/shared": "0.1.0"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsup",
|
|
36
|
+
"typecheck": "tsc --noEmit -p .",
|
|
37
|
+
"dev": "tsup && node dist/cli.js"
|
|
38
|
+
}
|
|
6
39
|
}
|