@hyperez/regna-code 0.1.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,28 +15,33 @@ Regna Code is a thin client. The models, knowledge, and routing live behind the
15
15
  npm i -g @hyperez/regna-code
16
16
  ```
17
17
 
18
- Then just run it. On first launch it walks you through login:
18
+ Then just run it. On first launch it asks how you want to connect:
19
19
 
20
20
  ```
21
- regna # logs in if needed, then starts interactive
22
- regna "explain this repo" # with a prompt
23
- regna -p "summarize today's changes" < /dev/null # headless/scripted (close stdin)
21
+ Choose how to connect:
22
+ 1) Online - Regna cloud (regnax.ai)
23
+ 2) Offline - air-gapped / self-hosted gateway
24
24
  ```
25
25
 
26
- `regna` opens https://regnax.ai/console, you paste an API key once, and it is stored at `~/.regna/auth.json`. No environment setup needed.
26
+ - **Online**: opens https://regnax.ai/console, you paste an API key once. Default model `regna-pro`.
27
+ - **Offline**: you enter your internal gateway URL (OpenAI-compatible `/v1`) and key, then pick a default model from whatever the gateway serves. Offline runs with no engine self-network and the egress guard enforced.
28
+
29
+ Your choice and credentials are stored at `~/.regna/auth.json`. After that:
27
30
 
28
31
  ```
29
- regna login # (re)authenticate
30
- regna logout # remove stored credentials
32
+ regna # starts interactive
33
+ regna "explain this repo" # with a prompt
34
+ regna -p "summarize today's changes" < /dev/null # headless/scripted (close stdin)
31
35
  ```
32
36
 
33
- By default Regna Code talks to the Regna cloud (`https://regnax.ai/v1`). To use a self-hosted or air-gapped deployment, point it at your own endpoint before logging in:
34
-
35
37
  ```
36
- export REGNA_BASE_URL="http://<your-gateway>/v1"
38
+ regna login # re-run the Online/Offline chooser
39
+ regna login online # set up Online directly
40
+ regna login offline # set up Offline directly
41
+ regna logout # remove stored credentials
37
42
  ```
38
43
 
39
- For CI or scripted use, set `REGNA_API_KEY` directly; it takes priority over the stored login.
44
+ For CI or scripted use, set `REGNA_API_KEY` directly; it takes priority over the stored login. Add `REGNA_OFFLINE=1` and `REGNA_BASE_URL` for non-interactive air-gapped use.
40
45
 
41
46
  ## Environment variables
42
47
 
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{spawn as v}from"node:child_process";import{fileURLToPath as P}from"node:url";import{dirname as m,join as o}from"node:path";import{existsSync as p,readFileSync as N,writeFileSync as G,mkdirSync as w,unlinkSync as L,chmodSync as k}from"node:fs";import{homedir as U}from"node:os";import{createInterface as $}from"node:readline/promises";var T=m(P(import.meta.url)),c=m(T),g=o(c,"extensions"),_=o(c,"themes","regna.json"),f=o(c,"APPEND_SYSTEM.md"),D="https://regnax.ai/v1",C="regna/regna-pro",h=o(U(),".regna"),d=o(h,"auth.json"),x=o(h,"engine"),a=process.argv.slice(2),u=(...e)=>a.some(t=>e.some(n=>!!(t===n||t.startsWith(`${n}=`)||/^-[a-z]$/i.test(n)&&t.startsWith(n)&&t.length>n.length)));function E(){for(let e of[o(c,"package.json"),o(c,"..","package.json")])try{let t=JSON.parse(N(e,"utf8")).version;if(t)return`v${t}`}catch{}return""}var O=`Regna Code ${E()} - a terminal coding agent on Regna
2
+ import{spawn as x}from"node:child_process";import{fileURLToPath as D}from"node:url";import{dirname as R,join as i}from"node:path";import{existsSync as m,readFileSync as I,writeFileSync as F,mkdirSync as $,unlinkSync as M,chmodSync as j}from"node:fs";import{homedir as B}from"node:os";import{createInterface as K}from"node:readline";var Y=R(D(import.meta.url)),u=R(Y),E=i(u,"extensions"),_=i(u,"themes","regna.json"),A=i(u,"APPEND_SYSTEM.md"),k="https://regnax.ai/v1",q="regna/regna-pro",v=i(B(),".regna"),y=i(v,"auth.json"),P=i(v,"engine"),l=process.argv.slice(2),h=(...e)=>l.some(t=>e.some(n=>!!(t===n||t.startsWith(`${n}=`)||/^-[a-z]$/i.test(n)&&t.startsWith(n)&&t.length>n.length)));function N(){for(let e of[i(u,"package.json"),i(u,"..","package.json")])try{let t=JSON.parse(I(e,"utf8")).version;if(t)return`v${t}`}catch{}return""}var z=`Regna Code ${N()} - a terminal coding agent on Regna
3
3
 
