@xtrn/cli 1.0.2 → 1.1.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/dist/cli.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import{spawn as V}from"node:child_process";import*as R from"node:fs";import*as _ from"node:path";import{spawn as Y}from"node:child_process";import*as x from"node:fs";import*as M from"node:os";import*as c from"node:path";function X(o){let n=o;while(!0){let s=c.join(n,"node_modules");if(x.existsSync(s))return s;let t=c.dirname(n);if(t===n)return null;n=t}}function O(o,n,s){let t=c.dirname(o),e=X(t);if(!e)console.warn("[xtrn] Warning: node_modules not found for symlink");let i=c.join(s,"node_modules");if(e&&!x.existsSync(i))x.symlinkSync(e,i,"junction");let a=`export { XTRNState } from "@xtrn/server";
3
- export { default } from "${o}";
4
- `;x.writeFileSync(c.join(s,"_cf_entry.ts"),a);let g=`name = "xtrn-dev"
2
+ import{spawn as V}from"node:child_process";import*as R from"node:fs";import*as _ from"node:path";import{spawn as Y}from"node:child_process";import*as x from"node:fs";import*as T from"node:os";import*as g from"node:path";function X(s){let n=s;while(!0){let o=g.join(n,"node_modules");if(x.existsSync(o))return o;let t=g.dirname(n);if(t===n)return null;n=t}}function A(s,n,o){let t=g.dirname(s),r=X(t);if(!r)console.warn("[xtrn] Warning: node_modules not found for symlink");let i=g.join(o,"node_modules");if(r&&!x.existsSync(i))x.symlinkSync(r,i,"junction");let f=`export { XTRNState } from "@xtrn/server";
3
+ export { default } from "${s}";
4
+ `;x.writeFileSync(g.join(o,"_cf_entry.ts"),f);let u=`name = "xtrn-dev"
5
5
  main = "_cf_entry.ts"
6
6
  compatibility_date = "2026-02-14"
7
7
  compatibility_flags = ["nodejs_compat"]
8
8
 
9
9
  [vars]
10
- ${Object.entries(n).map(([m,l])=>`${m} = "${l}"`).join(`
10
+ ${Object.entries(n).map(([a,l])=>`${a} = "${l}"`).join(`
11
11
  `)}
12
12
 
13
13
  [[durable_objects.bindings]]
@@ -17,9 +17,9 @@ class_name = "XTRNState"
17
17
  [[migrations]]
18
18
  tag = "v1"
19
19
  new_sqlite_classes = ["XTRNState"]
20
- `;x.writeFileSync(c.join(s,"wrangler.toml"),g)}async function A(o,n){let s=c.dirname(o),t=c.basename(o),e=c.join(n,t);x.copyFileSync(o,e);let i=X(s);if(!i)console.warn("[xtrn] Warning: node_modules not found for symlink");let a=c.join(n,"node_modules");if(i&&!x.existsSync(a))x.symlinkSync(i,a,"junction");let g=`export { XTRNState } from "@xtrn/server";
20
+ `;x.writeFileSync(g.join(o,"wrangler.toml"),u)}async function O(s,n){let o=g.dirname(s),t=g.basename(s),r=g.join(n,t);x.copyFileSync(s,r);let i=X(o);if(!i)console.warn("[xtrn] Warning: node_modules not found for symlink");let f=g.join(n,"node_modules");if(i&&!x.existsSync(f))x.symlinkSync(i,f,"junction");let u=`export { XTRNState } from "@xtrn/server";
21
21
  export { default } from "${`./${t}`}";
22
- `,m=c.join(n,"_cf_entry.ts");x.writeFileSync(m,g);let l=`name = "xtrn-dev"
22
+ `,a=g.join(n,"_cf_entry.ts");x.writeFileSync(a,u);let l=`name = "xtrn-dev"
23
23
  main = "_cf_entry.ts"
24
24
  compatibility_date = "2026-02-14"
25
25
  compatibility_flags = ["nodejs_compat"]
@@ -31,15 +31,15 @@ class_name = "XTRNState"
31
31
  [[migrations]]
32
32
  tag = "v1"
33
33
  new_sqlite_classes = ["XTRNState"]
34
- `;return x.writeFileSync(c.join(n,"wrangler.toml"),l),await Z(n),c.join(n,"_cf_entry.js")}function Z(o){return new Promise((n,s)=>{let t=Y("wrangler",["deploy","--dry-run","--outdir","."],{cwd:o,stdio:["ignore","pipe","pipe"]}),e="";t.stdout?.on("data",()=>{}),t.stderr?.on("data",(i)=>{e+=i.toString()}),t.on("error",(i)=>{s(Error(`Failed to spawn wrangler: ${i.message}`))}),t.on("close",(i)=>{if(i!==0){s(Error(`wrangler deploy --dry-run failed (exit ${i}):
35
- ${e}`));return}n()})})}function $(){return x.mkdtempSync(c.join(M.tmpdir(),"xtrn-dev-"))}function S(o){x.rmSync(o,{recursive:!0,force:!0})}import*as N from"node:fs";import*as F from"node:path";import{Miniflare as G}from"miniflare";function K(o,n,s,t){let e=t||F.dirname(o);return{modules:!0,scriptPath:o,modulesRoot:e,compatibilityDate:"2026-02-14",compatibilityFlags:["nodejs_compat"],durableObjects:{XTRN_STATE:"XTRNState"},durableObjectsPersist:!0,port:n,bindings:s}}async function C(o,n,s,t){let e=K(o,n,s,t),i=new G(e);return await i.ready,i}function q(o){if(!N.existsSync(o))return{};let n=N.readFileSync(o,"utf-8"),s={};for(let t of n.split(`
36
- `)){let e=t.trim();if(!e||e.startsWith("#"))continue;let i=e.indexOf("=");if(i===-1)continue;s[e.slice(0,i)]=e.slice(i+1)}return s}async function W(o){return await(await o.dispatchFetch("http://localhost/details")).json()}var r={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",magenta:"\x1B[35m",white:"\x1B[37m",red:"\x1B[31m",gray:"\x1B[90m"};function P(o){let n="./index.ts",s=1234;for(let t of o)if(t.startsWith("--port="))s=Number.parseInt(t.slice(t.indexOf("=")+1),10);else if(!t.startsWith("--"))n=t;return{entryPoint:_.resolve(n),port:s}}async function k(o){for(let s=0;s<5;s++){try{let t=await fetch(`http://localhost:${o}/details`);if(t.ok)return await t.json()}catch{}await new Promise((t)=>setTimeout(t,200))}throw Error("Failed to fetch /details after server ready")}function D(o,n){let s=`http://localhost:${o}`;if(console.log(""),console.log(` ${r.bold}${r.cyan}${n.name}${r.reset} ${r.dim}${n.version}${r.reset}`),console.log(` ${r.green}➜${r.reset} ${r.bold}${s}${r.reset}`),console.log(""),n.tools.length>0){console.log(` ${r.bold}${r.white}Tools${r.reset}`);for(let t of n.tools){let e=t.tags.length>0?` ${r.dim}${t.tags.map((i)=>`[${i}]`).join(" ")}${r.reset}`:"";console.log(` ${r.cyan}${t.name}${r.reset}${e} ${r.gray}— ${t.description}${r.reset}`)}console.log("")}if(n.config.length>0){console.log(` ${r.bold}${r.white}Config${r.reset}`);for(let t of n.config)console.log(` ${r.yellow}${t.key}${r.reset} ${r.dim}${t.type}${r.reset}`);console.log("")}if(n.oauth)console.log(` ${r.bold}${r.white}OAuth${r.reset} ${r.magenta}${n.oauth.provider}${r.reset}`),console.log(` ${r.dim}authorize${r.reset} ${n.oauth.authorization_url}`),console.log(` ${r.dim}token${r.reset} ${n.oauth.token_url}`),console.log(` ${r.dim}callback${r.reset} ${n.oauth.callback_url}`),console.log(` ${r.dim}scopes${r.reset} ${n.oauth.scopes.join(", ")}`),console.log("")}function nn(o,n){let s=_.join(o,"wrangler.toml"),t=V("npx",["wrangler","dev","--config",s,"--port",String(n)],{cwd:o,stdio:["ignore","pipe","pipe"]}),e="",i=new Promise((a,f)=>{let g=(l)=>{if(l.toString().includes("Ready on"))t.stdout?.off("data",g),a()},m=(l)=>{e+=l.toString()};t.stdout?.on("data",g),t.stderr?.on("data",m),t.on("error",(l)=>f(l)),t.on("close",(l)=>{if(l!==0&&l!==null)f(Error(e.trim()||`wrangler exited with code ${l}`))})});return i.then(()=>{e="",t.stderr?.on("data",(a)=>{let f=a.toString();if(f.includes("ERROR")||f.includes("Error")||f.includes("error:"))process.stderr.write(`${r.red}[xtrn dev]${r.reset} ${f}`)})}),{proc:t,ready:i}}async function I(o){let{entryPoint:n,port:s}=P(o),t=_.dirname(n);if(!R.existsSync(n))console.error(`${r.red}[xtrn dev]${r.reset} Entry point not found: ${n}`),process.exit(1);if(!R.existsSync(_.join(t,"package.json")))console.error(`${r.red}[xtrn dev]${r.reset} No package.json found in ${t}`),process.exit(1);let e=_.join(t,".dev.vars"),i=q(e),a=$();O(n,i,a);let f=null,g=()=>{if(console.log(`
37
- ${r.dim}[xtrn dev] Shutting down...${r.reset}`),f)f.kill(),f=null;S(a),process.exit(0)};process.on("SIGINT",g),process.on("SIGTERM",g);try{console.log(`${r.dim}[xtrn dev] Starting...${r.reset}`);let m=nn(a,s);f=m.proc,f.on("error",(u)=>{console.error(`${r.red}[xtrn dev]${r.reset} Failed to start: ${u.message}`),S(a),process.exit(1)}),f.on("close",(u)=>{if(u!==0&&u!==null)console.error(`${r.red}[xtrn dev]${r.reset} Process exited with code ${u}`);S(a),process.exit(u??1)}),await m.ready;let l=await k(s);D(s,l),await new Promise(()=>{})}catch(m){if(f)f.kill();if(S(a),m instanceof Error)console.error(`${r.red}[xtrn dev]${r.reset} ${m.message}`);process.exit(1)}}import{mkdir as U,readFile as tn,writeFile as sn}from"node:fs/promises";import{dirname as on,join as h}from"node:path";import{fileURLToPath as en}from"node:url";async function H(o){let n=o[0],s=o[1]??"v1.0.0";if(!n)console.error("Error: <name> argument is required"),console.error("Usage: xtrn new <name> [version]"),process.exit(1);let t=s.startsWith("v")?s:`v${s}`,e=t.replace(/^v/,"");if(!/^\d+\.\d+\.\d+$/.test(e))console.error(`Error: Invalid version "${s}". Expected semver (e.g., v1.0.0)`),process.exit(1);let i=en(import.meta.url),a=on(i),f=h(a,"../templates"),g=h(process.cwd(),n);try{await U(g,{recursive:!1})}catch(m){if(m instanceof Error&&"code"in m&&m.code==="EEXIST")console.error(`Error: Directory "${n}" already exists`),process.exit(1);throw m}try{let m=[{src:"index.ts.template",dst:"index.ts"},{src:"package.json.template",dst:"package.json"},{src:"tsconfig.json.template",dst:"tsconfig.json"}];for(let l of m){let u=h(f,l.src),j=h(g,l.dst),w=await tn(u,"utf-8");if(w=w.replace(/\{\{NAME\}\}/g,n),w=w.replace(/\{\{VERSION\}\}/g,t),w=w.replace(/\{\{VERSION_BARE\}\}/g,e),l.dst==="package.json"){let d=JSON.parse(w);d.dependencies["@xtrn/server"]="^1.0.0",d.devDependencies["@xtrn/cli"]="^1.0.0",d.scripts.submit="xtrn submit",w=`${JSON.stringify(d,null,"\t")}
38
- `}await sn(j,w,"utf-8")}console.log(`✓ Created ${n}/`),console.log(""),console.log("Next steps:"),console.log(` cd ${n}`),console.log(" bun install"),console.log(" bun run dev")}catch(m){try{await U(g,{recursive:!0})}catch{}throw m}}import{spawn as rn}from"node:child_process";import*as y from"node:fs";import{cp as J,mkdir as z,readFile as an,readdir as fn,writeFile as mn}from"node:fs/promises";import*as T from"node:os";import*as v from"node:path";var p="xtrnai/servers",ln=new Set(["node_modules","bun.lock",".dev.vars","dist","_cf_entry.ts","wrangler.toml",".wrangler",".git",".github"]);function b(o,n,s){return new Promise((t)=>{let e=rn(o,n,{stdio:"pipe",cwd:s?.cwd}),i="",a="";e.stdout.on("data",(f)=>{i+=f.toString()}),e.stderr.on("data",(f)=>{a+=f.toString()}),e.on("close",(f)=>{t({stdout:i.trim(),stderr:a.trim(),code:f??1})}),e.on("error",()=>{t({stdout:"",stderr:`Failed to execute: ${o}`,code:1})})})}async function xn(){if((await b("gh",["--version"])).code!==0)console.error("[xtrn submit] GitHub CLI (gh) is not installed."),console.error(""),console.error("Install it:"),console.error(" macOS: brew install gh"),console.error(" Linux: https://github.com/cli/cli/blob/trunk/docs/install_linux.md"),console.error(" Windows: winget install --id GitHub.cli"),console.error(""),console.error("Then authenticate:"),console.error(" gh auth login"),process.exit(1);if((await b("gh",["auth","status"])).code!==0)console.error("[xtrn submit] GitHub CLI is not authenticated."),console.error(""),console.error("Run:"),console.error(" gh auth login"),process.exit(1)}function cn(o){if(!y.existsSync(v.join(o,"index.ts")))console.error("[xtrn submit] No index.ts found in current directory."),console.error("Run this command from your XTRN server project root."),process.exit(1);if(!y.existsSync(v.join(o,"package.json")))console.error("[xtrn submit] No package.json found in current directory."),console.error("Run this command from your XTRN server project root."),process.exit(1)}async function gn(o){let n=v.resolve(o,"index.ts"),s=$(),t=null;try{console.log("[xtrn submit] Bundling server...");let e=await A(n,s);console.log("[xtrn submit] Starting headless Miniflare..."),t=await C(e,0,{});let i=await W(t),a=i.name,f=i.version;if(typeof a!=="string"||!a)throw Error("Server /details missing 'name' field");if(typeof f!=="string"||!f)throw Error("Server /details missing 'version' field");return console.log(`[xtrn submit] Detected: ${a} v${f}`),{name:a,version:f}}finally{if(t)await t.dispose();S(s)}}async function B(o,n){await z(n,{recursive:!0});let s=await fn(o,{withFileTypes:!0});for(let t of s){if(ln.has(t.name))continue;let e=v.join(o,t.name),i=v.join(n,t.name);if(t.isDirectory())await B(e,i);else await J(e,i)}}function un(o){let n=JSON.parse(o),s=(t)=>{if(!t||typeof t!=="object")return;let e={};for(let[i,a]of Object.entries(t))if(typeof a==="string"&&(a.startsWith("file:")||a.startsWith("link:")||a.startsWith("workspace:")))e[i]="^1.0.0";else e[i]=a;return e};if(n.dependencies)n.dependencies=s(n.dependencies);if(n.devDependencies)n.devDependencies=s(n.devDependencies);return`${JSON.stringify(n,null,"\t")}
39
- `}async function vn(o,n){console.log("[xtrn submit] Preparing files..."),await B(o,n);let s=v.join(n,"package.json");if(y.existsSync(s)){let t=await an(s,"utf-8"),e=un(t);await mn(s,e,"utf-8"),console.log("[xtrn submit] Rewrote package.json (local deps → ^1.0.0)")}}async function yn(o,n,s){let t=y.mkdtempSync(v.join(T.tmpdir(),"xtrn-submit-")),e=v.join(t,"xtrn-servers");try{console.log(`[xtrn submit] Forking ${p}...`);let i=await b("gh",["repo","fork",p,"--clone=true","--default-branch-only"],{cwd:t});if(i.code!==0&&!i.stderr.includes("already exists"))throw Error(`Fork failed: ${i.stderr}`);if(!y.existsSync(e))throw Error(`Expected cloned repo at ${e}, not found. Output: ${i.stdout} ${i.stderr}`);let a=`submit/${o}/v${n}`;console.log(`[xtrn submit] Creating branch ${a}...`);let f=await b("git",["checkout","-b",a],{cwd:e});if(f.code!==0)throw Error(`Branch creation failed: ${f.stderr}`);let g=v.join(e,"servers",o,`v${n}`);await z(g,{recursive:!0}),await J(s,g,{recursive:!0}),console.log("[xtrn submit] Committing...");let m=await b("git",["add","."],{cwd:e});if(m.code!==0)throw Error(`git add failed: ${m.stderr}`);let l=await b("git",["commit","-m",`Add ${o} v${n}`],{cwd:e});if(l.code!==0)throw Error(`git commit failed: ${l.stderr}`);console.log("[xtrn submit] Pushing...");let u=await b("git",["push","origin",a],{cwd:e});if(u.code!==0)throw Error(`git push failed: ${u.stderr}`);console.log("[xtrn submit] Creating pull request...");let j=await b("gh",["pr","create","--repo",p,"--title",`Add ${o} v${n}`,"--body",`Automated submission via \`xtrn submit\` CLI.
34
+ `;return x.writeFileSync(g.join(n,"wrangler.toml"),l),await Z(n),g.join(n,"_cf_entry.js")}function Z(s){return new Promise((n,o)=>{let t=Y("wrangler",["deploy","--dry-run","--outdir","."],{cwd:s,stdio:["ignore","pipe","pipe"]}),r="";t.stdout?.on("data",()=>{}),t.stderr?.on("data",(i)=>{r+=i.toString()}),t.on("error",(i)=>{o(Error(`Failed to spawn wrangler: ${i.message}`))}),t.on("close",(i)=>{if(i!==0){o(Error(`wrangler deploy --dry-run failed (exit ${i}):
35
+ ${r}`));return}n()})})}function d(){return x.mkdtempSync(g.join(T.tmpdir(),"xtrn-dev-"))}function S(s){x.rmSync(s,{recursive:!0,force:!0})}import*as $ from"node:fs";import*as F from"node:path";import{Miniflare as G}from"miniflare";function K(s,n,o,t){let r=t||F.dirname(s);return{modules:!0,scriptPath:s,modulesRoot:r,compatibilityDate:"2026-02-14",compatibilityFlags:["nodejs_compat"],durableObjects:{XTRN_STATE:"XTRNState"},durableObjectsPersist:!0,port:n,bindings:o}}async function C(s,n,o,t){let r=K(s,n,o,t),i=new G(r);return await i.ready,i}function q(s){if(!$.existsSync(s))return{};let n=$.readFileSync(s,"utf-8"),o={};for(let t of n.split(`
36
+ `)){let r=t.trim();if(!r||r.startsWith("#"))continue;let i=r.indexOf("=");if(i===-1)continue;o[r.slice(0,i)]=r.slice(i+1)}return o}async function W(s){return await(await s.dispatchFetch("http://localhost/details")).json()}var e={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",magenta:"\x1B[35m",white:"\x1B[37m",red:"\x1B[31m",gray:"\x1B[90m"};function P(s){let n="./index.ts",o=1234;for(let t of s)if(t.startsWith("--port="))o=Number.parseInt(t.slice(t.indexOf("=")+1),10);else if(!t.startsWith("--"))n=t;return{entryPoint:_.resolve(n),port:o}}async function k(s){for(let o=0;o<5;o++){try{let t=await fetch(`http://localhost:${s}/details`);if(t.ok)return await t.json()}catch{}await new Promise((t)=>setTimeout(t,200))}throw Error("Failed to fetch /details after server ready")}function D(s,n){let o=`http://localhost:${s}`;if(console.log(""),console.log(` ${e.bold}${e.cyan}${n.name}${e.reset} ${e.dim}${n.version}${e.reset}`),console.log(` ${e.green}➜${e.reset} ${e.bold}${o}${e.reset}`),console.log(""),n.tools.length>0){console.log(` ${e.bold}${e.white}Tools${e.reset}`);for(let t of n.tools){let r=t.tags.length>0?` ${e.dim}${t.tags.map((i)=>`[${i}]`).join(" ")}${e.reset}`:"";console.log(` ${e.cyan}${t.name}${e.reset}${r} ${e.gray}— ${t.description}${e.reset}`)}console.log("")}if(n.config.length>0){console.log(` ${e.bold}${e.white}Config${e.reset}`);for(let t of n.config)console.log(` ${e.yellow}${t.key}${e.reset} ${e.dim}${t.type}${e.reset}`);console.log("")}if(n.requiredEnv.length>0){console.log(` ${e.bold}${e.white}Required Env${e.reset}`);for(let t of n.requiredEnv)console.log(` ${e.yellow}${t}${e.reset}`);console.log("")}if(n.oauth)console.log(` ${e.bold}${e.white}OAuth${e.reset} ${e.magenta}${n.oauth.provider}${e.reset}`),console.log(` ${e.dim}authorize${e.reset} ${n.oauth.authorization_url}`),console.log(` ${e.dim}token${e.reset} ${n.oauth.token_url}`),console.log(` ${e.dim}callback${e.reset} ${n.oauth.callback_url}`),console.log(` ${e.dim}scopes${e.reset} ${n.oauth.scopes.join(", ")}`),console.log("")}function nn(s,n){let o=_.join(s,"wrangler.toml"),t=V("npx",["wrangler","dev","--config",o,"--port",String(n)],{cwd:s,stdio:["ignore","pipe","pipe"]}),r="",i=new Promise((f,m)=>{let u=(l)=>{if(l.toString().includes("Ready on"))t.stdout?.off("data",u),f()},a=(l)=>{r+=l.toString()};t.stdout?.on("data",u),t.stderr?.on("data",a),t.on("error",(l)=>m(l)),t.on("close",(l)=>{if(l!==0&&l!==null)m(Error(r.trim()||`wrangler exited with code ${l}`))})});return i.then(()=>{r="",t.stderr?.on("data",(f)=>{let m=f.toString();if(m.includes("ERROR")||m.includes("Error")||m.includes("error:"))process.stderr.write(`${e.red}[xtrn dev]${e.reset} ${m}`)})}),{proc:t,ready:i}}async function I(s){let{entryPoint:n,port:o}=P(s),t=_.dirname(n);if(!R.existsSync(n))console.error(`${e.red}[xtrn dev]${e.reset} Entry point not found: ${n}`),process.exit(1);if(!R.existsSync(_.join(t,"package.json")))console.error(`${e.red}[xtrn dev]${e.reset} No package.json found in ${t}`),process.exit(1);let r=_.join(t,".dev.vars"),i=q(r),f=d();A(n,i,f);let m=null,u=()=>{if(console.log(`
37
+ ${e.dim}[xtrn dev] Shutting down...${e.reset}`),m)m.kill(),m=null;S(f),process.exit(0)};process.on("SIGINT",u),process.on("SIGTERM",u);try{console.log(`${e.dim}[xtrn dev] Starting...${e.reset}`);let a=nn(f,o);m=a.proc,m.on("error",(c)=>{console.error(`${e.red}[xtrn dev]${e.reset} Failed to start: ${c.message}`),S(f),process.exit(1)}),m.on("close",(c)=>{if(c!==0&&c!==null)console.error(`${e.red}[xtrn dev]${e.reset} Process exited with code ${c}`);S(f),process.exit(c??1)}),await a.ready;let l=await k(o);D(o,l),await new Promise(()=>{})}catch(a){if(m)m.kill();if(S(f),a instanceof Error)console.error(`${e.red}[xtrn dev]${e.reset} ${a.message}`);process.exit(1)}}import{mkdir as U,readFile as tn,writeFile as on}from"node:fs/promises";import{dirname as sn,join as N}from"node:path";import{fileURLToPath as en}from"node:url";async function H(s){let n=s[0],o=s[1]??"v1.0.0";if(!n)console.error("Error: <name> argument is required"),console.error("Usage: xtrn new <name> [version]"),process.exit(1);let t=o.startsWith("v")?o:`v${o}`,r=t.replace(/^v/,"");if(!/^\d+\.\d+\.\d+$/.test(r))console.error(`Error: Invalid version "${o}". Expected semver (e.g., v1.0.0)`),process.exit(1);let i=en(import.meta.url),f=sn(i),m=N(f,"../templates"),u=N(process.cwd(),n);try{await U(u,{recursive:!1})}catch(a){if(a instanceof Error&&"code"in a&&a.code==="EEXIST")console.error(`Error: Directory "${n}" already exists`),process.exit(1);throw a}try{let a=[{src:"index.ts.template",dst:"index.ts"},{src:"package.json.template",dst:"package.json"},{src:"tsconfig.json.template",dst:"tsconfig.json"},{src:"README.md.template",dst:"README.md"}];for(let l of a){let c=N(m,l.src),j=N(u,l.dst),w=await tn(c,"utf-8");if(w=w.replace(/\{\{NAME\}\}/g,n),w=w.replace(/\{\{VERSION\}\}/g,t),w=w.replace(/\{\{VERSION_BARE\}\}/g,r),l.dst==="package.json"){let E=JSON.parse(w);E.dependencies["@xtrn/server"]="^1.0.0",E.devDependencies["@xtrn/cli"]="^1.0.0",E.scripts.submit="xtrn submit",w=`${JSON.stringify(E,null,"\t")}
38
+ `}await on(j,w,"utf-8")}console.log(`✓ Created ${n}/`),console.log(""),console.log("Next steps:"),console.log(` cd ${n}`),console.log(" bun install"),console.log(" bun run dev")}catch(a){try{await U(u,{recursive:!0})}catch{}throw a}}import{spawn as rn}from"node:child_process";import*as y from"node:fs";import{cp as J,mkdir as z,readFile as fn,readdir as mn,writeFile as an}from"node:fs/promises";import*as M from"node:os";import*as v from"node:path";var p="xtrnai/servers",ln=new Set(["node_modules","bun.lock",".dev.vars","dist","_cf_entry.ts","wrangler.toml",".wrangler",".git",".github"]);function b(s,n,o){return new Promise((t)=>{let r=rn(s,n,{stdio:"pipe",cwd:o?.cwd}),i="",f="";r.stdout.on("data",(m)=>{i+=m.toString()}),r.stderr.on("data",(m)=>{f+=m.toString()}),r.on("close",(m)=>{t({stdout:i.trim(),stderr:f.trim(),code:m??1})}),r.on("error",()=>{t({stdout:"",stderr:`Failed to execute: ${s}`,code:1})})})}async function xn(){if((await b("gh",["--version"])).code!==0)console.error("[xtrn submit] GitHub CLI (gh) is not installed."),console.error(""),console.error("Install it:"),console.error(" macOS: brew install gh"),console.error(" Linux: https://github.com/cli/cli/blob/trunk/docs/install_linux.md"),console.error(" Windows: winget install --id GitHub.cli"),console.error(""),console.error("Then authenticate:"),console.error(" gh auth login"),process.exit(1);if((await b("gh",["auth","status"])).code!==0)console.error("[xtrn submit] GitHub CLI is not authenticated."),console.error(""),console.error("Run:"),console.error(" gh auth login"),process.exit(1)}function gn(s){if(!y.existsSync(v.join(s,"index.ts")))console.error("[xtrn submit] No index.ts found in current directory."),console.error("Run this command from your XTRN server project root."),process.exit(1);if(!y.existsSync(v.join(s,"package.json")))console.error("[xtrn submit] No package.json found in current directory."),console.error("Run this command from your XTRN server project root."),process.exit(1)}async function un(s){let n=v.resolve(s,"index.ts"),o=d(),t=null;try{console.log("[xtrn submit] Bundling server...");let r=await O(n,o);console.log("[xtrn submit] Starting headless Miniflare..."),t=await C(r,0,{});let i=await W(t),f=i.name,m=i.version;if(typeof f!=="string"||!f)throw Error("Server /details missing 'name' field");if(typeof m!=="string"||!m)throw Error("Server /details missing 'version' field");return console.log(`[xtrn submit] Detected: ${f} v${m}`),{name:f,version:m}}finally{if(t)await t.dispose();S(o)}}async function B(s,n){await z(n,{recursive:!0});let o=await mn(s,{withFileTypes:!0});for(let t of o){if(ln.has(t.name))continue;let r=v.join(s,t.name),i=v.join(n,t.name);if(t.isDirectory())await B(r,i);else await J(r,i)}}function cn(s){let n=JSON.parse(s),o=(t)=>{if(!t||typeof t!=="object")return;let r={};for(let[i,f]of Object.entries(t))if(typeof f==="string"&&(f.startsWith("file:")||f.startsWith("link:")||f.startsWith("workspace:")))r[i]="^1.0.0";else r[i]=f;return r};if(n.dependencies)n.dependencies=o(n.dependencies);if(n.devDependencies)n.devDependencies=o(n.devDependencies);return`${JSON.stringify(n,null,"\t")}
39
+ `}async function vn(s,n){console.log("[xtrn submit] Preparing files..."),await B(s,n);let o=v.join(n,"package.json");if(y.existsSync(o)){let t=await fn(o,"utf-8"),r=cn(t);await an(o,r,"utf-8"),console.log("[xtrn submit] Rewrote package.json (local deps → ^1.0.0)")}}async function yn(s,n,o){let t=y.mkdtempSync(v.join(M.tmpdir(),"xtrn-submit-")),r=v.join(t,"xtrn-servers");try{console.log(`[xtrn submit] Forking ${p}...`);let i=await b("gh",["repo","fork",p,"--clone=true","--default-branch-only"],{cwd:t});if(i.code!==0&&!i.stderr.includes("already exists"))throw Error(`Fork failed: ${i.stderr}`);if(!y.existsSync(r))throw Error(`Expected cloned repo at ${r}, not found. Output: ${i.stdout} ${i.stderr}`);let f=`submit/${s}/v${n}`;console.log(`[xtrn submit] Creating branch ${f}...`);let m=await b("git",["checkout","-b",f],{cwd:r});if(m.code!==0)throw Error(`Branch creation failed: ${m.stderr}`);let u=v.join(r,"servers",s,`v${n}`);await z(u,{recursive:!0}),await J(o,u,{recursive:!0}),console.log("[xtrn submit] Committing...");let a=await b("git",["add","."],{cwd:r});if(a.code!==0)throw Error(`git add failed: ${a.stderr}`);let l=await b("git",["commit","-m",`Add ${s} v${n}`],{cwd:r});if(l.code!==0)throw Error(`git commit failed: ${l.stderr}`);console.log("[xtrn submit] Pushing...");let c=await b("git",["push","origin",f],{cwd:r});if(c.code!==0)throw Error(`git push failed: ${c.stderr}`);console.log("[xtrn submit] Creating pull request...");let j=await b("gh",["pr","create","--repo",p,"--title",`Add ${s} v${n}`,"--body",`Automated submission via \`xtrn submit\` CLI.
40
40
 
41
- **Server:** ${o}
42
- **Version:** v${n}`],{cwd:e});if(j.code!==0)throw Error(`PR creation failed: ${j.stderr}`);return j.stdout}finally{y.rmSync(t,{recursive:!0,force:!0})}}async function L(o){let n=process.cwd();await xn(),cn(n);let{name:s,version:t}=await gn(n),e=y.mkdtempSync(v.join(T.tmpdir(),"xtrn-prepared-"));try{await vn(n,e);let i=await yn(s,t,e);console.log(""),console.log("✓ Pull request created!"),console.log(` ${i}`),console.log(""),console.log("Your server will be reviewed and deployed automatically once merged.")}finally{y.rmSync(e,{recursive:!0,force:!0})}}var Q="1.0.0",E=`
41
+ **Server:** ${s}
42
+ **Version:** v${n}`],{cwd:r});if(j.code!==0)throw Error(`PR creation failed: ${j.stderr}`);return j.stdout}finally{y.rmSync(t,{recursive:!0,force:!0})}}async function L(s){let n=process.cwd();await xn(),gn(n);let{name:o,version:t}=await un(n),r=y.mkdtempSync(v.join(M.tmpdir(),"xtrn-prepared-"));try{await vn(n,r);let i=await yn(o,t,r);console.log(""),console.log("✓ Pull request created!"),console.log(` ${i}`),console.log(""),console.log("Your server will be reviewed and deployed automatically once merged.")}finally{y.rmSync(r,{recursive:!0,force:!0})}}var Q="1.0.0",h=`
43
43
  xtrn v${Q} — XTRN server development toolkit
44
44
 
45
45
  Usage:
@@ -63,4 +63,4 @@ Examples:
63
63
  xtrn dev ./server.ts Start dev server with custom entry
64
64
  xtrn dev --port=3000 Start dev server on port 3000
65
65
  xtrn submit Fork xtrn-servers and open PR
66
- `.trim();async function wn(){let o=process.argv.slice(2),n=o[0];if(!n||n==="--help"||n==="-h")console.log(E),process.exit(0);if(n==="--version"||n==="-v")console.log(`xtrn v${Q}`),process.exit(0);if(n==="new"){let s=o.slice(1);if(s.includes("--help")||s.includes("-h"))console.log(E),process.exit(0);await H(s);return}if(n==="dev"){let s=o.slice(1);if(s.includes("--help")||s.includes("-h"))console.log(E),process.exit(0);await I(s);return}if(n==="submit"){let s=o.slice(1);if(s.includes("--help")||s.includes("-h"))console.log(E),process.exit(0);await L(s);return}console.error(`Unknown command: ${n}`),console.error('Run "xtrn --help" for usage.'),process.exit(1)}wn().catch((o)=>{console.error("[xtrn] Fatal:",o),process.exit(1)});
66
+ `.trim();async function wn(){let s=process.argv.slice(2),n=s[0];if(!n||n==="--help"||n==="-h")console.log(h),process.exit(0);if(n==="--version"||n==="-v")console.log(`xtrn v${Q}`),process.exit(0);if(n==="new"){let o=s.slice(1);if(o.includes("--help")||o.includes("-h"))console.log(h),process.exit(0);await H(o);return}if(n==="dev"){let o=s.slice(1);if(o.includes("--help")||o.includes("-h"))console.log(h),process.exit(0);await I(o);return}if(n==="submit"){let o=s.slice(1);if(o.includes("--help")||o.includes("-h"))console.log(h),process.exit(0);await L(o);return}console.error(`Unknown command: ${n}`),console.error('Run "xtrn --help" for usage.'),process.exit(1)}wn().catch((s)=>{console.error("[xtrn] Fatal:",s),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtrn/cli",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "xtrn": "./dist/cli.js"
@@ -19,10 +19,10 @@ Then open `http://localhost:1234/details`.
19
19
 
20
20
  ## Server API
21
21
 
22
- Import everything from `@xtrn/server/server`:
22
+ Import everything from `@xtrn/server`:
23
23
 
24
24
  ```typescript
25
- import { XTRNServer, defineConfig, ToolTag } from "@xtrn/server/server";
25
+ import { XTRNServer, defineConfig, ToolTag } from "@xtrn/server";
26
26
  import { z } from "zod";
27
27
  ```
28
28
 
@@ -31,7 +31,7 @@ import { z } from "zod";
31
31
  No configuration or authentication required.
32
32
 
33
33
  ```typescript
34
- import { XTRNServer, defineConfig } from "@xtrn/server/server";
34
+ import { XTRNServer, defineConfig } from "@xtrn/server";
35
35
  import { z } from "zod";
36
36
 
37
37
  const server = new XTRNServer({
@@ -59,7 +59,7 @@ export default server;
59
59
  Requires users to provide configuration values (API keys, preferences).
60
60
 
61
61
  ```typescript
62
- import { XTRNServer, defineConfig } from "@xtrn/server/server";
62
+ import { XTRNServer, defineConfig } from "@xtrn/server";
63
63
  import { z } from "zod";
64
64
 
65
65
  const server = new XTRNServer({
@@ -94,7 +94,7 @@ export default server;
94
94
  Requires users to complete an OAuth flow. The platform provides a fresh access token directly.
95
95
 
96
96
  ```typescript
97
- import { XTRNServer, defineConfig } from "@xtrn/server/server";
97
+ import { XTRNServer, defineConfig } from "@xtrn/server";
98
98
  import { z } from "zod";
99
99
 
100
100
  const server = new XTRNServer({
@@ -133,7 +133,7 @@ export default server;
133
133
  Combines both user configuration and OAuth authentication.
134
134
 
135
135
  ```typescript
136
- import { XTRNServer, defineConfig, ToolTag } from "@xtrn/server/server";
136
+ import { XTRNServer, defineConfig, ToolTag } from "@xtrn/server";
137
137
  import { z } from "zod";
138
138
 
139
139
  const server = new XTRNServer({
@@ -173,6 +173,39 @@ server.registerTool({
173
173
  export default server;
174
174
  ```
175
175
 
176
+ ### Env-Only Server
177
+
178
+ Requires specific environment variables to be set by the deployer (e.g., internal API keys, base URLs).
179
+
180
+ ```typescript
181
+ import { XTRNServer, defineConfig } from "@xtrn/server";
182
+ import { z } from "zod";
183
+
184
+ const server = new XTRNServer({
185
+ name: "env-server",
186
+ version: "1.0.0",
187
+ config: defineConfig({
188
+ requiredEnv: ["API_KEY", "BASE_URL"],
189
+ }),
190
+ });
191
+
192
+ server.registerTool({
193
+ name: "fetch",
194
+ description: "Fetch data from internal API",
195
+ schema: z.object({ endpoint: z.string() }),
196
+ handler: async (ctx) => {
197
+ // ctx.env.API_KEY — string (typed from requiredEnv)
198
+ // ctx.env.BASE_URL — string (typed from requiredEnv)
199
+ const response = await fetch(`${ctx.env.BASE_URL}/${ctx.req.endpoint}`, {
200
+ headers: { Authorization: `Bearer ${ctx.env.API_KEY}` },
201
+ });
202
+ return ctx.res.json(await response.json());
203
+ },
204
+ });
205
+
206
+ export default server;
207
+ ```
208
+
176
209
  ## OAuth Configuration
177
210
 
178
211
  ```typescript
@@ -200,6 +233,45 @@ OAUTH_CLIENT_SECRET=your-client-secret
200
233
  OAUTH_CALLBACK_URL=http://localhost:1234/auth/callback
201
234
  ```
202
235
 
236
+ ## Required Environment Variables
237
+
238
+ Server developers can declare environment variables that must be set by the deployer. Useful for internal API keys, base URLs, or other secrets that shouldn't be managed by end users.
239
+
240
+ ### Declaration
241
+
242
+ ```typescript
243
+ config: defineConfig({
244
+ requiredEnv: ["API_KEY", "BASE_URL"],
245
+ })
246
+ ```
247
+
248
+ ### Access in Handlers
249
+
250
+ Environment variables are fully typed from the `requiredEnv` array:
251
+
252
+ ```typescript
253
+ handler: async (ctx) => {
254
+ ctx.env.API_KEY // string
255
+ ctx.env.BASE_URL // string
256
+ }
257
+ ```
258
+
259
+ ### Local Development
260
+
261
+ Add to `.dev.vars` in your server directory:
262
+
263
+ ```env
264
+ API_KEY=your-api-key
265
+ BASE_URL=https://api.example.com
266
+ ```
267
+
268
+ ### Validation
269
+
270
+ - `/details` returns `requiredEnv` array (no validation on this route)
271
+ - Tool calls validate all required env vars are present
272
+ - Missing vars return HTTP 500 listing which vars are missing
273
+ - Names starting with `OAUTH_` or `XTRN_` are reserved and rejected at construction
274
+
203
275
  ## Context Object
204
276
 
205
277
  | Property | Type | Availability | Description |
@@ -207,12 +279,13 @@ OAUTH_CALLBACK_URL=http://localhost:1234/auth/callback
207
279
  | `ctx.req` | `T` (inferred from schema) | Always | Validated tool parameters |
208
280
  | `ctx.config` | `Object` (inferred from userConfig) | Always (`{}` if no userConfig) | Validated user configuration |
209
281
  | `ctx.accessToken` | `string` | OAuth servers only | Access token from platform |
282
+ | `ctx.env` | `Object` (inferred from requiredEnv) | Always (`{}` if no requiredEnv) | Required environment variables |
210
283
  | `ctx.res` | `XTRNResponse` | Always | Response helper methods |
211
284
 
212
285
  ## Tool Tags
213
286
 
214
287
  ```typescript
215
- import { ToolTag } from "@xtrn/server/server";
288
+ import { ToolTag } from "@xtrn/server";
216
289
 
217
290
  server.registerTool({
218
291
  name: "delete-item",
@@ -8,7 +8,7 @@
8
8
  "dev": "xtrn dev"
9
9
  },
10
10
  "dependencies": {
11
- "@xtrn/server": "^0.1.0",
11
+ "@xtrn/server": "^1.0.0",
12
12
  "zod": "^4.0.0"
13
13
  },
14
14
  "devDependencies": {