@hyperez/regna-code 0.2.4 → 0.2.6

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
@@ -23,7 +23,7 @@ Then just run it. On first launch it asks how you want to connect:
23
23
  2) Offline - air-gapped / self-hosted gateway
24
24
  ```
25
25
 
26
- - **Online**: opens https://regnax.ai/console, you paste an API key once. Default model `regna-pro`.
26
+ - **Online**: opens https://regnax.ai/console, you paste an API key once. The cloud serves a single model, `regna-1`.
27
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
28
 
29
29
  Your choice and credentials are stored at `~/.regna/auth.json`. After that:
@@ -49,10 +49,8 @@ For CI or scripted use, set `REGNA_API_KEY` directly; it takes priority over the
49
49
  |---|---|---|
50
50
  | `REGNA_API_KEY` | (none) | Regna API key. Optional once you have run `regna login`. When set, it overrides the stored credential (use for CI). |
51
51
  | `REGNA_BASE_URL` | `https://regnax.ai/v1` | Regna API base URL (OpenAI-compatible). Point this at a self-hosted endpoint for on-premise use. |
52
- | `REGNA_MODEL` | `regna/regna-pro` | Starting model. The cloud catalog is `regna-basic`, `regna-pro`, `regna-max`. |
53
- | `REGNA_CODER_MODEL` | (none) | Exact model id that `/regna-coder` selects. |
54
- | `REGNA_GENERAL_MODEL` | (none) | Exact model id that `/regna-general` selects. |
55
- | `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. |
52
+ | `REGNA_MODEL` | (auto) | Starting model. By default the current single cloud model is detected from the gateway automatically; set this to pin a specific id. |
53
+ | `REGNA_EXPOSE_ALIASES` | (unset) | `1` also exposes path-style (slash) model ids. Hidden by default. The id pinned via `REGNA_MODEL` is always shown. |
56
54
  | `REGNA_POLICY` | `off` | Network egress guard: `off` / `warn` (notify, allow) / `enforce` (block external network tools). Set `enforce` for locked-down or air-gapped use. |
57
55
  | `REGNA_OFFLINE` | (unset) | `1` runs fully offline: the runtime skips all self-network, including the one-time download of search helpers (`fd`/`ripgrep`). They fall back to the built-in search. Use for air-gapped installs. |
58
56
  | `REGNA_ALLOW_HOSTS` | (none) | Extra allowed hosts (comma-separated) when the policy is on. URL or `host:port` accepted. |
@@ -61,6 +59,9 @@ For CI or scripted use, set `REGNA_API_KEY` directly; it takes priority over the
61
59
  | `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. |
62
60
  | `REGNA_RETRIEVAL_PATH` | `/api/retrieval/search` | Override the retrieval endpoint path. |
63
61
  | `REGNA_DOCS_TIMEOUT_MS` | `15000` | Retrieval timeout (clamped 1000~120000). |
62
+ | `REGNA_MCP_ENABLED` | (unset) | `0` disables MCP entirely. Default on, but with no `mcp.json` config it does nothing. |
63
+ | `REGNA_MCP_TIMEOUT_MS` | `30000` | MCP server connect/list-tools timeout (clamped 5000~180000). |
64
+ | `REGNA_MCP_CALL_TIMEOUT_MS` | `120000` | MCP tool-call timeout (clamped 5000~600000). |
64
65
 
65
66
  ## Commands
66
67
 
@@ -70,10 +71,39 @@ For CI or scripted use, set `REGNA_API_KEY` directly; it takes priority over the
70
71
  | `regna logout` | Remove stored credentials. |
71
72
  | `/exit` | Exit Regna Code from inside a session. |
72
73
  | `/compact` | Compact the session context (also runs automatically as it fills). |
73
- | `/regna-coder` | Switch to a coding model (prefers an id containing `coder`, else default). |
74
- | `/regna-general` | Switch to a general model (prefers `instruct`, else default). |
75
74
  | `/regna-brand` | Toggle the Regna Code header/title branding. |
76
75
  | `/regna-docs <question>` | Document-grounded query. Searches indexed documents with `regna_docs_search`, answers using only retrieved evidence with `[source n]` citations, and says it does not know when there is no evidence. Requires `REGNA_DOCS_ENABLED=1`. |
