@promptctl/cc-candybar 1.19.0 → 1.20.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/dist/index.mjs +13 -11
- package/package.json +5 -5
- package/src/check.ts +359 -0
- package/src/config/cli.ts +19 -118
- package/src/config/loader/layout.ts +8 -4
- package/src/index.ts +12 -5
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import e from"node:process";import{json as t}from"node:stream/consumers";import n,{createReadStream as r,existsSync as i,statSync as a,watch as o}from"node:fs";import s,{basename as c,dirname as l,join as u}from"node:path";import d,{homedir as f}from"node:os";import{spawn as p,spawnSync as m}from"node:child_process";import h from"node:net";import{URL as g,URLSearchParams as _,fileURLToPath as v}from"node:url";import{parseArgs as y}from"node:util";import{get as b}from"node:https";import{createHash as x}from"node:crypto";import{setTimeout as S}from"node:timers/promises";import{open as C,readFile as ee,readdir as te,stat as w}from"node:fs/promises";import{createInterface as ne}from"node:readline";import{clearInterval as re,setInterval as ie}from"node:timers";import T from"node:v8";var E=Object.create,ae=Object.defineProperty,oe=Object.getOwnPropertyDescriptor,se=Object.getOwnPropertyNames,ce=Object.getPrototypeOf,le=Object.prototype.hasOwnProperty,D=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),ue=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var i=se(t),a=0,o=i.length,s;a<o;a++)s=i[a],!le.call(e,s)&&s!==n&&ae(e,s,{get:(e=>t[e]).bind(null,s),enumerable:!(r=oe(t,s))||r.enumerable});return e},de=(e,t,n)=>(n=e==null?{}:E(ce(e)),ue(t||!e||!e.__esModule?ae(n,`default`,{value:e,enumerable:!0}):n,e));function O(e,...t){process.env.CC_CANDYBAR_DEBUG&&console.error(`[DEBUG] ${e}`,...t)}const fe={"click.pbcopy":1e3,"click.open":1e3},pe=new Map;function me(e){let t=fe[e];if(t===void 0)return{allowed:!0};let n=pe.get(e);if(n===void 0)return{allowed:!0};let r=Date.now()-n;return r>=t?{allowed:!0}:{allowed:!1,minIntervalMs:t,sinceLastMs:r}}function he(e){pe.set(e,Date.now())}function ge(e,t,n){return{ok:!1,reason:`rate-limited`,stdout:``,stderr:``,exitCode:null,signal:null,error:`rate-limited: ${e} min interval ${t}ms, last start ${n}ms ago`}}let k=null;function _e(e){k=e}async function ve(e){let t=me(e.category);if(!t.allowed)return ge(e.category,t.minIntervalMs,t.sinceLastMs);he(e.category);let n=Date.now();return k?.onStart(e.category),new Promise(t=>{let r;try{r=p(e.bin,e.args??[],{cwd:e.cwd,env:e.env,stdio:[`pipe`,`pipe`,`pipe`]})}catch(r){k?.onEnd(e.category,Date.now()-n),t({ok:!1,reason:`spawn-error`,stdout:``,stderr:``,exitCode:null,signal:null,error:r instanceof Error?r.message:String(r)});return}let i=``,a=``,o=!1,s=null,c=null,l=!1,u=r=>{o||(o=!0,s&&clearTimeout(s),c&&clearTimeout(c),k?.onEnd(e.category,Date.now()-n),t(r))};r.stdout?.on(`data`,e=>{i+=e.toString(`utf8`)}),r.stderr?.on(`data`,e=>{a+=e.toString(`utf8`)}),r.on(`error`,e=>{u({ok:!1,reason:`spawn-error`,stdout:i,stderr:a,exitCode:null,signal:null,error:e.message})}),r.on(`close`,(e,t)=>{if(l){u({ok:!1,reason:`timeout`,stdout:i,stderr:a,exitCode:e,signal:t});return}if(e===0){u({ok:!0,stdout:i,stderr:a,exitCode:e});return}u({ok:!1,reason:t?`signal`:`non-zero`,stdout:i,stderr:a,exitCode:e,signal:t})}),e.timeoutMs&&e.timeoutMs>0&&(s=setTimeout(()=>{l=!0,r.pid!==void 0&&(r.kill(`SIGTERM`),c=setTimeout(()=>{r.kill(`SIGKILL`)},250))},e.timeoutMs)),e.stdinInput!==void 0&&r.stdin?r.stdin.end(e.stdinInput):r.stdin&&r.stdin.end()})}function ye(e){let t=me(e.category);if(!t.allowed)return ge(e.category,t.minIntervalMs,t.sinceLastMs);he(e.category);let n=Date.now();k?.onStart(e.category);let r=[`pipe`,`pipe`,`pipe`];try{let t=m(e.bin,e.args??[],{cwd:e.cwd,env:e.env,input:e.stdinInput,timeout:e.timeoutMs&&e.timeoutMs>0?e.timeoutMs:void 0,stdio:r,encoding:`utf8`});k?.onEnd(e.category,Date.now()-n);let i=typeof t.stdout==`string`?t.stdout:``,a=typeof t.stderr==`string`?t.stderr:``;if(t.error)return{ok:!1,reason:t.error.code===`ETIMEDOUT`?`timeout`:`spawn-error`,stdout:i,stderr:a,exitCode:null,signal:t.signal??null,error:t.error.message};if(t.status===0)return{ok:!0,stdout:i,stderr:a,exitCode:t.status};let o=e.timeoutMs!==void 0&&e.timeoutMs>0;return{ok:!1,reason:t.signal?o&&t.signal===`SIGTERM`?`timeout`:`signal`:`non-zero`,stdout:i,stderr:a,exitCode:t.status,signal:t.signal??null}}catch(t){return k?.onEnd(e.category,Date.now()-n),{ok:!1,reason:`spawn-error`,stdout:``,stderr:``,exitCode:null,signal:null,error:t instanceof Error?t.message:String(t)}}}function be(e){let t=me(e.category);if(!t.allowed)return ge(e.category,t.minIntervalMs,t.sinceLastMs);he(e.category);let n=Date.now();k?.onStart(e.category);let r=xe(e);return k?.onEnd(e.category,Date.now()-n),r}function xe(e){let t;try{t=p(e.bin,e.args??[],{cwd:e.cwd,env:e.env,detached:!0,stdio:`ignore`})}catch(e){return{ok:!1,reason:`spawn-error`,stdout:``,stderr:``,exitCode:null,signal:null,error:e instanceof Error?e.message:String(e)}}return t.once(`error`,()=>{}),t.pid===void 0?{ok:!1,reason:`spawn-error`,stdout:``,stderr:``,exitCode:null,signal:null,error:`spawn(${e.bin}): no pid (binary not found or unexecutable)`}:(t.unref(),{ok:!0,stdout:``,stderr:``,exitCode:null})}function Se(e){let t=process.env[e];return t&&t.length>0?t:void 0}function Ce(){let e=Se(`XDG_STATE_HOME`)??s.join(d.homedir(),`.local`,`state`);return s.join(e,`cc-candybar`)}function we(){let e=Se(`XDG_CACHE_HOME`)??s.join(d.homedir(),`.cache`);return s.join(e,`cc-candybar`)}function Te(){return Ce()}function Ee(){let e=process.env.CC_CANDYBAR_SOCKET;if(e)return e;let t=d.userInfo().uid;return s.join(`/tmp`,`cc-candybar-${t}`,`socket`)}function De(e){let t=s.dirname(e);n.mkdirSync(t,{recursive:!0,mode:448});let r=n.lstatSync(t);if(r.isSymbolicLink())throw Error(`socket parent is a symlink: ${t}`);if(!r.isDirectory())throw Error(`socket parent is not a directory: ${t}`);let i=d.userInfo().uid;if(typeof i==`number`&&r.uid!==i)throw Error(`socket parent is not owned by uid ${i}: ${t} (owner uid=${r.uid})`);if(r.mode&63)throw Error(`socket parent has unsafe permissions: ${t} (mode=${(r.mode&511).toString(8)}, expected 0700)`);for(let t of[e,Oe(e)])try{if(n.lstatSync(t).isSymbolicLink())throw Error(`path is a symlink: ${t}`)}catch(e){if(e.code!==`ENOENT`)throw e}}function Oe(e){return`${e}.lease`}function ke(){return Oe(Ee())}function A(){return s.join(Ce(),`session-state.json`)}function Ae(){return s.join(Ce(),`spawn.lock`)}function je(){return s.join(Ce(),`spawn.cooldown`)}function Me(){return s.join(Ce(),`daemon.log`)}const Ne=1e4;function Pe(e){if(typeof e!=`number`||!Number.isFinite(e))return;let t=Math.floor(e);if(!(t<=0))return t>Ne?Ne:t}var Fe=class extends Error{constructor(e){super(e),this.name=`ProtocolError`}};function Ie(e){let t=Buffer.from(JSON.stringify(e),`utf8`),n=Buffer.alloc(4);return n.writeUInt32BE(t.length,0),Buffer.concat([n,t])}function Le(e,t){let n=Buffer.alloc(0);return function(r){for(n=Buffer.concat([n,r]);n.length>=4;){let r=n.readUInt32BE(0);if(r>16777216){t(new Fe(`frame too large: ${r}`));return}if(n.length<4+r)return;let i=n.subarray(4,4+r);n=n.subarray(4+r);try{e(JSON.parse(i.toString(`utf8`)))}catch(e){let n=new Fe(e instanceof Error?e.message:String(e));e instanceof Error&&(n.cause=e),t(n);return}}}}function Re(e,t,n){return new Promise((r,i)=>{let a=!1,o=t=>{if(!a){a=!0,clearTimeout(s),e.removeAllListeners();try{t()}catch{}}},s=setTimeout(()=>{o(()=>{e.destroy(),i(Error(`TIMEOUT`))})},n),c=Le(e=>o(()=>r(e)),e=>o(()=>i(e)));e.on(`data`,c),e.on(`error`,e=>o(()=>i(e))),e.on(`close`,()=>o(()=>i(Error(`socket closed before response`)))),e.write(Ie(t))})}async function ze(e,t,n){let r=null;try{r=await We(Ee(),t.connectMs);let i=await Re(r,{v:3,...e},t.budgetMs);return He(e.kind,i,n)}catch(e){return Ue(e)}finally{r&&r.destroy()}}function Be(e,t){return typeof e==`string`?e:t}function Ve(e){return typeof e==`number`&&Number.isInteger(e)&&e>=0?e:0}function He(e,t,n){let r=t;if(r.ok===!0){let r=n(t);return r===void 0?{kind:`permanent`,cause:`malformed_response`,message:`ok response without payload for "${e}" request`}:{kind:`ok`,value:r}}let i=Be(r.error,`(no error message)`);switch(r.code){case`VERSION_MISMATCH`:return{kind:`permanent`,cause:`version_mismatch`,clientV:3,daemonV:Ve(r.daemonV)};case`TIMEOUT`:return{kind:`transient`,cause:`timeout`,message:i};case`BAD_REQUEST`:return{kind:`permanent`,cause:`bad_request`,message:i};case`RENDER_FAILED`:return{kind:`permanent`,cause:`render_failed`,message:i};default:return{kind:`permanent`,cause:`malformed_response`,message:`unknown error code: ${String(r.code)}`}}}function Ue(e){if(e instanceof Fe||e instanceof SyntaxError)return{kind:`permanent`,cause:`malformed_response`,message:e instanceof Error?e.message:String(e)};let t=e instanceof Error?e.message:String(e);return t===`CONNECT_TIMEOUT`||t===`TIMEOUT`?{kind:`transient`,cause:`timeout`,message:t}:t.includes(`ECONNREFUSED`)||t.includes(`ENOENT`)||t.includes(`ENOTSOCK`)?{kind:`transient`,cause:`unreachable`,message:t}:{kind:`transient`,cause:`io_error`,message:t}}function We(e,t){return new Promise((n,r)=>{let i=h.createConnection({path:e}),a=setTimeout(()=>{i.destroy(),r(Error(`CONNECT_TIMEOUT`))},t);i.once(`connect`,()=>{clearTimeout(a),n(i)}),i.once(`error`,e=>{clearTimeout(a),r(e)})})}function Ge(e){if(e.kind===`transient`)return`daemon unavailable (${e.cause}: ${e.message})\nHint: daemon may not be running. Run \`cc-candybar\` once to spawn it.`;switch(e.cause){case`version_mismatch`:{let t=e.daemonV===0?`unknown`:`v${e.daemonV}`;return`daemon protocol mismatch (client v${e.clientV} ≠ daemon ${t})`}case`bad_request`:case`render_failed`:case`malformed_response`:return`daemon error (${e.cause}): ${e.message}`}}const Ke={connectMs:50,budgetMs:150},qe={connectMs:50,budgetMs:200};function Je(e){let t=e.output;return typeof t==`string`?t:void 0}function Ye(e,t,n,r){return ze({kind:`render`,hookData:e,args:t,cwd:n,termCols:r},Ke,Je)}function Xe(e,t){return ze({kind:`click`,verb:e,value:t},qe,Je)}function Ze(t={}){if(lt()!==null)return;let n=t.spawn??ut,r=ot();if(r.kind===`contended`){let t=$e();t!==null&&t>2e3&&(e.stderr.write(`cc-candybar: spawn-lock held ${t}ms (likely crashed holder) — spawning unlocked\n`),Qe(n));return}if(r.kind===`error`){e.stderr.write(`cc-candybar: spawn-lock unavailable (${r.reason}) — spawning unlocked\n`),Qe(n);return}try{Qe(n)}finally{ct()}}function Qe(e){nt()&&et(e)}function $e(){try{let e=n.statSync(Ae());return Date.now()-e.mtimeMs}catch{return null}}function et(t){try{t()||e.stderr.write(`cc-candybar: daemon spawn returned false (unable to resolve script path?)
|
|
3
|
-
`)}catch(t){e.stderr.write(`cc-candybar: daemon spawn failed: ${t.message}\n`)}}function tt(e){return e===null?{kind:`allow`}:e<-1e4?{kind:`allow-future-garbage`,futureMs:-e}:e<3e3?{kind:`deny`}:{kind:`allow`}}function nt(){let t=je(),n=tt(rt(t));return n.kind===`deny`?!1:(n.kind===`allow-future-garbage`&&e.stderr.write(`cc-candybar: spawn.cooldown mtime is ${n.futureMs}ms in the future — ignoring and spawning\n`),it(t),!0)}function rt(e){try{return Date.now()-n.statSync(e).mtimeMs}catch{return null}}function it(t){try{n.mkdirSync(s.dirname(t),{recursive:!0}),n.writeFileSync(t,`${e.pid} ${Date.now()}\n`,{mode:384})}catch(t){e.stderr.write(`cc-candybar: could not record spawn.cooldown: ${t.message}\n`)}}let at=null;function ot(){let t=Ae();for(let r=0;r<2;r++){try{let r=n.openSync(t,`wx`,384);try{n.writeSync(r,JSON.stringify({pid:e.pid,ts:Date.now()}))}catch{}return at={fd:r,path:t},{kind:`held`}}catch(e){let n=e.code;if(n!==`EEXIST`)return{kind:`error`,reason:`openSync(${t}): ${n??e.message}`}}if(!st(t))return{kind:`contended`};try{n.unlinkSync(t)}catch(e){if(e.code!==`ENOENT`)return{kind:`error`,reason:`unlink stale spawn.lock: ${e.message}`}}}return{kind:`contended`}}function st(e){try{let t=n.statSync(e);return Date.now()-t.mtimeMs>1e4}catch{return!0}}function ct(){if(!at)return;let{fd:e,path:t}=at;at=null;try{n.closeSync(e)}catch{}try{n.unlinkSync(t)}catch{}}function lt(){try{return n.mkdirSync(Te(),{recursive:!0}),null}catch(e){return`mkdir ${Te()}: ${e.message}`}}function ut(){let t=e.execPath,n=e.argv[1];return n?be({bin:t,args:[`--max-old-space-size=400`,n,`daemon`],category:`daemon-spawn`}).ok:!1}const dt=`cc-candybar`,ft=`dispatch`,pt=`set-state`,mt=`step-state`,ht=`copy`,gt=`open-vscode`,_t=`show-config-error`,vt=`show-config-warning`;function yt(e){return e.map(encodeURIComponent).join(`/`)}function bt(e){return e.length===0?[]:e.split(`/`).map(decodeURIComponent)}function xt(e){return`${dt}://${ft}/${e.map(e=>`e=${encodeURIComponent(`${e.verb}/${yt(e.args)}`)}`).join(`&`)}`}function St(e){return new _(e).getAll(`e`).map(Ct)}function Ct(e){let t=e.indexOf(`/`);return t===-1?{verb:e,value:``}:{verb:e.slice(0,t),value:e.slice(t+1)}}const wt=`@promptctl/cc-candybar`,Tt=`CCCandybarURLHandler`,Et=[];function Dt(e){return/^[A-Za-z0-9_./=,:-]+$/.test(e)?e:`'${e.replace(/'/g,`'\\''`)}'`}function Ot(e){return[`pnpm`,`dlx`,`${wt}@1.
|
|
3
|
+
`)}catch(t){e.stderr.write(`cc-candybar: daemon spawn failed: ${t.message}\n`)}}function tt(e){return e===null?{kind:`allow`}:e<-1e4?{kind:`allow-future-garbage`,futureMs:-e}:e<3e3?{kind:`deny`}:{kind:`allow`}}function nt(){let t=je(),n=tt(rt(t));return n.kind===`deny`?!1:(n.kind===`allow-future-garbage`&&e.stderr.write(`cc-candybar: spawn.cooldown mtime is ${n.futureMs}ms in the future — ignoring and spawning\n`),it(t),!0)}function rt(e){try{return Date.now()-n.statSync(e).mtimeMs}catch{return null}}function it(t){try{n.mkdirSync(s.dirname(t),{recursive:!0}),n.writeFileSync(t,`${e.pid} ${Date.now()}\n`,{mode:384})}catch(t){e.stderr.write(`cc-candybar: could not record spawn.cooldown: ${t.message}\n`)}}let at=null;function ot(){let t=Ae();for(let r=0;r<2;r++){try{let r=n.openSync(t,`wx`,384);try{n.writeSync(r,JSON.stringify({pid:e.pid,ts:Date.now()}))}catch{}return at={fd:r,path:t},{kind:`held`}}catch(e){let n=e.code;if(n!==`EEXIST`)return{kind:`error`,reason:`openSync(${t}): ${n??e.message}`}}if(!st(t))return{kind:`contended`};try{n.unlinkSync(t)}catch(e){if(e.code!==`ENOENT`)return{kind:`error`,reason:`unlink stale spawn.lock: ${e.message}`}}}return{kind:`contended`}}function st(e){try{let t=n.statSync(e);return Date.now()-t.mtimeMs>1e4}catch{return!0}}function ct(){if(!at)return;let{fd:e,path:t}=at;at=null;try{n.closeSync(e)}catch{}try{n.unlinkSync(t)}catch{}}function lt(){try{return n.mkdirSync(Te(),{recursive:!0}),null}catch(e){return`mkdir ${Te()}: ${e.message}`}}function ut(){let t=e.execPath,n=e.argv[1];return n?be({bin:t,args:[`--max-old-space-size=400`,n,`daemon`],category:`daemon-spawn`}).ok:!1}const dt=`cc-candybar`,ft=`dispatch`,pt=`set-state`,mt=`step-state`,ht=`copy`,gt=`open-vscode`,_t=`show-config-error`,vt=`show-config-warning`;function yt(e){return e.map(encodeURIComponent).join(`/`)}function bt(e){return e.length===0?[]:e.split(`/`).map(decodeURIComponent)}function xt(e){return`${dt}://${ft}/${e.map(e=>`e=${encodeURIComponent(`${e.verb}/${yt(e.args)}`)}`).join(`&`)}`}function St(e){return new _(e).getAll(`e`).map(Ct)}function Ct(e){let t=e.indexOf(`/`);return t===-1?{verb:e,value:``}:{verb:e.slice(0,t),value:e.slice(t+1)}}const wt=`@promptctl/cc-candybar`,Tt=`CCCandybarURLHandler`,Et=[];function Dt(e){return/^[A-Za-z0-9_./=,:-]+$/.test(e)?e:`'${e.replace(/'/g,`'\\''`)}'`}function Ot(e){return[`pnpm`,`dlx`,`${wt}@1.20.0`,...e.map(Dt)].join(` `)}function kt(){return s.join(d.homedir(),`Applications`,`${Tt}.app`)}function At(){return s.join(d.homedir(),`.claude`,`settings.json`)}function jt(){if(process.platform!==`darwin`)throw Error(`URL handler installation requires macOS (found platform: ${process.platform}).`)}function Mt(){return s.join(d.homedir(),`Library`,`Application Support`,`CCCandybar`)}function Nt(){return s.join(Mt(),`url-handler.mjs`)}function Pt(e,t,n){let r=e.replace(/"/g,`\\"`),i=t.replace(/"/g,`\\"`);return[`on open location L`,`\tdo shell script "NODE_PATH='${n.replace(/"/g,`\\"`)}' '${r}' '${i}' url-handle " & quoted form of L`,`end open location`].join(`
|
|
4
4
|
`)}function j(e){if(!e)throw Error(`install-url-handler: process.argv[1] not set`);return e.endsWith(`.mjs`)||e.endsWith(`.js`)?e:s.resolve(s.dirname(e),`..`,`dist`,`index.mjs`)}function Ft(){let e=j(process.argv[1]);if(!n.existsSync(e))throw Error(`install-url-handler: bundled dist not found at ${e}. Reinstall the package.`);n.mkdirSync(Mt(),{recursive:!0});let t=Nt();return n.copyFileSync(e,t),t}function M(){return[{key:`CFBundleIdentifier`,xml:`<string>com.cccandybar.url-handler</string>`},{key:`CFBundleURLTypes`,xml:[`<array>`,` <dict>`,` <key>CFBundleURLName</key>`,` <string>Claude Powerline Click Action</string>`,` <key>CFBundleURLSchemes</key>`,` <array>`,` <string>${dt}</string>`,` </array>`,` </dict>`,`</array>`].join(`
|
|
5
5
|
`)}]}function It(){jt();let e=Ft();process.stdout.write(`Copied dist to ${e}\n`);let t=j(process.argv[1]),r=s.join(s.dirname(t),`..`,`node_modules`);if(!n.existsSync(r))throw Error(`install-url-handler: node_modules not found at ${r}. Install deps first.`);let i=kt();n.mkdirSync(s.dirname(i),{recursive:!0}),n.existsSync(i)&&n.rmSync(i,{recursive:!0,force:!0}),process.stdout.write(`Building ${i}\n`);let a=ye({bin:`/usr/bin/osacompile`,args:[`-o`,i,`-e`,Pt(process.execPath,e,r)],category:`install.osacompile`});if(!a.ok)throw process.stderr.write(a.stderr),Error(`osacompile failed (${a.reason})`);let o=s.join(i,`Contents`,`Info.plist`);for(let{key:e}of M())ye({bin:`/usr/bin/plutil`,args:[`-remove`,e,o],category:`install.plutil`});for(let{key:e,xml:t}of M()){let n=ye({bin:`/usr/bin/plutil`,args:[`-insert`,e,`-xml`,t,o],category:`install.plutil`});if(!n.ok)throw process.stderr.write(n.stderr),Error(`plutil -insert ${e} failed (${n.reason})`)}process.stdout.write(`Registering ${dt}:// with Launch Services\n`);let c=ye({bin:`/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister`,args:[`-f`,i],category:`install.lsregister`});if(!c.ok)throw process.stderr.write(c.stderr),Error(`lsregister failed (${c.reason})`);process.stdout.write(`✓ ${Tt}.app installed and registered.\n`),process.stdout.write(` Test: open '${dt}://hello-world' && pbpaste\n`)}function Lt(e,t=dt){let n=`${t}://`;if(!e.startsWith(n))throw Error(`expected ${n} scheme, got: ${e}`);let r=e.slice(n.length),i=r.indexOf(`/`);return i===-1?{verb:ht,value:r}:{verb:decodeURIComponent(r.slice(0,i)),value:r.slice(i+1)}}async function Rt(e){e||(process.stderr.write(`url-handle: missing URL argument.
|
|
6
6
|
`),process.exit(1));let t;try{t=Lt(e)}catch(e){process.stderr.write(`url-handle: ${e instanceof Error?e.message:String(e)}\n`),process.exit(1)}let n=await Xe(t.verb,t.value);n.kind===`ok`&&process.exit(0),n.kind===`transient`&&(Ze(),process.stderr.write(`url-handle: daemon unavailable (${n.cause}: ${n.message})\n`),process.exit(1)),process.stderr.write(zt(n)+`
|
|
@@ -81,7 +81,7 @@ Use 'override' annotation for methods overridden by subclass.`)}}var ea=oa(0),ta
|
|
|
81
81
|
`)),r.push(...this.renderOptionRow(n[e],e,t));return r}renderNoMatchRow(e){let t=` ${ci(`(no matches)`,V(e))} `,n=this.disabled?new J({color:`#666666`,bgcolor:`#333333`,dim:!0}):new J({color:this.resolvePalette(`foreground`),bgcolor:this.resolvePalette(`surface`),dim:!0});return[new Y(` `,n),new Y(t,n),new Y(` `,n)]}renderOptionRow(e,t,n){let r=` ${ci(e.label,V(n))} `,i=e.idx===this.selectedIndex,a=t===this.highlightedIndex,o=this.disabled?new J({color:`#666666`,bgcolor:`#333333`,dim:!0}):a?new J({color:this.resolvePalette(`text-primary`),bgcolor:this.resolvePalette(`primary-muted`)}):i?new J({color:this.resolvePalette(`on-primary`),bgcolor:this.resolvePalette(`primary`)}):new J({color:this.resolvePalette(`foreground`),bgcolor:this.resolvePalette(`surface`)});return[new Y(` `,o),new Y(r,o),new Y(` `,o)]}maxLabelLen(){let e=0;for(let t of this.options){let n=B(t);n>e&&(e=n)}return e}measure(e){let t=this.maxLabelLen()+4;return{minimum:t,maximum:t}}resolvePalette(e){let t=this._theme.palette.get(e);return G.fromRgba(t)}}})();var Xo=function(e,t,n){for(var r=arguments.length>2,i=0;i<t.length;i++)n=r?t[i].call(e,n):t[i].call(e);return r?n:void 0},Zo=function(e,t,n,r,i,a){function o(e){if(e!==void 0&&typeof e!=`function`)throw TypeError(`Function expected`);return e}for(var s=r.kind,c=s===`getter`?`get`:s===`setter`?`set`:`value`,l=!t&&e?r.static?e:e.prototype:null,u=t||(l?Object.getOwnPropertyDescriptor(l,r.name):{}),d,f=!1,p=n.length-1;p>=0;p--){var m={};for(var h in r)m[h]=h===`access`?{}:r[h];for(var h in r.access)m.access[h]=r.access[h];m.addInitializer=function(e){if(f)throw TypeError(`Cannot add initializers after decoration has completed`);a.push(o(e||null))};var g=(0,n[p])(s===`accessor`?{get:u.get,set:u.set}:u[c],m);if(s===`accessor`){if(g===void 0)continue;if(typeof g!=`object`||!g)throw TypeError(`Object expected`);(d=o(g.get))&&(u.get=d),(d=o(g.set))&&(u.set=d),(d=o(g.init))&&i.unshift(d)}else (d=o(g))&&(s===`field`?i.unshift(d):u[c]=d)}l&&Object.defineProperty(l,r.name,u),f=!0};(()=>{var e,t,n,r,i;let a=Co,o=[],s,c=[],l=[],u,d=[],f=[],p,m=[],h=[],g,_=[],v=[],y,b=[],x=[],S,C=[],ee=[],te,w,ne,re;return class extends a{static{let ie=typeof Symbol==`function`&&Symbol.metadata?Object.create(a[Symbol.metadata]??null):void 0;s=[X.observable],u=[(e=X.observable).ref.bind(e)],p=[(t=X.observable).ref.bind(t)],g=[(n=X.observable).ref.bind(n)],y=[(r=X.observable).ref.bind(r)],S=[(i=X.observable).ref.bind(i)],te=[X.action],w=[X.action],ne=[X.action],re=[X.action],Zo(this,null,s,{kind:`accessor`,name:`value`,static:!1,private:!1,access:{has:e=>`value`in e,get:e=>e.value,set:(e,t)=>{e.value=t}},metadata:ie},c,l),Zo(this,null,u,{kind:`accessor`,name:`min`,static:!1,private:!1,access:{has:e=>`min`in e,get:e=>e.min,set:(e,t)=>{e.min=t}},metadata:ie},d,f),Zo(this,null,p,{kind:`accessor`,name:`max`,static:!1,private:!1,access:{has:e=>`max`in e,get:e=>e.max,set:(e,t)=>{e.max=t}},metadata:ie},m,h),Zo(this,null,g,{kind:`accessor`,name:`step`,static:!1,private:!1,access:{has:e=>`step`in e,get:e=>e.step,set:(e,t)=>{e.step=t}},metadata:ie},_,v),Zo(this,null,y,{kind:`accessor`,name:`width`,static:!1,private:!1,access:{has:e=>`width`in e,get:e=>e.width,set:(e,t)=>{e.width=t}},metadata:ie},b,x),Zo(this,null,S,{kind:`accessor`,name:`_theme`,static:!1,private:!1,access:{has:e=>`_theme`in e,get:e=>e._theme,set:(e,t)=>{e._theme=t}},metadata:ie},C,ee),Zo(this,null,te,{kind:`method`,name:`setTheme`,static:!1,private:!1,access:{has:e=>`setTheme`in e,get:e=>e.setTheme},metadata:ie},null,o),Zo(this,null,w,{kind:`method`,name:`handleKey`,static:!1,private:!1,access:{has:e=>`handleKey`in e,get:e=>e.handleKey},metadata:ie},null,o),Zo(this,null,ne,{kind:`method`,name:`handleMouse`,static:!1,private:!1,access:{has:e=>`handleMouse`in e,get:e=>e.handleMouse},metadata:ie},null,o),Zo(this,null,re,{kind:`method`,name:`setValue`,static:!1,private:!1,access:{has:e=>`setValue`in e,get:e=>e.setValue},metadata:ie},null,o),ie&&Object.defineProperty(this,Symbol.metadata,{enumerable:!0,configurable:!0,writable:!0,value:ie})}id=Xo(this,o);focusable=!0;#e=Xo(this,c,void 0);get value(){return this.#e}set value(e){this.#e=e}#t=(Xo(this,l),Xo(this,d,void 0));get min(){return this.#t}set min(e){this.#t=e}#n=(Xo(this,f),Xo(this,m,void 0));get max(){return this.#n}set max(e){this.#n=e}#r=(Xo(this,h),Xo(this,_,void 0));get step(){return this.#r}set step(e){this.#r=e}#i=(Xo(this,v),Xo(this,b,void 0));get width(){return this.#i}set width(e){this.#i=e}#a=(Xo(this,x),Xo(this,C,void 0));get _theme(){return this.#a}set _theme(e){this.#a=e}_dragging=(Xo(this,ee),!1);constructor(e={}){super(),this.id=e.id??`slider-${Math.random().toString(36).slice(2,8)}`,this.min=e.min??0,this.max=e.max??100,this.step=e.step??1;let t=e.width??20;if(!Number.isInteger(t)||t<1)throw RangeError(`Slider width must be a positive integer; got ${t}`);this.width=t,this.value=Qo(e.value??this.min,this.min,this.max,this.step),this.disabled=e.disabled??!1,this._theme=e.theme??va}setTheme(e){this._theme=e}handleKey(e){if(!this.disabled)switch(e.key){case`left`:this.setValue(this.value-this.step),e.stop();return;case`right`:this.setValue(this.value+this.step),e.stop();return;case`home`:this.setValue(this.min),e.stop();return;case`end`:this.setValue(this.max),e.stop();return}}handleMouse(e){if(!this.disabled){if(e.type===`mouse_down`){this._dragging=!0,this.setValueFromMouseX(e.x);return}if(e.type===`mouse_move`){this._dragging&&this.setValueFromMouseX(e.x);return}if(e.type===`mouse_up`){let t=this._dragging;this._dragging=!1,t&&(this.setValueFromMouseX(e.x),this.emitSubmit());return}}}setValue(e){let t=Qo(e,this.min,this.max,this.step);t!==this.value&&(this.value=t,this.emitChange())}setValueFromMouseX(e){let t=this.bounds;if(!t)return;let n=Math.max(1,this.width-1),r=Math.max(0,Math.min(this.width-1,e-t.x))/n,i=this.min+r*(this.max-this.min);this.setValue(i)}render(e){let t=e.asciiOnly?`-`:`─`,n=e.asciiOnly?`*`:`●`,r=this.max-this.min,i=r===0?0:(this.value-this.min)/r,a=Math.round(i*(this.width-1)),o={underline:this.focused},s=this.disabled?new J({color:`#666666`,bgcolor:`#333333`,dim:!0,...o}):new J({color:this.resolvePalette(`primary`),...o}),c=this.disabled?new J({color:`#666666`,bgcolor:`#333333`,dim:!0,...o}):new J({color:this.resolvePalette(`surface`),...o}),l=this.disabled?new J({color:`#666666`,bgcolor:`#333333`,dim:!0,bold:!0,...o}):new J({color:this.resolvePalette(`primary`),bold:!0,...o}),u=[];a>0&&u.push(new Y(t.repeat(a),s)),u.push(new Y(n,l));let d=this.width-a-1;return d>0&&u.push(new Y(t.repeat(d),c)),u}measure(e){return{minimum:this.width,maximum:this.width}}resolvePalette(e){let t=this._theme.palette.get(e);return G.fromRgba(t)}}})();function Qo(e,t,n,r){let i=Math.max(t,Math.min(n,e));if(r<=0)return i;let a=t+Math.round((i-t)/r)*r;return Math.max(t,Math.min(n,a))}new TextEncoder,new TextDecoder(`utf-8`),new TextDecoder(`ascii`);function $o(e,t){if(!(e instanceof Ka))throw TypeError(`template function expected a RichText fragment, got ${typeof e==`object`?Object.prototype.toString.call(e):typeof e}`);let n=e.copy();return n.style=e.style.add(t),n}function es(e){return{fn:(t=>$o(t,e)),argTypes:[`liftable`],returnType:`T`}}function ts(){let e={};for(let t of Object.keys(Li))e[t]=es(new J({color:G.parse(t)}));return e}const ns={fn:((e,t)=>{if(!Number.isInteger(e)||e<0||e>255)throw RangeError(`color index ${e} is out of range (0-255)`);return $o(t,new J({color:G.fromAnsi(e)}))}),argTypes:[`int`,`liftable`],returnType:`T`},rs=/^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/,is={fn:((e,t)=>{if(!rs.test(e))throw RangeError(`hex expected #RRGGBB or #RRGGBBAA, got ${JSON.stringify(e)}`);return $o(t,new J({color:G.parse(e)}))}),argTypes:[`string`,`liftable`],returnType:`T`},as={fn:((e,t,n,r)=>$o(r,new J({color:G.fromRgb(e,t,n)}))),argTypes:[`int`,`int`,`int`,`liftable`],returnType:`T`},os={fn:((e,t)=>$o(t,new J({bgcolor:G.parse(e)}))),argTypes:[`string`,`liftable`],returnType:`T`};function ss(e,t){return new J({[e]:t})}function cs(){let e={};for(let t of Ea)e[t]=es(ss(t,!0)),e[`not_${t}`]=es(ss(t,!1));for(let[t,n]of Object.entries(Oa))e[t]=es(ss(n,!0));return e}const ls={fn:((e,t)=>$o(t,J.parse(e))),argTypes:[`string`,`liftable`],returnType:`T`},us={fn:((e,t)=>$o(t,new J({link:e}))),argTypes:[`string`,`liftable`],returnType:`T`};function ds(){return{...ts(),color:ns,hex:is,rgb:as,on:os,...cs(),link:us,style:ls}}const fs=/^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/;function ps(e){if(!fs.test(e))throw RangeError(`palette background expected #RRGGBB or #RRGGBBAA, got ${JSON.stringify(e)}`);return G.parse(e).getTruecolor()}function ms(e,t,n,r){let i=e.resolve(t,n===void 0?void 0:{against:n});if(i===null){let e=n===void 0?`; for specs with alpha or auto-contrast, use paletteOver`:``;throw Error(`palette spec ${JSON.stringify(t)} did not resolve — check the spec string is valid and the variable exists${e}`)}return $o(r,new J({color:G.fromRgba(i)}))}const hs=/^[A-Za-z_][A-Za-z0-9_]*$/;function gs(e){let t={};for(let n of e.palette.vars.keys()){if(!hs.test(n))continue;let r=n;t[r]={fn:(t=>ms(e,r,void 0,t)),argTypes:[`liftable`],returnType:`T`}}return t}function _s(e){return{fn:((t,n)=>ms(e,t,void 0,n)),argTypes:[`string`,`liftable`],returnType:`T`}}function vs(e){return{fn:((t,n,r)=>ms(e,t,ps(n),r)),argTypes:[`string`,`string`,`liftable`],returnType:`T`}}function ys(e){return{fn:((t,n)=>ms(e,`auto`,ps(t),n)),argTypes:[`string`,`liftable`],returnType:`T`}}function bs(e){return{...gs(e),palette:_s(e),paletteOver:vs(e),auto:ys(e)}}function xs(){return ds()}new J({color:`red`,dim:!0});const Ss={dark:`textual-dark`,light:`textual-light`};function Cs(e){return Ss[e]??e}function ws(e,t){return e??t??`textual-dark`}function Ts(){return Object.keys(Ss)}function Es(){return[...ua(),...Ts()]}const Ds=[`powerline`,`capsule`,`plain`];function Os(e){return Ds.includes(e)}function ks(e,t){let n=e??t??`powerline`;return Os(n)?n:`powerline`}const As=[`unicode`,`ascii`],js=[`truecolor`,`256`,`ansi`,`none`];var Ms=class extends Error{file;issues;constructor(e,t){super(Fs(e,t)),this.name=`ConfigError`,this.file=e,this.issues=t}};function Q(e,t){let n=0,r;for(let i of t){if(i===``||/^\d+$/.test(i))continue;let t=Ns(e,n,i);if(t===-1)return r===void 0?void 0:Ps(e,r);n=t,r=t}return r===void 0?void 0:Ps(e,r)}function Ns(e,t,n){let r=n.replace(/[\\^$.*+?()[\]{}|]/g,`\\$&`),i=RegExp(`(?:["']${r}["']|\\b${r}\\b)\\s*:`,`g`);i.lastIndex=t;let a=i.exec(e);return a?a.index:-1}function Ps(e,t){let n=1;for(let r=0;r<t&&r<e.length;r++)e.charCodeAt(r)===10&&n++;return n}function Fs(e,t){if(t.length===0)return`${e}: invalid config (no details)`;let n=[`Invalid config in ${e} (${t.length} issue${t.length===1?``:`s`}):`];for(let e of t){let t=[];e.line!==void 0&&t.push(`line ${e.line}${e.col===void 0?``:`:${e.col}`}`),e.path&&t.push(e.path);let r=t.length>0?`[${t.join(` • `)}] `:``;n.push(` ${r}${e.message}`)}return n.join(`
|
|
82
82
|
`)}function Is(e,t,n,r){let i=n[r];return typeof i==`string`?i:(e.issues.push({path:`${t}.${r}`,message:`${t}.${r} must be a string, got ${Hs(i)}`,line:Q(e.source,[...t.split(`.`),r])}),null)}function Ls(e,t,n,r){let i=n.default;if(i!==void 0){if(!(r===`string`&&typeof i==`string`||r===`number`&&typeof i==`number`||r===`boolean`&&typeof i==`boolean`)){e.issues.push({path:`${t}.default`,message:`default must be a ${r}, got ${Hs(i)}`,line:Q(e.source,[...t.split(`.`),`default`])});return}return i}}function Rs(e,t,n,r){let i=n[r];if(i!==void 0){if(typeof i!=`string`){e.issues.push({path:`${t}.${r}`,message:`${t}.${r} must be a string, got ${Hs(i)}`,line:Q(e.source,[...t.split(`.`),r])});return}return i}}function zs(e,t,n){let r=Rs(e,t,n,`palette`);if(r!==void 0){if(!e.allowedPalettes.has(r)){e.issues.push({path:`${t}.palette`,message:`Unknown palette "${r}". Expected one of: ${[...e.allowedPalettes].sort().join(`, `)}`,line:Q(e.source,[...t.split(`.`),`palette`])});return}return r}}function Bs(e,t,n,r,i){let a=n[r];if(a!==void 0){if(typeof a!=`string`||!i.includes(a)){e.issues.push({path:`${t}.${r}`,message:`${t}.${r} must be one of: ${i.join(`, `)}; got ${Us(a)}`,line:Q(e.source,[...t.split(`.`),r])});return}return a}}function Vs(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Hs(e){return e===null?`null`:Array.isArray(e)?`array`:typeof e}function Us(e){return typeof e==`string`?JSON.stringify(e):e===void 0?`undefined`:String(e)}function Ws(e,t,n,r){return Vs(r)?(Ks(e,n,r,t.noun,new Set(Object.keys(t.fields))),Gs(e,t.fields,n,r)):(e.issues.push({path:n,message:`${n} must be an object, got ${Hs(r)}`,line:Q(e.source,n.split(`.`))}),null)}function Gs(e,t,n,r){let i=t,a={},o=!0;for(let[t,s]of Object.entries(i)){let i=s.parse(e,n,t,r);i===void 0?s.required&&(o=!1):a[t]=i}return o?a:null}function Ks(e,t,n,r,i){for(let a of Object.keys(n))i.has(a)||e.issues.push({path:`${t}.${a}`,message:`Unknown ${r} "${a}". Expected one of: ${[...i].join(`, `)}`,line:Q(e.source,[...t.split(`.`),a])})}function qs(e,t,n,r){if(!Vs(r))return e.issues.push({path:n,message:`${t.noun} must be an object, got ${Hs(r)}`,line:Q(e.source,n.split(`.`))}),null;let i=t.arms,a=Object.keys(i),o=Object.keys(r).filter(e=>e in i),s=Object.keys(r).filter(e=>!(e in i));for(let r of s)e.issues.push({path:`${n}.${r}`,message:`Unknown ${t.noun} key "${r}". Expected exactly one of: ${a.join(`, `)}`,line:Q(e.source,[...n.split(`.`),r])});if(o.length===0)return e.issues.push({path:n,message:`${t.noun} must declare exactly one of: ${a.join(`, `)}`,line:Q(e.source,n.split(`.`))}),null;if(o.length>1)return e.issues.push({path:n,message:`${t.noun} must declare exactly one of: ${a.join(`, `)} (found: ${o.join(`, `)})`,line:Q(e.source,n.split(`.`))}),null;let c=o[0];return i[c].parse(e,`${n}.${c}`,r[c])}function Js(e,t,n,r){if(!Vs(r))return e.issues.push({path:n,message:`${n} must be an object, got ${Hs(r)}`,line:Q(e.source,n.split(`.`))}),null;let i=r[t.tag];if(typeof i!=`string`)return e.issues.push({path:`${n}.${t.tag}`,message:`${n}.${t.tag} must be a string, got ${Hs(i)}`,line:Q(e.source,n.split(`.`))}),null;let a=t.arms;return i in a?a[i].parse(e,n,r):(e.issues.push({path:`${n}.${t.tag}`,message:`Unknown ${t.noun} "${i}". Expected one of: ${Object.keys(a).join(`, `)}`,line:Q(e.source,[...n.split(`.`),t.tag])}),null)}function Ys(e){return(...t)=>e()(...t)}function Xs(e,...t){return(n,r,i)=>{let a=e(n,r,i);if(a===null)return null;for(let e of t){if(e.ok(a))continue;let{field:t,message:i}=e.issue(a),o=t===``?r:`${r}.${t}`;return n.issues.push({path:o,message:i,line:Q(n.source,o.split(`.`))}),null}return a}}function Zs(){return{required:!1,json:{type:`string`},parse:(e,t,n,r)=>Rs(e,t,r,n)}}function Qs(){return{required:!1,json:{type:`boolean`},parse:(e,t,n,r)=>{let i=r[n];if(i!==void 0){if(typeof i!=`boolean`){e.issues.push({path:`${t}.${n}`,message:`${n} must be a boolean, got ${Hs(i)}`,line:Q(e.source,[n])});return}return i}}}}function $s(e){let{min:t,max:n}=e;return{required:!1,json:{type:`integer`,minimum:t,maximum:n},parse:(e,r,i,a)=>{let o=a[i];if(o!==void 0){if(typeof o!=`number`||!Number.isInteger(o)||o<t||o>n){e.issues.push({path:`${r}.${i}`,message:`${i} must be an integer between ${t} and ${n}, got ${Us(o)}`,line:Q(e.source,[i])});return}return o}}}}function ec(){return{required:!1,json:{type:`string`},parse:(e,t,n,r)=>zs(e,t,r)}}function tc(){return{required:!0,json:{type:`string`},parse:(e,t,n,r)=>Is(e,t,r,n)??void 0}}function nc(e){return{required:!1,json:{enum:[...e]},parse:(t,n,r,i)=>Bs(t,n,i,r,e)}}function rc(e,t={closed:!0}){let n=e,r={},i=[];for(let[e,t]of Object.entries(n))r[e]=t.json,t.required&&i.push(e);return{type:`object`,properties:r,...i.length>0&&{required:i},...t.closed&&{additionalProperties:!1}}}function ic(e,t,n){let r=e,i={[t]:{const:n},...r.properties},a=[t,...r.required??[]];return{...r,properties:i,required:a}}function ac(e){let t=e.arms;return{anyOf:Object.values(t).map(e=>e.json)}}function oc(e){let t=e.arms;return{anyOf:Object.entries(t).map(([e,t])=>({type:`object`,properties:{[e]:t.json},required:[e],additionalProperties:!1}))}}function sc(e,t=br){return{globals:{...t.globals,...e.globals??{}},variables:{...t.variables,...e.variables??{}},segments:{...t.segments,...e.segments??{}},root:e.root===void 0?t.root:e.root,actions:{...t.actions,...e.actions??{}},helpers:{...t.helpers,...e.helpers??{}}}}const cc={required:!1,json:{enum:[...js]},parse:(e,t,n,r)=>{if(r[n]===`auto`){e.issues.push({path:`${t}.${n}`,message:`${t}.${n}: "auto" is not supported — the render daemon runs detached, so terminal detection would read the daemon's environment, not your terminal's. Pick an explicit depth: ${js.join(`, `)}`,line:Q(e.source,[...t.split(`.`),n])});return}return Bs(e,t,r,n,js)}},lc={noun:`globals key`,fields:{default_bg:Zs(),default_fg:Zs(),default_empty_value:Zs(),default_separator:Zs(),default_truncate_marker:Zs(),palette:ec(),style:nc(Ds),autoWrap:Qs(),padding:$s({min:0,max:16}),charset:nc(As),colorCompatibility:cc}};function uc(e,t){return t===void 0?{}:Ws(e,lc,`globals`,t)??{}}function dc(e,t,n,r){return n.cache===void 0?(or.includes(r)&&e.issues.push({path:`${t}.cache`,message:`${r} variables must declare a cache policy (one of: ${rr.join(`, `)})`,line:Q(e.source,t.split(`.`))}),null):_c(e,`${t}.cache`,n.cache)}function fc(e,t,n){if(n.cache!==void 0)return _c(e,`${t}.cache`,n.cache)??void 0}function pc(e){return{required:!0,json:bc(),parse:(t,n,r,i)=>dc(t,n,i,e)??void 0}}function mc(){return{required:!1,json:bc(),parse:(e,t,n,r)=>fc(e,t,r)}}function hc(e,t,n){return e.issues.push({path:t,message:n,line:Q(e.source,t.split(`.`))}),null}const gc={noun:`cache`,arms:{ttl:{json:{type:`string`},parse:(e,t,n)=>typeof n==`string`&&Sc(n)?{ttl:n}:hc(e,t,`cache.ttl must be a duration string like "5s", "100ms", "2m", "1h"; got ${Us(n)}`)},watch_file:{json:{type:`string`},parse:(e,t,n)=>typeof n==`string`&&n!==``?{watch_file:n}:hc(e,t,`cache.watch_file must be a non-empty path string, got ${Us(n)}`)},depends_on:{json:{type:`array`,items:{type:`string`}},parse:(e,t,n)=>Array.isArray(n)&&n.every(e=>typeof e==`string`)?{depends_on:n}:hc(e,t,`cache.depends_on must be an array of variable-name strings, got ${Us(n)}`)},key:{json:{type:`string`},parse:(e,t,n)=>typeof n==`string`&&n!==``?{key:n}:hc(e,t,`cache.key must be a non-empty template string, got ${Us(n)}`)},never:{json:{const:!0},parse:(e,t,n)=>n===!0?{never:!0}:hc(e,t,`cache.never must be the literal boolean true, got ${Us(n)}`)}}};function _c(e,t,n){return qs(e,gc,t,n)}const vc={noun:`time-variable cache`,arms:{ttl:gc.arms.ttl}};function yc(){return{required:!1,json:oc(vc),parse:(e,t,n,r)=>r.cache===void 0?void 0:qs(e,vc,`${t}.cache`,r.cache)??void 0}}function bc(){return oc(gc)}const xc=/^(\d+(?:\.\d+)?)(ms|s|m|h)$/;function Sc(e){return xc.test(e)}function Cc(e,t,n){if(n===void 0)return{};if(!Vs(n))return e.issues.push({path:t,message:`${t} must be an object, got ${Hs(n)}`,line:Q(e.source,t.split(`.`))}),{};let r={};for(let[i,a]of Object.entries(n)){let n=Js(e,Lc,`${t}.${i}`,a);n!==null&&(r[i]=n)}return r}function wc(){return{required:!0,json:{type:[`string`,`number`,`boolean`]},parse:(e,t,n,r)=>{let i=r.value;if(typeof i!=`string`&&typeof i!=`number`&&typeof i!=`boolean`){e.issues.push({path:`${t}.value`,message:`literal value must be string|number|boolean, got ${Hs(i)}`,line:Q(e.source,t.split(`.`))});return}return i}}}function Tc(){return{required:!0,json:{enum:[...ar]},parse:(e,t,n,r)=>{let i=r.field;if(typeof i!=`string`||!ar.includes(i)){e.issues.push({path:`${t}.field`,message:`git field must be one of: ${ar.join(`, `)}, got ${JSON.stringify(i)}`,line:Q(e.source,[...t.split(`.`),`field`])});return}return i}}}function Ec(){return{required:!1,json:{type:[`string`,`number`,`boolean`]},parse:(e,t,n,r)=>{let i=r.type;return Ls(e,t,r,i===`number`||i===`boolean`||i===`string`?i:`string`)}}}const Dc={value:wc(),default:Zs()},Oc={path:tc(),type:nc([`string`,`number`,`boolean`]),default:Ec()},kc={name:tc(),default:Zs()},Ac={path:tc(),readMode:nc([`whole`,`first-line`]),regex:Zs(),cache:pc(`file`),default:Zs()},jc={command:tc(),regex:Zs(),cache:pc(`shell`),default:Zs()},Mc={template:tc(),cache:mc(),default:Zs()},Nc={layout:tc(),cache:yc(),default:Zs()},Pc={field:Tc(),cache:pc(`git`),default:Zs()},Fc={key:tc(),default:Zs()};function Ic(e,t){return{json:ic(rc(t),`kind`,e),parse:(n,r,i)=>{let a=Gs(n,t,r,i);return a===null?null:{kind:e,...a}}}}const Lc={tag:`kind`,noun:`source kind`,arms:{literal:Ic(`literal`,Dc),input:Ic(`input`,Oc),env:Ic(`env`,kc),file:Ic(`file`,Ac),shell:Ic(`shell`,jc),template:Ic(`template`,Mc),time:Ic(`time`,Nc),git:Ic(`git`,Pc),state:Ic(`state`,Fc)}};function Rc(){return ac(Lc)}function zc(){return{type:`object`,additionalProperties:Rc()}}function Bc(e,t){if(t===void 0)return{};if(!Vs(t))return e.issues.push({path:`segments`,message:`segments must be an object, got ${Hs(t)}`,line:Q(e.source,[`segments`])}),{};let n={};for(let[r,i]of Object.entries(t)){let t=Ws(e,Uc,`segments.${r}`,i);t!==null&&(n[r]=t)}return n}function Vc(){return{required:!1,json:{anyOf:[{const:`auto`},{type:`integer`,minimum:1}]},parse:(e,t,n,r)=>{let i=r[n];if(i!==void 0){if(i===`auto`)return`auto`;if(typeof i==`number`&&Number.isInteger(i)&&i>0)return i;e.issues.push({path:`${t}.${n}`,message:`width must be "auto" or a positive integer, got ${Us(i)}`,line:Q(e.source,[...t.split(`.`),n])})}}}}function Hc(){return{required:!1,json:zc(),parse:(e,t,n,r)=>{let i=r[n];if(i!==void 0)return Cc(e,`${t}.${n}`,i)}}}const Uc={noun:`segment key`,fields:{template:tc(),width:Vc(),justify:nc(sr),truncate:nc(cr),bg:Zs(),fg:Zs(),when:Zs(),palette:ec(),vars:Hc()}},Wc=`closed`;function Gc(e,t){return{kind:`state`,key:e,default:t}}function Kc(e,t){return{set:e,cycle:[Wc,t]}}function qc(e,t,n,r){for(let i of[`variables`,`actions`,`segments`])for(let a of Object.keys(t[i]??{}))a.startsWith(n)&&e.issues.push({path:`${i}.${a}`,message:`"${a}" is in the reserved "${n}" namespace (synthesized by ${r}) — rename it`,line:Q(e.source,[`root`])})}const Jc={kind:`container`,direction:`vertical`,children:[]};function Yc(e){return{required:!0,json:{const:e},parse:()=>e}}function Xc(){return{required:!0,json:{type:`string`},parse:(e,t,n,r)=>{let i=r[n];return typeof i==`string`&&i.length>0?i:(e.issues.push({path:`${t}.${n}`,message:`a segment node must have a non-empty "name" (a segment name), got ${Us(i)}`,line:Q(e.source,[`root`])}),``)}}}function Zc(){return{required:!0,json:{enum:[...tr]},parse:(e,t,n,r)=>{let i=r[n];return typeof i==`string`&&tr.includes(i)?i:(e.issues.push({path:`${t}.${n}`,message:`a container "direction" must be one of: ${tr.join(`, `)} (got ${JSON.stringify(i)})`,line:Q(e.source,[`root`])}),`vertical`)}}}function Qc(e){return{required:!0,json:{type:`array`,items:{$ref:`#/definitions/LayoutNode`}},parse:(t,n,r,i)=>{let a=i[r];return Array.isArray(a)?a.map((i,a)=>e(t,`${n}.${r}[${a}]`,i)):(t.issues.push({path:`${n}.${r}`,message:`a container must have a "children" array of layout nodes, got ${Hs(a)}`,line:Q(t.source,[`root`])}),[])}}}const $c={noun:`layout-node key`,fields:{kind:Yc(`segment`),name:Xc(),when:Zs()}},el={noun:`layout-node key`,fields:{kind:Yc(`container`),direction:Zc(),children:Qc(Ys(()=>al)),when:Zs()}};function tl(){return{required:!0,json:{type:`string`},parse:(e,t,n,r)=>{let i=r[n];return typeof i==`string`&&i.length>0?i:(e.issues.push({path:`${t}.${n}`,message:`a "seg" node must have a non-empty segment name, got ${Us(i)}`,line:Q(e.source,[`root`])}),``)}}}const nl={noun:`layout-node key`,fields:{seg:tl(),when:Zs()}},rl={noun:`layout-node key`,fields:{h:Qc(Ys(()=>al)),when:Zs()}},il={noun:`layout-node key`,fields:{v:Qc(Ys(()=>al)),when:Zs()}},al=(e,t,n)=>{if(typeof n==`string`)return n.length===0?(e.issues.push({path:t,message:`a bare-string layout node must be a non-empty segment name`,line:Q(e.source,[`root`])}),Jc):{kind:`segment`,name:n};if(!Vs(n))return e.issues.push({path:t,message:`a layout node must be a string (segment name) or an object with "kind" / "seg" / "h" / "v", got ${Hs(n)}`,line:Q(e.source,[`root`])}),Jc;if(n.kind===`container`)return Ws(e,el,t,n)??Jc;if(n.kind===`segment`)return Ws(e,$c,t,n)??Jc;if(n.kind===`cells`)return e.issues.push({path:t,message:`kind: "cells" is no longer supported — use the h-arm spelling instead:
|
|
83
83
|
Old: { kind: "cells", segments: ["seg1", "seg2"] }
|
|
84
|
-
New: { h: ["seg1", "seg2"] }`,line:Q(e.source,[`root`])}),Jc;if(n.kind===`group`){let r=Ws(e,dl,t,n);return r===null?Jc:(e.groups.push({name:r.name,label:r.label,...r.open!==void 0&&{open:r.open},...r.direction!==void 0&&{direction:r.direction},...r.key!==void 0&&{key:r.key},...r.bg!==void 0&&{bg:r.bg},...r.fg!==void 0&&{fg:r.fg},...r.when!==void 0&&{when:r.when},path:t}),pl(r))}let r=`h`in n,i=`v`in n,a=`seg`in n;if(+!!r+ +!!i+ +!!a>1){let r=[`seg`,`h`,`v`].filter(e=>e in n);return e.issues.push({path:t,message:`a layout node may have exactly one of "seg", "h", or "v" — got ${r.map(e=>`"${e}"`).join(` and `)} together`,line:Q(e.source,[`root`])}),Jc}if(a){let r=Ws(e,nl,t,n);return r===null?Jc:{kind:`segment`,name:r.seg,...r.when!==void 0&&{when:r.when}}}if(r){let r=Ws(e,rl,t,n);return r===null?Jc:{kind:`container`,direction:`horizontal`,children:r.h,...r.when!==void 0&&{when:r.when}}}if(i){let r=Ws(e,il,t,n);return r===null?Jc:{kind:`container`,direction:`vertical`,children:r.v,...r.when!==void 0&&{when:r.when}}}return e.issues.push({path:`${t}.kind`,message:`a layout node "kind" must be "container", "segment", or "group", or use the terse A-grammar: a bare string, or an object with "seg", "h", or "v" (got ${JSON.stringify(n.kind)})`,line:Q(e.source,[`root`])}),Jc},ol=`groups.`,sl=/^[A-Za-z_][A-Za-z0-9_]*$/;function cl(){return{required:!0,json:{type:`string`,pattern:sl.source},parse:(e,t,n,r)=>{let i=r[n];if(typeof i!=`string`||!sl.test(i)||i===`closed`){e.issues.push({path:`${t}.${n}`,message:`a group "name" must be an identifier (letters, digits, _; not starting with a digit) and not the reserved "${Wc}", got ${Us(i)}`,line:Q(e.source,[`root`])});return}return i}}}function ll(){return{required:!1,json:{type:`string`,minLength:1},parse:(e,t,n,r)=>{let i=r[n];if(i!==void 0){if(typeof i!=`string`||i===``||i.includes(`/`)){e.issues.push({path:`${t}.${n}`,message:`a group "key" must be a non-empty, slash-free SessionState key, got ${Us(i)}`,line:Q(e.source,[`root`])});return}return i}}}}function ul(){return{required:!0,json:{type:`string`,pattern:`^[^\\n\\r]*$`},parse:(e,t,n,r)=>{let i=Is(e,t,r,n);if(i!==null){if(/[\n\r]/.test(i)){e.issues.push({path:`${t}.${n}`,message:`${t}.${n}: group label must not contain newlines`,line:Q(e.source,[...t.split(`.`),n])});return}return i}}}}const dl={noun:`layout-node key`,fields:{kind:Yc(`group`),name:cl(),label:ul(),open:Qs(),direction:nc(tr),key:ll(),bg:Zs(),fg:Zs(),when:Zs(),children:Qc(Ys(()=>al))}};function fl(e){return e.key??`groups.`+e.name}function pl(e){let t=ol+e.name;return{kind:`container`,direction:`vertical`,children:[{kind:`segment`,name:t},{kind:`container`,direction:e.direction??`vertical`,children:e.children,when:`{{ eq .${t} "${e.name}" }}`}],...e.when!==void 0&&{when:e.when}}}function ml(e){return e.replace(/\\/g,`\\\\`).replace(/"/g,`\\"`)}function hl(e,t,n){e.issues.push({path:t,message:n,line:Q(e.source,[`root`])})}function gl(e,t){let n=e.groups;if(n.length===0)return;
|
|
84
|
+
New: { h: ["seg1", "seg2"] }`,line:Q(e.source,[`root`])}),Jc;if(n.kind===`group`){let r=Ws(e,dl,t,n);return r===null?Jc:(e.groups.push({name:r.name,label:r.label,...r.open!==void 0&&{open:r.open},...r.direction!==void 0&&{direction:r.direction},...r.key!==void 0&&{key:r.key},...r.bg!==void 0&&{bg:r.bg},...r.fg!==void 0&&{fg:r.fg},...r.when!==void 0&&{when:r.when},path:t}),pl(r))}let r=`h`in n,i=`v`in n,a=`seg`in n;if(+!!r+ +!!i+ +!!a>1){let r=[`seg`,`h`,`v`].filter(e=>e in n);return e.issues.push({path:t,message:`a layout node may have exactly one of "seg", "h", or "v" — got ${r.map(e=>`"${e}"`).join(` and `)} together`,line:Q(e.source,[`root`])}),Jc}if(a){let r=Ws(e,nl,t,n);return r===null?Jc:{kind:`segment`,name:r.seg,...r.when!==void 0&&{when:r.when}}}if(r){let r=Ws(e,rl,t,n);return r===null?Jc:{kind:`container`,direction:`horizontal`,children:r.h,...r.when!==void 0&&{when:r.when}}}if(i){let r=Ws(e,il,t,n);return r===null?Jc:{kind:`container`,direction:`vertical`,children:r.v,...r.when!==void 0&&{when:r.when}}}return e.issues.push({path:`${t}.kind`,message:`a layout node "kind" must be "container", "segment", or "group", or use the terse A-grammar: a bare string, or an object with "seg", "h", or "v" (got ${JSON.stringify(n.kind)})`,line:Q(e.source,[`root`])}),Jc},ol=`groups.`,sl=/^[A-Za-z_][A-Za-z0-9_]*$/;function cl(){return{required:!0,json:{type:`string`,pattern:sl.source},parse:(e,t,n,r)=>{let i=r[n];if(typeof i!=`string`||!sl.test(i)||i===`closed`){e.issues.push({path:`${t}.${n}`,message:`a group "name" must be an identifier (letters, digits, _; not starting with a digit) and not the reserved "${Wc}", got ${Us(i)}`,line:Q(e.source,[`root`])});return}return i}}}function ll(){return{required:!1,json:{type:`string`,minLength:1},parse:(e,t,n,r)=>{let i=r[n];if(i!==void 0){if(typeof i!=`string`||i===``||i.includes(`/`)){e.issues.push({path:`${t}.${n}`,message:`a group "key" must be a non-empty, slash-free SessionState key, got ${Us(i)}`,line:Q(e.source,[`root`])});return}return i}}}}function ul(){return{required:!0,json:{type:`string`,pattern:`^[^\\n\\r]*$`},parse:(e,t,n,r)=>{let i=Is(e,t,r,n);if(i!==null){if(/[\n\r]/.test(i)){e.issues.push({path:`${t}.${n}`,message:`${t}.${n}: group label must not contain newlines`,line:Q(e.source,[...t.split(`.`),n])});return}return i}}}}const dl={noun:`layout-node key`,fields:{kind:Yc(`group`),name:cl(),label:ul(),open:Qs(),direction:nc(tr),key:ll(),bg:Zs(),fg:Zs(),when:Zs(),children:Qc(Ys(()=>al))}};function fl(e){return e.key??`groups.`+e.name}function pl(e){let t=ol+e.name;return{kind:`container`,direction:`vertical`,children:[{kind:`segment`,name:t},{kind:`container`,direction:e.direction??`vertical`,children:e.children,when:`{{ eq .${t} "${e.name}" }}`}],...e.when!==void 0&&{when:e.when}}}function ml(e){return e.replace(/\\/g,`\\\\`).replace(/"/g,`\\"`)}function hl(e,t,n){e.issues.push({path:t,message:n,line:Q(e.source,[`root`])})}function gl(e,t){qc(e,t,ol,`group nodes`);let n=e.groups;if(n.length===0)return;let r=new Set;for(let t of n)r.has(t.name)&&hl(e,t.path,`duplicate group name "${t.name}" — group names must be unique (they name the synthesized state var, action, and toggle segment)`),r.add(t.name);for(let t of n)for(let r of n)t!==r&&t.path.startsWith(`${r.path}.`)&&fl(t)===fl(r)&&hl(e,t.path,`group "${t.name}" shares key "${fl(t)}" with its ancestor group "${r.name}" — a shared key holds ONE open group, so an ancestor and a descendant cannot share one. Sibling accordions share a key; nested groups use distinct keys.`);let i=new Map;for(let t of n){let n=fl(t);if(i.has(n)||i.set(n,Wc),t.open===!0){let r=i.get(n);r!==`closed`&&hl(e,t.path,`groups "${r}" and "${t.name}" share key "${n}" and both declare open: true — a shared key holds one open group; pick one`),i.set(n,t.name)}}let a={},o={},s={};for(let e of n){let t=ol+e.name,r=fl(e),c=ml(e.label),l=n.filter(t=>t!==e&&e.path.startsWith(t.path+`.`)).length,u=` `.repeat(l);a[t]=Gc(r,i.get(r)),o[t]=Kc(r,e.name),s[t]={template:`{{ action "${t}" "${u}${c} ▸" "${u}${c} ▾" }}`,...e.bg!==void 0&&{bg:e.bg},...e.fg!==void 0&&{fg:e.fg}}}t.variables={...t.variables??{},...a},t.actions={...t.actions??{},...o},t.segments={...t.segments??{},...s}}var _l=class extends Error{name=`TemplateError`;kind=`EvalError`;pos;source;constructor(e,t,n={}){super(e),this.pos=t,this.source=n.source}get sourceSnippet(){return this.source?wl(this.source,this.pos):``}toString(){let e=`${this.name}: ${this.message}\n at line ${this.pos.line}, column ${this.pos.column}`,t=this.sourceSnippet;return t?`${e}\n\n${t}`:e}},vl=class extends _l{name=`ParseError`;kind=`ParseError`;expected;found;constructor(e,t,n={}){super(e,t,n),this.expected=n.expected,this.found=n.found}},yl=class extends _l{name=`EvalError`;kind=`EvalError`},bl=class extends yl{name=`FuncNotFoundError`;kind=`FuncNotFoundError`;funcName;suggestions;constructor(e,t,n={}){let r=Tl(e,n.available??[]),i=r.length===0?``:` (did you mean: ${r.join(`, `)}?)`;super(`function ${JSON.stringify(e)} is not registered${i}`,t,n),this.funcName=e,this.suggestions=r}},xl=class extends yl{name=`TypeMismatchError`;kind=`TypeMismatchError`;funcName;argIndex;expected;receivedSummary;constructor(e,t,n,r,i,a={}){super(`function ${JSON.stringify(e)} arg ${t}: expected ${n}, but a ${r} flowed in. Pass it through \`unstyled\` (or your engine's flatten function) to convert.`,i,a),this.funcName=e,this.argIndex=t,this.expected=n,this.receivedSummary=r}},Sl=class extends yl{name=`MissingFieldError`;kind=`MissingFieldError`;path;constructor(e,t,n={}){super(`field "${e.join(`.`)}" not found on receiver`,t,n),this.path=e}},Cl=class extends yl{name=`FailError`;kind=`FailError`;constructor(e,t={line:0,column:0,offset:0},n={}){super(e,t,n)}};function wl(e,t){let n=e.split(`
|
|
85
85
|
`),r=t.line-1,i=[];if(r-1>=0&&r-1<n.length&&i.push({num:r,text:n[r-1]??``}),r>=0&&r<n.length&&i.push({num:r+1,text:n[r]??``}),r+1<n.length&&i.push({num:r+2,text:n[r+1]??``}),i.length===0)return``;let a=String(i[i.length-1]?.num??1).length,o=[];for(let e of i)if(o.push(` ${String(e.num).padStart(a,` `)} | ${e.text}`),e.num===t.line){let e=` ${` `.repeat(a)} | `,n=` `.repeat(Math.max(0,t.column-1));o.push(`${e}${n}^`)}return o.join(`
|
|
86
86
|
`)}function Tl(e,t){if(t.length===0)return[];let n=t.map(t=>({name:t,dist:El(e,t)})).sort((e,t)=>e.dist-t.dist),r=Math.max(2,Math.floor(e.length/2));return n.filter(e=>e.dist<=r).slice(0,3).map(e=>e.name)}function El(e,t){if(e===t)return 0;let n=e.length,r=t.length;if(n===0)return r;if(r===0)return n;let i=Array(r+1),a=Array(r+1);for(let e=0;e<=r;e++)i[e]=e;for(let o=1;o<=n;o++){a[0]=o;for(let n=1;n<=r;n++){let r=e[o-1]===t[n-1]?0:1;a[n]=Math.min((a[n-1]??0)+1,(i[n]??0)+1,(i[n-1]??0)+r)}for(let e=0;e<=r;e++)i[e]=a[e]??0}return i[r]??0}function Dl(e){throw Error(`unhandled node kind: ${JSON.stringify(e)}`)}const Ol=new Map([[`if`,`If`],[`else`,`Else`],[`end`,`End`],[`range`,`Range`],[`with`,`With`],[`break`,`Break`],[`continue`,`Continue`],[`define`,`Define`],[`template`,`Template`],[`block`,`Block`],[`true`,`Bool`],[`false`,`Bool`],[`nil`,`Nil`]]),kl={left:`{{`,right:`}}`};var Al=class{src;leftDelim;rightDelim;offset=0;line=1;column=1;mode=`text`;pending;constructor(e,t=kl){this.src=e,this.leftDelim=t.left,this.rightDelim=t.right}pos(){return{line:this.line,column:this.column,offset:this.offset}}err(e,t,n={}){return new vl(e,t,{...n,source:this.src})}next(){if(this.pending){let e=this.pending;return this.pending=void 0,e}return this.read()}peek(){return this.pending||=this.read(),this.pending}read(){switch(this.mode){case`text`:return this.readText();case`action`:return this.readAction();case`done`:return this.makeToken(`EOF`,``,this.pos())}}readText(){if(this.offset>=this.src.length)return this.mode=`done`,this.makeToken(`EOF`,``,this.pos());let e=this.pos(),t=this.offset;for(;t<this.src.length&&!this.src.startsWith(this.leftDelim,t);)t+=1;let n=t<this.src.length&&this.src[t+this.leftDelim.length]===`-`,r=t;if(n)for(;r>this.offset&&jl(this.src[r-1]??``);)--r;if(r>this.offset){let t=this.src.slice(this.offset,r);return this.advanceTo(r),this.makeToken(`Text`,t,e)}return this.advanceTo(t),this.emitLeftDelim(n)}emitLeftDelim(e){let t=this.pos();if(this.advanceBy(this.leftDelim.length),e)for(this.advanceBy(1);jl(this.src[this.offset]??``);)this.advanceBy(1);return this.mode=`action`,this.src.startsWith(`/*`,this.offset)?this.readComment(t,e):this.makeToken(`LeftDelim`,e?`${this.leftDelim}-`:this.leftDelim,t,e?{trimLeft:!0}:{})}readComment(e,t){this.advanceBy(2);let n=this.offset,r=this.src.indexOf(`*/`,this.offset);if(r===-1)throw this.err(`unclosed comment`,e,{expected:`*/${this.rightDelim}`,found:`end of input`});this.advanceTo(r);let i=this.src.slice(n,r).trim();for(this.advanceBy(2);jl(this.src[this.offset]??``);)this.advanceBy(1);let a=!1;if(this.src[this.offset]===`-`&&(a=!0,this.advanceBy(1)),!this.src.startsWith(this.rightDelim,this.offset))throw this.err(`expected \`${this.rightDelim}\` to close comment`,this.pos(),{expected:this.rightDelim,found:this.src[this.offset]??`end of input`});if(this.advanceBy(this.rightDelim.length),a)for(;jl(this.src[this.offset]??``);)this.advanceBy(1);return this.mode=`text`,this.makeToken(`Comment`,i,e,{...t?{trimLeft:!0}:{},...a?{trimRight:!0}:{}})}readAction(){if(this.skipActionSpaces(),this.src.startsWith(`-${this.rightDelim}`,this.offset)){let e=this.pos();for(this.advanceBy(1+this.rightDelim.length),this.mode=`text`;jl(this.src[this.offset]??``);)this.advanceBy(1);return this.makeToken(`RightDelim`,`-${this.rightDelim}`,e,{trimRight:!0})}if(this.src.startsWith(this.rightDelim,this.offset)){let e=this.pos();return this.advanceBy(this.rightDelim.length),this.mode=`text`,this.makeToken(`RightDelim`,this.rightDelim,e)}if(this.offset>=this.src.length)throw this.err(`unclosed action`,this.pos(),{expected:this.rightDelim,found:`end of input`});let e=this.src[this.offset],t=this.pos();switch(e){case`(`:return this.advanceBy(1),this.makeToken(`LeftParen`,`(`,t);case`)`:return this.advanceBy(1),this.makeToken(`RightParen`,`)`,t);case`|`:return this.advanceBy(1),this.makeToken(`Pipe`,`|`,t);case`,`:return this.advanceBy(1),this.makeToken(`Comma`,`,`,t);case`:`:if(this.src[this.offset+1]===`=`)return this.advanceBy(2),this.makeToken(`Declare`,`:=`,t);throw this.err("expected `:=`",t,{expected:`:=`,found:`:`});case`=`:return this.advanceBy(1),this.makeToken(`Assign`,`=`,t);case`"`:return this.readQuotedString(t);case"`":return this.readRawString(t);case`'`:return this.readChar(t)}if(e===`$`)return this.readVariable(t);if(e!==void 0&&(Nl(e)||Ml(e)&&Nl(this.src[this.offset+1]??``)||e===`.`&&Nl(this.src[this.offset+1]??``)))return this.readNumber(t);if(e===`.`)return this.readDotOrField(t);if(e!==void 0&&Rl(e))return this.readIdentifier(t);throw this.err(`unexpected character ${JSON.stringify(e)}`,t,{found:e??`end of input`})}readQuotedString(e){let t=this.offset;for(this.advanceBy(1);this.offset<this.src.length;){let n=this.src[this.offset];if(n===`\\`){this.advanceBy(1),this.offset<this.src.length&&this.advanceBy(1);continue}if(n===`
|
|
87
87
|
`)throw this.err(`newline in interpreted string`,e,{found:`\\n`});if(n===`"`)return this.advanceBy(1),this.makeToken(`String`,this.src.slice(t,this.offset),e);this.advanceBy(1)}throw this.err(`unterminated string literal`,e,{expected:`"`,found:`end of input`})}readRawString(e){let t=this.offset;for(this.advanceBy(1);this.offset<this.src.length;){if(this.src[this.offset]==="`")return this.advanceBy(1),this.makeToken(`String`,this.src.slice(t,this.offset),e);this.advanceBy(1)}throw this.err(`unterminated raw string literal`,e,{expected:"`",found:`end of input`})}readChar(e){let t=this.offset;for(this.advanceBy(1);this.offset<this.src.length;){let n=this.src[this.offset];if(n===`\\`){this.advanceBy(1),this.offset<this.src.length&&this.advanceBy(1);continue}if(n===`
|
|
@@ -121,11 +121,10 @@ Use 'override' annotation for methods overridden by subclass.`)}}var ea=oa(0),ta
|
|
|
121
121
|
`)+`
|
|
122
122
|
`}function ex(e){return e===null?`config: DSL not active
|
|
123
123
|
`:JSON.stringify(e,null,2)+`
|
|
124
|
-
`}function tx(e){let t=typeof e==`string`?e:JSON.stringify(e);return t.length>60?t.slice(0,57)+`…`:t}function nx(e){return e<1e3?`${e}ms`:e<6e4?`${(e/1e3).toFixed(1)}s`:`${Math.floor(e/6e4)}m`}function rx(e){let t=
|
|
125
|
-
Usage: cc-candybar
|
|
126
|
-
`),e.exit(2)),
|
|
127
|
-
`,
|
|
128
|
-
`,kick:!0,debug:`daemon unavailable (transient: ${e.cause}: ${e.message}) — kicking daemon`};case`permanent`:return{output:dx(e),kick:!1,debug:`daemon refused request (permanent: ${e.cause}) — not kicking`}}}function mx(){let t=e.env.COLUMNS;if(t){let e=parseInt(t,10);if(!isNaN(e)&&e>0)return e}let n=e.stderr.columns;if(n&&n>0)return n}function hx(){console.log(`
|
|
124
|
+
`}function tx(e){let t=typeof e==`string`?e:JSON.stringify(e);return t.length>60?t.slice(0,57)+`…`:t}function nx(e){return e<1e3?`${e}ms`:e<6e4?`${(e/1e3).toFixed(1)}s`:`${Math.floor(e/6e4)}m`}function rx(){let e=ax();return e===null?null:n.readFileSync(e,`utf-8`)}function ix(){let t;try{t=rx()}catch(t){e.stderr.write(`schema: cannot read bundled schema: ${t instanceof Error?t.message:String(t)}\n`),e.exit(2)}t===null&&(e.stderr.write("schema: bundled schema not found (expected schema/cc-candybar.schema.json). Run `pnpm gen:schema` from a source checkout.\n"),e.exit(2)),e.stdout.write(t),e.exit(0)}function ax(){let t=s.join(`schema`,`cc-candybar.schema.json`),r=e.argv[1];if(r===void 0)return null;let i=s.dirname(s.resolve(r));for(;;){let e=s.join(i,t);if(n.existsSync(e))return e;let r=s.dirname(i);if(r===i)return null;i=r}}function ox(e){let t=`/home/tester`,n=Math.floor(Date.now()/1e3);return{hook_event_name:`Status`,session_id:`test0a1b-2c3d-4e5f-6a7b-8c9d0e1f2a3b`,version:`1.15.0`,home:t,cwd:`${t}/code/cc-candybar/src`,transcript_path:`${t}/.claude/projects/x/test.jsonl`,model:{id:`claude-opus-4-8`,display_name:`Opus 4.8`},workspace:{current_dir:`${t}/code/cc-candybar/src`,project_dir:`${t}/code/cc-candybar`},git:{repoName:`cc-candybar`,branch:`main`,sha:`abc1234`,ahead:2,behind:1,staged:3,unstaged:2,untracked:1,conflicts:0,upstream:`origin/main`,stash:1,status:`dirty`,operation:`rebase`,timeSinceCommit:780},session:{cost:.39,tokens:241400},today:{cost:12.5,tokens:34e5},context:{totalTokens:48487,contextLeft:24},metrics:{lastResponseTime:8.2,responseTime:4.2,sessionDuration:930,messageCount:8,linesAdded:512,linesRemoved:88},block:{nativeUtilization:63,resetsAt:n+2*3600},weekly:{percentage:21,resetsAt:n+5*86400},cache:{expiresAt:n+900},tmux:{session:`work`},theme:{effective:e}}}function sx(t,r=e.cwd()){let i=t===void 0?lg(r,r):s.resolve(sg(t));if(t!==void 0&&i!==null){let e;try{e=n.statSync(i,{throwIfNoEntry:!1})}catch(e){return{kind:`unreadable`,path:i,message:e instanceof Error?e.message:String(e)}}if(e!==void 0&&!e.isFile())return{kind:`unreadable`,path:i,message:`not a file`}}let a=[],o=ug(r,r);o!==null&&a.push(o);try{return{kind:`clean`,configPath:i,warnings:a,rendered:cx(i,r,a)}}catch(e){let t=e;return i!==null&&typeof t==`object`&&t&&typeof t.code==`string`&&t.path===i&&typeof t.message==`string`?{kind:`unreadable`,path:i,message:t.message}:{kind:`fatal`,configPath:i,message:e instanceof Ms||e instanceof Error?e.message:String(e),warnings:a}}}function cx(e,t,n){let{config:r,source:i}=dg(e),a=fg(r,e??`<default>`,i),o=new Jv,s=new y_(o,a.globals.default_empty_value??``,void 0,new ly);try{let e=mv(a,s,{cwd:t});n.push(...e.loadWarnings),Hv(a);let r=ws(null,a.globals.palette);return gv(a,e,o,s,ox(r),D_(r),{style:ks(null,a.globals.style),width:200,colorCompatibility:a.globals.colorCompatibility??`truecolor`,wrap:a.globals.autoWrap??!0,padding:a.globals.padding??1,charset:a.globals.charset??`unicode`})}finally{s.dispose()}}function lx(e){return e.map(e=>`warning: ${e}\n`).join(``)}function ux(e){switch(e.kind){case`clean`:return{stdout:`✓ ${e.configPath??`bundled default (no config file found)`}: config OK${e.warnings.length>0?` (${e.warnings.length} warning${e.warnings.length===1?``:`s`})`:``}\n`,stderr:lx(e.warnings),code:0};case`fatal`:return{stdout:``,stderr:lx(e.warnings)+`✗ ${e.configPath??`<default>`}\n${e.message}\n`,code:1};case`unreadable`:return{stdout:``,stderr:`check: cannot read ${e.path}: ${e.message}\n`,code:2}}}function dx(t){(t.length>1||t[0]===``)&&(e.stderr.write(`check: expected at most one non-empty path
|
|
125
|
+
Usage: cc-candybar check [config-file]
|
|
126
|
+
`),e.exit(2));let n=ux(sx(t[0]));e.stdout.write(n.stdout),e.stderr.write(n.stderr),e.exit(n.code)}const fx=`${nb}${tb}`;function px(e){return`${fx}⚠ cc-candybar: ${mx(e)}${rb}\n`}function mx(e){switch(e.cause){case`version_mismatch`:{let t=e.daemonV===0?`unknown`:`v${e.daemonV}`;return`protocol mismatch (client v${e.clientV} ≠ daemon ${t})`}case`bad_request`:return`daemon rejected request: ${eb(e.message,60)}`;case`render_failed`:return`render failed: ${eb(e.message,60)}`;case`malformed_response`:return`malformed daemon response: ${eb(e.message,60)}`}}function hx(e){switch(e.kind){case`ok`:return{output:e.value,kick:!1,debug:null};case`transient`:return{output:`
|
|
127
|
+
`,kick:!0,debug:`daemon unavailable (transient: ${e.cause}: ${e.message}) — kicking daemon`};case`permanent`:return{output:px(e),kick:!1,debug:`daemon refused request (permanent: ${e.cause}) — not kicking`}}}function gx(){let t=e.env.COLUMNS;if(t){let e=parseInt(t,10);if(!isNaN(e)&&e>0)return e}let n=e.stderr.columns;if(n&&n>0)return n}function _x(){console.log(`
|
|
129
128
|
cc-candybar - Beautiful powerline statusline for Claude Code
|
|
130
129
|
|
|
131
130
|
Usage: cc-candybar [options]
|
|
@@ -156,8 +155,11 @@ Subcommands (macOS):
|
|
|
156
155
|
request totals. Does not spawn a daemon.
|
|
157
156
|
|
|
158
157
|
Config tooling:
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
check [config-file] Validate a config on the full render pipeline (parse
|
|
159
|
+
→ merge → validate → register → render) with no
|
|
160
|
+
daemon. With no path, checks the same file the daemon
|
|
161
|
+
would load from here. Exit 0 clean (warnings on
|
|
162
|
+
stderr), 1 invalid, 2 unreadable. "lint" is an alias.
|
|
161
163
|
schema Print the JSON Schema for the config file shape
|
|
162
164
|
(.cc-candybar.json5). Point an editor's $schema at it
|
|
163
165
|
for autocomplete + structural validation.
|
|
@@ -166,7 +168,7 @@ Config tooling:
|
|
|
166
168
|
config [--json] The effective merged config. (All three query the
|
|
167
169
|
running daemon; none spawn one.)
|
|
168
170
|
|
|
169
|
-
`)}async function
|
|
171
|
+
`)}async function vx(){try{(e.argv.includes(`--help`)||e.argv.includes(`-h`))&&(_x(),e.exit(0));let n=e.argv[2];if(n===`install`&&(Bt(e.argv.slice(3)),e.exit(0)),n===`install-url-handler`&&(It(),e.exit(0)),n===`url-handle`){await Rt(e.argv[3]);return}if(n===`daemon`){mb();return}if(n===`daemon-stats`&&(await Hb(e.argv.slice(3)),e.exit(0)),n===`check`||n===`lint`){dx(e.argv.slice(3));return}if(n===`schema`){ix();return}Gy(n)&&(await Yb(n,e.argv.slice(3)),e.exit(0)),e.stdin.isTTY===!0&&(console.error(`Error: This tool requires input from Claude Code
|
|
170
172
|
|
|
171
173
|
cc-candybar is designed to be used as a Claude Code statusLine command.
|
|
172
174
|
It reads hook data from stdin and outputs formatted statusline.
|
|
@@ -182,4 +184,4 @@ Add to ~/.claude/settings.json:
|
|
|
182
184
|
Run with --help for more options.
|
|
183
185
|
|
|
184
186
|
To test output manually:
|
|
185
|
-
echo '{"session_id":"test-session","workspace":{"project_dir":"/path/to/project"},"model":{"id":"claude-sonnet-4-5","display_name":"Claude"}}' | cc-candybar --style=powerline`),e.exit(1)),O(`Working directory: ${e.cwd()}`),O(`Process args:`,e.argv);let r=await t(e.stdin);O(`Received hook data:`,JSON.stringify(r,null,2)),r||(console.error(`Error: No input data received from stdin`),
|
|
187
|
+
echo '{"session_id":"test-session","workspace":{"project_dir":"/path/to/project"},"model":{"id":"claude-sonnet-4-5","display_name":"Claude"}}' | cc-candybar --style=powerline`),e.exit(1)),O(`Working directory: ${e.cwd()}`),O(`Process args:`,e.argv);let r=await t(e.stdin);O(`Received hook data:`,JSON.stringify(r,null,2)),r||(console.error(`Error: No input data received from stdin`),_x(),e.exit(1));let i=hx(await Ye(r,e.argv,e.cwd(),gx()));i.debug!==null&&O(i.debug),i.kick&&Ze(),e.stdout.write(i.output),e.exit(0)}catch(t){let n=t instanceof Error?t.message:String(t);console.error(`Error generating statusline:`,n),e.exit(1)}}vx();export{};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@promptctl/cc-candybar",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.0",
|
|
4
4
|
"description": "Statusline renderer for Claude Code — a JSON5-configurable DSL with daemon-cached data sources, byte-clean palette-aware composition, and OSC8 click verbs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|
|
@@ -95,10 +95,10 @@
|
|
|
95
95
|
"mobx": "^6.15.0"
|
|
96
96
|
},
|
|
97
97
|
"optionalDependencies": {
|
|
98
|
-
"@promptctl/cc-candybar-darwin-arm64": "1.
|
|
99
|
-
"@promptctl/cc-candybar-darwin-x64": "1.
|
|
100
|
-
"@promptctl/cc-candybar-linux-x64": "1.
|
|
101
|
-
"@promptctl/cc-candybar-linux-arm64": "1.
|
|
98
|
+
"@promptctl/cc-candybar-darwin-arm64": "1.20.0",
|
|
99
|
+
"@promptctl/cc-candybar-darwin-x64": "1.20.0",
|
|
100
|
+
"@promptctl/cc-candybar-linux-x64": "1.20.0",
|
|
101
|
+
"@promptctl/cc-candybar-linux-arm64": "1.20.0"
|
|
102
102
|
},
|
|
103
103
|
"pnpm": {
|
|
104
104
|
"supportedArchitectures": {
|
package/src/check.ts
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
// [LAW:verifiable-goals] `cc-candybar check [path]` — the authoring agent's eyes.
|
|
2
|
+
// Config diagnostics otherwise surface VISUALLY (composeWithDiagnostics renders
|
|
3
|
+
// error/warning icons into the bar), a channel a blind config author never sees.
|
|
4
|
+
// This command runs the production pipeline and projects its verdict onto a
|
|
5
|
+
// text + exit-code contract a script can close its own loop on:
|
|
6
|
+
// 0 — config loads and renders (warnings, if any, on stderr)
|
|
7
|
+
// 1 — config is invalid (parse / validate / register / render failure)
|
|
8
|
+
// 2 — usage error or a named file could not be read
|
|
9
|
+
//
|
|
10
|
+
// [LAW:single-enforcer] No parallel validation path: the verdict is reached
|
|
11
|
+
// through the exact functions the daemon runs (RenderCache.reloadInto →
|
|
12
|
+
// buildState, then the per-request render in server.ts) — resolveDslConfigPath →
|
|
13
|
+
// detectConfigCollisions → loadConfig → validateConfig → registerDslConfig →
|
|
14
|
+
// deriveActionValidators → renderDsl. "check passes" and "the daemon renders"
|
|
15
|
+
// cannot diverge, because they are one code path.
|
|
16
|
+
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import process from "node:process";
|
|
20
|
+
import {
|
|
21
|
+
loadConfig,
|
|
22
|
+
validateConfig,
|
|
23
|
+
resolveDslConfigPath,
|
|
24
|
+
detectConfigCollisions,
|
|
25
|
+
ConfigError,
|
|
26
|
+
} from "./config/dsl-loader.js";
|
|
27
|
+
import { expandHome } from "./config/loader/discovery.js";
|
|
28
|
+
import { VariableStore } from "./var-system/store.js";
|
|
29
|
+
import { SourceRegistry } from "./var-system/sources.js";
|
|
30
|
+
import { SessionState } from "./daemon/session-state.js";
|
|
31
|
+
import { registerDslConfig, renderDsl } from "./dsl/render.js";
|
|
32
|
+
import { deriveActionValidators } from "./daemon/verbs/state-validators.js";
|
|
33
|
+
import { effectiveThemeName, effectiveStripStyle } from "./themes/policy.js";
|
|
34
|
+
import { resolverForThemeName } from "./themes/palette-resolvers.js";
|
|
35
|
+
import {
|
|
36
|
+
DEFAULT_CHARSET,
|
|
37
|
+
DEFAULT_COLOR_COMPATIBILITY,
|
|
38
|
+
DEFAULT_PADDING,
|
|
39
|
+
DEFAULT_WRAP,
|
|
40
|
+
} from "./render/strip.js";
|
|
41
|
+
|
|
42
|
+
// [LAW:no-ambient-temporal-coupling] A fixed width keeps the verdict a function
|
|
43
|
+
// of the config alone, not of whichever terminal invoked the check. Templates
|
|
44
|
+
// evaluate in full before any width-driven wrap/pagination, so width shapes
|
|
45
|
+
// layout, never diagnostics.
|
|
46
|
+
const CHECK_WIDTH = 200;
|
|
47
|
+
|
|
48
|
+
// One faked Claude Code hook event, shaped like the daemon's augmented payload
|
|
49
|
+
// (see src/daemon/render-payload.ts) — the `input` vars read out of it by their
|
|
50
|
+
// dotted `path`. [LAW:verifiable-goals] It is deliberately RICH (dirty git with
|
|
51
|
+
// every worktree count, an upstream, a stash, a recent commit; home set; live
|
|
52
|
+
// session/today/context/metrics/rate-limit data) so gated segments actually
|
|
53
|
+
// RENDER their content instead of gating off. A minimal payload would let a
|
|
54
|
+
// field-name typo in the git/directory/metrics/budget branches slip through —
|
|
55
|
+
// those branches only run when their data is present.
|
|
56
|
+
//
|
|
57
|
+
// `effectiveTheme` is threaded in exactly as the daemon threads it (server.ts
|
|
58
|
+
// resolves effectiveThemeName once and feeds BOTH the payload's
|
|
59
|
+
// `theme.effective` field and the basePalette below) [LAW:one-source-of-truth].
|
|
60
|
+
//
|
|
61
|
+
// test/example-configs.test.ts asserts rendered content against these literal
|
|
62
|
+
// values (780s → "◷ 13m", cost $0.39, version 1.15.0, …); changing one here
|
|
63
|
+
// fails that suite loudly rather than drifting silently.
|
|
64
|
+
export function checkPayload(effectiveTheme: string): Record<string, unknown> {
|
|
65
|
+
const home = "/home/tester";
|
|
66
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
67
|
+
return {
|
|
68
|
+
hook_event_name: "Status",
|
|
69
|
+
session_id: "test0a1b-2c3d-4e5f-6a7b-8c9d0e1f2a3b",
|
|
70
|
+
version: "1.15.0",
|
|
71
|
+
home,
|
|
72
|
+
cwd: `${home}/code/cc-candybar/src`,
|
|
73
|
+
transcript_path: `${home}/.claude/projects/x/test.jsonl`,
|
|
74
|
+
model: { id: "claude-opus-4-8", display_name: "Opus 4.8" },
|
|
75
|
+
workspace: {
|
|
76
|
+
current_dir: `${home}/code/cc-candybar/src`,
|
|
77
|
+
project_dir: `${home}/code/cc-candybar`,
|
|
78
|
+
},
|
|
79
|
+
git: {
|
|
80
|
+
repoName: "cc-candybar",
|
|
81
|
+
branch: "main",
|
|
82
|
+
sha: "abc1234",
|
|
83
|
+
ahead: 2,
|
|
84
|
+
behind: 1,
|
|
85
|
+
staged: 3,
|
|
86
|
+
unstaged: 2,
|
|
87
|
+
untracked: 1,
|
|
88
|
+
conflicts: 0,
|
|
89
|
+
upstream: "origin/main",
|
|
90
|
+
stash: 1,
|
|
91
|
+
status: "dirty",
|
|
92
|
+
operation: "rebase",
|
|
93
|
+
timeSinceCommit: 780,
|
|
94
|
+
},
|
|
95
|
+
session: { cost: 0.39, tokens: 241400 },
|
|
96
|
+
today: { cost: 12.5, tokens: 3_400_000 },
|
|
97
|
+
context: { totalTokens: 48487, contextLeft: 24 },
|
|
98
|
+
metrics: {
|
|
99
|
+
lastResponseTime: 8.2,
|
|
100
|
+
responseTime: 4.2,
|
|
101
|
+
sessionDuration: 930,
|
|
102
|
+
messageCount: 8,
|
|
103
|
+
linesAdded: 512,
|
|
104
|
+
linesRemoved: 88,
|
|
105
|
+
},
|
|
106
|
+
block: { nativeUtilization: 63, resetsAt: nowSec + 2 * 3600 },
|
|
107
|
+
weekly: { percentage: 21, resetsAt: nowSec + 5 * 86400 },
|
|
108
|
+
cache: { expiresAt: nowSec + 15 * 60 },
|
|
109
|
+
tmux: { session: "work" },
|
|
110
|
+
theme: { effective: effectiveTheme },
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// [LAW:dataflow-not-control-flow] The check result is DATA — a pure function of
|
|
115
|
+
// the target file's contents — discriminated into the three outcomes the exit-
|
|
116
|
+
// code contract projects. `checkConfig` carries the decision; `runCheck` only
|
|
117
|
+
// maps it to (streams, exit), so the contract is testable without spawning a
|
|
118
|
+
// process or stubbing process.exit.
|
|
119
|
+
//
|
|
120
|
+
// `configPath` null means the bundled default was checked (no config file
|
|
121
|
+
// found — the daemon renders the same default in that state).
|
|
122
|
+
export type CheckOutcome =
|
|
123
|
+
| {
|
|
124
|
+
readonly kind: "clean";
|
|
125
|
+
readonly configPath: string | null;
|
|
126
|
+
readonly warnings: readonly string[];
|
|
127
|
+
readonly rendered: string;
|
|
128
|
+
}
|
|
129
|
+
| {
|
|
130
|
+
readonly kind: "fatal";
|
|
131
|
+
readonly configPath: string | null;
|
|
132
|
+
readonly message: string;
|
|
133
|
+
readonly warnings: readonly string[];
|
|
134
|
+
}
|
|
135
|
+
| {
|
|
136
|
+
readonly kind: "unreadable";
|
|
137
|
+
readonly path: string;
|
|
138
|
+
readonly message: string;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Run the daemon's load-and-render pipeline against one config target.
|
|
142
|
+
//
|
|
143
|
+
// With no target, the path resolves exactly as the daemon resolves it
|
|
144
|
+
// (resolveDslConfigPath: $CC_CANDYBAR_CONFIG → project/cwd → XDG), so the file
|
|
145
|
+
// this checks IS the file the daemon would load from this directory.
|
|
146
|
+
//
|
|
147
|
+
// [LAW:no-silent-failure] With an explicit target, the named file must exist
|
|
148
|
+
// and be readable — a missing file is `unreadable`, never a fall-through to the
|
|
149
|
+
// bundled default. (The daemon's --config-to-missing-file behavior — render the
|
|
150
|
+
// default, watch for the file to appear — is liveness for a long-running
|
|
151
|
+
// renderer; a verdict command must not report "clean" about a file it never
|
|
152
|
+
// read.)
|
|
153
|
+
export function checkConfig(
|
|
154
|
+
target: string | undefined,
|
|
155
|
+
cwd: string = process.cwd(),
|
|
156
|
+
): CheckOutcome {
|
|
157
|
+
// [LAW:one-source-of-truth] No pre-read: the ONE content read of the config
|
|
158
|
+
// file is the readFileSync inside loadConfig. Readability is established by
|
|
159
|
+
// the same read that parses (no double I/O); the catch below classifies its
|
|
160
|
+
// errno failure as `unreadable`. The explicit-target statSync is a metadata
|
|
161
|
+
// probe at the argv trust boundary, not a second read: a directory target
|
|
162
|
+
// (`check .`) fails read() with a path-less EISDIR the catch could not
|
|
163
|
+
// attribute, so the not-a-file usage error is decided here.
|
|
164
|
+
const configPath =
|
|
165
|
+
target !== undefined
|
|
166
|
+
? path.resolve(expandHome(target))
|
|
167
|
+
: resolveDslConfigPath(cwd, cwd);
|
|
168
|
+
if (target !== undefined && configPath !== null) {
|
|
169
|
+
// throwIfNoEntry suppresses only ENOENT (left for the content read to
|
|
170
|
+
// classify); EACCES/EPERM on the probe itself is equally "could not read
|
|
171
|
+
// the named file" — same outcome, not an uncaught stack.
|
|
172
|
+
let st: fs.Stats | undefined;
|
|
173
|
+
try {
|
|
174
|
+
st = fs.statSync(configPath, { throwIfNoEntry: false });
|
|
175
|
+
} catch (e) {
|
|
176
|
+
return {
|
|
177
|
+
kind: "unreadable",
|
|
178
|
+
path: configPath,
|
|
179
|
+
message: e instanceof Error ? e.message : String(e),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (st !== undefined && !st.isFile()) {
|
|
183
|
+
return {
|
|
184
|
+
kind: "unreadable",
|
|
185
|
+
path: configPath,
|
|
186
|
+
message: "not a file",
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// [LAW:dataflow-not-control-flow] Collision detection runs independent of
|
|
192
|
+
// load success — mirror of RenderCache.reloadInto: even if the .json5 fails
|
|
193
|
+
// to parse, the author still wants to know a shadowed .json sibling exists.
|
|
194
|
+
const warnings: string[] = [];
|
|
195
|
+
const collision = detectConfigCollisions(cwd, cwd);
|
|
196
|
+
if (collision !== null) warnings.push(collision);
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const rendered = loadRegisterRender(configPath, cwd, warnings);
|
|
200
|
+
return { kind: "clean", configPath, warnings, rendered };
|
|
201
|
+
} catch (e) {
|
|
202
|
+
// A filesystem error on the config file itself (ENOENT/EACCES from
|
|
203
|
+
// loadConfig's read — errno errors carry the failing `.path`, which is the
|
|
204
|
+
// discriminator against deeper fs failures) is the `unreadable` outcome:
|
|
205
|
+
// the named file could not be read at all, distinct from a file that read
|
|
206
|
+
// but is invalid. [LAW:no-silent-failure] — never a fall-through to the
|
|
207
|
+
// bundled default. Duck-typed, not `instanceof Error`: fs errors can cross
|
|
208
|
+
// a realm boundary (jest/graceful-fs), where instanceof lies.
|
|
209
|
+
const errno = e as Partial<NodeJS.ErrnoException> | null;
|
|
210
|
+
if (
|
|
211
|
+
configPath !== null &&
|
|
212
|
+
typeof errno === "object" &&
|
|
213
|
+
errno !== null &&
|
|
214
|
+
typeof errno.code === "string" &&
|
|
215
|
+
errno.path === configPath &&
|
|
216
|
+
typeof errno.message === "string"
|
|
217
|
+
) {
|
|
218
|
+
return { kind: "unreadable", path: configPath, message: errno.message };
|
|
219
|
+
}
|
|
220
|
+
// Same classification RenderCache.reloadInto applies: ConfigError and
|
|
221
|
+
// register/render throws (template parse, MissingFieldError, action arity)
|
|
222
|
+
// are all author-facing diagnostics — the daemon would surface each via
|
|
223
|
+
// composeWithDiagnostics, so check surfaces each as fatal text.
|
|
224
|
+
const message =
|
|
225
|
+
e instanceof ConfigError
|
|
226
|
+
? e.message
|
|
227
|
+
: e instanceof Error
|
|
228
|
+
? e.message
|
|
229
|
+
: String(e);
|
|
230
|
+
return { kind: "fatal", configPath, message, warnings };
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// The buildState + per-request-render mirror: every call below is the function
|
|
235
|
+
// the daemon calls, in the daemon's order [LAW:single-enforcer]. Returns the
|
|
236
|
+
// rendered line; appends the register pass's advisory `loadWarnings` (partial
|
|
237
|
+
// declaration failures) to `warnings` — the same channel RenderCache merges
|
|
238
|
+
// them into.
|
|
239
|
+
function loadRegisterRender(
|
|
240
|
+
configPath: string | null,
|
|
241
|
+
cwd: string,
|
|
242
|
+
warnings: string[],
|
|
243
|
+
): string {
|
|
244
|
+
const { config: merged, source } = loadConfig(configPath);
|
|
245
|
+
const config = validateConfig(merged, configPath ?? "<default>", source);
|
|
246
|
+
|
|
247
|
+
const store = new VariableStore();
|
|
248
|
+
const registry = new SourceRegistry(
|
|
249
|
+
store,
|
|
250
|
+
config.globals.default_empty_value ?? "",
|
|
251
|
+
undefined,
|
|
252
|
+
new SessionState(),
|
|
253
|
+
);
|
|
254
|
+
try {
|
|
255
|
+
const compiled = registerDslConfig(config, registry, { cwd });
|
|
256
|
+
// Registered before the validator pass so a derive throw (a key-kind
|
|
257
|
+
// clash) still carries the partial-load warnings into the fatal outcome.
|
|
258
|
+
warnings.push(...compiled.loadWarnings);
|
|
259
|
+
// Derivation only (the throw-on-clash coherence pass over the action
|
|
260
|
+
// table); the daemon additionally registers the results in its global
|
|
261
|
+
// validator registry, which a one-shot check has no wire to serve.
|
|
262
|
+
deriveActionValidators(config);
|
|
263
|
+
|
|
264
|
+
// Fresh session (no clicked theme/style), so the session half of each
|
|
265
|
+
// resolution is null — the config default over the floor, exactly what the
|
|
266
|
+
// daemon renders for a session that has never clicked.
|
|
267
|
+
const effectiveTheme = effectiveThemeName(null, config.globals.palette);
|
|
268
|
+
return renderDsl(
|
|
269
|
+
config,
|
|
270
|
+
compiled,
|
|
271
|
+
store,
|
|
272
|
+
registry,
|
|
273
|
+
checkPayload(effectiveTheme),
|
|
274
|
+
resolverForThemeName(effectiveTheme),
|
|
275
|
+
{
|
|
276
|
+
style: effectiveStripStyle(null, config.globals.style),
|
|
277
|
+
width: CHECK_WIDTH,
|
|
278
|
+
colorCompatibility:
|
|
279
|
+
config.globals.colorCompatibility ?? DEFAULT_COLOR_COMPATIBILITY,
|
|
280
|
+
wrap: config.globals.autoWrap ?? DEFAULT_WRAP,
|
|
281
|
+
padding: config.globals.padding ?? DEFAULT_PADDING,
|
|
282
|
+
charset: config.globals.charset ?? DEFAULT_CHARSET,
|
|
283
|
+
},
|
|
284
|
+
);
|
|
285
|
+
} finally {
|
|
286
|
+
// [LAW:single-enforcer] The registry owns every async handle the config
|
|
287
|
+
// declared (timers, fs watchers, git subscriptions); a one-shot check must
|
|
288
|
+
// not leak them past the verdict.
|
|
289
|
+
registry.dispose();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const EXIT_CLEAN = 0;
|
|
294
|
+
const EXIT_FATAL = 1;
|
|
295
|
+
const EXIT_USAGE = 2;
|
|
296
|
+
|
|
297
|
+
// [LAW:dataflow-not-control-flow] The outcome → (streams, exit-code) mapping is
|
|
298
|
+
// DATA: a total fold over CheckOutcome returning one descriptor; runCheck runs
|
|
299
|
+
// the two unconditional writes + exit against it. Verdict on stdout, every
|
|
300
|
+
// diagnostic (warnings included) on stderr — so `check` in a pipeline yields a
|
|
301
|
+
// parseable verdict while a human still sees the advisories.
|
|
302
|
+
export interface CheckPlan {
|
|
303
|
+
readonly stdout: string;
|
|
304
|
+
readonly stderr: string;
|
|
305
|
+
readonly code: number;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function warningLines(warnings: readonly string[]): string {
|
|
309
|
+
return warnings.map((w) => `warning: ${w}\n`).join("");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function checkPlan(o: CheckOutcome): CheckPlan {
|
|
313
|
+
switch (o.kind) {
|
|
314
|
+
case "clean": {
|
|
315
|
+
const where = o.configPath ?? "bundled default (no config file found)";
|
|
316
|
+
const count =
|
|
317
|
+
o.warnings.length > 0
|
|
318
|
+
? ` (${o.warnings.length} warning${o.warnings.length === 1 ? "" : "s"})`
|
|
319
|
+
: "";
|
|
320
|
+
return {
|
|
321
|
+
stdout: `✓ ${where}: config OK${count}\n`,
|
|
322
|
+
stderr: warningLines(o.warnings),
|
|
323
|
+
code: EXIT_CLEAN,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
case "fatal":
|
|
327
|
+
return {
|
|
328
|
+
stdout: "",
|
|
329
|
+
stderr:
|
|
330
|
+
warningLines(o.warnings) +
|
|
331
|
+
`✗ ${o.configPath ?? "<default>"}\n${o.message}\n`,
|
|
332
|
+
code: EXIT_FATAL,
|
|
333
|
+
};
|
|
334
|
+
case "unreadable":
|
|
335
|
+
return {
|
|
336
|
+
stdout: "",
|
|
337
|
+
stderr: `check: cannot read ${o.path}: ${o.message}\n`,
|
|
338
|
+
code: EXIT_USAGE,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// `cc-candybar check [path]` — the argv binding. Extra arguments and an empty
|
|
344
|
+
// path argument are usage errors (loud, not silently ignored — the likeliest
|
|
345
|
+
// cause is an unquoted or mis-expanded shell variable). An empty string is not
|
|
346
|
+
// "no argument": `checkConfig(undefined)` means "resolve like the daemon",
|
|
347
|
+
// while `""` is a malformed target that would otherwise EISDIR on the cwd.
|
|
348
|
+
export function runCheck(args: readonly string[]): never {
|
|
349
|
+
if (args.length > 1 || args[0] === "") {
|
|
350
|
+
process.stderr.write(
|
|
351
|
+
"check: expected at most one non-empty path\nUsage: cc-candybar check [config-file]\n",
|
|
352
|
+
);
|
|
353
|
+
process.exit(EXIT_USAGE);
|
|
354
|
+
}
|
|
355
|
+
const plan = checkPlan(checkConfig(args[0]));
|
|
356
|
+
process.stdout.write(plan.stdout);
|
|
357
|
+
process.stderr.write(plan.stderr);
|
|
358
|
+
process.exit(plan.code);
|
|
359
|
+
}
|
package/src/config/cli.ts
CHANGED
|
@@ -1,128 +1,17 @@
|
|
|
1
|
-
// [LAW:single-enforcer] Config-tooling CLI entry
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
1
|
+
// [LAW:single-enforcer] Config-tooling CLI entry point for `schema`. It carries
|
|
2
|
+
// no schema LOGIC — it serves the build-generated artifact (derived from the
|
|
3
|
+
// loader's declarations), so it is a binding, not a reimplementation
|
|
4
|
+
// [LAW:one-source-of-truth]. Config *validation* lives in `cc-candybar check`
|
|
5
|
+
// (src/check.ts) — the full-pipeline verdict command; the old `lint` is its
|
|
6
|
+
// alias.
|
|
7
7
|
|
|
8
8
|
import fs from "node:fs";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import process from "node:process";
|
|
11
|
-
import { loadConfig, validateConfig } from "./dsl-loader.js";
|
|
12
|
-
import { ConfigError } from "./loader/diagnostics.js";
|
|
13
11
|
|
|
14
|
-
// Exit codes are a contract (the CLI guideline: not just 0/1), so scripts and
|
|
15
|
-
// editors can distinguish "your config is wrong" from "I couldn't run":
|
|
16
|
-
// 0 — config is valid
|
|
17
|
-
// 1 — config is invalid (ConfigError: structural, cross-ref, or cycle)
|
|
18
|
-
// 2 — usage error or the file could not be read
|
|
19
12
|
const EXIT_VALID = 0;
|
|
20
|
-
const EXIT_INVALID = 1;
|
|
21
13
|
const EXIT_USAGE = 2;
|
|
22
14
|
|
|
23
|
-
// [LAW:dataflow-not-control-flow] The lint result is DATA — a pure function of
|
|
24
|
-
// the target file's contents — discriminated into the three outcomes the exit-
|
|
25
|
-
// code contract projects. `lintConfig` carries the decision; `runLint` only maps
|
|
26
|
-
// it to (stream, exit). The decision is unit-testable without spawning a process
|
|
27
|
-
// or stubbing process.exit, which is what makes the exit-code goal verifiable.
|
|
28
|
-
export type LintOutcome =
|
|
29
|
-
| { readonly kind: "valid"; readonly path: string }
|
|
30
|
-
| { readonly kind: "invalid"; readonly message: string }
|
|
31
|
-
| {
|
|
32
|
-
readonly kind: "unreadable";
|
|
33
|
-
readonly path: string;
|
|
34
|
-
readonly message: string;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// Run the real loader (parse → merge-with-default → cross-ref + cycle validation)
|
|
38
|
-
// against an arbitrary file. No daemon: the loader imports only fs, JSON5, and
|
|
39
|
-
// pure validators, so the same errors the daemon would surface at render time are
|
|
40
|
-
// surfaced here ahead of time.
|
|
41
|
-
//
|
|
42
|
-
// [LAW:single-enforcer] loadConfig + validateConfig is the identical pipeline
|
|
43
|
-
// RenderCache.reloadInto runs in the daemon — the validation authority is one
|
|
44
|
-
// function, so lint cannot drift from production. The source we read is passed to
|
|
45
|
-
// validateConfig only to sharpen line numbers (semantic issues map to lines).
|
|
46
|
-
export function lintConfig(target: string): LintOutcome {
|
|
47
|
-
const resolved = path.resolve(target);
|
|
48
|
-
|
|
49
|
-
let source: string;
|
|
50
|
-
try {
|
|
51
|
-
source = fs.readFileSync(resolved, "utf-8");
|
|
52
|
-
} catch (e) {
|
|
53
|
-
return {
|
|
54
|
-
kind: "unreadable",
|
|
55
|
-
path: target,
|
|
56
|
-
message: e instanceof Error ? e.message : String(e),
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
const { config } = loadConfig(resolved);
|
|
62
|
-
validateConfig(config, resolved, source);
|
|
63
|
-
} catch (e) {
|
|
64
|
-
if (e instanceof ConfigError)
|
|
65
|
-
return { kind: "invalid", message: e.message };
|
|
66
|
-
throw e;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return { kind: "valid", path: target };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// `cc-candybar lint <path>` — the argv binding. Missing-arg is a usage concern
|
|
73
|
-
// (not a lint outcome), handled here; everything else is the projection of
|
|
74
|
-
// lintConfig's outcome onto the stream + exit-code contract.
|
|
75
|
-
export function runLint(args: readonly string[]): void {
|
|
76
|
-
const target = args[0];
|
|
77
|
-
if (target === undefined || target === "") {
|
|
78
|
-
process.stderr.write(
|
|
79
|
-
"lint: missing <path>\nUsage: cc-candybar lint <config-file>\n",
|
|
80
|
-
);
|
|
81
|
-
process.exit(EXIT_USAGE);
|
|
82
|
-
}
|
|
83
|
-
applyLintOutcome(lintConfig(target));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// [LAW:dataflow-not-control-flow] The outcome → (stream, text, exit-code) mapping
|
|
87
|
-
// is DATA. `lintPlan` is a total fold returning that descriptor (a non-returning
|
|
88
|
-
// arm fails the typecheck, so the projection stays exhaustive over LintOutcome);
|
|
89
|
-
// `applyLintOutcome` runs the single write + exit against it. The side effects
|
|
90
|
-
// are unconditional; the data decides their content.
|
|
91
|
-
interface LintPlan {
|
|
92
|
-
readonly stream: NodeJS.WriteStream;
|
|
93
|
-
readonly text: string;
|
|
94
|
-
readonly code: number;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function lintPlan(o: LintOutcome): LintPlan {
|
|
98
|
-
switch (o.kind) {
|
|
99
|
-
case "valid":
|
|
100
|
-
return {
|
|
101
|
-
stream: process.stdout,
|
|
102
|
-
text: `✓ ${o.path}: config valid\n`,
|
|
103
|
-
code: EXIT_VALID,
|
|
104
|
-
};
|
|
105
|
-
case "invalid":
|
|
106
|
-
return {
|
|
107
|
-
stream: process.stderr,
|
|
108
|
-
text: o.message + "\n",
|
|
109
|
-
code: EXIT_INVALID,
|
|
110
|
-
};
|
|
111
|
-
case "unreadable":
|
|
112
|
-
return {
|
|
113
|
-
stream: process.stderr,
|
|
114
|
-
text: `lint: cannot read ${o.path}: ${o.message}\n`,
|
|
115
|
-
code: EXIT_USAGE,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function applyLintOutcome(o: LintOutcome): never {
|
|
121
|
-
const plan = lintPlan(o);
|
|
122
|
-
plan.stream.write(plan.text);
|
|
123
|
-
process.exit(plan.code);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
15
|
// Read the build-generated JSON Schema for the config file shape (RawDslConfig),
|
|
127
16
|
// or null when the artifact is absent. Pure read — `runSchema` owns the side
|
|
128
17
|
// effects, so the locate-and-read path is testable.
|
|
@@ -137,7 +26,19 @@ export function loadSchemaText(): string | null {
|
|
|
137
26
|
// time (scripts/gen-schema.ts → emitConfigSchema); served verbatim here (the
|
|
138
27
|
// emitter runs at build, not ship time).
|
|
139
28
|
export function runSchema(): void {
|
|
140
|
-
|
|
29
|
+
// [LAW:no-silent-failure] A schema that exists but cannot be read (EACCES, a
|
|
30
|
+
// deletion racing the existsSync in locateSchema) is a distinct failure from
|
|
31
|
+
// schema-not-found — report it as what it is, not a misleading top-level
|
|
32
|
+
// render error.
|
|
33
|
+
let text: string | null;
|
|
34
|
+
try {
|
|
35
|
+
text = loadSchemaText();
|
|
36
|
+
} catch (e) {
|
|
37
|
+
process.stderr.write(
|
|
38
|
+
`schema: cannot read bundled schema: ${e instanceof Error ? e.message : String(e)}\n`,
|
|
39
|
+
);
|
|
40
|
+
process.exit(EXIT_USAGE);
|
|
41
|
+
}
|
|
141
42
|
if (text === null) {
|
|
142
43
|
process.stderr.write(
|
|
143
44
|
"schema: bundled schema not found (expected schema/cc-candybar.schema.json). " +
|
|
@@ -575,14 +575,18 @@ export function synthesizeGroupDecls(
|
|
|
575
575
|
ctx: ValidateCtx,
|
|
576
576
|
out: Mutable<RawDslConfig>,
|
|
577
577
|
): void {
|
|
578
|
-
const groups = ctx.groups;
|
|
579
|
-
if (groups.length === 0) return;
|
|
580
|
-
|
|
581
578
|
// [LAW:single-enforcer] The disclosure primitive's shared reserved-namespace
|
|
582
579
|
// enforcer (mirroring {{ menu }}'s `menus.`) — a user name under `groups.`
|
|
583
|
-
// would silently shadow a synthesized artifact.
|
|
580
|
+
// would silently shadow a synthesized artifact. Reserved UNCONDITIONALLY,
|
|
581
|
+
// before the no-groups early return, so the reservation is a stable contract
|
|
582
|
+
// ("you never author groups.*"), not a rule that only switches on when a
|
|
583
|
+
// group node happens to be declared this load — same placement as the menus
|
|
584
|
+
// pass (synthesizeMenuDecls).
|
|
584
585
|
reservedNamespaceCollisions(ctx, out, GROUP_NS, "group nodes");
|
|
585
586
|
|
|
587
|
+
const groups = ctx.groups;
|
|
588
|
+
if (groups.length === 0) return;
|
|
589
|
+
|
|
586
590
|
const seen = new Set<string>();
|
|
587
591
|
for (const g of groups) {
|
|
588
592
|
if (seen.has(g.name)) {
|
package/src/index.ts
CHANGED
|
@@ -11,7 +11,8 @@ import { tryRenderViaDaemon } from "./daemon/client";
|
|
|
11
11
|
import { runDaemonStats } from "./daemon/client-stats";
|
|
12
12
|
import { runDebug } from "./daemon/client-debug";
|
|
13
13
|
import { isDebugWhat } from "./daemon/debug-types";
|
|
14
|
-
import {
|
|
14
|
+
import { runSchema } from "./config/cli";
|
|
15
|
+
import { runCheck } from "./check";
|
|
15
16
|
import { obtainDaemonKick } from "./daemon/acquire";
|
|
16
17
|
import { planOutcome } from "./render/outcome-plan";
|
|
17
18
|
|
|
@@ -66,8 +67,11 @@ Subcommands (macOS):
|
|
|
66
67
|
request totals. Does not spawn a daemon.
|
|
67
68
|
|
|
68
69
|
Config tooling:
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
check [config-file] Validate a config on the full render pipeline (parse
|
|
71
|
+
→ merge → validate → register → render) with no
|
|
72
|
+
daemon. With no path, checks the same file the daemon
|
|
73
|
+
would load from here. Exit 0 clean (warnings on
|
|
74
|
+
stderr), 1 invalid, 2 unreadable. "lint" is an alias.
|
|
71
75
|
schema Print the JSON Schema for the config file shape
|
|
72
76
|
(.cc-candybar.json5). Point an editor's $schema at it
|
|
73
77
|
for autocomplete + structural validation.
|
|
@@ -113,8 +117,11 @@ async function main(): Promise<void> {
|
|
|
113
117
|
await runDaemonStats(process.argv.slice(3));
|
|
114
118
|
process.exit(0);
|
|
115
119
|
}
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
// [LAW:one-type-per-behavior] `lint` is an alias of `check` — one config
|
|
121
|
+
// verdict, one pipeline, one exit-code contract (0/1/2). check subsumes the
|
|
122
|
+
// old lint (same loader, plus register + render coverage).
|
|
123
|
+
if (subcommand === "check" || subcommand === "lint") {
|
|
124
|
+
runCheck(process.argv.slice(3)); // owns its own exit code (0/1/2)
|
|
118
125
|
return;
|
|
119
126
|
}
|
|
120
127
|
if (subcommand === "schema") {
|