4
4
  Usage:
5
5
  regna [message...] start interactive, optionally with a first message
@@ -7,7 +7,9 @@ Usage:
7
7
  regna -p "summarize changes" < /dev/null headless/scripted (close stdin)
8
8
 
9
9
  Auth:
10
- regna login log in to Regna and store your API key
10
+ regna login choose Online/Offline and store credentials
11
+ regna login online set up Online (Regna cloud) directly
12
+ regna login offline set up Offline (air-gapped gateway) directly
11
13
  regna logout remove stored credentials
12
14
 
13
15
  In-session commands:
@@ -25,27 +27,42 @@ Environment:
25
27
  REGNA_POLICY network egress guard: off | warn | enforce
26
28
 
27
29
  Docs: https://github.com/HyperEZ/regna-code
28
- `;function A(){try{let e=JSON.parse(N(d,"utf8"));return e&&typeof e.apiKey=="string"?e:null}catch{return null}}function B(e,t){w(h,{recursive:!0}),G(d,`${JSON.stringify({apiKey:e,baseUrl:t},null,2)}
29
- `);try{k(d,384)}catch{}}function j(){try{return L(d),!0}catch{return!1}}function F(e){try{return`${new URL(e).origin}/console`}catch{return"https://regnax.ai/console"}}function K(e){try{let[t,n]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["cmd",["/c","start","",e]]:["xdg-open",[e]];v(t,n,{stdio:"ignore",detached:!0}).unref()}catch{}}async function Y(e,t){try{let n=new AbortController,s=setTimeout(()=>n.abort(),8e3),r=await fetch(`${e.replace(/\/+$/,"")}/models`,{headers:{Authorization:`Bearer ${t}`},signal:n.signal});return clearTimeout(s),r.status===401||r.status===403?"invalid":r.ok?"ok":"unknown"}catch{return"unknown"}}async function S(){let e=(process.env.REGNA_BASE_URL||"").trim()||A()?.baseUrl||D,t=F(e);process.stdout.write(`
30
- Regna Code ${E()}
31
- Log in to continue.
30
+ `;function H(){try{let e=JSON.parse(I(y,"utf8"));return e&&typeof e.apiKey=="string"?e:null}catch{return null}}function J(e){$(v,{recursive:!0}),F(y,`${JSON.stringify(e,null,2)}
31
+ `);try{j(y,384)}catch{}}function V(){try{return M(y),!0}catch{return!1}}function W(e){try{return`${new URL(e).origin}/console`}catch{return"https://regnax.ai/console"}}function Z(e){try{let[t,n]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["cmd",["/c","start","",e]]:["xdg-open",[e]];x(t,n,{stdio:"ignore",detached:!0}).unref()}catch{}}async function b(e,t){try{let n=new AbortController,r=setTimeout(()=>n.abort(),8e3),s=await fetch(`${e.replace(/\/+$/,"")}/models`,{headers:{Authorization:`Bearer ${t}`},signal:n.signal});if(clearTimeout(r),s.status===401||s.status===403)return{status:"invalid",ids:[]};if(!s.ok)return{status:"unknown",ids:[]};let o=await s.json().catch(()=>({}));return{status:"ok",ids:Array.isArray(o?.data)?o.data.map(p=>p&&typeof p.id=="string"?p.id:"").filter(Boolean):[]}}catch{return{status:"unknown",ids:[]}}}function Q(e){let t=(e||"").trim();if(!t)return"";/^https?:\/\//i.test(t)||(t=`http://${t}`);try{let n=new URL(t),r=`${n.origin}${n.pathname}`.replace(/\/+$/,"");return/\/v\d+$/.test(r)||(r=`${r}/v1`),r}catch{return""}}function X(){let e=K({input:process.stdin}),t=[],n=[],r=!1;return e.on("line",o=>{n.length?n.shift()(o):t.push(o)}),e.on("close",()=>{for(r=!0;n.length;)n.shift()(null)}),{ask:o=>(process.stdout.write(o),new Promise(a=>{t.length?a(t.shift()):r?a(null):n.push(a)})),close:()=>e.close()}}async function ee(e){return process.stdout.write(`
32
+ Regna Code ${N()}
33
+ Choose how to connect:
32
34
 
