@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 +37 -7
- package/dist/bin/regna.mjs +23 -25
- package/dist/extensions/mcp.js +5 -0
- package/dist/extensions/provider.js +1 -1
- package/package.json +2 -1
- package/dist/extensions/model.js +0 -1
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.
|
|
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` |
|
|
53
|
-
| `
|
|
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
|
|
package/dist/bin/regna.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{spawn as
|
|
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 (
|
|
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
|
|
31
|
-
`);try{
|
|
32
|
-
Regna Code ${
|
|
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
|
|
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
|
-
`),
|
|
44
|
-
`),process.exit(1));let{status:s}=await
|
|
45
|
-
`),process.exit(1)),{mode:"online",baseUrl:t,apiKey:r}}async function
|
|
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=
|
|
50
|
-
`),process.exit(1));let{status:s,ids:o}=await
|
|
51
|
-
`),process.exit(1));let
|
|
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
|
-
`),
|
|
54
|
-
`));let
|
|
55
|
-
Default model [1]: `)||"").trim(),
|
|
56
|
-
`);return{mode:"offline",baseUrl:n,apiKey:r,model:
|
|
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")||
|
|
60
|
-
`),process.exit(0));if(
|
|
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
|
|
63
|
-
`),process.exit(1)));var
|
|
64
|
-
`),process.exit(1));for(let[,s]of n)
|
|
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)),
|
|
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)});
|
|
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
|
|
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.
|
|
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": {
|
package/dist/extensions/model.js
DELETED
|
@@ -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};
|