76
+ | `/mcp` | List configured MCP servers, their connection status, and tool counts. |
77
+
78
+ ## MCP servers
79
+
80
+ Regna Code connects to [Model Context Protocol](https://modelcontextprotocol.io) servers and exposes their tools to the agent, named `mcp__<server>__<tool>`. This gives you the whole MCP ecosystem (filesystem, GitHub, databases, browsers, and more) on top of the built-in `read`/`write`/`edit`/`bash`/`grep`/`find`/`ls` tools.
81
+
82
+ Configure servers in either file (same schema as other MCP clients; a project file overrides a same-named global server):
83
+
84
+ - Global: `~/.regna/mcp.json`
85
+ - Project: `./.mcp.json`
86
+
87
+ ```json
88
+ {
89
+ "mcpServers": {
90
+ "filesystem": {
91
+ "command": "npx",
92
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/project"]
93
+ },
94
+ "github": {
95
+ "type": "http",
96
+ "url": "https://api.githubcopilot.com/mcp/",
97
+ "headers": { "Authorization": "Bearer ${GITHUB_MCP_TOKEN}" }
98
+ }
99
+ }
100
+ }
101
+ ```
102
+
103
+ - **stdio** servers use `command` (+ optional `args`, `env`, `cwd`). **Remote** servers use `url` with `type` `http` (default, Streamable HTTP) or `sse`.
104
+ - `${VAR}` inside any value is substituted from the environment, so secrets stay out of the config file.
105
+ - A failing server is skipped (its error is shown in `/mcp`); the rest keep working.
106
+ - Controls: `REGNA_MCP_ENABLED=0` disables MCP; `REGNA_MCP_TIMEOUT_MS` / `REGNA_MCP_CALL_TIMEOUT_MS` tune timeouts. In air-gapped mode (`REGNA_OFFLINE=1`) remote servers are skipped and stdio servers still run.
77
107
 
78
108
  ## Document search (`REGNA_DOCS_ENABLED=1`)
79
109
 
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
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
2
+ import{spawn as I}from"node:child_process";import{fileURLToPath as M}from"node:url";import{dirname as v,join as a}from"node:path";import{existsSync as m,readFileSync as b,writeFileSync as j,mkdirSync as P,unlinkSync as B,chmodSync as K}from"node:fs";import{homedir as Y}from"node:os";import{createInterface as q}from"node:readline";var z=v(M(import.meta.url)),g=v(z),A=a(g,"extensions"),$=a(g,"themes","regna.json"),R=a(g,"APPEND_SYSTEM.md"),k="https://regnax.ai/v1",H="regna/regna-1",N=a(Y(),".regna"),E=a(N,"auth.json"),O=a(N,"engine"),u=process.argv.slice(2),h=(...e)=>u.some(t=>e.some(n=>!!(t===n||t.startsWith(`${n}=`)||/^-[a-z]$/i.test(n)&&t.startsWith(n)&&t.length>n.length)));function _(){for(let e of[a(g,"package.json"),a(g,"..","package.json")])try{let t=JSON.parse(b(e,"utf8")).version;if(t)return`v${t}`}catch{}return""}var J=`Regna Code ${_()} - a terminal coding agent on Regna
3
3
 
4
4
  Usage:
5
5
  regna [message...] start interactive, optionally with a first message
@@ -13,8 +13,6 @@ Auth:
13
13
  regna logout remove stored credentials
14
14
 
15
15
  In-session commands:
16
- /regna-coder switch to a coding model
17
- /regna-general switch to a general model
18
16
  /regna-brand toggle the Regna Code branding
19
17
  /regna-docs <q> document-grounded query (requires REGNA_DOCS_ENABLED=1)
20
18
  /exit exit Regna Code
@@ -23,46 +21,46 @@ In-session commands:
23
21
  Environment:
24
22
  REGNA_API_KEY Regna API key (optional once logged in)
25
23
  REGNA_BASE_URL Regna API base URL (default https://regnax.ai/v1)
26
- REGNA_MODEL starting model (default regna/regna-pro)
24
+ REGNA_MODEL starting model (overrides the auto-detected gateway model)
27
25
  REGNA_POLICY network egress guard: off | warn | enforce
28
26
 
29
27
  Docs: https://github.com/HyperEZ/regna-code
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()}
28
+ `;function V(){try{let e=JSON.parse(b(E,"utf8"));return e&&typeof e.apiKey=="string"?e:null}catch{return null}}function W(e){P(N,{recursive:!0}),j(E,`${JSON.stringify(e,null,2)}
29
+ `);try{K(E,384)}catch{}}function Z(){try{return B(E),!0}catch{return!1}}function Q(e){try{return`${new URL(e).origin}/console`}catch{return"https://regnax.ai/console"}}function X(e){try{let[t,n]=process.platform==="darwin"?["open",[e]]:process.platform==="win32"?["cmd",["/c","start","",e]]:["xdg-open",[e]];I(t,n,{stdio:"ignore",detached:!0}).unref()}catch{}}async function S(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(d=>d&&typeof d.id=="string"?d.id:"").filter(Boolean):[]}}catch{return{status:"unknown",ids:[]}}}function ee(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 te(){let e=q({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(i=>{t.length?i(t.shift()):r?i(null):n.push(i)})),close:()=>e.close()}}async function ne(e){return process.stdout.write(`
30
+ Regna Code ${_()}
33
31
  Choose how to connect:
34
32
 
35
33
  1) Online - Regna cloud (regnax.ai)
36
34
  2) Offline - air-gapped / self-hosted gateway
37
35
 
38
- `),(await e(" Select [1]: ")||"").trim()==="2"?"offline":"online"}async function te(e){let t=k,n=W(t);process.stdout.write(`
36
+ `),(await e(" Select [1]: ")||"").trim()==="2"?"offline":"online"}async function se(e){let t=k,n=Q(t);process.stdout.write(`
39
37
  Online mode (Regna cloud).
40
38
  1) Get an API key at ${n}
41
39
  2) Paste it below.
42
40
 
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(`
41
+ `),X(n);let r=(await e(" API key: ")||"").trim();r||(process.stderr.write(` No key entered. Aborting.
42
+ `),process.exit(1));let{status:s}=await S(t,r);return s==="invalid"&&(process.stderr.write(` That key was rejected. Run: regna login
43
+ `),process.exit(1)),{mode:"online",baseUrl:t,apiKey:r}}async function re(e){process.stdout.write(`
46
44
  Offline mode (air-gapped).
47
45
  Point Regna Code at your internal gateway (OpenAI-compatible /v1).
48
46
 
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(`
47
+ `);let t=(await e(" Gateway URL [http://localhost:8077/v1]: ")||"").trim(),n=ee(t)||"http://localhost:8077/v1",r=(await e(" API key: ")||"").trim();r||(process.stderr.write(` No key entered. Aborting.
48
+ `),process.exit(1));let{status:s,ids:o}=await S(n,r);s==="invalid"&&(process.stderr.write(` That key was rejected by the gateway. Run: regna login
49
+ `),process.exit(1));let i="regna/default";if(s==="ok"&&o.length>0){let d=o.filter(y=>y==="default"||!y.includes("/")),c=d.length?d:o;if(c.length===1)i=`regna/${c[0]}`;else{process.stdout.write(`
52
50
  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(`
51
+ `),c.forEach((D,F)=>process.stdout.write(` ${F+1}) ${D}
52
+ `));let y=(await e(`
53
+ Default model [1]: `)||"").trim(),U=Math.max(1,Math.min(c.length,parseInt(y||"1",10)||1))-1;i=`regna/${c[U]}`}}else process.stdout.write(` (Could not list models now; will use the gateway's active model.)
54
+ `);return{mode:"offline",baseUrl:n,apiKey:r,model:i}}async function G(e){let{ask:t,close:n}=te();try{let s=(e||await ne(t))==="offline"?await re(t):await se(t);return W(s),process.stdout.write(`
57
55
  Saved. Mode: ${s.mode}${s.model?`, default model: ${s.model}`:""}.
58
56
 
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.
57
+ `),s}finally{n()}}(h("--help","-h")||u[0]==="help")&&(process.stdout.write(J),process.exit(0));(h("--version")||u[0]==="version")&&(process.stdout.write(`Regna Code ${_()}
58
+ `),process.exit(0));if(u[0]==="login"){let e=u[1]==="online"||u[1]==="offline"?u[1]:void 0;await G(e),process.exit(0)}u[0]==="logout"&&(process.stdout.write(Z()?`Logged out.
61
59
  `:`Not logged in.
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.
60
+ `),process.exit(0));var l=null,f=(process.env.REGNA_API_KEY||"").trim();f?l={mode:process.env.REGNA_OFFLINE==="1"?"offline":"online",apiKey:f}:(l=V(),l?.apiKey?f=l.apiKey.trim():process.stdin.isTTY&&process.stdout.isTTY?(l=await G(),f=l.apiKey.trim()):(process.stderr.write(`[regna] Not logged in. Run: regna login
61
+ `),process.exit(1)));async function oe(e,t){try{let n=new AbortController,r=setTimeout(()=>n.abort(),4e3),s;try{s=await fetch(`${e.replace(/\/+$/,"")}/models`,{headers:{Authorization:`Bearer ${t}`},signal:n.signal})}finally{clearTimeout(r)}if(!s.ok)return null;let o=await s.json(),d=(Array.isArray(o?.data)?o.data:[]).map(c=>c&&typeof c.id=="string"?c.id:"").filter(c=>/^regna-\d+$/.test(c)).sort();return d.length>0?`regna/${d[0]}`:null}catch{return null}}var L=process.env.REGNA_OFFLINE==="1"||l?.mode==="offline",C=(process.env.REGNA_BASE_URL||"").trim()||l?.baseUrl||k,w=(process.env.REGNA_MODEL||"").trim();w||(L?w=l?.model||"regna/default":w=await oe(C,f)||l?.model||H);process.env.REGNA_API_KEY=f;process.env.REGNA_BASE_URL=C;L&&(process.env.REGNA_OFFLINE||(process.env.REGNA_OFFLINE="1"),process.env.REGNA_POLICY||(process.env.REGNA_POLICY="enforce"));var p=[],ie=process.env.REGNA_DISCOVER==="1";if(!ie){p.push("--no-extensions");let e=["provider","policy","branding","context","exit","docs-search","docs-analysis","mcp"],t=s=>{let o=a(A,`${s}.js`);if(m(o))return o;let i=a(A,`${s}.ts`);return m(i)?i: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 ${A}). Install is corrupted. Aborting.
62
+ `),process.exit(1));for(let[,s]of n)p.push("-e",s)}!h("--theme")&&m($)&&p.push("--theme",$);h("--system-prompt")||(m(R)||(process.stderr.write(`[regna] Missing required prompt: APPEND_SYSTEM.md (${R}). Install is corrupted. Aborting.
65
63
  (To set the prompt yourself on purpose, run with --system-prompt.)
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.
64
+ `),process.exit(1)),p.push("--append-system-prompt",`@${R}`));h("--model","-m")||p.push("--model",w);p.push(...u);try{P(O,{recursive:!0})}catch{}var x={...process.env,PI_CODING_AGENT_DIR:O};process.env.REGNA_OFFLINE==="1"&&(x.PI_OFFLINE="1");x.PI_SKIP_VERSION_CHECK="1";function ae(){let e=a("node_modules","@earendil-works","pi-coding-agent","dist","cli.js"),t=g;for(let n=0;n<10;n++){let r=a(t,e);if(m(r))return r;let s=v(t);if(s===t)break;t=s}return null}function ce(){let e=process.env.REGNA_ENGINE;if(e)return{cmd:e,prefix:[],label:e};let t=ae();return t?{cmd:process.execPath,prefix:[t],label:t}:{cmd:"pi",prefix:[],label:"runtime (PATH)"}}var{cmd:le,prefix:de,label:ue}=ce(),T=I(le,[...de,...p],{stdio:"inherit",env:x});T.on("error",e=>{e&&e.code==="ENOENT"&&(process.stderr.write(`[regna] Runtime executable not found (${ue}). Reinstall (npm i -g @hyperez/regna-code) or set REGNA_ENGINE.
67
65
  `),process.exit(127)),process.stderr.write(`[regna] Failed to start the runtime: ${e?.message??String(e)}
68
- `),process.exit(1)});S.on("exit",(e,t)=>{if(t){process.kill(process.pid,t);return}process.exit(e??0)});
66
+ `),process.exit(1)});T.on("exit",(e,t)=>{if(t){process.kill(process.pid,t);return}process.exit(e??0)});
@@ -0,0 +1,5 @@
1
+ import{Type as L}from"typebox";import{Client as k}from"@modelcontextprotocol/sdk/client/index.js";import{StdioClientTransport as O,getDefaultEnvironment as U}from"@modelcontextprotocol/sdk/client/stdio.js";import{StreamableHTTPClientTransport as j}from"@modelcontextprotocol/sdk/client/streamableHttp.js";import{SSEClientTransport as F}from"@modelcontextprotocol/sdk/client/sse.js";import{createHash as X}from"node:crypto";import{readFileSync as G}from"node:fs";import{join as v}from"node:path";import{homedir as z}from"node:os";var D="regna-code",H="1.0.0",J=3e4,Z=5e3,q=18e4,B=12e4,V=5e3,K=6e5,w=64,M=2e5,S=512e3,x=10;function C(t,n=process.env){return t.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g,(s,o)=>n[o]??"")}function A(t,n=process.env){let s={};for(let[o,i]of Object.entries(t??{}))s[o]=C(String(i),n);return s}function $(t,n,s,o){let i=Number(t);return!Number.isFinite(i)||i<=0?n:Math.min(o,Math.max(s,i))}function Q(t){return X("sha1").update(t).digest("hex").slice(0,8)}function W(t,n,s){let o=e=>e.replace(/[^a-zA-Z0-9_-]/g,"_"),i=`mcp__${o(t)}__${o(n)}`,r=i.length>w?`${i.slice(0,w-9)}_${Q(`${t}\0${n}`)}`:i;if(s){let e=r,u=1;for(;s.has(e);){let m=`_${u++}`;e=`${r.slice(0,w-m.length)}${m}`}s.add(e),r=e}return r}function Y(t){let n=new Map,s=t?.mcpServers;if(!s||typeof s!="object")return n;for(let[o,i]of Object.entries(s)){if(!i||typeof i!="object")continue;let r=i;typeof r.url=="string"&&r.url.trim()?n.set(o,{type:r.type==="sse"?"sse":"http",url:r.url,headers:r.headers}):typeof r.command=="string"&&r.command.trim()&&n.set(o,{command:r.command,args:Array.isArray(r.args)?r.args.map(String):void 0,env:r.env,cwd:typeof r.cwd=="string"?r.cwd:void 0})}return n}function tt(t){try{return JSON.parse(G(t,"utf8"))}catch{return null}}function et(t,n=z()){let s=new Map;for(let o of[v(n,".regna","mcp.json"),v(t,".mcp.json")]){let i=Y(tt(o));for(let[r,e]of i)s.set(r,e)}return s}function P(t){return typeof t.url=="string"}function nt(t,n){if(P(t)){if(n)throw new Error("remote MCP disabled by REGNA_OFFLINE");let o=new URL(C(t.url));if(o.protocol!=="http:"&&o.protocol!=="https:")throw new Error(`invalid url scheme: ${o.protocol}`);let r={requestInit:{headers:A(t.headers)}};return t.type==="sse"?new F(o,r):new j(o,r)}let s=C(t.command);if(!s.trim())throw new Error("empty command");return new O({command:s,args:(t.args??[]).map(o=>C(o)),env:{...U(),...A(t.env)},cwd:t.cwd?C(t.cwd):void 0,stderr:"ignore"})}async function N(t,n,s){t.catch(()=>{});let o,i=new Promise((r,e)=>{o=setTimeout(()=>e(new Error(`${s} timed out after ${n}ms`)),n)});try{return await Promise.race([t,i])}finally{clearTimeout(o)}}async function E(t){try{await t.close()}catch{}}function rt(t){let n=[],s=0,o=0,i=r=>{if(s>=S)return;let e=r;e.length>M&&(e=`${e.slice(0,M)}
2
+ [... truncated ${r.length-M} chars]`),s+e.length>S&&(e=`${e.slice(0,S-s)}
3
+ [... output truncated]`),s+=e.length,n.push({type:"text",text:e})};for(let r of Array.isArray(t)?t:[]){let e=r;if(e.type==="text"&&typeof e.text=="string")i(e.text);else if(e.type==="image"&&typeof e.data=="string")o<x?(n.push({type:"image",data:e.data,mimeType:typeof e.mimeType=="string"?e.mimeType:"image/png"}),o++):i(`[image omitted: exceeded ${x}-image limit]`);else if(e.type==="resource"&&e.resource){let u=e.resource;typeof u.text=="string"?i(u.text):i(`[resource ${u.uri??""} (${u.mimeType??"binary"}) omitted]`)}else e.type==="audio"?i(`[audio content (${typeof e.mimeType=="string"?e.mimeType:"?"}) omitted]`):i(`[unsupported MCP content: ${JSON.stringify(r).slice(0,500)}]`)}return n.length===0&&n.push({type:"text",text:"(tool returned no content)"}),n}async function ot(t,n,s,o,i,r,e,u){let m=nt(s,o),d=new k({name:D,version:H},{capabilities:{}});try{await N(d.connect(m),i,`MCP "${n}" connect`)}catch(c){throw await E(d),c}e.push({name:n,client:d});let p;try{p=await N(d.listTools(),i,`MCP "${n}" listTools`)}catch(c){throw await E(d),c}let f=p?.tools??[],g=0;for(let c of f)if(!(!c?.name||typeof c.name!="string"))try{let a=c.inputSchema&&typeof c.inputSchema=="object"?c.inputSchema:{type:"object",properties:{}},l=c.name,T=W(n,l,u);t.registerTool({name:T,label:`${n}: ${l}`,description:(c.description?.trim()||`MCP tool "${l}" from server "${n}".`)+` (MCP server: ${n})`,promptSnippet:`${T}: MCP tool from ${n}`,parameters:L.Unsafe(a),async execute(st,R,b,it,ct){let h={name:n,status:"connected",toolCount:0};try{let _=await d.callTool({name:l,arguments:R??{}},void 0,{signal:b??void 0,timeout:r}),y=rt(_.content);return _.isError?{content:[{type:"text",text:`MCP tool "${l}" reported an error:`},...y],details:h}:{content:y,details:h}}catch(_){let y=_,I=y?.name==="AbortError"?"cancelled":y?.message||"call failed";return{content:[{type:"text",text:`MCP tool "${l}" failed: ${I}.`}],details:h}}}}),g++}catch{}return g}async function Ct(t){if((process.env.REGNA_MCP_ENABLED??"").trim()==="0")return;let n=process.cwd(),s=et(n);if(s.size===0)return;let o=(process.env.REGNA_OFFLINE??"").trim()==="1",i=$(process.env.REGNA_MCP_TIMEOUT_MS,J,Z,q),r=$(process.env.REGNA_MCP_CALL_TIMEOUT_MS,B,V,K),e=[],u=new Set,m=[];await Promise.all([...s.entries()].map(async([p,f])=>{if(o&&P(f)){m.push({name:p,status:"skipped",toolCount:0,detail:"offline"});return}try{let g=await ot(t,p,f,o,i,r,e,u);m.push({name:p,status:"connected",toolCount:g})}catch(g){m.push({name:p,status:"failed",toolCount:0,detail:g?.message||"connect failed"})}}));let d=!1;t.on("session_shutdown",async()=>{d||(d=!0,await Promise.all(e.map(({client:p})=>E(p))))}),t.registerCommand("mcp",{description:"List configured MCP servers, their connection status, and tool counts.",handler:async(p,f)=>{if(m.length===0){f.ui.notify("No MCP servers configured. Add ~/.regna/mcp.json or ./.mcp.json (mcpServers).","info");return}let g=m.slice().sort((a,l)=>a.name.localeCompare(l.name)).map(a=>{let l=a.status==="connected"?"\u2713":a.status==="skipped"?"-":"\u2717",T=a.status==="connected"?`${a.toolCount} tools`:a.detail||a.status;return`${l} ${a.name}: ${T}`}),c=m.some(a=>a.status==="failed");f.ui.notify(`MCP servers:
4
+ ${g.join(`
5
+ `)}`,c?"warning":"info")}})}export{Ct as default,C as expandEnv,et as loadServerConfigs,rt as mapToolContent,$ as normalizeMs,Y as parseServers,W as toolName};
@@ -1 +1 @@
1
- var u="regna",E="https://regnax.ai/v1";var f=["regna-basic","regna-pro","regna-max"],p={"regna-basic":"Regna Basic","regna-pro":"Regna Pro","regna-max":"Regna Max"},R={"regna-basic":2e5,"regna-pro":4e5,"regna-max":1e6},A={"regna-basic":8192,"regna-pro":16384,"regna-max":32768},_=512*1024,m=24,h=200;function v(){let o=(process.env.REGNA_BASE_URL??"").trim(),e=o===""?E:o,r;try{r=new URL(e)}catch{return E}if(r.protocol!=="http:"&&r.protocol!=="https:")return E;let i=`${r.origin}${r.pathname}`.replace(/\/+$/,"");return/\/v\d+$/.test(i)?i:`${i}/v1`}function I(o){try{return/(^|\.)regnax\.ai$/i.test(new URL(o).hostname)}catch{return!1}}function S(o){let e=o.toLowerCase(),r=e==="default"||e==="current"||e==="auto",i=/qwq|qwen3|think|reason|max/.test(e),n=f.includes(e),a=parseInt((process.env.REGNA_CONTEXT_WINDOW??"").trim(),10),c=Number.isFinite(a)&&a>0?a:R[e]??(n?2e5:32768),l=A[e]??(n?16384:8192);return{id:o,name:p[e]??(r?"Regna (active model)":`Regna \xB7 ${o}`),reasoning:i,input:["text"],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:c,maxTokens:l}}async function N(o,e){let r=new AbortController,i=setTimeout(()=>r.abort(),4e3);try{let n=await fetch(`${o}/models`,{headers:{Authorization:`Bearer ${e}`},signal:r.signal});if(!n.ok)return[];let a=await n.text();if(a.length>_)return[];let c;try{c=JSON.parse(a)}catch{return[]}let d=(Array.isArray(c.data)?c.data:[]).map(t=>t&&typeof t.id=="string"?t.id:"").filter(t=>t.length>0&&t.length<=h);return Array.from(new Set(d)).slice(0,m)}catch{return[]}finally{clearTimeout(i)}}async function M(o){let e=v(),r=(process.env.REGNA_API_KEY??"").trim(),i=(process.env.REGNA_EXPOSE_ALIASES??"").trim()==="1",n=r?await N(e,r):[],a=new Set,c=s=>{let g=(s??"").trim();g&&a.add(g.startsWith(`${u}/`)?g.slice(u.length+1):g)};c(process.env.REGNA_MODEL),c(process.env.REGNA_CODER_MODEL),c(process.env.REGNA_GENERAL_MODEL),i||(n=n.filter(s=>s==="default"||!s.includes("/")||a.has(s)));let l=I(e),d=f.filter(s=>n.includes(s)),t;if(l||d.length>0){t=l?[...f]:d.slice();for(let s of a)n.includes(s)&&!t.includes(s)&&t.push(s);!l&&t.length===0&&(t=["default"])}else n.includes("default")||n.unshift("default"),t=n;!n.some(s=>s!=="default")&&!l&&console.warn(`[regna] Model discovery failed or REGNA_API_KEY is unset. Set REGNA_API_KEY and run /reload. (base=${e})`),o.registerProvider(u,{name:"Regna",baseUrl:e,apiKey:"$REGNA_API_KEY",api:"openai-completions",models:t.map(S)})}export{M as default};
1
+ var E="regna",_="https://regnax.ai/v1";var p=/^regna-\d+$/,A=["regna-1"],R={"regna-1":"Regna 1"},m={"regna-1":1e6},h={"regna-1":32768},v=1e6,x=32768;function M(o){let s=/^regna-(\d+)$/.exec(o.toLowerCase());return s?`Regna ${s[1]}`:`Regna \xB7 ${o}`}var T=512*1024,I=24,L=200;function P(){let o=(process.env.REGNA_BASE_URL??"").trim(),s=o===""?_:o,n;try{n=new URL(s)}catch{return _}if(n.protocol!=="http:"&&n.protocol!=="https:")return _;let c=`${n.origin}${n.pathname}`.replace(/\/+$/,"");return/\/v\d+$/.test(c)?c:`${c}/v1`}function S(o){try{return/(^|\.)regnax\.ai$/i.test(new URL(o).hostname)}catch{return!1}}function N(o){let s=o.id,n=s.toLowerCase(),c=n==="default"||n==="current"||n==="auto",a=p.test(n),u=/qwq|qwen3|think|reason/.test(n),t=parseInt((process.env.REGNA_CONTEXT_WINDOW??"").trim(),10),d=typeof o.contextWindow=="number"&&o.contextWindow>0?o.contextWindow:void 0,g=Number.isFinite(t)&&t>0?t:d??m[n]??(a?v:32768),l=h[n]??(a?x:8192);return{id:s,name:R[n]??(a?M(s):c?"Regna (active model)":`Regna \xB7 ${s}`),reasoning:u,input:["text"],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:g,maxTokens:l}}async function y(o,s){let n=new AbortController,c=setTimeout(()=>n.abort(),4e3);try{let a=await fetch(`${o}/models`,{headers:{Authorization:`Bearer ${s}`},signal:n.signal});if(!a.ok)return[];let u=await a.text();if(u.length>T)return[];let t;try{t=JSON.parse(u)}catch{return[]}let d=Array.isArray(t.data)?t.data:[],g=new Set,l=[];for(let i of d){let r=i&&typeof i.id=="string"?i.id:"";if(r.length===0||r.length>L||g.has(r))continue;g.add(r);let w=i&&typeof i.context_window=="number"&&i.context_window>0?i.context_window:void 0;if(l.push({id:r,contextWindow:w}),l.length>=I)break}return l}catch{return[]}finally{clearTimeout(c)}}async function O(o){let s=P(),n=(process.env.REGNA_API_KEY??"").trim(),c=(process.env.REGNA_EXPOSE_ALIASES??"").trim()==="1",a=n?await y(s,n):[],u=new Map(a.map(e=>[e.id,e.contextWindow])),t=a.map(e=>e.id),d=new Set;(e=>{let f=(e??"").trim();f&&d.add(f.startsWith(`${E}/`)?f.slice(E.length+1):f)})(process.env.REGNA_MODEL),c||(t=t.filter(e=>e==="default"||!e.includes("/")||d.has(e)));let l=S(s),i=t.filter(e=>p.test(e)).sort(),r;if(l){r=i.length>0?i.slice():[...A];for(let e of d)t.includes(e)&&!r.includes(e)&&r.push(e)}else if(i.length>0){r=i.slice();for(let e of d)t.includes(e)&&!r.includes(e)&&r.push(e)}else t.includes("default")||t.unshift("default"),r=t;!t.some(e=>e!=="default")&&!l&&console.warn(`[regna] Model discovery failed or REGNA_API_KEY is unset. Set REGNA_API_KEY and run /reload. (base=${s})`),o.registerProvider(E,{name:"Regna",baseUrl:s,apiKey:"$REGNA_API_KEY",api:"openai-completions",models:r.map(e=>N({id:e,contextWindow:u.get(e)}))})}export{O as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperez/regna-code",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
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,
@@ -34,6 +34,7 @@
34
34
  "dependencies": {
35
35
  "@earendil-works/pi-coding-agent": "0.80.2",
36
36
  "@earendil-works/pi-tui": "0.80.2",
37
+ "@modelcontextprotocol/sdk": "^1.29.0",
37
38
  "typebox": "^1.3.1"
38
39
  },
39
40
  "engines": {
@@ -1 +0,0 @@
1
- var l="regna",f={coder:["regna-max","regna-pro","regna-basic"],general:["regna-pro","regna-basic","regna-max"]};function u(r,c,i){if(r.length===0)return;if(i&&r.includes(i))return i;for(let n of f[c])if(r.includes(n))return n;let d=(n,s)=>n.toLowerCase().includes(s),o=r.find(n=>d(n,"coder"));if(c==="coder")return o??(r.includes("default")?"default":r[0]);let e=r.find(n=>d(n,"instruct")&&!d(n,"coder"));return e||(r.includes("default")?"default":r.find(n=>n!==o)??r[0])}function E(r){let c={coder:process.env.REGNA_CODER_MODEL?.trim()||void 0,general:process.env.REGNA_GENERAL_MODEL?.trim()||void 0},i=!1,d=async(o,e)=>{let n=e.modelRegistry.getAll().filter(t=>t.provider===l).map(t=>t.id);if(n.length===0){e.ui.notify("No regna provider models. Check the provider extension and REGNA_API_KEY.","warning");return}let s=c[o];if(s&&!n.includes(s)){let t=o==="coder"?"REGNA_CODER_MODEL":"REGNA_GENERAL_MODEL";e.ui.notify(`${t}='${s}' is not in the registered list; ignoring. Available: ${n.join(", ")}`,"warning")}let a=u(n,o,s),g=a?e.modelRegistry.find(l,a):void 0;if(!g){e.ui.notify(`Could not find a ${o} model (${l}/${a??"?"}).`,"warning");return}if(i=!0,!await r.setModel(g))i=!1,e.ui.notify(`Switch failed (API key may be missing): ${l}/${a}`,"warning");else{let t=a==="default"?" (active model)":"";e.ui.notify(`Model set: ${l}/${a}${t}`,"info")}};r.registerCommand("regna-coder",{description:"Switch to a Regna coding model (/regna-coder)",handler:async(o,e)=>d("coder",e)}),r.registerCommand("regna-general",{description:"Switch to a Regna general model (/regna-general)",handler:async(o,e)=>d("general",e)}),r.on("model_select",async(o,e)=>{if(i){i=!1;return}o.source!=="restore"&&o.model.provider===l&&e.ui.notify(`Regna model: ${o.model.id}`,"info")})}export{E as default,u as pickRegnaModelId};