33
- 1) Get an API key at ${t}
35
+ 1) Online - Regna cloud (regnax.ai)
36
+ 2) Offline - air-gapped / self-hosted gateway
37
+
38
+ `),(await e(" Select [1]: ")||"").trim()==="2"?"offline":"online"}async function te(e){let t=k,n=W(t);process.stdout.write(`
39
+ Online mode (Regna cloud).
40
+ 1) Get an API key at ${n}
34
41
  2) Paste it below.
35
42
 
36
- `),K(t);let n=$({input:process.stdin,output:process.stdout}),s="";try{s=(await n.question(" API key: ")).trim()}finally{n.close()}s||(process.stderr.write(` No key entered. Aborting.
37
- `),process.exit(1));let r=await Y(e,s);return r==="invalid"&&(process.stderr.write(` That key was rejected. Check it and run: regna login
38
- `),process.exit(1)),B(s,e),process.stdout.write(r==="ok"?` Logged in.
43
+ `),Z(n);let r=(await e(" API key: ")||"").trim();r||(process.stderr.write(` No key entered. Aborting.
44
+ `),process.exit(1));let{status:s}=await b(t,r);return s==="invalid"&&(process.stderr.write(` That key was rejected. Run: regna login
45
+ `),process.exit(1)),{mode:"online",baseUrl:t,apiKey:r}}async function ne(e){process.stdout.write(`
46
+ Offline mode (air-gapped).
47
+ Point Regna Code at your internal gateway (OpenAI-compatible /v1).
39
48
 
