allwright 1.100.0 → 1.100.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/allwright.js +1 -1
  2. package/package.json +1 -1
package/bin/allwright.js CHANGED
@@ -1188,7 +1188,7 @@ ${p}`)}Ee(["systemctl","reload","nginx"]),y.info(`\u2713 nginx: ${t.hostname} \u
1188
1188
  `).slice(0,6e4),tables:n.length}}var rr,tl,tb,iR,sb=Q(()=>{"use strict";rr=A(require("node:fs"),1),tl=A(require("node:path"),1),tb=A(require("node:zlib"),1),iR=128*1024*1024});var _u={};Ve(_u,{MAX_JIRA_ISSUES:()=>ab,extractNamedBlock:()=>sr,profileDump:()=>lb,profileSqlDump:()=>cb,rebirthStudyInFlight:()=>cR,runRebirthDesign:()=>kR,runRebirthStudy:()=>gR,splitDesign:()=>rl,splitSpec:()=>Su});function cR(t){return nl.has(t)}function lb(t){try{if(fs.default.statSync(t).isDirectory())return nb(t)?rb(t):{ok:!1,markdown:`\`${t}\` is a directory but holds no .bson collections - point at a SQL dump file or a mongodump folder`,tables:0}}catch{return{ok:!1,markdown:`Could not read the dump at \`${t}\``,tables:0}}return cb(t)}function cb(t){if(/\.(rar|zip|7z|tar|tgz|bz2|xz)$/i.test(t))return{ok:!1,markdown:`\`${t}\` is an archive, not a SQL dump - extract the .sql inside it and use that (plain .sql or .dump text; the profiler reads text, it does not unpack archives)`,tables:0};let e;try{let n=fs.default.openSync(t,"r"),r=fs.default.fstatSync(n).size,s=Math.min(r,32*1024*1024),i=Buffer.alloc(s);if(fs.default.readSync(n,i,0,s,0),fs.default.closeSync(n),/\.gz$/i.test(t))try{i=ob.default.gunzipSync(fs.default.readFileSync(t),{maxOutputLength:64*1024*1024})}catch{return{ok:!1,markdown:`Could not decompress \`${t}\` (or it is larger than 64MB unpacked) - gunzip it and give the .sql`,tables:0}}if(e=i.toString("utf8"),i.subarray(0,Math.min(s,65536)).includes(0))return{ok:!1,markdown:`\`${t}\` is binary, not SQL text - export a plain-text dump (pg_dump / mysqldump default output) and use that`,tables:0};let a=r>s,l=[];for(let u of e.matchAll(/CREATE TABLE[^`"\w]*[`"]?([\w.]+)[`"]?\s*\(([\s\S]*?)\)\s*;/gi)){let f=u[2].split(`
1189
1189
  `).map(b=>b.trim()).filter(b=>b&&!/^(PRIMARY|UNIQUE|KEY|CONSTRAINT|FOREIGN|INDEX)/i.test(b)).map(b=>b.split(/\s+/)[0].replace(/[`",]/g,"")).filter(Boolean);l.push({name:u[1],columns:f,rows:0})}for(let u of l){let p=u.name.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),f=0;for(let v of e.matchAll(new RegExp(`INSERT INTO [\`"]?${p}[\`"]?[^;]*;`,"gi")))f+=(v[0].match(/\),\s*\(/g)?.length??0)+1;let b=e.match(new RegExp(`COPY [\`"]?${p}[\`"]?[^\\n]*FROM stdin;\\n([\\s\\S]*?)\\n\\\\\\.`,"i"));b&&(f+=b[1].split(`
1190
1190
  `).filter(Boolean).length),u.rows=f}let c=["# Data profile (from the dump, by code - no database was connected)","",`${l.length} table(s) found in \`${t}\`${a?" (large dump - schema read from its head; row counts are minimums)":""}.`,"",`| Table | Columns | Rows${a?" (\u2265)":""} |`,"| --- | --- | --- |",...l.sort((u,p)=>p.rows-u.rows).map(u=>`| ${u.name} | ${u.columns.length} | ${u.rows.toLocaleString()} |`),"","## Columns",...l.map(u=>`- **${u.name}**: ${u.columns.join(", ")||"(none parsed)"}`)];return{ok:l.length>0,markdown:c.join(`
1191
- `).slice(0,6e4),tables:l.length}}catch(n){return{ok:!1,markdown:`Could not read the dump at \`${t}\`: ${n instanceof Error?n.message:n}`,tables:0}}}function sr(t,e){let r=new RegExp("```"+e+"\\n([\\s\\S]*?)```","").exec(t);return r?{block:r[1].trim(),rest:t.replace(r[0],"").trim()}:{block:"",rest:t}}async function gR(t){let{msg:e,transport:n}=t;if(nl.has(e.rebirthId))return{ok:!1,error:"a study is already running for this rebirth - it reports as it goes"};nl.add(e.rebirthId);let r={inputTokens:0,outputTokens:0,cacheReadTokens:0,costUsd:0},s=i=>{let a=r;r={inputTokens:0,outputTokens:0,cacheReadTokens:0,costUsd:0},n.send({type:"rebirth.study.update",rebirthId:e.rebirthId,stageNumber:i.stageNumber,status:i.status,note:i.note??"",progressDone:i.progressDone??0,progressTotal:i.progressTotal??0,artifact:i.artifact??"",items:i.items??[],brief:i.brief??"",usageDelta:a})};try{let i=Re(t.cfg,t.project,"assist");if("error"in i)return{ok:!1,error:i.error};let a=(0,sl.randomUUID)(),l=!1,c=async(_,x)=>{let E=await i.adapter.run({repoPath:t.repoPath,prompt:_,timeoutSeconds:x*60,bin:i.bin,mode:"read_only",maxTurns:60,sessionId:a,resume:l,onLog:()=>{}});return l=!0,r={inputTokens:r.inputTokens+(E.inputTokens??0),outputTokens:r.outputTokens+(E.outputTokens??0),cacheReadTokens:r.cacheReadTokens+(E.cacheReadTokens??0),costUsd:r.costUsd+(E.costUsd??0)},E};if(e.refineMessage.trim()){s({stageNumber:4,status:"in_progress",note:"Updating the SPEC with your correction\u2026"}),l=!1;let _=await c(mR(e.refineMessage),10);if(!_.ok||!_.resultText.trim())return s({stageNumber:4,status:"failed",note:`refine failed: ${_.error??"no answer"}`}),{ok:!1,error:_.error??"the refine pass produced nothing"};let x=sr(_.resultText,"brief");x.block&&s({stageNumber:1,status:"done",note:"Project understanding updated with your correction",brief:x.block});let E=sr(x.rest,"understanding"),M=sr(E.rest,"requirements");E.block&&s({stageNumber:1,status:"done",note:"Understanding updated with your correction",artifact:E.block.slice(0,1e5)}),M.block&&s({stageNumber:3,status:"done",note:"Requirements updated with your correction",artifact:M.block.slice(0,1e5)});let{spec:L,tickets:C}=Su(M.rest);return s({stageNumber:4,status:"done",note:"SPEC updated - review and approve it again",artifact:L}),C.length&&s({stageNumber:6,status:"in_progress",note:"Breakdown refreshed from the updated SPEC",items:C}),{ok:!0}}let u=[];if(t.repoAvailable){s({stageNumber:1,status:"in_progress",note:"Reading the codebase - this is the long part"});let _=await c(uR,20);!_.ok||!_.resultText.trim()?(s({stageNumber:1,status:"failed",note:_.error??"the study pass produced nothing"}),u.push("the code study failed - its findings are not in this plan")):s({stageNumber:1,status:"done",note:"Codebase studied",artifact:_.resultText.trim().slice(0,1e5)})}else s({stageNumber:1,status:"skipped",note:"no code yet - map the legacy checkout on the runner (allwright repo add) and re-run the study to fold it in"}),u.push("the CODE was not available - nothing here is based on reading it");if(e.dbDumpPath.trim()){s({stageNumber:2,status:"in_progress",note:`Profiling the dump at ${e.dbDumpPath}`});let _=lb(e.dbDumpPath.trim());s({stageNumber:2,status:_.ok?"done":"failed",note:_.ok?`${_.tables} table(s) profiled`:"the dump could not be profiled",artifact:_.markdown})}else s({stageNumber:2,status:"skipped",note:"no DB dump given - add one and re-run the study to profile the data"}),u.push("the DATABASE was not profiled - data shapes in this plan are inferred, not read");let p=!1;if(e.legacyJiraKey.trim()&&t.jira){let _=e.legacyJiraKey.trim().toUpperCase(),x=Math.min(2e3,Math.max(50,e.maxJiraIssues||ab));s({stageNumber:3,status:"in_progress",note:`Reading ${_} tickets - 0/?`,progressDone:0});try{let M=(await t.jira.searchHistory(`project = ${_} ORDER BY created DESC`,{max:x,pageSize:dR,onPage:L=>s({stageNumber:3,status:"in_progress",note:`Reading ${_} tickets - ${L} read (max ${x})`,progressDone:L,progressTotal:x})})).map(L=>`${L.key} [${L.status}] ${L.summary}`);if(M.length){s({stageNumber:3,status:"in_progress",note:`Distilling ${M.length} tickets into the requirements ledger\u2026`,progressDone:M.length,progressTotal:M.length});let L=await c(hR(M.join(`
1191
+ `).slice(0,6e4),tables:l.length}}catch(n){return{ok:!1,markdown:`Could not read the dump at \`${t}\`: ${n instanceof Error?n.message:n}`,tables:0}}}function sr(t,e){let r=new RegExp("```"+e+"\\n([\\s\\S]*?)```","").exec(t);return r?{block:r[1].trim(),rest:t.replace(r[0],"").trim()}:{block:"",rest:t}}async function gR(t){let{msg:e,transport:n}=t;if(nl.has(e.rebirthId))return{ok:!1,error:"a study is already running for this rebirth - it reports as it goes"};nl.add(e.rebirthId);let r={inputTokens:0,outputTokens:0,cacheReadTokens:0,costUsd:0},s=i=>{let a=r;r={inputTokens:0,outputTokens:0,cacheReadTokens:0,costUsd:0},n.send({type:"rebirth.study.update",rebirthId:e.rebirthId,stageNumber:i.stageNumber,status:i.status,note:i.note??"",progressDone:i.progressDone??0,progressTotal:i.progressTotal??0,artifact:i.artifact??"",items:i.items??[],brief:i.brief??"",usageDelta:a})};try{let i=Re(t.cfg,t.project,"assist");if("error"in i)return{ok:!1,error:i.error};let a=(0,sl.randomUUID)(),l=!1,c=async(_,x)=>{let E=await i.adapter.run({repoPath:t.repoPath,prompt:_,timeoutSeconds:x*60,bin:i.bin,mode:"read_only",maxTurns:60,sessionId:a,resume:l,onLog:()=>{}});return l=!0,r={inputTokens:r.inputTokens+(E.inputTokens??0),outputTokens:r.outputTokens+(E.outputTokens??0),cacheReadTokens:r.cacheReadTokens+(E.cacheReadTokens??0),costUsd:r.costUsd+(E.costUsd??0)},E};if(e.refineMessage.trim()){s({stageNumber:4,status:"in_progress",note:"Updating the SPEC with your correction\u2026"}),l=!1;let _=await c(mR(e.refineMessage),30);if(!_.ok||!_.resultText.trim())return s({stageNumber:4,status:"failed",note:`refine failed: ${_.error??"no answer"}`}),{ok:!1,error:_.error??"the refine pass produced nothing"};let x=sr(_.resultText,"brief");x.block&&s({stageNumber:1,status:"done",note:"Project understanding updated with your correction",brief:x.block});let E=sr(x.rest,"understanding"),M=sr(E.rest,"requirements");E.block&&s({stageNumber:1,status:"done",note:"Understanding updated with your correction",artifact:E.block.slice(0,1e5)}),M.block&&s({stageNumber:3,status:"done",note:"Requirements updated with your correction",artifact:M.block.slice(0,1e5)});let{spec:L,tickets:C}=Su(M.rest);return s({stageNumber:4,status:"done",note:"SPEC updated - review and approve it again",artifact:L}),C.length&&s({stageNumber:6,status:"in_progress",note:"Breakdown refreshed from the updated SPEC",items:C}),{ok:!0}}let u=[];if(t.repoAvailable){s({stageNumber:1,status:"in_progress",note:"Reading the codebase - this is the long part"});let _=await c(uR,20);!_.ok||!_.resultText.trim()?(s({stageNumber:1,status:"failed",note:_.error??"the study pass produced nothing"}),u.push("the code study failed - its findings are not in this plan")):s({stageNumber:1,status:"done",note:"Codebase studied",artifact:_.resultText.trim().slice(0,1e5)})}else s({stageNumber:1,status:"skipped",note:"no code yet - map the legacy checkout on the runner (allwright repo add) and re-run the study to fold it in"}),u.push("the CODE was not available - nothing here is based on reading it");if(e.dbDumpPath.trim()){s({stageNumber:2,status:"in_progress",note:`Profiling the dump at ${e.dbDumpPath}`});let _=lb(e.dbDumpPath.trim());s({stageNumber:2,status:_.ok?"done":"failed",note:_.ok?`${_.tables} table(s) profiled`:"the dump could not be profiled",artifact:_.markdown})}else s({stageNumber:2,status:"skipped",note:"no DB dump given - add one and re-run the study to profile the data"}),u.push("the DATABASE was not profiled - data shapes in this plan are inferred, not read");let p=!1;if(e.legacyJiraKey.trim()&&t.jira){let _=e.legacyJiraKey.trim().toUpperCase(),x=Math.min(2e3,Math.max(50,e.maxJiraIssues||ab));s({stageNumber:3,status:"in_progress",note:`Reading ${_} tickets - 0/?`,progressDone:0});try{let M=(await t.jira.searchHistory(`project = ${_} ORDER BY created DESC`,{max:x,pageSize:dR,onPage:L=>s({stageNumber:3,status:"in_progress",note:`Reading ${_} tickets - ${L} read (max ${x})`,progressDone:L,progressTotal:x})})).map(L=>`${L.key} [${L.status}] ${L.summary}`);if(M.length){s({stageNumber:3,status:"in_progress",note:`Distilling ${M.length} tickets into the requirements ledger\u2026`,progressDone:M.length,progressTotal:M.length});let L=await c(hR(M.join(`
1192
1192
  `).slice(0,12e4)),12);L.ok&&L.resultText.trim()?(s({stageNumber:3,status:"done",note:`${M.length} tickets distilled`,artifact:L.resultText.trim().slice(0,1e5),progressDone:M.length,progressTotal:M.length}),p=!0):s({stageNumber:3,status:"failed",note:L.error??"the distill pass produced nothing"})}else s({stageNumber:3,status:"skipped",note:`${_} returned no readable tickets - either the board is empty, or this server's Jira account cannot see it (wrong site or not invited). Check the credential, then run the study again`}),u.push(`the JIRA HISTORY came back EMPTY for ${_} - requirements are not based on the old board`)}catch(E){s({stageNumber:3,status:"failed",note:`Jira read failed: ${E instanceof Error?E.message:E}`})}}else s({stageNumber:3,status:"skipped",note:e.legacyJiraKey?"no Jira credential on this runner":"no legacy Jira key given"}),u.push("the JIRA HISTORY was not read - requirements come only from what else was provided");s({stageNumber:4,status:"in_progress",note:"Writing the SPEC from everything studied\u2026"});let f=await c(pR(e.extraContext.trim(),u),15);if(!f.ok||!f.resultText.trim())return s({stageNumber:4,status:"failed",note:f.error??"the SPEC pass produced nothing"}),{ok:!1,error:f.error??"SPEC generation failed"};let{spec:b,tickets:v}=Su(f.resultText);s({stageNumber:4,status:"done",note:"SPEC drafted - review it, refine it in your words if something is missing, then approve",artifact:b}),s({stageNumber:1,status:"done",note:"Writing the project understanding (plain language)\u2026"});let k=await c(fR,8);return k.ok&&k.resultText.trim()&&s({stageNumber:1,status:"done",note:"Project understanding ready - read and verify it",brief:k.resultText.trim().slice(0,1e5)}),v.length&&s({stageNumber:6,status:"in_progress",note:`${v.length} tickets proposed by the SPEC - created on Jira after you approve`,items:v}),y.info(`rebirth study done for ${e.rebirthId}: code \u2713, data ${e.dbDumpPath?"\u2713":"skipped"}, requirements ${p?"\u2713":"skipped/failed"}, SPEC \u2713 (${v.length} tickets proposed)`),{ok:!0}}catch(i){return{ok:!1,error:i instanceof Error?i.message:String(i)}}finally{nl.delete(e.rebirthId)}}function Su(t){let e=/```json\s*\n([\s\S]*?)```/.exec(t),n=[];if(e)try{n=(JSON.parse(e[1]).tickets??[]).filter(s=>typeof s.title=="string"&&s.title.trim()).slice(0,60).map(s=>({title:s.title.trim().slice(0,300),detail:typeof s.detail=="string"?s.detail.trim().slice(0,4e3):""}))}catch{}return{spec:t.replace(/```json\s*\n[\s\S]*?```/g,"").trim().slice(0,15e4),tickets:n}}function rl(t){let e=sr(t,"direction").block,n=[],r=/```screen:([^\n]+)\n([\s\S]*?)```/g,s;for(;(s=r.exec(t))&&n.length<8;)n.push({name:s[1].trim().slice(0,80),html:s[2].trim().slice(0,2e5)});let i=[];try{let a=JSON.parse(sr(t,"inventory").block||"[]");Array.isArray(a)&&(i=a.filter(l=>!!l&&typeof l=="object").map(l=>({name:String(l.name??"").slice(0,80),note:String(l.note??"").slice(0,200)})).filter(l=>l.name).slice(0,40))}catch{}return{direction:e,screens:n,inventory:i}}async function kR(t){let{msg:e,transport:n}=t,r=s=>n.send({type:"rebirth.study.update",rebirthId:e.rebirthId,stageNumber:4,status:"in_progress",note:s,progressDone:0,progressTotal:0,artifact:"",items:[],brief:"",design:{direction:"",screens:[],inventory:[],specs:[],partial:!0},usageDelta:{inputTokens:0,outputTokens:0,cacheReadTokens:0,costUsd:0}});try{let s=Re(t.cfg,t.project,"assist");if("error"in s)return{ok:!1,error:s.error};let i=!!e.screenName;r(e.stackOnly?e.refineMessage?"Revising the tech stack recommendation with your reasoning\u2026":"Working out the tech stack recommendation from the SPEC and the legacy code\u2026":e.specOnly?`Reading the legacy code for "${e.screenName}" - fields, filters, conditions\u2026`:i?`Designing the "${e.screenName}" screen\u2026`:e.refineMessage?"Revising the UI design with your correction\u2026":"Designing the UI - direction first, then the screens\u2026");let a=await s.adapter.run({repoPath:t.repoPath,prompt:e.stackOnly?vR(e.spec,e.brief,e.refineMessage,e.currentStack):e.specOnly?wR(e.screenName,"",e.screenBrief,e.screenSpec):i?ib(e.screenName,e.screenBrief,e.currentDirection,e.currentScreenHtml,e.screenSpec,e.referenceScreenHtml):yR(e.spec,e.brief,e.refineMessage,e.currentDirection),timeoutSeconds:1200,bin:s.bin,mode:"read_only",maxTurns:60,sessionId:(0,sl.randomUUID)(),onLog:()=>{}});if(!a.ok||!a.resultText.trim())return r(`UI design failed: ${a.error??"no answer"}`),{ok:!1,error:a.error??"the design pass produced nothing"};if(e.stackOnly){let M=sr(a.resultText,"techstack").block.slice(0,4e4);return M?(n.send({type:"rebirth.study.update",rebirthId:e.rebirthId,stageNumber:6,status:"in_progress",note:"Tech stack recommendation ready - review it, reason with it, then confirm to unlock Build.",progressDone:0,progressTotal:0,artifact:"",items:[],brief:"",techStack:M,usageDelta:{inputTokens:a.inputTokens??0,outputTokens:a.outputTokens??0,cacheReadTokens:a.cacheReadTokens??0,costUsd:a.costUsd??0}}),{ok:!0}):(r("The tech stack recommendation did not come back - try again"),{ok:!1,error:"no techstack block in the answer"})}if(e.specOnly){let M=sr(a.resultText,"screenspec").block.slice(0,3e4);return M?(n.send({type:"rebirth.study.update",rebirthId:e.rebirthId,stageNumber:4,status:"in_progress",note:`The "${e.screenName}" details are ready.`,progressDone:0,progressTotal:0,artifact:"",items:[],brief:"",design:{direction:"",screens:[],inventory:[],specs:[{name:e.screenName,doc:M}],partial:!0},usageDelta:{inputTokens:a.inputTokens??0,outputTokens:a.outputTokens??0,cacheReadTokens:a.cacheReadTokens??0,costUsd:a.costUsd??0}}),{ok:!0}):(r(`The "${e.screenName}" details did not come back - try again`),{ok:!1,error:"no screenspec block in the answer"})}let l=M=>({inputTokens:M.inputTokens??0,outputTokens:M.outputTokens??0,cacheReadTokens:M.cacheReadTokens??0,costUsd:M.costUsd??0}),c=(M,L,C)=>n.send({type:"rebirth.study.update",rebirthId:e.rebirthId,stageNumber:4,status:"in_progress",note:L,progressDone:0,progressTotal:0,artifact:"",items:[],brief:"",design:M,usageDelta:C});if(i){let L=rl(a.resultText).screens.filter(C=>C.name===e.screenName).slice(0,1);return L.length?(c({direction:"",screens:L,inventory:[],specs:[],partial:!0},`The "${e.screenName}" screen is ready.`,l(a)),{ok:!0}):(r(`The "${e.screenName}" screen did not come back named right - try again`),{ok:!1,error:"the screen block was missing from the answer"})}let u=rl(a.resultText);if(!u.inventory.length)return r("The design plan came back without a screen inventory - run it again"),{ok:!1,error:"no inventory in the plan answer"};c({direction:u.direction,screens:[],inventory:u.inventory,specs:[],partial:!0},`Screen list ready - ${u.inventory.length} screen(s) mapped. Drawing the missing ones\u2026`,l(a));let p=u.direction||e.currentDirection,f=new Set(e.existingScreenNames),b=u.inventory.filter(M=>!f.has(M.name)),k=b.slice(0,6),_=0,x=e.referenceScreenHtml;for(let[M,L]of k.entries()){r(`Drawing "${L.name}" (${M+1}/${k.length})\u2026`);let C=await s.adapter.run({repoPath:t.repoPath,prompt:ib(L.name,L.note,p,"","",x),timeoutSeconds:900,bin:s.bin,mode:"read_only",maxTurns:40,sessionId:(0,sl.randomUUID)(),onLog:()=>{}}),w=C.ok?rl(C.resultText).screens.find(P=>P.name===L.name):void 0;w?(_+=1,x||(x=w.html),c({direction:"",screens:[w],inventory:[],specs:[],partial:!0},`"${L.name}" is ready (${_}/${k.length}). ${M+1<k.length?"Drawing the next\u2026":""}`,l(C))):c({direction:"",screens:[],inventory:[],specs:[],partial:!0},`"${L.name}" did not come back usable - skipped (ask for it again per-screen).`,l(C))}let E=b.length-k.length;return r(`UI design ready - ${_} screen(s) drawn this run`+(E>0?`, ${E} still to design (open each and click Design, or run this again)`:"")+". Open the studio to review."),{ok:!0}}catch(s){return{ok:!1,error:s instanceof Error?s.message:String(s)}}}var fs,ob,sl,nl,ab,dR,uR,hR,pR,fR,mR,yR,bR,wR,vR,ib,Tu=Q(()=>{"use strict";fs=A(require("node:fs"),1),ob=A(require("node:zlib"),1),sl=require("node:crypto");St();kt();sb();nl=new Set;ab=500,dR=50;uR=`You are studying a LEGACY codebase that will be rebuilt from scratch. Read it thoroughly - this document is what the rebuild's designers work from, and what you miss stays missed.
1193
1193
  Write a study document with these headings:
1194
1194
  What it is: the product, in plain words.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "allwright",
3
- "version": "1.100.0",
3
+ "version": "1.100.1",
4
4
  "description": "Allwright \u2014 the AI teammate that runs on YOUR machine. Polls your Jira, runs jailed agents in your local checkouts, opens PRs, executes approved deploys, and audits security/testing/architecture. Credentials and code never leave your host.",
5
5
  "bin": {
6
6
  "allwright": "bin/allwright.js",