@hyperez/regna-code 0.1.1 → 0.1.2

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
@@ -7,7 +7,7 @@ Regna Code is a thin client. The models, knowledge, and routing live behind the
7
7
  ## Requirements
8
8
 
9
9
  - Node.js >= 22.19.0
10
- - A Regna API endpoint and key.
10
+ - A Regna account (sign up at https://regnax.ai).
11
11
 
12
12
  ## Install
13
13
 
@@ -15,27 +15,34 @@ 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
- Set your key and run:
18
+ Then just run it. On first launch it walks you through login:
19
19
 
20
20
  ```
21
- export REGNA_API_KEY="..." # your Regna API key
22
-
23
- regna # interactive
21
+ regna # logs in if needed, then starts interactive
24
22
  regna "explain this repo" # with a prompt
25
23
  regna -p "summarize today's changes" < /dev/null # headless/scripted (close stdin)
26
24
  ```
27
25
 
28
- By default Regna Code talks to the Regna cloud API (`https://regnax.ai/v1`). To use a self-hosted or air-gapped deployment, point it at your own endpoint:
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.
27
+
28
+ ```
29
+ regna login # (re)authenticate
30
+ regna logout # remove stored credentials
31
+ ```
32
+
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:
29
34
 
30
35
  ```
31
36
  export REGNA_BASE_URL="http://<your-gateway>/v1"
32
37
  ```
33
38
 
39
+ For CI or scripted use, set `REGNA_API_KEY` directly; it takes priority over the stored login.
40
+
34
41
  ## Environment variables
35
42
 
36
43
  | Variable | Default | Description |
37
44
  |---|---|---|
38
- | `REGNA_API_KEY` | (none) | Regna API key. Required. Read from the environment only. |
45
+ | `REGNA_API_KEY` | (none) | Regna API key. Optional once you have run `regna login`. When set, it overrides the stored credential (use for CI). |
39
46
  | `REGNA_BASE_URL` | `https://regnax.ai/v1` | Regna API base URL (OpenAI-compatible). Point this at a self-hosted endpoint for on-premise use. |
40
47
  | `REGNA_MODEL` | `regna/default` | Starting model pattern. |
41
48
  | `REGNA_CODER_MODEL` | (none) | Exact model id that `/regna-coder` selects. |
@@ -43,7 +50,6 @@ export REGNA_BASE_URL="http://<your-gateway>/v1"
43
50
  | `REGNA_EXPOSE_ALIASES` | (unset) | `1` also exposes path-style (slash) model ids. Hidden by default. Ids pinned via `REGNA_MODEL`/`REGNA_CODER_MODEL`/`REGNA_GENERAL_MODEL` are always shown. |
44
51
  | `REGNA_POLICY` | `off` | Network egress guard: `off` / `warn` (notify, allow) / `enforce` (block external network tools). Set `enforce` for locked-down or air-gapped use. |
45
52
  | `REGNA_ALLOW_HOSTS` | (none) | Extra allowed hosts (comma-separated) when the policy is on. URL or `host:port` accepted. |
46
- | `REGNA_OFFLINE` | (unset) | `1` runs in air-gapped mode: blocks the runtime's own outbound network (update checks and the like). Your Regna API calls are unaffected. |
47
53
  | `REGNA_DISCOVER` | (unset) | `1` lets the runtime auto-discover extensions instead of loading the verified set explicitly. |
48
54
  | `REGNA_ENGINE` | (bundled) | Override the path to the runtime executable. Resolved from the bundled dependency by default. |
49
55
  | `REGNA_DOCS_ENABLED` | (unset) | `1` enables the document search tool (`regna_docs_search`) and the `/regna-docs` skill. Only turn this on when the backend's retrieval endpoint is available. |
@@ -54,6 +60,8 @@ export REGNA_BASE_URL="http://<your-gateway>/v1"
54
60
 
55
61
  | Command | Action |
56
62
  |---|---|
63
+ | `regna login` | Log in to Regna and store your API key at `~/.regna/auth.json`. |
64
+ | `regna logout` | Remove stored credentials. |
57
65
  | `/regna-coder` | Switch to a coding model (prefers an id containing `coder`, else default). |
58
66
  | `/regna-general` | Switch to a general model (prefers `instruct`, else default). |
59
67
  | `/regna-brand` | Toggle the Regna Code header/title branding. |
@@ -70,11 +78,10 @@ When enabled, two features turn on. Use them only where the backend's retrieval
70
78
 
71
79
  ## Air-gapped mode
72
80
 
73
- Regna Code runs fully offline against a self-hosted deployment. Set:
81
+ The runtime never phones home (no update checks or self-network), so Regna Code runs fully offline against a self-hosted deployment. Set:
74
82
 
75
83
  ```
76
84
  export REGNA_BASE_URL="http://<your-gateway>/v1"
77
- export REGNA_OFFLINE=1 # block the runtime's own outbound network
78
85
  export REGNA_POLICY=enforce # block external network tools; allow the gateway, localhost, and private/internal ranges
79
86
  ```
80
87
 
@@ -84,9 +91,9 @@ The egress guard is a defense layer, not a hard sandbox. Enforce real boundaries
84
91
 
85
92
  | Symptom | Action |
86
93
  |---|---|
87
- | `401 status code` | Check `REGNA_API_KEY`. |
94
+ | `401 status code` | Re-authenticate: `regna login`. |
88
95
  | Headless run hangs | Close stdin: `regna -p ... < /dev/null`. |
89
- | Only `regna/default` shows | Check `REGNA_BASE_URL` and key, then `/reload`. |
96
+ | Only `regna/default` shows | Run `regna login`, or check `REGNA_BASE_URL`, then `/reload`. |
90
97
  | `[regna policy] blocked` | If legitimate, add `REGNA_ALLOW_HOSTS` or set `REGNA_POLICY=warn`/`off`. |
91
98
  | Missing extension/prompt, aborted | Reinstall. Fail-closed behavior is intentional. |
92
99
  | Runtime executable not found | Reinstall (`npm i -g @hyperez/regna-code`) or set `REGNA_ENGINE`. |
@@ -1,29 +1,49 @@
1
1
  #!/usr/bin/env node
2
- import{spawn as A}from"node:child_process";import{fileURLToPath as x}from"node:url";import{dirname as f,join as n}from"node:path";import{existsSync as c,readFileSync as E}from"node:fs";import{createRequire as b}from"node:module";var w=f(x(import.meta.url)),p=f(w),m=n(p,"extensions"),h=n(p,"themes","regna.json"),g=n(p,"APPEND_SYSTEM.md"),l=process.argv.slice(2),d=(...e)=>l.some(s=>e.some(t=>!!(s===t||s.startsWith(`${t}=`)||/^-[a-z]$/i.test(t)&&s.startsWith(t)&&s.length>t.length)));function R(){for(let e of[n(p,"package.json"),n(p,"..","package.json")])try{let s=JSON.parse(E(e,"utf8")).version;if(s)return`v${s}`}catch{}return""}var y=`Regna Code ${R()} - a terminal coding agent on Regna
2
+ import{spawn as w}from"node:child_process";import{fileURLToPath as b}from"node:url";import{dirname as E,join as o}from"node:path";import{existsSync as l,readFileSync as y,writeFileSync as S,mkdirSync as k,unlinkSync as P,chmodSync as L}from"node:fs";import{homedir as G}from"node:os";import{createRequire as U}from"node:module";import{createInterface as $}from"node:readline/promises";var I=E(b(import.meta.url)),u=E(I),f=o(u,"extensions"),v=o(u,"themes","regna.json"),h=o(u,"APPEND_SYSTEM.md"),T="https://regnax.ai/v1",N=o(G(),".regna"),m=o(N,"auth.json"),a=process.argv.slice(2),g=(...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 A(){for(let e of[o(u,"package.json"),o(u,"..","package.json")])try{let t=JSON.parse(y(e,"utf8")).version;if(t)return`v${t}`}catch{}return""}var C=`Regna Code ${A()} - a terminal coding agent on Regna
3
3
 
4
4
  Usage:
5
5
  regna [message...] start interactive, optionally with a first message
6
6
  regna "explain this repo" run with a prompt
7
7
  regna -p "summarize changes" < /dev/null headless/scripted (close stdin)
8
8
 
9
+ Auth:
10
+ regna login log in to Regna and store your API key
11
+ regna logout remove stored credentials
12
+
9
13
  In-session commands:
10
14
  /regna-coder switch to a coding model
11
15
  /regna-general switch to a general model
12
16
  /regna-brand toggle the Regna Code branding
13
17
  /regna-docs <q> document-grounded query (requires REGNA_DOCS_ENABLED=1)
14
18
 
15
- Key environment variables:
16
- REGNA_API_KEY Regna API key (required)
19
+ Environment:
20
+ REGNA_API_KEY Regna API key (optional once logged in)
17
21
  REGNA_BASE_URL Regna API base URL (default https://regnax.ai/v1)
18
22
  REGNA_MODEL starting model (default regna/default)
19
23
  REGNA_POLICY network egress guard: off | warn | enforce
20
- REGNA_OFFLINE=1 air-gapped mode (block the runtime's own outbound network)
21
24
 
22
25
  Docs: https://github.com/HyperEZ/regna-code
23
- `;(d("--help","-h")||l[0]==="help")&&(process.stdout.write(y),process.exit(0));(d("--version")||l[0]==="version")&&(process.stdout.write(`Regna Code ${R()}
24
- `),process.exit(0));var o=[],P=process.env.REGNA_DISCOVER==="1";if(!P){o.push("--no-extensions");let e=["provider","policy","branding","model","docs-search","docs-analysis"],s=r=>{let a=n(m,`${r}.js`);if(c(a))return a;let i=n(m,`${r}.ts`);return c(i)?i:null},t=e.map(r=>[r,s(r)]),u=t.filter(([,r])=>!r).map(([r])=>r);u.length>0&&(process.stderr.write(`[regna] Missing required extensions: ${u.join(", ")} (in ${m}). Install is corrupted. Aborting.
25
- `),process.exit(1));for(let[,r]of t)o.push("-e",r)}!d("--theme")&&c(h)&&o.push("--theme",h);d("--system-prompt")||(c(g)||(process.stderr.write(`[regna] Missing required prompt: APPEND_SYSTEM.md (${g}). Install is corrupted. Aborting.
26
+ `;function R(){try{let e=JSON.parse(y(m,"utf8"));return e&&typeof e.apiKey=="string"?e:null}catch{return null}}function D(e,t){k(N,{recursive:!0}),S(m,`${JSON.stringify({apiKey:e,baseUrl:t},null,2)}
27
+ `);try{L(m,384)}catch{}}function B(){try{return P(m),!0}catch{return!1}}function O(e){try{return`${new URL(e).origin}/console`}catch{return"https://regnax.ai/console"}}function j(e){try{let[t,n]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["cmd",["/c","start","",e]]:["xdg-open",[e]];w(t,n,{stdio:"ignore",detached:!0}).unref()}catch{}}async function q(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 _(){let e=(process.env.REGNA_BASE_URL||"").trim()||R()?.baseUrl||T,t=O(e);process.stdout.write(`
28
+ Regna Code ${A()}
29
+ Log in to continue.
30
+
31
+ 1) Get an API key at ${t}
32
+ 2) Paste it below.
33
+
34
+ `),j(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.
35
+ `),process.exit(1));let r=await q(e,s);return r==="invalid"&&(process.stderr.write(` That key was rejected. Check it and run: regna login
36
+ `),process.exit(1)),D(s,e),process.stdout.write(r==="ok"?` Logged in.
37
+
38
+ `:` Saved (could not verify right now).
39
+
40
+ `),s}(g("--help","-h")||a[0]==="help")&&(process.stdout.write(C),process.exit(0));(g("--version")||a[0]==="version")&&(process.stdout.write(`Regna Code ${A()}
41
+ `),process.exit(0));a[0]==="login"&&(await _(),process.exit(0));a[0]==="logout"&&(process.stdout.write(B()?`Logged out.
42
+ `:`Not logged in.
43
+ `),process.exit(0));var d=(process.env.REGNA_API_KEY||"").trim();if(!d){let e=R();e?.apiKey&&(d=e.apiKey.trim(),!process.env.REGNA_BASE_URL&&e.baseUrl&&(process.env.REGNA_BASE_URL=e.baseUrl))}if(!d)if(process.stdin.isTTY&&process.stdout.isTTY){if(d=await _(),!process.env.REGNA_BASE_URL){let e=R();e?.baseUrl&&(process.env.REGNA_BASE_URL=e.baseUrl)}}else process.stderr.write(`[regna] Not logged in. Run: regna login
44
+ `),process.exit(1);process.env.REGNA_API_KEY=d;var i=[],K=process.env.REGNA_DISCOVER==="1";if(!K){i.push("--no-extensions");let e=["provider","policy","branding","model","docs-search","docs-analysis"],t=r=>{let p=o(f,`${r}.js`);if(l(p))return p;let c=o(f,`${r}.ts`);return l(c)?c: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 ${f}). Install is corrupted. Aborting.
45
+ `),process.exit(1));for(let[,r]of n)i.push("-e",r)}!g("--theme")&&l(v)&&i.push("--theme",v);g("--system-prompt")||(l(h)||(process.stderr.write(`[regna] Missing required prompt: APPEND_SYSTEM.md (${h}). Install is corrupted. Aborting.
26
46
  (To set the prompt yourself on purpose, run with --system-prompt.)
27
- `),process.exit(1)),o.push("--append-system-prompt",`@${g}`));d("--model","-m")||o.push("--model",process.env.REGNA_MODEL||"regna/default");o.push(...l);var N={...process.env};process.env.REGNA_OFFLINE==="1"&&(N.PI_OFFLINE="1");function _(){let e=process.env.REGNA_ENGINE;if(e)return{cmd:e,prefix:[],label:e};try{let t=b(import.meta.url).resolve("@earendil-works/pi-coding-agent/package.json"),u=f(t),r=JSON.parse(E(t,"utf8")),a=typeof r.bin=="string"?r.bin:r.bin?.pi;if(a){let i=n(u,a);if(c(i))return{cmd:process.execPath,prefix:[i],label:i}}}catch{}return{cmd:"pi",prefix:[],label:"runtime (PATH)"}}var{cmd:G,prefix:I,label:k}=_(),v=A(G,[...I,...o],{stdio:"inherit",env:N});v.on("error",e=>{e&&e.code==="ENOENT"&&(process.stderr.write(`[regna] Runtime executable not found (${k}). Reinstall (npm i -g @hyperez/regna-code) or set REGNA_ENGINE.
47
+ `),process.exit(1)),i.push("--append-system-prompt",`@${h}`));g("--model","-m")||i.push("--model",process.env.REGNA_MODEL||"regna/default");i.push(...a);var Y={...process.env,PI_OFFLINE:"1"};function F(){let e=process.env.REGNA_ENGINE;if(e)return{cmd:e,prefix:[],label:e};try{let n=U(import.meta.url).resolve("@earendil-works/pi-coding-agent/package.json"),s=E(n),r=JSON.parse(y(n,"utf8")),p=typeof r.bin=="string"?r.bin:r.bin?.pi;if(p){let c=o(s,p);if(l(c))return{cmd:process.execPath,prefix:[c],label:c}}}catch{}return{cmd:"pi",prefix:[],label:"runtime (PATH)"}}var{cmd:M,prefix:H,label:z}=F(),x=w(M,[...H,...i],{stdio:"inherit",env:Y});x.on("error",e=>{e&&e.code==="ENOENT"&&(process.stderr.write(`[regna] Runtime executable not found (${z}). Reinstall (npm i -g @hyperez/regna-code) or set REGNA_ENGINE.
28
48
  `),process.exit(127)),process.stderr.write(`[regna] Failed to start the runtime: ${e?.message??String(e)}
29
- `),process.exit(1)});v.on("exit",(e,s)=>{if(s){process.kill(process.pid,s);return}process.exit(e??0)});
49
+ `),process.exit(1)});x.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.1",
3
+ "version": "0.1.2",
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,
@@ -29,8 +29,8 @@
29
29
  "LICENSE"
30
30
  ],
31
31
  "dependencies": {
32
- "@earendil-works/pi-coding-agent": "^0.79.4",
33
- "@earendil-works/pi-tui": "^0.79.4",
32
+ "@earendil-works/pi-coding-agent": "^0.80.2",
33
+ "@earendil-works/pi-tui": "^0.80.2",
34
34
  "typebox": "^1.3.1"
35
35
  },
36
36
  "engines": {