@hyperez/regna-code 0.3.0 → 0.4.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 +25 -0
- package/dist/bin/regna.mjs +5 -5
- package/dist/extensions/task.js +4 -0
- package/dist/postinstall.mjs +19 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,6 +76,31 @@ For CI or scripted use, set `REGNA_API_KEY` directly; it takes priority over the
|
|
|
76
76
|
| `/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`. |
|
|
77
77
|
| `/mcp` | List configured MCP servers, their connection status, and tool counts. |
|
|
78
78
|
|
|
79
|
+
## Subagents (`regna_task`)
|
|
80
|
+
|
|
81
|
+
The model can delegate self-contained subtasks to separate Regna agents with the built-in
|
|
82
|
+
`regna_task` tool. Each subagent runs as a child `regna` process in print mode with a fresh
|
|
83
|
+
context, the same working directory, the same gateway key, and the same policy gates. Sibling
|
|
84
|
+
tool calls run in parallel, so asking for several independent investigations in one message
|
|
85
|
+
fans them out concurrently. Subagents cannot spawn further subagents.
|
|
86
|
+
|
|
87
|
+
| Variable | Meaning |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `REGNA_TASK_DISABLED=1` | Do not register the tool. |
|
|
90
|
+
| `REGNA_TASK_TIMEOUT_MS` | Per-subagent time limit (default 300000, clamped 30s to 30m). |
|
|
91
|
+
| `REGNA_TASK_MAX_CONCURRENCY` | Concurrent subagent cap (default 4, clamped 1 to 8). |
|
|
92
|
+
|
|
93
|
+
## Project configuration (`.regna/`)
|
|
94
|
+
|
|
95
|
+
Project-level settings, skills, and prompt templates live in a `.regna/` directory at the
|
|
96
|
+
project root (loaded after the one-time project trust prompt):
|
|
97
|
+
|
|
98
|
+
- `.regna/settings.json` — project settings
|
|
99
|
+
- `.regna/skills/<name>/SKILL.md` — project skills ([Agent Skills](https://agentskills.io) format; `.agents/skills/` is also read)
|
|
100
|
+
- `.regna/prompts/*.md` — prompt templates (`/name` from inside a session)
|
|
101
|
+
|
|
102
|
+
Global state stays under `~/.regna/`.
|
|
103
|
+
|
|
79
104
|
## MCP servers
|
|
80
105
|
|
|
81
106
|
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.
|
package/dist/bin/regna.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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
|
|
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 k,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"),P="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
|
|
@@ -25,7 +25,7 @@ Environment:
|
|
|
25
25
|
REGNA_POLICY network egress guard: off | warn | enforce
|
|
26
26
|
|
|
27
27
|
Docs: https://github.com/HyperEZ/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){
|
|
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){k(N,{recursive:!0}),j(E,`${JSON.stringify(e,null,2)}
|
|
29
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
30
|
Regna Code ${_()}
|
|
31
31
|
Choose how to connect:
|
|
@@ -33,7 +33,7 @@ Docs: https://github.com/HyperEZ/regna-code
|
|
|
33
33
|
1) Online - Regna cloud (regnax.ai)
|
|
34
34
|
2) Offline - air-gapped / self-hosted gateway
|
|
35
35
|
|
|
36
|
-
`),(await e(" Select [1]: ")||"").trim()==="2"?"offline":"online"}async function se(e){let t=
|
|
36
|
+
`),(await e(" Select [1]: ")||"").trim()==="2"?"offline":"online"}async function se(e){let t=P,n=Q(t);process.stdout.write(`
|
|
37
37
|
Online mode (Regna cloud).
|
|
38
38
|
1) Get an API key at ${n}
|
|
39
39
|
2) Paste it below.
|
|
@@ -58,9 +58,9 @@ Docs: https://github.com/HyperEZ/regna-code
|
|
|
58
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.
|
|
59
59
|
`:`Not logged in.
|
|
60
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||
|
|
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||P,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","task"],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
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.
|
|
63
63
|
(To set the prompt yourself on purpose, run with --system-prompt.)
|
|
64
|
-
`),process.exit(1)),p.push("--append-system-prompt",`@${R}`));h("--model","-m")||p.push("--model",w);p.push(...u);try{
|
|
64
|
+
`),process.exit(1)),p.push("--append-system-prompt",`@${R}`));h("--model","-m")||p.push("--model",w);p.push(...u);try{k(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.
|
|
65
65
|
`),process.exit(127)),process.stderr.write(`[regna] Failed to start the runtime: ${e?.message??String(e)}
|
|
66
66
|
`),process.exit(1)});T.on("exit",(e,t)=>{if(t){process.kill(process.pid,t);return}process.exit(e??0)});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{spawn as y}from"node:child_process";import{existsSync as C}from"node:fs";import{dirname as R,join as N}from"node:path";import{fileURLToPath as I}from"node:url";import{Type as l}from"typebox";var h=100*1024,v=32*1024,P=3e5,L=3e4,U=18e5,O=4,G=5e3;function x(e){let t=Number.parseInt((e??"").trim(),10);return Number.isFinite(t)&&t>0?t:0}function K(e){let t=Number.parseInt((e??"").trim(),10);return!Number.isFinite(t)||t<=0?P:Math.min(U,Math.max(L,t))}function $(e){let t=Number.parseInt((e??"").trim(),10);return!Number.isFinite(t)||t<=0?O:Math.min(8,Math.max(1,t))}function c(e,t){return e.length<=t?e:`[... first ${e.length-t} chars truncated ...]
|
|
2
|
+
${e.slice(e.length-t)}`}function F(){let e=R(I(import.meta.url)),t=N(R(e),"bin","regna.mjs");return C(t)?t:null}function m(e,t){return{content:[{type:"text",text:e}],details:t}}function J(e){if((process.env.REGNA_TASK_DISABLED??"").trim()==="1"||x(process.env.REGNA_TASK_DEPTH)>=1)return;let t=F();if(!t)return;let p=0;e.registerTool({name:"regna_task",label:"Subagent task",description:"Delegate a self-contained subtask to a separate Regna agent and get its final report. The subagent starts with a fresh context (it does not see this conversation), works in the same directory with the same tools, and cannot spawn further subagents. Include every fact it needs in the prompt. Call regna_task multiple times in ONE message to run subtasks in parallel (e.g. investigating independent modules, running broad searches). Prefer doing quick single-file work yourself.",promptSnippet:"regna_task: delegate a subtask to a parallel subagent with fresh context",parameters:l.Object({prompt:l.String({description:"complete, self-contained task description for the subagent",maxLength:v}),name:l.Optional(l.String({description:"short label for logs (default: task)",maxLength:64}))}),async execute(H,i,d,X,j){let a=(i.name??"task").trim()||"task",g=Date.now(),o=f=>m(f,{name:a,exitCode:null,durationMs:Date.now()-g});if(typeof i.prompt!="string"||i.prompt.trim()==="")return o("Subagent failed: empty prompt.");if(d?.aborted)return o("Subagent was cancelled.");let A=$(process.env.REGNA_TASK_MAX_CONCURRENCY);if(p>=A)return o(`Subagent limit reached (${A} running). Wait for current subagents to finish, then launch the rest.`);p+=1;try{return await new Promise(f=>{let r=y(process.execPath,[t,"-p","--no-session",i.prompt],{cwd:process.cwd(),env:{...process.env,REGNA_TASK_DEPTH:String(x(process.env.REGNA_TASK_DEPTH)+1)},stdio:["ignore","pipe","pipe"]}),u="",T="",S=!1,_=null,s=n=>{S||(S=!0,_&&clearTimeout(_),clearTimeout(w),d?.removeEventListener("abort",E),f(n))},k=()=>{r.kill("SIGTERM"),_=setTimeout(()=>r.kill("SIGKILL"),G)},E=()=>{k(),s(o("Subagent was cancelled."))},w=setTimeout(()=>{k(),s(m(`Subagent timed out. Partial output:
|
|
3
|
+
${c(u,h)||"(none)"}`,{name:a,exitCode:null,durationMs:Date.now()-g}))},K(process.env.REGNA_TASK_TIMEOUT_MS));d?.addEventListener("abort",E),r.stdout.on("data",n=>{u=c(u+n.toString("utf8"),h*2)}),r.stderr.on("data",n=>{T=c(T+n.toString("utf8"),4*1024)}),r.on("error",n=>s(o(`Subagent failed to start: ${n.message}`))),r.on("close",n=>{let M=Date.now()-g,b=c(u.trim(),h);if(n===0&&b!=="")return s(m(b,{name:a,exitCode:n,durationMs:M}));let D=b||T.trim()||"(no output)";s(m(`Subagent exited with code ${n??"unknown"}:
|
|
4
|
+
${D}`,{name:a,exitCode:n,durationMs:M}))})})}finally{p-=1}}})}export{c as capTail,J as default,$ as normalizeTaskConcurrency,K as normalizeTaskTimeout,x as parseTaskDepth};
|
package/dist/postinstall.mjs
CHANGED
|
@@ -136,6 +136,24 @@ patchEngineFile(
|
|
|
136
136
|
"",
|
|
137
137
|
);
|
|
138
138
|
|
|
139
|
+
// 7) Project config dir: ".pi" -> ".regna". CONFIG_DIR_NAME (dist/config.js) reads the engine
|
|
140
|
+
// package.json's piConfig.configDir, and every project-level path (settings, skills, prompts,
|
|
141
|
+
// extensions, packages, project-trust wording) derives from it. Without this, users are told
|
|
142
|
+
// to create `.pi/` directories, leaking the hidden engine. Global state is unaffected
|
|
143
|
+
// (the launcher pins PI_CODING_AGENT_DIR to ~/.regna/engine).
|
|
144
|
+
try {
|
|
145
|
+
const pjPath = findEngineFile("package.json");
|
|
146
|
+
if (pjPath) {
|
|
147
|
+
const pj = JSON.parse(readFileSync(pjPath, "utf8"));
|
|
148
|
+
if (pj?.piConfig && pj.piConfig.configDir !== ".regna") {
|
|
149
|
+
pj.piConfig.configDir = ".regna";
|
|
150
|
+
writeFileSync(pjPath, `${JSON.stringify(pj, null, 2)}\n`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
/* best effort; never fail the install */
|
|
155
|
+
}
|
|
156
|
+
|
|
139
157
|
// The engine version these patches are written against. The package pins this exact version,
|
|
140
158
|
// so a mismatch means something forced a different engine in. We verify and warn rather than
|
|
141
159
|
// fail silently: if the engine's compiled shape changed, the patches above become no-ops.
|
|
@@ -152,6 +170,7 @@ try {
|
|
|
152
170
|
{ file: IM, label: "Themes section hidden", absent: /addLoadedSection\("Themes"/ },
|
|
153
171
|
{ file: join("dist", "core", "system-prompt.js"), label: "system-prompt de-branded", absent: /operating inside pi,/ },
|
|
154
172
|
{ file: join("dist", "core", "system-prompt.js"), label: "pi docs block removed", absent: /Pi documentation \(read only/ },
|
|
173
|
+
{ file: "package.json", label: "project config dir .regna", want: /"configDir":\s*"\.regna"/ },
|
|
155
174
|
];
|
|
156
175
|
const failed = [];
|
|
157
176
|
for (const c of checks) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperez/regna-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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,
|