40
- `:` Saved (could not verify right now).
49
+ `);let t=(await e(" Gateway URL [http://localhost:8077/v1]: ")||"").trim(),n=Q(t)||"http://localhost:8077/v1",r=(await e(" API key: ")||"").trim();r||(process.stderr.write(` No key entered. Aborting.
50
+ `),process.exit(1));let{status:s,ids:o}=await b(n,r);s==="invalid"&&(process.stderr.write(` That key was rejected by the gateway. Run: regna login
51
+ `),process.exit(1));let a="regna/default";if(s==="ok"&&o.length>0){let p=o.filter(w=>w==="default"||!w.includes("/")),f=p.length?p:o;if(f.length===1)a=`regna/${f[0]}`;else{process.stdout.write(`
52
+ Models served by the gateway:
53
+ `),f.forEach((T,U)=>process.stdout.write(` ${U+1}) ${T}
54
+ `));let w=(await e(`
55
+ Default model [1]: `)||"").trim(),C=Math.max(1,Math.min(f.length,parseInt(w||"1",10)||1))-1;a=`regna/${f[C]}`}}else process.stdout.write(` (Could not list models now; will use the gateway's active model.)
56
+ `);return{mode:"offline",baseUrl:n,apiKey:r,model:a}}async function G(e){let{ask:t,close:n}=X();try{let s=(e||await ee(t))==="offline"?await ne(t):await te(t);return J(s),process.stdout.write(`
57
+ Saved. Mode: ${s.mode}${s.model?`, default model: ${s.model}`:""}.
41
58
 
42
- `),s}(u("--help","-h")||a[0]==="help")&&(process.stdout.write(O),process.exit(0));(u("--version")||a[0]==="version")&&(process.stdout.write(`Regna Code ${E()}
43
- `),process.exit(0));a[0]==="login"&&(await S(),process.exit(0));a[0]==="logout"&&(process.stdout.write(j()?`Logged out.
59
+ `),s}finally{n()}}(h("--help","-h")||l[0]==="help")&&(process.stdout.write(z),process.exit(0));(h("--version")||l[0]==="version")&&(process.stdout.write(`Regna Code ${N()}
60
+ `),process.exit(0));if(l[0]==="login"){let e=l[1]==="online"||l[1]==="offline"?l[1]:void 0;await G(e),process.exit(0)}l[0]==="logout"&&(process.stdout.write(V()?`Logged out.
44
61
  `:`Not logged in.
45
- `),process.exit(0));var l=(process.env.REGNA_API_KEY||"").trim();if(!l){let e=A();e?.apiKey&&(l=e.apiKey.trim(),!process.env.REGNA_BASE_URL&&e.baseUrl&&(process.env.REGNA_BASE_URL=e.baseUrl))}if(!l)if(process.stdin.isTTY&&process.stdout.isTTY){if(l=await S(),!process.env.REGNA_BASE_URL){let e=A();e?.baseUrl&&(process.env.REGNA_BASE_URL=e.baseUrl)}}else process.stderr.write(`[regna] Not logged in. Run: regna login
46
- `),process.exit(1);process.env.REGNA_API_KEY=l;var i=[],q=process.env.REGNA_DISCOVER==="1";if(!q){i.push("--no-extensions");let e=["provider","policy","branding","model","context","exit","docs-search","docs-analysis"],t=r=>{let R=o(g,`${r}.js`);if(p(R))return R;let y=o(g,`${r}.ts`);return p(y)?y:null},n=e.map(r=>[r,t(r)]),s=n.filter(([,r])=>!r).map(([r])=>r);s.length>0&&(process.stderr.write(`[regna] Missing required extensions: ${s.join(", ")} (in ${g}). Install is corrupted. Aborting.
47
- `),process.exit(1));for(let[,r]of n)i.push("-e",r)}!u("--theme")&&p(_)&&i.push("--theme",_);u("--system-prompt")||(p(f)||(process.stderr.write(`[regna] Missing required prompt: APPEND_SYSTEM.md (${f}). Install is corrupted. Aborting.
62
+ `),process.exit(0));var c=null,g=(process.env.REGNA_API_KEY||"").trim();g?c={mode:process.env.REGNA_OFFLINE==="1"?"offline":"online",apiKey:g}:(c=H(),c?.apiKey?g=c.apiKey.trim():process.stdin.isTTY&&process.stdout.isTTY?(c=await G(),g=c.apiKey.trim()):(process.stderr.write(`[regna] Not logged in. Run: regna login
63
+ `),process.exit(1)));var O=process.env.REGNA_OFFLINE==="1"||c?.mode==="offline",se=(process.env.REGNA_BASE_URL||"").trim()||c?.baseUrl||k,re=(process.env.REGNA_MODEL||"").trim()||c?.model||(O?"regna/default":q);process.env.REGNA_API_KEY=g;process.env.REGNA_BASE_URL=se;O&&(process.env.REGNA_OFFLINE||(process.env.REGNA_OFFLINE="1"),process.env.REGNA_POLICY||(process.env.REGNA_POLICY="enforce"));var d=[],oe=process.env.REGNA_DISCOVER==="1";if(!oe){d.push("--no-extensions");let e=["provider","policy","branding","model","context","exit","docs-search","docs-analysis"],t=s=>{let o=i(E,`${s}.js`);if(m(o))return o;let a=i(E,`${s}.ts`);return m(a)?a:null},n=e.map(s=>[s,t(s)]),r=n.filter(([,s])=>!s).map(([s])=>s);r.length>0&&(process.stderr.write(`[regna] Missing required extensions: ${r.join(", ")} (in ${E}). Install is corrupted. Aborting.
64
+ `),process.exit(1));for(let[,s]of n)d.push("-e",s)}!h("--theme")&&m(_)&&d.push("--theme",_);h("--system-prompt")||(m(A)||(process.stderr.write(`[regna] Missing required prompt: APPEND_SYSTEM.md (${A}). Install is corrupted. Aborting.
48
65
  (To set the prompt yourself on purpose, run with --system-prompt.)
49
- `),process.exit(1)),i.push("--append-system-prompt",`@${f}`));u("--model","-m")||i.push("--model",process.env.REGNA_MODEL||C);i.push(...a);try{w(x,{recursive:!0})}catch{}var b={...process.env,PI_CODING_AGENT_DIR:x};process.env.REGNA_OFFLINE==="1"&&(b.PI_OFFLINE="1");function M(){let e=o("node_modules","@earendil-works","pi-coding-agent","dist","cli.js"),t=c;for(let n=0;n<10;n++){let s=o(t,e);if(p(s))return s;let r=m(t);if(r===t)break;t=r}return null}function H(){let e=process.env.REGNA_ENGINE;if(e)return{cmd:e,prefix:[],label:e};let t=M();return t?{cmd:process.execPath,prefix:[t],label:t}:{cmd:"pi",prefix:[],label:"runtime (PATH)"}}var{cmd:z,prefix:J,label:V}=H(),I=v(z,[...J,...i],{stdio:"inherit",env:b});I.on("error",e=>{e&&e.code==="ENOENT"&&(process.stderr.write(`[regna] Runtime executable not found (${V}). Reinstall (npm i -g @hyperez/regna-code) or set REGNA_ENGINE.
66
+ `),process.exit(1)),d.push("--append-system-prompt",`@${A}`));h("--model","-m")||d.push("--model",re);d.push(...l);try{$(P,{recursive:!0})}catch{}var L={...process.env,PI_CODING_AGENT_DIR:P};process.env.REGNA_OFFLINE==="1"&&(L.PI_OFFLINE="1");function ie(){let e=i("node_modules","@earendil-works","pi-coding-agent","dist","cli.js"),t=u;for(let n=0;n<10;n++){let r=i(t,e);if(m(r))return r;let s=R(t);if(s===t)break;t=s}return null}function ae(){let e=process.env.REGNA_ENGINE;if(e)return{cmd:e,prefix:[],label:e};let t=ie();return t?{cmd:process.execPath,prefix:[t],label:t}:{cmd:"pi",prefix:[],label:"runtime (PATH)"}}var{cmd:ce,prefix:le,label:de}=ae(),S=x(ce,[...le,...d],{stdio:"inherit",env:L});S.on("error",e=>{e&&e.code==="ENOENT"&&(process.stderr.write(`[regna] Runtime executable not found (${de}). Reinstall (npm i -g @hyperez/regna-code) or set REGNA_ENGINE.
50
67
  `),process.exit(127)),process.stderr.write(`[regna] Failed to start the runtime: ${e?.message??String(e)}
51
- `),process.exit(1)});I.on("exit",(e,t)=>{if(t){process.kill(process.pid,t);return}process.exit(e??0)});
68
+ `),process.exit(1)});S.on("exit",(e,t)=>{if(t){process.kill(process.pid,t);return}process.exit(e??0)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperez/regna-code",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "description": "Regna Code. A terminal coding agent on Regna. Install with `npm i -g @hyperez/regna-code` and run `regna`. Works with the Regna cloud API and on-premise deployments.",
5
5
  "license": "PolyForm-Noncommercial-1.0.0",
6
6
  "private": false,